extends Node class_name StateWallRun var machine: MovementStateMachine var params: MovementParams: get: return machine.params var elapsed: float = 0.0 var _run_speed: float = 0.0 var _last_vel: Vector3 = Vector3.ZERO var _current_tangent: Vector3 = Vector3.ZERO func enter(_data: Dictionary = {}) -> void: elapsed = 0.0 machine.register_chain_mechanic("wall_run") # Cap the upward momentum so they don't fly up the wall, # and prevent negative y momentum so the wall "catches" them. machine.player.velocity.y = clampf(machine.player.velocity.y, 0.0, 1.5) machine.on_ground = false _last_vel = machine.player.velocity if machine.player.wallrun_player and not machine.player.wallrun_player.playing: machine.player.wallrun_player.play() var hvel := Vector3(machine.player.velocity.x, 0.0, machine.player.velocity.z) _current_tangent = machine.wall_normal.cross(Vector3.UP).normalized() if hvel.dot(_current_tangent) < 0.0: _current_tangent = -_current_tangent # Preserve speed along the wall if it's faster than base wall_run_speed var base_speed = machine.get_effective_speed(params.wall_run_speed) var projected_speed = hvel.dot(_current_tangent) _run_speed = maxf(base_speed, projected_speed) # Camera tilt var rig = _get_camera_rig() if rig: rig.set_wall_tilt(machine.wall_side) func exit() -> void: # Clear camera tilt var rig = _get_camera_rig() if rig: rig.clear_wall_tilt() if machine.player.wallrun_player: machine.player.wallrun_player.stop() func update(delta: float) -> void: elapsed += delta if elapsed > params.wall_run_duration: machine.wall_normal = Vector3.ZERO machine.wall_side = 0.0 machine.wall_cooldown_timer = 0.3 machine.switch_to("air") return var player := machine.player var vel: Vector3 = player.velocity if player.wallrun_player and not player.wallrun_player.playing: player.wallrun_player.play() if vel.distance_squared_to(_last_vel) > 100.0: machine.wall_normal = Vector3.ZERO machine.wall_side = 0.0 machine.switch_to("air") return # ── Gradual gravity pull (starts light, increases over time) ────────── var gravity_factor := 0.1 + 0.9 * (elapsed / params.wall_run_duration) vel.y -= params.wall_run_gravity * gravity_factor * delta # ── Move along wall tangent ─────────────────────────────────────────── var wall_tangent := machine.wall_normal.cross(Vector3.UP).normalized() # Ensure the new tangent aligns with our locked forward direction if _current_tangent.dot(wall_tangent) < 0.0: wall_tangent = -wall_tangent _current_tangent = wall_tangent vel.x = wall_tangent.x * _run_speed vel.z = wall_tangent.z * _run_speed # ── Look away to break wall run ─────────────────────────────────────── var look_dir := -player.global_transform.basis.z if look_dir.dot(machine.wall_normal) > 0.4: machine.wall_normal = Vector3.ZERO machine.wall_side = 0.0 machine.wall_cooldown_timer = 0.3 machine.switch_to("air") return # ── Push slightly toward wall to maintain contact ───────────────────── vel -= machine.wall_normal * 2.0 # ── Wall jump ───────────────────────────────────────────────────────── if machine.input_jump_just_pressed and machine.jump_cooldown_timer <= 0.0: # Base jump off velocity on current preserved momentum var jump_vel: Vector3 = _current_tangent * _run_speed # Spring off the wall jump_vel += machine.wall_normal * params.wall_run_jump_off_normal var h_look := Vector3(look_dir.x, 0.0, look_dir.z) if h_look.length_squared() > 0.01: jump_vel += h_look.normalized() * params.wall_run_jump_horizontal jump_vel.y = params.wall_run_auto_jump_speed player.velocity = jump_vel machine.wall_normal = Vector3.ZERO machine.wall_side = 0.0 machine.wall_cooldown_timer = 0.3 machine.register_chain_mechanic("wall_jump") machine.jump_cooldown_timer = params.jump_cooldown machine.switch_to("air") return # ── Wall cling (no movement input) ──────────────────────────────────── if machine.input_dir.length() < 0.1: machine.switch_to("wall_cling") return player.velocity = vel player.move_and_slide() _last_vel = player.velocity # ── Check still on wall ─────────────────────────────────────────────── var still_on_wall := machine.detect_wall_horizontal() if still_on_wall == Vector3.ZERO: machine.wall_normal = Vector3.ZERO machine.wall_side = 0.0 machine.switch_to("air") return # ── Hit floor during wall run ───────────────────────────────────────── if player.is_on_floor(): machine.on_ground = true machine.wall_normal = Vector3.ZERO machine.wall_side = 0.0 machine.current_jump_count = 0 machine.switch_to("ground") func _get_camera_rig(): if machine.player and machine.player.has_node("HeadPivot"): return machine.player.get_node("HeadPivot") return null