mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 08:23:45 +00:00
feat(*): add wall/ceiling surface pull and retune ball-ship materials
This commit is contained in:
@@ -60,10 +60,42 @@ var _corner_visuals: Array[Dictionary] = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("arena_boundary")
|
||||
_build_corner_curves()
|
||||
_build_base_fillets()
|
||||
|
||||
|
||||
# Wall+ceiling-only proximity force field ("artificial gravity" grav-plating,
|
||||
# weaker than the floor's plain default gravity, which this deliberately
|
||||
# leaves untouched). Ship and Ball each call this with their own
|
||||
# strength/range so wall adherence and ceiling adherence can be tuned
|
||||
# independently per body — see ship.gd/ball.gd. Quadratic falloff keeps a
|
||||
# casual flyby near a wall almost force-free, concentrating the pull in
|
||||
# roughly the last third of the range so it only bites once something is
|
||||
# genuinely close to the surface.
|
||||
func get_surface_pull(
|
||||
global_pos: Vector3, wall_strength: float, wall_range: float,
|
||||
ceiling_strength: float, ceiling_range: float
|
||||
) -> Vector3:
|
||||
var p := to_local(global_pos)
|
||||
var pull := Vector3.ZERO
|
||||
pull += Vector3(1, 0, 0) * _falloff(INNER_HALF_X - p.x, wall_range) * wall_strength
|
||||
pull += Vector3(-1, 0, 0) * _falloff(INNER_HALF_X + p.x, wall_range) * wall_strength
|
||||
# End walls are gated off inside the goal mouth — there is no physical
|
||||
# wall there (see GOAL_MOUTH_HALF_WIDTH / _build_base_fillets), so a shot
|
||||
# heading straight for the net doesn't feel a phantom sideways tug.
|
||||
if abs(p.x) >= GOAL_MOUTH_HALF_WIDTH:
|
||||
pull += Vector3(0, 0, 1) * _falloff(INNER_HALF_Z - p.z, wall_range) * wall_strength
|
||||
pull += Vector3(0, 0, -1) * _falloff(INNER_HALF_Z + p.z, wall_range) * wall_strength
|
||||
pull += Vector3.UP * _falloff(INNER_HEIGHT - p.y, ceiling_range) * ceiling_strength
|
||||
return pull
|
||||
|
||||
|
||||
func _falloff(dist: float, field_range: float) -> float:
|
||||
var t: float = clamp(1.0 - dist / field_range, 0.0, 1.0)
|
||||
return t * t
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
# The translucent field material tints everything behind it, so any face
|
||||
# the camera has crossed to the outside of is hidden entirely — looking
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
class_name Ball
|
||||
extends RigidBody3D
|
||||
|
||||
# Physics ball. Floor gravity is untouched (gravity_scale in ball.tscn); this
|
||||
# only adds the wall/ceiling "surface pull" (see ArenaBoundary.get_surface_pull)
|
||||
# so the ball can cling near a wall for dribbling or hang against the ceiling
|
||||
# for ceiling shots, plus a safety speed clamp (the ball previously had none).
|
||||
|
||||
@export_group("Surface Pull")
|
||||
@export var wall_pull_strength = 4.0 # Weaker than the ship's — assist, not adherence
|
||||
@export var wall_pull_range = 2.0
|
||||
@export var ceiling_pull_strength = 5.5 # Stays below the ball's effective gravity (0.8 * 9.8)
|
||||
@export var ceiling_pull_range = 2.0
|
||||
|
||||
# Kept close to ship_observations.gd's BALL_SPEED_SCALE (30.0) so this feature
|
||||
# doesn't push ball velocity further out of the range trained policies expect.
|
||||
const MAX_SPEED := 32.0
|
||||
|
||||
var _boundary: ArenaBoundary
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_boundary = get_tree().get_first_node_in_group("arena_boundary")
|
||||
|
||||
|
||||
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
|
||||
if _boundary:
|
||||
var pull := _boundary.get_surface_pull(
|
||||
global_position, wall_pull_strength, wall_pull_range,
|
||||
ceiling_pull_strength, ceiling_pull_range
|
||||
)
|
||||
state.apply_central_force(pull * mass)
|
||||
|
||||
if state.linear_velocity.length() > MAX_SPEED:
|
||||
state.linear_velocity = state.linear_velocity.normalized() * MAX_SPEED
|
||||
@@ -0,0 +1 @@
|
||||
uid://da7cgcp5pt1ld
|
||||
@@ -19,6 +19,12 @@ extends RigidBody3D
|
||||
@export var drag_coefficient = 0.98 # Linear drag (air resistance)
|
||||
@export var angular_drag = 0.95 # Rotational drag
|
||||
|
||||
@export_group("Surface Pull")
|
||||
@export var wall_pull_strength = 6.0 # Wall grav-plating strength (m/s^2-equivalent)
|
||||
@export var wall_pull_range = 3.0 # Metres from a wall where pull begins
|
||||
@export var ceiling_pull_strength = 11.5 # Ceiling grav-plating strength; nets above gravity so a ship can hold a ceiling
|
||||
@export var ceiling_pull_range = 3.0 # Metres from the ceiling where pull begins
|
||||
|
||||
# Accent colours per team, applied to the nose and tail fin meshes so the
|
||||
# two sides are tellable apart at a glance.
|
||||
const TEAM_COLORS := {
|
||||
@@ -34,6 +40,7 @@ var team: int = 0:
|
||||
|
||||
var controller: ShipController
|
||||
var _current_action: ShipAction = ShipAction.new()
|
||||
var _boundary: ArenaBoundary
|
||||
|
||||
# Instrument signals for efficient data distribution
|
||||
signal speed_changed(speed: float)
|
||||
@@ -74,6 +81,8 @@ func _ready():
|
||||
|
||||
_apply_team_color()
|
||||
|
||||
_boundary = get_tree().get_first_node_in_group("arena_boundary")
|
||||
|
||||
|
||||
func _apply_team_color() -> void:
|
||||
if not is_inside_tree():
|
||||
@@ -112,6 +121,7 @@ func _integrate_forces(state):
|
||||
|
||||
# === TRANSLATION (Movement) ===
|
||||
apply_thruster_forces(state, _current_action)
|
||||
apply_surface_pull(state)
|
||||
|
||||
# === ROTATION (Turning) ===
|
||||
apply_rotation_forces(state, _current_action.rotation)
|
||||
@@ -153,6 +163,16 @@ func apply_thruster_forces(state: PhysicsDirectBodyState3D, action: ShipAction):
|
||||
state.apply_central_force(world_thrust)
|
||||
|
||||
|
||||
func apply_surface_pull(state: PhysicsDirectBodyState3D) -> void:
|
||||
if _boundary == null:
|
||||
return
|
||||
var pull := _boundary.get_surface_pull(
|
||||
global_position, wall_pull_strength, wall_pull_range,
|
||||
ceiling_pull_strength, ceiling_pull_range
|
||||
)
|
||||
state.apply_central_force(pull * mass)
|
||||
|
||||
|
||||
func apply_rotation_forces(state: PhysicsDirectBodyState3D, rotation_input: Vector3):
|
||||
if rotation_input.length() < 0.01:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user