jump ani fixing and transition animations
This commit is contained in:
@@ -250,7 +250,12 @@ func _apply_color(col: Color) -> void:
|
||||
func update_state(state: String, speed: float, is_crouching: bool = false) -> void:
|
||||
var previous_speed := movement_speed
|
||||
var slowdown := previous_speed - speed
|
||||
if state in ["ground", "idle"]:
|
||||
if state == "stop" and current_state != "stop":
|
||||
_brake_left = BRAKE_DURATION
|
||||
_brake_strength = clampf(maxf(previous_speed, speed) / 10.0, 0.55, 1.0)
|
||||
if absf(_locomotion_fwd) > 0.15:
|
||||
_brake_fwd = signf(_locomotion_fwd)
|
||||
elif state in ["ground", "idle"]:
|
||||
if speed > 4.5 and slowdown < 0.05:
|
||||
_brake_armed = true
|
||||
if _brake_armed and previous_speed > 4.5 and slowdown > 0.12:
|
||||
@@ -259,7 +264,7 @@ func update_state(state: String, speed: float, is_crouching: bool = false) -> vo
|
||||
_brake_armed = false
|
||||
if absf(_locomotion_fwd) > 0.15:
|
||||
_brake_fwd = signf(_locomotion_fwd)
|
||||
elif state not in ["ground", "idle"]:
|
||||
elif state not in ["ground", "idle", "stop"]:
|
||||
_brake_left = 0.0
|
||||
_brake_armed = true
|
||||
current_state = state
|
||||
@@ -378,7 +383,7 @@ func _set_shadows_recursive(node: Node) -> void:
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
var brake_target := 0.0
|
||||
if _brake_left > 0.0 and current_state in ["ground", "idle"]:
|
||||
if _brake_left > 0.0 and current_state in ["ground", "idle", "stop"]:
|
||||
_brake_left = maxf(0.0, _brake_left - delta)
|
||||
var progress := 1.0 - _brake_left / BRAKE_DURATION
|
||||
var envelope := smoothstep(0.0, 1.0, progress / 0.30) \
|
||||
@@ -457,6 +462,15 @@ func _process(delta: float) -> void:
|
||||
t_upper_arm_r_rot.x = -0.5
|
||||
t_lower_arm_l_rot.x = -1.2
|
||||
t_lower_arm_r_rot.x = -1.2
|
||||
|
||||
"stop":
|
||||
# Neutral base under the catching-step overlay below. Keeping the run
|
||||
# cycle out of this state prevents residual speed from accelerating it.
|
||||
t_root_rot.x = deg_to_rad(8.0)
|
||||
t_thigh_r_rot.x = deg_to_rad(-24.0)
|
||||
t_calf_r_rot.x = deg_to_rad(12.0)
|
||||
t_thigh_l_rot.x = deg_to_rad(8.0)
|
||||
t_calf_l_rot.x = deg_to_rad(14.0)
|
||||
|
||||
"slide":
|
||||
t_root_pos.y = -0.6
|
||||
@@ -485,6 +499,20 @@ func _process(delta: float) -> void:
|
||||
t_upper_arm_r_rot.z = -0.5
|
||||
t_lower_arm_l_rot.x = -0.5
|
||||
t_lower_arm_r_rot.x = -0.5
|
||||
|
||||
"air_alt":
|
||||
# Mirror the simple fallback silhouette to match the alternating
|
||||
# offline-authored variants used by skinned motion-lab characters.
|
||||
t_thigh_l_rot.x = 0.2
|
||||
t_thigh_r_rot.x = -0.4
|
||||
t_calf_l_rot.x = 0.1
|
||||
t_calf_r_rot.x = 0.5
|
||||
t_upper_arm_l_rot.x = -0.8
|
||||
t_upper_arm_r_rot.x = -0.8
|
||||
t_upper_arm_l_rot.z = -0.5
|
||||
t_upper_arm_r_rot.z = 0.5
|
||||
t_lower_arm_l_rot.x = -0.5
|
||||
t_lower_arm_r_rot.x = -0.5
|
||||
|
||||
"wall_run":
|
||||
t_root_rot.z = deg_to_rad(-15)
|
||||
@@ -549,7 +577,7 @@ func _process(delta: float) -> void:
|
||||
# Blend a single catching step over the locomotion result. It rises quickly
|
||||
# and releases over the same span as the run-to-idle crossfade, preserving
|
||||
# momentum without freezing the whole mannequin in an exaggerated plant.
|
||||
if current_state in ["ground", "idle"] and _brake_weight > 0.001:
|
||||
if current_state in ["ground", "idle", "stop"] and _brake_weight > 0.001:
|
||||
var travel_sign := -1.0 if _brake_fwd < 0.0 else 1.0
|
||||
t_root_pos.y = lerpf(t_root_pos.y, -0.025, _brake_weight)
|
||||
t_root_rot.x = lerp_angle(
|
||||
|
||||
@@ -48,8 +48,11 @@ const CLIP_FALLBACKS := {
|
||||
"Run": ["Run", "Walk", "Idle"],
|
||||
"Sprint": ["Sprint", "Run", "Walk", "Idle"],
|
||||
"Jump": ["Jump", "Fall", "Idle"],
|
||||
"JumpAlt": ["JumpAlt", "Jump", "Fall", "Idle"],
|
||||
"Fall": ["Fall", "Jump", "Idle"],
|
||||
"FallAlt": ["FallAlt", "Fall", "Jump", "Idle"],
|
||||
"Land": ["Land", "Idle"],
|
||||
"Stop": ["Stop", "Land", "Idle"],
|
||||
"Crouch": ["CrouchIdle", "Crouch", "Idle"],
|
||||
"CrouchWalk": ["CrouchWalk", "Crouch", "CrouchIdle", "Walk"],
|
||||
"SlideStart": ["SlideStart", "Slide"],
|
||||
@@ -85,7 +88,7 @@ const CLIP_FALLBACKS := {
|
||||
"Throw": ["Throw", "Hit"],
|
||||
}
|
||||
|
||||
const LOOPING_CLIPS := ["Idle", "Walk", "Run", "Sprint", "Fall", "Crouch",
|
||||
const LOOPING_CLIPS := ["Idle", "Walk", "Run", "Sprint", "Fall", "FallAlt", "Crouch",
|
||||
"CrouchIdle", "CrouchWalk", "Slide", "WallRunLeft", "WallRunRight",
|
||||
"WallCling", "Grapple",
|
||||
"StrafeWalkForward", "StrafeWalkBackward", "StrafeWalkLeft", "StrafeWalkRight",
|
||||
@@ -99,17 +102,13 @@ const BLEND_TIME := 0.36
|
||||
## drifts across their thresholds, and that is where hard cuts were most
|
||||
## visible.
|
||||
const BLEND_TIMES := {
|
||||
"Dash": 0.20, "Jump": 0.24, "Hit": 0.14, "Land": 0.28,
|
||||
# SlideStart has a distinct authored crouch pose; blending into it from
|
||||
# airborne/standing can send the lower body through a ground-plane flip.
|
||||
"SlideStart": 0.0, "Slide": 0.30, "Death": 0.34,
|
||||
"Dash": 0.20, "Jump": 0.12, "JumpAlt": 0.12,
|
||||
"Fall": 0.12, "FallAlt": 0.12, "Hit": 0.14, "Land": 0.28,
|
||||
"Stop": 0.12, "SlideStart": 0.12, "Slide": 0.30, "Death": 0.34,
|
||||
"Throw": 0.20, "PistolReload": 0.30,
|
||||
"Idle": 0.42, "PistolIdle": 0.42, "Walk": 0.40, "Run": 0.40, "Sprint": 0.40,
|
||||
"CrouchIdle": 0.40, "CrouchWalk": 0.40, "Fall": 0.32,
|
||||
# The two authored wall performances start in distinct side poses. A full
|
||||
# body crossfade from Fall/ground interpolates the hips and thighs through
|
||||
# an upside-down midpoint, so hand off directly to the authored first pose.
|
||||
"WallRunLeft": 0.0, "WallRunRight": 0.0,
|
||||
"CrouchIdle": 0.40, "CrouchWalk": 0.40,
|
||||
"WallRunLeft": 0.12, "WallRunRight": 0.12,
|
||||
"WallCling": 0.32, "Grapple": 0.32,
|
||||
}
|
||||
const GROUND_STATE_BLEND := 0.30
|
||||
@@ -135,6 +134,7 @@ var _wall_surface_normal_world := Vector3.ZERO
|
||||
var _wall_surface_point_world := Vector3.ZERO
|
||||
var _motion_yaw := 0.0
|
||||
var _motion_was_wall_run := false
|
||||
var _stop_entry_speed := 0.0
|
||||
|
||||
var _resolved_clips: Dictionary = {} # canonical name -> actual clip name
|
||||
var _current_clip: String = ""
|
||||
@@ -234,6 +234,7 @@ func load_model(path: String) -> void:
|
||||
_wall_surface_point_world = Vector3.ZERO
|
||||
_motion_yaw = 0.0
|
||||
_motion_was_wall_run = false
|
||||
_stop_entry_speed = 0.0
|
||||
_loco_blend_target = Vector2.ZERO
|
||||
_loco_blend_visual = Vector2.ZERO
|
||||
_loco_scale_target = 1.0
|
||||
@@ -857,7 +858,7 @@ func update_state(state: String, speed: float, is_crouching: bool = false) -> vo
|
||||
_prev_state = state
|
||||
return
|
||||
# A heavy landing plays the Land one-shot before locomotion resumes.
|
||||
if state in ["ground", "idle"] and _prev_state == "air" \
|
||||
if state in ["ground", "idle"] and _prev_state in ["air", "air_alt"] \
|
||||
and _vertical_speed() < -12.0 and _resolved_clips.has("Land"):
|
||||
_oneshot_lock = 0.25
|
||||
_play_clip("Land")
|
||||
@@ -866,6 +867,10 @@ func update_state(state: String, speed: float, is_crouching: bool = false) -> vo
|
||||
_prev_state = state
|
||||
if state != "slide":
|
||||
_slide_start_remaining = 0.0
|
||||
if state == "stop" and previous_state != "stop":
|
||||
_stop_entry_speed = speed
|
||||
elif state != "stop":
|
||||
_stop_entry_speed = 0.0
|
||||
|
||||
var clip := "Idle"
|
||||
match state:
|
||||
@@ -878,6 +883,13 @@ func update_state(state: String, speed: float, is_crouching: bool = false) -> vo
|
||||
"air":
|
||||
# Rising = jump, falling = the fall loop.
|
||||
clip = "Jump" if _vertical_speed() > 0.5 else "Fall"
|
||||
"air_alt":
|
||||
# Offline-mirrored source clips swap the leading knee and arm while
|
||||
# preserving the exact Titanfall timing and silhouette.
|
||||
clip = "JumpAlt" if _vertical_speed() > 0.5 else "FallAlt"
|
||||
"stop":
|
||||
clip = "Stop"
|
||||
_loco_scale_target = 1.0
|
||||
"slide":
|
||||
if previous_state != "slide":
|
||||
_slide_start_remaining = _animation_length_if_distinct(
|
||||
@@ -910,10 +922,11 @@ func update_state(state: String, speed: float, is_crouching: bool = false) -> vo
|
||||
clip = "Death"
|
||||
|
||||
if clip != "":
|
||||
# A wall jump hands the full body from a side performance to Jump/Fall.
|
||||
# Interpolating those two poses can put the thighs through the horizontal
|
||||
# midpoint, which reads as a delayed leg flip on the jump frame.
|
||||
_play_clip(clip, false, 0.0 if exiting_wall_run else -1.0)
|
||||
var blend_override := -1.0
|
||||
if state in ["wall_run", "slide", "air", "air_alt", "stop"] \
|
||||
or exiting_wall_run:
|
||||
blend_override = _speed_scaled_transition(speed)
|
||||
_play_clip(clip, false, blend_override)
|
||||
|
||||
if _pose_mod:
|
||||
_pose_mod.state = state
|
||||
@@ -927,6 +940,8 @@ func update_state(state: String, speed: float, is_crouching: bool = false) -> vo
|
||||
var scale := 1.0
|
||||
if clip in ["SlideStart", "Slide"]:
|
||||
scale = _slide_playback_scale(speed)
|
||||
elif clip == "Stop":
|
||||
scale = _stop_playback_scale(_stop_entry_speed)
|
||||
elif clip == "Grapple" or clip in ["WallRunLeft", "WallRunRight"]:
|
||||
# Grapple is held, and the wall clips are compact propulsion loops.
|
||||
# Neither should become a frantic treadmill at high traversal speed.
|
||||
@@ -1187,6 +1202,20 @@ func _slide_playback_scale(speed: float) -> float:
|
||||
return clampf(speed / maxf(run_anim_reference_speed, 0.01), 0.75, 1.8)
|
||||
|
||||
|
||||
func _stop_playback_scale(entry_speed: float) -> float:
|
||||
# Finish the weight-catching step while the physical stopping glide still
|
||||
# has momentum. Faster entries compress the one-shot without hard-cutting it.
|
||||
return clampf(entry_speed / maxf(run_anim_reference_speed, 0.01), 1.0, 2.2)
|
||||
|
||||
|
||||
func _speed_scaled_transition(speed: float) -> float:
|
||||
# High-speed traversal needs a quicker silhouette handoff, but never a
|
||||
# zero-frame snap. At ordinary run speed this is ~0.12 s; near the movement
|
||||
# cap it approaches 0.07 s.
|
||||
var speed_factor := clampf((speed - 3.0) / 15.0, 0.0, 1.0)
|
||||
return lerpf(0.18, 0.07, speed_factor)
|
||||
|
||||
|
||||
## Pick the cloth solver's detail level from how far the camera is.
|
||||
##
|
||||
## Re-checked a few times a second rather than every frame: the answer changes
|
||||
@@ -1248,8 +1277,23 @@ func _play_ground_locomotion(speed: float, crouched: bool) -> void:
|
||||
if _current_state_node != state_node:
|
||||
var exiting_wall_run := _current_state_node in [
|
||||
"WallRunLeft", "WallRunRight"]
|
||||
_state_trans.xfade_time = 0.0 if exiting_wall_run \
|
||||
else GROUND_STATE_BLEND
|
||||
var exiting_stop: bool = \
|
||||
_current_state_node == String(_resolved_clips.get("Stop", ""))
|
||||
if exiting_wall_run:
|
||||
_state_trans.xfade_time = _speed_scaled_transition(speed)
|
||||
elif exiting_stop:
|
||||
# The stop one-shot has already absorbed the residual momentum. Enter
|
||||
# the Idle point directly under the crossfade; carrying Sprint's stale
|
||||
# BlendSpace coordinate forward would create a final fast run cycle.
|
||||
_loco_blend_target = Vector2.ZERO
|
||||
_loco_blend_visual = Vector2.ZERO
|
||||
_loco_scale_target = 1.0
|
||||
_loco_scale_visual = 1.0
|
||||
_anim_tree.set("parameters/loco_blend/blend_position", Vector2.ZERO)
|
||||
_anim_tree.set("parameters/loco_scale/scale", 1.0)
|
||||
_state_trans.xfade_time = 0.18
|
||||
else:
|
||||
_state_trans.xfade_time = GROUND_STATE_BLEND
|
||||
_anim_tree.set("parameters/state/transition_request", state_node)
|
||||
_current_state_node = state_node
|
||||
|
||||
|
||||
Reference in New Issue
Block a user