Refactor animation generation: replace create_walk.gd with create_animations.gd
- Removed the old create_walk.gd script, which generated walk animations. - Introduced create_animations.gd to unify the generation of walk_left, walk_right, and stand_up animations. - Added a new SpinBox for rest timeout configuration in physics_test_harness.gd. - Enhanced StickmanRig to support automatic recovery from ragdoll state with configurable timeout. - Implemented recovery logic in StickmanRig, allowing for smooth transitions from ragdoll to animated state. - Updated animation generation logic to use new pose templates for standing and lying down positions.
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
@tool
|
||||
extends EditorScript
|
||||
|
||||
## Unified animation generator: bakes walk_left, walk_right and stand_up into
|
||||
## the open scene's AnimationPlayer. Supersedes the old create_walk.gd.
|
||||
##
|
||||
## Note: runtime ragdoll recovery does NOT play the baked stand_up — StickmanRig
|
||||
## tweens the IK targets directly from the captured ragdoll pose to the standing
|
||||
## pose (a fixed first keyframe can never match an arbitrary rest pose). The
|
||||
## baked stand_up is kept as an authored reference / manual-play animation.
|
||||
|
||||
const STAND_UP_DURATION := 0.8
|
||||
|
||||
## IK target marker node paths (relative to the rig root), keyed by marker name.
|
||||
const POSE_PATHS: Dictionary = {
|
||||
"Torso": "IK_Targets/Torso",
|
||||
"Head": "IK_Targets/Head",
|
||||
"Left_Hand": "IK_Targets/Left_Hand",
|
||||
"Right_Hand": "IK_Targets/Right_Hand",
|
||||
"Left_Leg": "IK_Targets/Left_Leg",
|
||||
"Right_Leg": "IK_Targets/Right_Leg",
|
||||
}
|
||||
|
||||
## Ordered marker names, used to iterate the pose templates deterministically.
|
||||
const POSE_MARKERS: Array[String] = ["Torso", "Head", "Left_Hand", "Right_Hand", "Left_Leg", "Right_Leg"]
|
||||
|
||||
## Pose templates (rig-local). POSE_DOWN is a generic "lying on back" rest pose;
|
||||
## POSE_STANDING matches master_rig.tscn's authored IK-target defaults. The
|
||||
## baked stand_up is an authored reference only — runtime recovery tweens the
|
||||
## IK targets directly from the captured ragdoll pose instead.
|
||||
const POSE_DOWN: Dictionary = {
|
||||
"Torso": { "pos": Vector2(0, 330), "rot": -PI / 2.0 },
|
||||
"Head": { "pos": Vector2(-60, 300), "rot": 0.0 },
|
||||
"Left_Hand": { "pos": Vector2(100, 330), "rot": 0.0 },
|
||||
"Right_Hand": { "pos": Vector2(-100, 330), "rot": 0.0 },
|
||||
"Left_Leg": { "pos": Vector2(90, 300), "rot": 0.0 },
|
||||
"Right_Leg": { "pos": Vector2(-90, 300), "rot": 0.0 },
|
||||
}
|
||||
|
||||
const POSE_STANDING: Dictionary = {
|
||||
"Torso": { "pos": Vector2(0, 10), "rot": 0.0 },
|
||||
"Head": { "pos": Vector2(100, -614), "rot": 0.0 },
|
||||
"Left_Hand": { "pos": Vector2(90, 110), "rot": 0.0 },
|
||||
"Right_Hand": { "pos": Vector2(-90, 110), "rot": 0.0 },
|
||||
"Left_Leg": { "pos": Vector2(-110, 380), "rot": 0.0 },
|
||||
"Right_Leg": { "pos": Vector2(110, 390), "rot": 0.0 },
|
||||
}
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var root = EditorInterface.get_edited_scene_root()
|
||||
if not root:
|
||||
push_error("EditorScript: No active scene open.")
|
||||
return
|
||||
|
||||
var anim_player = root.get_node_or_null("AnimationPlayer") as AnimationPlayer
|
||||
if not anim_player:
|
||||
push_error("EditorScript: AnimationPlayer node not found under root.")
|
||||
return
|
||||
|
||||
_generate_walk_animation(anim_player, "walk_right", 1, false) # FacingProfile.RIGHT (1)
|
||||
_generate_walk_animation(anim_player, "walk_left", 0, true) # FacingProfile.LEFT (0)
|
||||
_generate_pose_animation(anim_player, "stand_up", STAND_UP_DURATION, POSE_DOWN, POSE_STANDING)
|
||||
|
||||
|
||||
func _generate_walk_animation(anim_player: AnimationPlayer, anim_name: String, profile_enum: int, flip_x: bool) -> void:
|
||||
var anim = Animation.new()
|
||||
anim.length = 0.8
|
||||
anim.loop_mode = Animation.LOOP_LINEAR
|
||||
var dir_mult: float = -1.0 if flip_x else 1.0
|
||||
|
||||
# 1. Profile Track (0 = LEFT, 1 = RIGHT)
|
||||
var profile_track = anim.add_track(Animation.TYPE_VALUE)
|
||||
anim.track_set_path(profile_track, ".:facing_profile")
|
||||
anim.value_track_set_update_mode(profile_track, Animation.UPDATE_DISCRETE)
|
||||
anim.track_insert_key(profile_track, 0.0, profile_enum)
|
||||
|
||||
# 2. Keyframe positions
|
||||
var raw_tracks = {
|
||||
"IK_Targets/Torso:position": [Vector2(0, 10), Vector2(0, -15), Vector2(0, 10), Vector2(0, -15), Vector2(0, 10)],
|
||||
"IK_Targets/Head:position": [Vector2(100, -614), Vector2(100, -639), Vector2(100, -614), Vector2(100, -639), Vector2(100, -614)],
|
||||
"IK_Targets/Right_Leg:position": [Vector2(110, 390), Vector2(0, 397), Vector2(-110, 380), Vector2(-20, 320), Vector2(110, 390)],
|
||||
"IK_Targets/Left_Leg:position": [Vector2(-110, 380), Vector2(-20, 320), Vector2(110, 390), Vector2(0, 397), Vector2(-110, 380)],
|
||||
"IK_Targets/Right_Hand:position": [Vector2(-90, 110), Vector2(0, 115), Vector2(90, 110), Vector2(0, 115), Vector2(-90, 110)],
|
||||
"IK_Targets/Left_Hand:position": [Vector2(90, 110), Vector2(0, 115), Vector2(-90, 110), Vector2(0, 115), Vector2(90, 110)]
|
||||
}
|
||||
|
||||
var times = [0.0, 0.2, 0.4, 0.6, 0.8]
|
||||
|
||||
for path in raw_tracks:
|
||||
var track_idx = anim.add_track(Animation.TYPE_VALUE)
|
||||
anim.track_set_path(track_idx, path)
|
||||
anim.track_set_interpolation_type(track_idx, Animation.INTERPOLATION_CUBIC)
|
||||
|
||||
for i in range(times.size()):
|
||||
var orig_pos: Vector2 = raw_tracks[path][i]
|
||||
var mirrored_pos = Vector2(orig_pos.x * dir_mult, orig_pos.y)
|
||||
anim.track_insert_key(track_idx, times[i], mirrored_pos)
|
||||
|
||||
_add_animation(anim_player, anim_name, anim)
|
||||
|
||||
|
||||
## Generates a two-keyframe pose animation (start -> end) keying the 6 IK-target
|
||||
## positions plus the Torso rotation. loop_mode is always LOOP_NONE.
|
||||
func _generate_pose_animation(anim_player: AnimationPlayer, anim_name: String, duration: float, start_pose: Dictionary, end_pose: Dictionary) -> void:
|
||||
var anim = Animation.new()
|
||||
anim.length = duration
|
||||
anim.loop_mode = Animation.LOOP_NONE
|
||||
|
||||
for marker_name: String in POSE_MARKERS:
|
||||
var start_pos: Vector2 = start_pose[marker_name]["pos"]
|
||||
var end_pos: Vector2 = end_pose[marker_name]["pos"]
|
||||
var pos_track = anim.add_track(Animation.TYPE_VALUE)
|
||||
anim.track_set_path(pos_track, "%s:position" % POSE_PATHS[marker_name])
|
||||
anim.track_set_interpolation_type(pos_track, Animation.INTERPOLATION_CUBIC)
|
||||
anim.track_insert_key(pos_track, 0.0, start_pos)
|
||||
anim.track_insert_key(pos_track, duration, end_pos)
|
||||
|
||||
var start_rot: float = start_pose["Torso"]["rot"]
|
||||
var end_rot: float = end_pose["Torso"]["rot"]
|
||||
var rot_track = anim.add_track(Animation.TYPE_VALUE)
|
||||
anim.track_set_path(rot_track, "IK_Targets/Torso:rotation")
|
||||
anim.track_set_interpolation_type(rot_track, Animation.INTERPOLATION_CUBIC)
|
||||
anim.track_insert_key(rot_track, 0.0, start_rot)
|
||||
anim.track_insert_key(rot_track, duration, end_rot)
|
||||
|
||||
_add_animation(anim_player, anim_name, anim)
|
||||
|
||||
|
||||
func _add_animation(anim_player: AnimationPlayer, anim_name: String, anim: Animation) -> void:
|
||||
var lib = anim_player.get_animation_library("")
|
||||
if not lib:
|
||||
lib = AnimationLibrary.new()
|
||||
anim_player.add_animation_library("", lib)
|
||||
|
||||
if lib.has_animation(anim_name):
|
||||
lib.remove_animation(anim_name)
|
||||
|
||||
lib.add_animation(anim_name, anim)
|
||||
print("Successfully generated '%s' animation!" % anim_name)
|
||||
Reference in New Issue
Block a user