jump ani fixing and transition animations
This commit is contained in:
@@ -6,7 +6,10 @@ class_name MovementParams
|
||||
@export var crouch_speed: float = 4.5
|
||||
@export var ground_friction: float = 10.0
|
||||
@export var ground_acceleration: float = 60.0
|
||||
@export var ground_deceleration: float = 45.0
|
||||
# Releasing movement carries roughly one metre of momentum from full speed.
|
||||
# The old 45/s response erased half the velocity in a single rendered frame,
|
||||
# leaving no physical time for an authored stopping step to read.
|
||||
@export var ground_deceleration: float = 9.0
|
||||
|
||||
# ── Jump / Air ────────────────────────────────────────────────────────────────
|
||||
@export var jump_velocity: float = 8.5
|
||||
|
||||
@@ -83,6 +83,9 @@ var grapple_rope: MeshInstance3D
|
||||
# broadcasts position/velocity; remote peers interpolate toward it (see
|
||||
# _process). The server stays authoritative for health, shield, and kills.
|
||||
var synced_movement_state: String = "idle"
|
||||
## Presentation-only state. Physics/network gameplay still uses
|
||||
## synced_movement_state; this adds stop and alternating-air variants.
|
||||
var synced_animation_state: String = "idle"
|
||||
var synced_movement_speed: float = 0.0
|
||||
var synced_is_crouching: bool = false
|
||||
var synced_position: Vector3 = Vector3.ZERO
|
||||
@@ -91,6 +94,7 @@ var synced_is_ads: bool = false
|
||||
var synced_wall_side: float = 0.0 # -1 wall left, +1 wall right (wall-glide bank)
|
||||
var synced_wall_normal: Vector3 = Vector3.ZERO
|
||||
var synced_wall_contact_point: Vector3 = Vector3.ZERO
|
||||
var _airborne_alternate := false
|
||||
var synced_helmet_closed: bool = false
|
||||
var synced_is_dancing: bool = false # dance emote (B), shown on the model
|
||||
## Which of the five routines in DanceRoutines is playing. Replicated, so other
|
||||
@@ -1063,9 +1067,10 @@ func _physics_process(_delta: float) -> void:
|
||||
synced_is_ads = _read_ads()
|
||||
|
||||
# Drive the visual model (skinned GLB or procedural) from local state
|
||||
var h_speed = Vector2(velocity.x, velocity.z).length()
|
||||
var animation_state := _visual_animation_state(sm.current_state, h_speed)
|
||||
var visual = get_visual_model()
|
||||
if visual:
|
||||
var h_speed = Vector2(velocity.x, velocity.z).length()
|
||||
if visual.has_method("set_locomotion"):
|
||||
var d := _local_move_dir()
|
||||
visual.set_locomotion(d.x, d.y, 1.0 if synced_is_ads else 0.0)
|
||||
@@ -1081,7 +1086,7 @@ func _physics_process(_delta: float) -> void:
|
||||
visual.set_wall_run_motion(velocity)
|
||||
if visual.has_method("set_helmet_closed"):
|
||||
visual.set_helmet_closed(synced_helmet_closed)
|
||||
visual.update_state(sm.current_state, h_speed, sm.input_crouch)
|
||||
visual.update_state(animation_state, h_speed, sm.input_crouch)
|
||||
if visual.has_method("set_dancing"):
|
||||
visual.set_dancing(synced_is_dancing, synced_dance_index)
|
||||
if visual.has_method("set_grapple_target") and sm.current_state == "grapple":
|
||||
@@ -1090,6 +1095,7 @@ func _physics_process(_delta: float) -> void:
|
||||
|
||||
# Publish state for remote peers
|
||||
synced_movement_state = sm.current_state
|
||||
synced_animation_state = animation_state
|
||||
synced_movement_speed = Vector2(velocity.x, velocity.z).length()
|
||||
synced_is_crouching = sm.input_crouch
|
||||
synced_wall_side = sm.wall_side
|
||||
@@ -1109,6 +1115,17 @@ func _local_move_dir() -> Vector2:
|
||||
return Vector2(local_vel.x / hspeed, -local_vel.z / hspeed)
|
||||
|
||||
|
||||
func _visual_animation_state(state: String, horizontal_speed: float) -> String:
|
||||
if state == "air":
|
||||
return "air_alt" if _airborne_alternate else "air"
|
||||
if state == "ground" and is_instance_valid(_machine) \
|
||||
and not _machine.input_crouch \
|
||||
and _machine.input_dir.length_squared() < 0.01 \
|
||||
and horizontal_speed > 0.6:
|
||||
return "stop"
|
||||
return state
|
||||
|
||||
|
||||
## Whether the local player's active weapon is aiming down sights.
|
||||
func _read_ads() -> bool:
|
||||
if not is_instance_valid(camera):
|
||||
@@ -1130,6 +1147,10 @@ func _on_movement_event(ev: String, data: Dictionary) -> void:
|
||||
grapple_latch_player.play()
|
||||
elif ev == "dash":
|
||||
_speedline_burst = 1.0
|
||||
elif ev == "jump":
|
||||
# Alternate the complete authored airborne silhouette on successive
|
||||
# ground jumps so bunny hops do not always lead with the same knee.
|
||||
_airborne_alternate = not _airborne_alternate
|
||||
elif ev == "double_jump":
|
||||
_trigger_action("double_jump")
|
||||
if is_instance_valid(_jet_vfx):
|
||||
@@ -1183,7 +1204,7 @@ func _process(delta: float) -> void:
|
||||
if visual.has_method("set_helmet_closed"):
|
||||
visual.set_helmet_closed(synced_helmet_closed)
|
||||
visual.update_state(
|
||||
synced_movement_state,
|
||||
synced_animation_state,
|
||||
synced_movement_speed,
|
||||
synced_is_crouching,
|
||||
)
|
||||
|
||||
@@ -52,11 +52,8 @@ func update(delta: float) -> void:
|
||||
# ── Horizontal movement (exponential interpolation for smoothness) ───
|
||||
var hvel := Vector3(vel.x, 0.0, vel.z)
|
||||
var target_vel := wish_dir * effective_speed
|
||||
|
||||
if wish_dir.length_squared() > 0.0:
|
||||
hvel = hvel.lerp(target_vel, 1.0 - exp(-params.ground_acceleration * delta))
|
||||
else:
|
||||
hvel = hvel.lerp(Vector3.ZERO, 1.0 - exp(-params.ground_deceleration * delta))
|
||||
hvel = _next_horizontal_velocity(
|
||||
hvel, target_vel, wish_dir.length_squared() > 0.0, delta)
|
||||
|
||||
vel.x = hvel.x
|
||||
vel.z = hvel.z
|
||||
@@ -127,6 +124,14 @@ func update(delta: float) -> void:
|
||||
return
|
||||
|
||||
|
||||
func _next_horizontal_velocity(current: Vector3, target: Vector3,
|
||||
has_input: bool, delta: float) -> Vector3:
|
||||
var response := params.ground_acceleration \
|
||||
if has_input else params.ground_deceleration
|
||||
return current.lerp(target if has_input else Vector3.ZERO,
|
||||
1.0 - exp(-response * delta))
|
||||
|
||||
|
||||
func _try_step_up(player: CharacterBody3D, wish_dir: Vector3, hvel: Vector3) -> void:
|
||||
for i in range(player.get_slide_collision_count()):
|
||||
var col = player.get_slide_collision(i)
|
||||
|
||||
@@ -41,6 +41,7 @@ func run_all() -> void:
|
||||
"test_dash_adds_speed_to_forward_momentum",
|
||||
"test_chain_bonus_soft_caps",
|
||||
"test_notify_landed_emits_scaled_event",
|
||||
"test_ground_release_has_short_stopping_glide",
|
||||
"test_slide_entry_boosts_and_keeps_direction",
|
||||
"test_grapple_state_registered_by_machine",
|
||||
]
|
||||
@@ -239,6 +240,25 @@ func test_notify_landed_emits_scaled_event() -> void:
|
||||
_expect(sm.current_jump_count == 0, "landing resets jump count")
|
||||
|
||||
|
||||
func test_ground_release_has_short_stopping_glide() -> void:
|
||||
var ground = sm.states["ground"]
|
||||
var velocity := Vector3(0.0, 0.0, -params.walk_speed)
|
||||
var distance := 0.0
|
||||
var first_frame_speed := 0.0
|
||||
for frame in 90:
|
||||
velocity = ground._next_horizontal_velocity(
|
||||
velocity, Vector3.ZERO, false, 1.0 / 60.0)
|
||||
if frame == 0:
|
||||
first_frame_speed = velocity.length()
|
||||
distance += velocity.length() / 60.0
|
||||
if velocity.length() <= 0.6:
|
||||
break
|
||||
_expect(first_frame_speed > params.walk_speed * 0.80,
|
||||
"release keeps most momentum on its first frame")
|
||||
_expect(distance > 0.75 and distance < 1.5,
|
||||
"release produces a short stopping glide (%.2f m)" % distance)
|
||||
|
||||
|
||||
func test_slide_entry_boosts_and_keeps_direction() -> void:
|
||||
sm.switch_to("ground")
|
||||
fake_player.velocity = Vector3(0, 0, -12) # faster than 0.8*walk_speed
|
||||
|
||||
Reference in New Issue
Block a user