feat: Implement Phase 16 - Move To: Walk and Run

- Introduced a new action type `move_to` allowing children to direct a stickman to a destination using either Walk or Run styles.
- Updated the ActionEditor UI to include Walk/Run buttons for selecting movement styles.
- Ensured compatibility with existing `walk_to` actions by converting them to the new `move_to` format during loading.
- Defined clear success criteria for the implementation, including distinct movement animations and behavior for both Walk and Run.
- Established a comprehensive verification contract to ensure functionality across various terrains and scenarios.
This commit is contained in:
2026-10-05 15:04:09 -04:00
parent 36596b2c28
commit 3f66cc3195
19 changed files with 2088 additions and 194 deletions
+57 -4
View File
@@ -1,7 +1,7 @@
@tool
extends EditorScript
## Unified animation generator: bakes walk_left, walk_right and stand_up into
## Unified animation generator: bakes walk_left, walk_right, run_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
@@ -10,6 +10,7 @@ extends EditorScript
## baked stand_up is kept as an authored reference / manual-play animation.
const STAND_UP_DURATION := 2.0
const RUN_ANIMATION_DURATION := 0.75
## IK target marker node paths (relative to the rig root), keyed by marker name.
const POSE_PATHS: Dictionary = {
@@ -58,9 +59,11 @@ func _run() -> void:
push_error("EditorScript: AnimationPlayer node not found under root.")
return
_generate_walk_animation(anim_player, "walk_right", false)
_generate_walk_animation(anim_player, "walk_left", true)
_generate_pose_animation(anim_player, "stand_up", STAND_UP_DURATION, POSE_DOWN, POSE_STANDING)
_generate_run_in_place_animation(anim_player, "run_in_place", false)
_generate_run_in_place_animation(anim_player, "run_right", false)
#_generate_walk_animation(anim_player, "walk_right", false)
#_generate_walk_animation(anim_player, "walk_left", true)
#_generate_pose_animation(anim_player, "stand_up", STAND_UP_DURATION, POSE_DOWN, POSE_STANDING)
func _generate_walk_animation(anim_player: AnimationPlayer, anim_name: String, flip_x: bool) -> void:
@@ -132,3 +135,53 @@ func _add_animation(anim_player: AnimationPlayer, anim_name: String, anim: Anima
lib.add_animation(anim_name, anim)
print("Successfully generated '%s' animation!" % anim_name)
func _generate_run_in_place_animation(anim_player: AnimationPlayer, anim_name: String, flip_x: bool = false) -> void:
var anim = Animation.new()
anim.length = RUN_ANIMATION_DURATION
anim.loop_mode = Animation.LOOP_LINEAR
var dir_mult: float = -1.0 if flip_x else 1.0
# Contact, down, break, and up poses with arms counter-swinging against the legs.
var raw_tracks = {
"IK_Targets/Torso:position": [
Vector2(0, 15), Vector2(0, -30), Vector2(0, 15), Vector2(0, -30), Vector2(0, 15)
],
"IK_Targets/Torso:rotation": [0.30, 0.36, 0.28, 0.20, 0.30],
"IK_Targets/Head:position": [
Vector2(100, -605), Vector2(100, -642), Vector2(100, -605), Vector2(100, -642), Vector2(100, -605)
],
"AnimationTargets/Right_Leg_Base:position": [
Vector2(80, 390), Vector2(-120, 290), Vector2(-80, 260), Vector2(110, 240), Vector2(80, 390)
],
"AnimationTargets/Left_Leg_Base:position": [
Vector2(-80, 260), Vector2(110, 240), Vector2(80, 390), Vector2(-120, 290), Vector2(-80, 260)
],
"IK_Targets/Right_Hand:position": [
Vector2(-250, -205), Vector2(-167, -40), Vector2(35, 0), Vector2(237, -40),
Vector2(320, -225), Vector2(237, -35), Vector2(35, 15), Vector2(-167, -35), Vector2(-250, -205)
],
"IK_Targets/Left_Hand:position": [
Vector2(320, -225), Vector2(237, -30), Vector2(55, 15), Vector2(-167, -35),
Vector2(-250, -205), Vector2(-167, -40), Vector2(55, 0), Vector2(237, -40), Vector2(320, -225)
]
}
var times = [0.0, 0.1875, 0.375, 0.5625, RUN_ANIMATION_DURATION]
var arm_times = [0.0, 0.09375, 0.1875, 0.28125, 0.375, 0.46875, 0.5625, 0.65625, RUN_ANIMATION_DURATION]
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)
anim.track_set_interpolation_loop_wrap(track_idx, true)
var key_times: Array = arm_times if path.ends_with("_Hand:position") else times
for i in range(raw_tracks[path].size()):
var value: Variant = raw_tracks[path][i]
if value is Vector2:
value = Vector2(value.x * dir_mult, value.y)
anim.track_insert_key(track_idx, key_times[i], value)
_add_animation(anim_player, anim_name, anim)