feat: animations follow the mechanics — upper/lower split + real grapple pose
Two fidelity gaps closed: - Reload/throw/shoot/hit used to REPLACE the whole-body clip, so reloading mid-slide snapped the character upright on standing legs. The model now runs a runtime AnimationTree (clips -> Transition -> TimeScale -> OneShot) whose one-shot layer is FILTERED to upper-body bones: the arms play the action while the legs keep sliding/running/falling. Land stays full-body. The rifle hold releases exactly while the one-shot node reports active. - Grapple no longer plays the library's horizontal-swim clip. The zip pose is procedural: Fall as the airborne base, the spine pivots to fly along the line to the actual anchor point (capped so overhead shots don't fold the body), the head sights the anchor, legs trail behind, the FREE left hand reaches up the rope — and the right hand keeps holding the rifle. The anchor point feeds in from the controller for local and remote players (synced_grapple_point). - anim_capture: deterministic open-ground teleport + two new regression shots (reload-while-sliding, grapple zip toward a real anchor). Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
87b8ae70df
commit
1ad3563e5a
+39
-1
@@ -68,9 +68,12 @@ func _process(_delta: float) -> bool:
|
||||
if not _model:
|
||||
printerr("anim_capture: no SkinnedModel (skin '%s' active?)" % _skin)
|
||||
return true
|
||||
# Freeze gameplay driving so we control the animation state directly.
|
||||
# Freeze gameplay driving so we control the animation state directly,
|
||||
# and teleport to open ground so walls never block the shot.
|
||||
_player.set_physics_process(false)
|
||||
_player.set_process(false)
|
||||
_player.global_position = Vector3(0, 1.2, 14)
|
||||
_player.rotation = Vector3.ZERO
|
||||
_model.set_owner_visible(true)
|
||||
# Give the model a weapon so armed poses read.
|
||||
if _model.has_method("set_weapon"):
|
||||
@@ -137,6 +140,41 @@ func _process(_delta: float) -> bool:
|
||||
elif _phase_frame >= 50:
|
||||
_snap("anim_ads_s")
|
||||
_debug_gun()
|
||||
_phase_frame = 0
|
||||
_mode = "combo"
|
||||
return false
|
||||
# Mechanic-fidelity combos: reload WHILE sliding (legs must keep sliding),
|
||||
# then the grapple zip pose toward an actual anchor point.
|
||||
if _mode == "combo":
|
||||
if _model.has_method("set_locomotion"):
|
||||
_model.set_locomotion(0.0, 0.0, 0.0)
|
||||
_model.update_state("slide", 10.0, true)
|
||||
if _phase_frame == 10:
|
||||
_model.play_action("reload")
|
||||
_phase_frame += 1
|
||||
if _phase_frame == 40:
|
||||
_snap("anim_slide_reload_f")
|
||||
_cam_to(Vector3(2.4, 0.9, -0.4))
|
||||
elif _phase_frame >= 42:
|
||||
_snap("anim_slide_reload_s")
|
||||
_phase_frame = 0
|
||||
_mode = "grapple_zip"
|
||||
return false
|
||||
if _mode == "grapple_zip":
|
||||
if _phase_frame == 0 and _model._anim_tree:
|
||||
# End the reload one-shot so the zip pose is unpolluted.
|
||||
_model._anim_tree.set("parameters/upper/request",
|
||||
AnimationNodeOneShot.ONE_SHOT_REQUEST_ABORT)
|
||||
_model.update_state("grapple", 12.0, false)
|
||||
if _model.has_method("set_grapple_target"):
|
||||
# Anchor up and ahead of the player (player faces -Z world).
|
||||
_model.set_grapple_target(_player.global_position + Vector3(1.5, 7.0, -7.0))
|
||||
_phase_frame += 1
|
||||
if _phase_frame == 48:
|
||||
_snap("anim_grapple_zip_f")
|
||||
_cam_to(Vector3(2.4, 0.9, -0.4))
|
||||
elif _phase_frame >= 50:
|
||||
_snap("anim_grapple_zip_s")
|
||||
return true
|
||||
return false
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user