feat: overhaul movement for flow — momentum-preserving states, smooth crouch, landing feel
- Quake-style air-strafe accelerate (real speed gain from strafing; external speed from dash/rockets never clamped) - Wall run: keeps entry momentum (incl. upward carry), accelerates along the wall instead of hard-setting velocity, gravity fades in over the run - Slide: slope physics (gravity projected along floor accelerates downhill), flat-ground decel instead of multiplicative friction, entry boost, direct slide->wallrun and slide->dash transitions - Dash: cooldown 10s -> 2s, per-player (was a static shared across instances), momentum fully kept on exit, FOV punch event - Grapple: taut pendulum with active rope control (W reels in, S pays out), release keeps swing energy - Wall climb: carries upward momentum in, jump-off kick, shared vault helper - Crouch capsule: single owner in the machine, smoothly lerped, ceiling check; camera eye height follows via crouch factor (no more head snapping) - Landing: machine emits 'land' events scaled by impact; camera dip + pitched down thud; footsteps get pitch variation - Camera rig: event-driven (land dip, dash FOV kick, vault pitch impulse), slide roll tilt - Model: Jump vs Fall split by vertical velocity, Land one-shot on heavy landings, wall-run body lean (synced to remotes via synced_wall_side) Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c4a538a469
commit
0ba14a9469
@@ -1,42 +1,65 @@
|
||||
extends Node
|
||||
class_name StateGrapple
|
||||
|
||||
## Swing grapple with active rope control:
|
||||
## - the rope acts as a hard pendulum constraint at its current length
|
||||
## - holding forward reels in (rope shortens, converts to speed)
|
||||
## - holding back pays rope out (up to the original latch length)
|
||||
## - strafe input steers tangentially around the swing sphere
|
||||
## - jump detaches with a boost along your current motion
|
||||
|
||||
var machine: MovementStateMachine
|
||||
var params: MovementParams:
|
||||
get: return machine.params
|
||||
|
||||
var _rope_length: float = 0.0
|
||||
|
||||
|
||||
func enter(_data: Dictionary = {}) -> void:
|
||||
machine.on_ground = false
|
||||
machine.wall_normal = Vector3.ZERO
|
||||
machine.wall_side = 0.0
|
||||
machine.register_chain_mechanic("grapple")
|
||||
_rope_length = machine.grapple_length
|
||||
|
||||
|
||||
func exit() -> void:
|
||||
pass
|
||||
|
||||
|
||||
func update(delta: float) -> void:
|
||||
var player := machine.player
|
||||
var vel: Vector3 = player.velocity
|
||||
|
||||
|
||||
# If player releases the button, disconnect
|
||||
if not machine.input_grapple:
|
||||
machine.switch_to("air")
|
||||
return
|
||||
|
||||
var grapple_pos: Vector3 = machine.grapple_point
|
||||
var current_dist: float = player.global_position.distance_to(grapple_pos)
|
||||
var dir_to_point: Vector3 = (grapple_pos - player.global_position).normalized()
|
||||
|
||||
var to_point: Vector3 = grapple_pos - player.global_position
|
||||
var current_dist: float = to_point.length()
|
||||
if current_dist < 0.01:
|
||||
machine.switch_to("air")
|
||||
return
|
||||
var dir_to_point: Vector3 = to_point / current_dist
|
||||
|
||||
# Jump to detach and get a boost
|
||||
if machine.input_jump_just_pressed and machine.jump_cooldown_timer <= 0.0:
|
||||
vel.y = params.jump_velocity
|
||||
# Add a directional boost if jumping while swinging
|
||||
vel.y = maxf(vel.y, params.jump_velocity)
|
||||
# Boost along current motion so the release keeps the swing's energy
|
||||
var h_vel := Vector3(vel.x, 0.0, vel.z)
|
||||
if h_vel.length_squared() > 0.1:
|
||||
var boost := h_vel.normalized() * params.grapple_jump_boost * 0.5
|
||||
vel.x += boost.x
|
||||
vel.z += boost.z
|
||||
# Plus a steer boost toward held direction
|
||||
var h_look = machine.wish_dir_world
|
||||
if h_look.length_squared() > 0.1:
|
||||
var h_boost = h_look.normalized() * params.grapple_jump_boost
|
||||
var h_boost = h_look.normalized() * params.grapple_jump_boost * 0.5
|
||||
vel.x += h_boost.x
|
||||
vel.z += h_boost.z
|
||||
|
||||
|
||||
player.velocity = vel
|
||||
machine.jump_cooldown_timer = params.jump_cooldown
|
||||
machine.register_chain_mechanic("grapple_jump")
|
||||
@@ -44,43 +67,44 @@ func update(delta: float) -> void:
|
||||
if player.jump_player:
|
||||
player.jump_player.play()
|
||||
return
|
||||
|
||||
|
||||
# Apply gravity
|
||||
vel.y -= params.gravity * delta
|
||||
|
||||
# Apply slight inward pull (reeling in)
|
||||
|
||||
# Base reel: constant gentle pull toward the hook
|
||||
vel += dir_to_point * params.grapple_pull_force * delta
|
||||
|
||||
# Pendulum / Rope physics
|
||||
# If the player tries to go further than the original rope length, pull them back aggressively
|
||||
var max_len: float = machine.grapple_length
|
||||
if current_dist > max_len:
|
||||
# Project velocity onto the tangent of the sphere (prevent moving further away)
|
||||
|
||||
# ── Active rope control ───────────────────────────────────────────────
|
||||
var fwd_input := -machine.input_dir.y # +1 holding forward, -1 back
|
||||
if fwd_input > 0.1:
|
||||
# Reel in: shorten rope and pull hard — converts to swing speed
|
||||
vel += dir_to_point * params.grapple_reel_force * fwd_input * delta
|
||||
_rope_length = maxf(_rope_length - 8.0 * fwd_input * delta, 2.0)
|
||||
elif fwd_input < -0.1:
|
||||
# Pay out rope back toward the original latch length
|
||||
_rope_length = minf(_rope_length + 8.0 * -fwd_input * delta, machine.grapple_length)
|
||||
|
||||
# Keep the working length taut to where we actually are (swinging inside
|
||||
# the sphere shortens the constraint, giving crisp Tarzan arcs)
|
||||
_rope_length = minf(_rope_length, maxf(current_dist, 2.0))
|
||||
|
||||
# ── Pendulum constraint at the current rope length ────────────────────
|
||||
if current_dist > _rope_length:
|
||||
# Kill outward radial velocity (the rope is taut and inextensible)
|
||||
var radial_vel = vel.project(dir_to_point)
|
||||
# If radial_vel is pointing AWAY from the grapple point (dot < 0), kill it
|
||||
if radial_vel.dot(dir_to_point) < 0:
|
||||
vel -= radial_vel
|
||||
|
||||
# Add a spring force to pull them back to the sphere
|
||||
var diff = current_dist - max_len
|
||||
var spring_force = dir_to_point * diff * params.grapple_spring_strength
|
||||
vel += spring_force * delta
|
||||
# Spring correction toward the sphere surface
|
||||
var diff = current_dist - _rope_length
|
||||
vel += dir_to_point * diff * params.grapple_spring_strength * delta
|
||||
|
||||
# Air control while swinging
|
||||
var wish_dir: Vector3 = machine.wish_dir_world
|
||||
if wish_dir.length_squared() > 0.01:
|
||||
wish_dir = wish_dir.normalized()
|
||||
# Add force tangentially to the rope
|
||||
# We want to steer, but not pull away from or directly into the rope.
|
||||
var tangent_wish = wish_dir - wish_dir.project(dir_to_point)
|
||||
if tangent_wish.length_squared() > 0.01:
|
||||
vel += tangent_wish.normalized() * params.grapple_air_control * delta
|
||||
# ── Tangential steering (strafe around the swing sphere) ──────────────
|
||||
var strafe_input := machine.input_dir.x
|
||||
if absf(strafe_input) > 0.1:
|
||||
var right := player.global_transform.basis.x
|
||||
var tangent_steer := right - right.project(dir_to_point)
|
||||
if tangent_steer.length_squared() > 0.01:
|
||||
vel += tangent_steer.normalized() * params.grapple_air_control * strafe_input * delta
|
||||
|
||||
player.velocity = vel
|
||||
player.move_and_slide()
|
||||
|
||||
# Remove ground detach so players can grapple along the floor
|
||||
# if player.is_on_floor():
|
||||
# machine.on_ground = true
|
||||
# machine.switch_to("ground")
|
||||
# return
|
||||
|
||||
Reference in New Issue
Block a user