Scenario Godot Animation
From skills by @scenario-labs · View on GitHub
Use when animating characters or scenes in Godot 4.7: AnimationPlayer clips and RESET, AnimationTree state machines and blend spaces built in code, Mixamo or glTF import, loop settings, BoneMap retargeting and shared animation libraries, root motion, foot sliding, foot IK with TwoBoneIK3D or custom SkeletonModifier3D, tweens, SpringArm3D cameras, cutscenes; or when a character T-poses, slides, floats, freezes mid-blend or a transition never fires.
This skill ships inside the skills package. Install the package to get this skill plus everything else in the bundle.
sv install scenario-labs/skillsGodot animation (animator and technical animator)
Target: Godot 4.7.2.stable, macOS, Forward+ with Jolt Physics. Every procedure in references/procedures.md was run live in 4.7.2 on 2026-10-02 against a procedurally built humanoid, so nothing needs a download.
Stance
- Measure motion, do not eyeball it. A walk has one correct ground speed: the speed at which the planted foot stops sliding. Measure it from the clip (
anim_motion.gd:foot_speed) and use that number for BlendSpace points and the controller. On a retargeted body the speed scales withSkeleton3D.motion_scale(a 1.15x body walked 1.38 m/s on a 1.2 m/s clip). - Code decides, the animation layer presents. Gameplay owns velocity and state; the AnimationTree only reads them through advance expressions or parameters. The controller must still work with the tree removed (Fair Fight, jzuvd0Lstuw 00:05:46).
- Look at exact times. Seek or step to fixed times and open a contact sheet (
gd_anim.pose_sheet). Real-time captures land on random frames. - A layer must say which bones it owns. Partial clips do not stay partial: glTF export pads every clip with every animated joint, and an unfiltered OneShot under the default deterministic blending snaps every unlisted bone to RESET. Filters, not missing tracks, define layers.
- Choose the driver per motion. Velocity-driven locomotion with in-place clips keeps control responsive; root motion suits one-shot moves (dodge, climb, vault) where the feet must stay planted [added]. A tree suits a normal locomotion set; very large move sets or many procedural layers favor code plus SkeletonModifier3D layers (the experts disagree: Fair Fight against trees, xpoPfUKI9tw; RiftOfNostaria, Bitlytic and Chris for them). Tweens for runtime values with unknown starts, AnimationPlayer for authored multi-property curves (Queble, KUyQzjpRsU8 00:01:48).
- Silent failures are the norm here. A BoneMap naming bones that do not exist, an expression transition at advance mode Enabled, a missing RESET key: each produced no error in 4.7.2. Audit after every import and every tree change. If a sibling skill named here is missing from your available skills, ask the user to install it (
npx skills add scenario-labs/skills --skill <name>); unattended, proceed from tool schemas and flag the gap.
Establish first
Ask once, then run:
- Source of motion: Mixamo or other FBX, Blender glTF, mocap, hand-keyed in Godot, procedural only.
- Rig: humanoid (SkeletonProfileHumanoid fits) or custom; T-pose or A-pose rest; real Root bone or not.
- Locomotion model: velocity-driven (in-place clips) or root motion; target speeds if the design fixes them.
- Layers needed: upper-body actions, aim or look-at, foot IK on uneven ground, hit reactions.
- Characters sharing clips, and their height range.
- Cutscenes: length, camera count, whether gameplay must be frozen, dialogue system in use.
- Physics tick, renderer and target hardware (for IK and modifier cost).
Workflow with gates
- Kit and project.
gd_anim.install_animkit(P); confirmproject.godothas Jolt for 3D and the brief's renderer. Gate: the kit's jobs returnok. - Import. Name cycles with a loop hint (
Walk-loop), import once, then set options in the.importfile (gd_anim.set_import_options, P3) and run--import. Turn onanimation/import_rest_as_RESET. Gate:gd_anim.audithas no flags; every cycle loops; RESET exists. - Retarget. Build the BoneMap from the imported bone names (
anim_import.gd:make_bonemap, P4);:becomes_on import. Gate: skeleton is%GeneralSkeleton, bones renamed, sampled poses within 2 mm of the source. - Share. Import clips As Animation Library, give each body its own BoneMap, attach with
anim_setup.gd:share_library(P5). Gate: 0 unresolved tracks, identical track layout. - Speeds.
foot_speedper cycle and per body (P9). Gate: controller and BlendSpace speeds within 2 percent. - Tree. Build in code (
anim_tree.gd, P6): BlendTree root, StateMachine inside, BlendSpace1D with sync, OneShot with a filter, TimeScale. Gate:drivewith scripted input gives the expected state list and times; tree audit clean. The transition helper that passed the drive test:
static func transition(expr: String, at_end: bool, xfade: float) -> AnimationNodeStateMachineTransition:
var t := AnimationNodeStateMachineTransition.new()
t.xfade_time = xfade
t.advance_mode = AnimationNodeStateMachineTransition.ADVANCE_MODE_AUTO # default Enabled never auto-fires
if at_end:
t.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_AT_END
if expr != "":
t.advance_expression = expr # read on tree.advance_expression_base_node
return t
# sm.add_transition(&"Ground", &"Fall", transition("not on_floor and not jump_pressed", false, 0.2)) Drive it from gameplay with tree.set("parameters/sm/Ground/blend_position", speed) and the state node's properties; fire actions with tree.set("parameters/act/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE).
- Layers and IK. OneShot filters (P7), FootPlacer plus TwoBoneIK3D (P10). Gate: foot within 2 cm of the target on a step, a hole and a 15 deg slope, read in
skeleton_updated. Modifiers run in child order, so the placer (targets and hips) goes before the solver:
var ik := TwoBoneIK3D.new()
skeleton.add_child(ik) # after FootPlacer
ik.setting_count = 2
for i in 2:
var side: String = ["Left", "Right"][i] # := cannot infer from an array index
ik.set_root_bone_name(i, side + "UpperLeg")
ik.set_middle_bone_name(i, side + "LowerLeg")
ik.set_end_bone_name(i, side + "Foot")
ik.set_target_node(i, ik.get_path_to(targets[i]))
ik.set_pole_node(i, ik.get_path_to(knee_poles[i])) # 0.6 m in front of the knee
ik.set_pole_direction(i, SkeletonModifier3D.SECONDARY_DIRECTION_PLUS_Z)
skeleton.skeleton_updated.connect(_check_feet) # final pose, after every modifier and influence Lower influence while running and set it to 0 in the air, so authored motion shows through (Lukky, MbaPDWfbNLo 00:09:30).
- Camera and cutscene. SpringArm3D with a sphere shape (P12); one AnimationPlayer timeline (P13). Gate: call order and times, camera at checkpoints,
animation_finished, section playback. - Look. Pose sheets: T-pose front and side, 8 walk phases, each state, IK terrain, cutscene timeline (P14). Gate: every sheet opened and described.
Expert checklist (state these in the plan)
- Modifier output is rolled back every frame (Fair Fight XFZQNsFejwk 00:02:04; P10 reads the animated pose a frame later). Fine for post effects; when modifiers own the pose, put
pose_persist.gdwith role RESTORE first and SNAPSHOT last: an incremental 5 deg modifier then reached 60 deg in 12 frames instead of staying at 5 (P16). RESTORE overrides the animation, so never use it on top of a playing clip. - IK solves position only. TwoBoneIK3D left the foot 12.1 deg off a 15 deg slope; a
CopyTransformModifier3Dafter it (rotation only, reference type Node, an align node with the ground tilt) brought it to 0.0 deg without moving the foot (P16; Lukky MbaPDWfbNLo 00:07:49). - Deterministic IK solves from scratch each frame; a one-iteration chain needs the angle limit at the full 180 deg (Lukky 00:04:46).
deterministicdefaults to false on IterateIK3D; turn it on for stepping legs (MeroDev 17xi4vDqQJk 00:02:28). - Bezier and property tracks cannot blend (docs): the tree blends value, position, rotation and scale tracks.
- Call Method tracks do not run in the editor preview (docs): test them in a run (P13).
- Capture update mode blends from the current value to the first key: a property at 5 with a key 10 at 0.5 s read 7.5 at 0.25 s under
play_with_capture(P16). - Cache
find_trackin a path-to-index map when scripts sample many tracks: Fair Fight measured 1.5 to 3x on large rigs (XFZQNsFejwk 00:04:03 to 00:06:45); on the 22-track test rig in GDScript, 1.2x (P16). It needs one track layout across clips, so turn offanimation/remove_immutable_tracks. - Gate one-shots on
animation_finishedplus a flag, or the next frame's locomotion call replaces the hit clip (DevWorm GMyw3eHDw7s 00:22:58). Measured: "hurt" held for its 0.4 s, then walk resumed (P16). - Blender source. Rigify Limb Segments 1 and Game Friendly single root before export (CoderNunk 0wMONEpEXpQ 00:00:19, 00:01:23).
nodes/apply_root_scale(default true) bakes import scale into meshes and clips, root stays at 1 (docs). - BlendSpace2D for 4-way sprites: points at plus or minus 1, axis range plus or minus 1.1 so diagonals snap to left and right; 2D up is negative Y (Chris WrMORzl3g1U 00:09:20, 00:13:09).
Numbers
| What | Value | Source |
|---|---|---|
| Human walk, run ground speed | about 1.2 to 1.5 m/s walk, 3 to 4 m/s jog [added] | measure the clip instead (P9) |
| No-slide speed measured | walk 1.200, run 3.504 m/s on clips built for 1.2 and 3.5 | P9 |
| Foot-slide tolerance | planted foot under 0.1 m/s; speeds within 2 percent [added] | baseline G5, P9 |
| Locomotion crossfade | 0.1 to 0.2 s; landing 0.05 s; Land to Ground at end with 0.15 s | P6 |
| Import FPS | 30 (default animation/fps) | importer |
motion_scale after Normalize Position Tracks | source hip height (0.97 m test rig) | P4 |
| IK foot error | under 2 cm required, under 0.1 mm measured | P10 |
| FootPlacer ray | 0.5 m above to 1.0 m below the animated foot; hips drop clamp 0.35 m | P10 |
| Spring arm | sphere r 0.2 stops 0.2 m plus margin 0.01 m short of a wall | P12 |
| IterateIK3D defaults | max_iterations 4, min_distance 0.001, angular_delta_limit 2 deg, deterministic false | class reference |
| Tween stagger | 0.05 s per item, 0.2 s per tween | P11; aagPe4dvM2M 00:04:04 |
| Modifier cost | Fair Fight halved a 2 ms overlay with incremental global transforms | XFZQNsFejwk 00:14:07 |
Quality gates
Score with references/critique.md; record each live check as "run in Godot 4.7.2 on <date>: pass, key numbers" and mark anything not run "not yet run" with the reason. Blocking: every track resolves; cycles loop and RESET covers every animated path; speeds match the measured no-slide speeds; the tree passes a scripted drive; a pose sheet was opened. Also: no overwrite_axis or other keys missing from 4.7.2 in .import edits; no SkeletonIK3D in new code; IK results read in skeleton_updated or modification_processed, never from _process.
Common mistakes
- BoneMap with
mixamorig:names. Godot importsmixamorig_Hips; the map matches nothing, the skeleton is still renamed GeneralSkeleton, every library track fails to resolve, and nothing is printed. Build the map from imported names; the audit flags a GeneralSkeleton with no Hips. - Expression transitions left at advance mode Enabled. They never fire. Set
ADVANCE_MODE_AUTO(2) on every transition driven by an expression or condition. - Two auto transitions true in one frame. A jump that clears
on_floorwent Ground to Fall, skipping JumpUp. Guard the expression (not on_floor and not jump_pressed) or give the jump transition priority 0. - Unfiltered OneShot or Add with a partial clip. Deterministic (the AnimationTree default): unlisted bones go to RESET, the character T-poses. Non-deterministic: they freeze. Filter the node.
- No RESET. Imported files have none by default; blends and saved scenes then start from rest or zero.
animation/import_rest_as_RESET, or author RESET upstream (Blargis, ghYilg9cq-I 00:02:39). - Reading IK results in
_process. You get the animated pose. Read inSkeleton3D.skeleton_updated(final, after influence) or in the modifier'smodification_processed(100 percent, before influence). - Root motion on Hips. It wobbles; insert a real Root (FinePointCGI, fq0hR2tIsRk 00:04:35). Velocity is
basis * get_root_motion_position() / delta. - Speed constants on retargeted bodies. A taller body has a longer stride through
motion_scale; re-measure per body. - Tweens without a kill guard. Two tweens on one property both write every frame (Queble, KUyQzjpRsU8 00:02:54). Keep a reference and
kill()it. - Method tracks in a headless or skipped cutscene. Default Deferred calls wait for a frame; a loop that never yields fires them all at the end. Use Immediate, or yield frames.
- Editing tree nodes at runtime. They are shared by every instance of the scene; per-character values belong in
parameters/.... - Ray-only spring arm. The camera lands on the wall plane; give the arm a sphere shape.
- Cameras keyed without a look target. A cutscene camera whose rotation keys drift lost the hero for the last 5 s; check framing on the timeline sheet, not on the first and last key.
- Library loaded on a body with its own clips. Skip Import on the second character's AnimationPlayer so only the shared library plays (Quilled, nb6uSeEZFCI 00:04:36).
Handoffs
- scenario-godot-expert: run protocol,
gd_run, capture and review tools. Read it first. - scenario-godot-gameplay: the CharacterBody3D controller and the state the tree reads; hitboxes from method tracks.
- scenario-godot-3d-world: collision layers for FootPlacer and spring arm; IK test geometry.
- scenario-godot-pipeline-automation: batch import and
.importedits at scale, CI runs. - scenario-godot-ui, scenario-godot-vfx, scenario-godot-audio: menu motion beyond tweens; effects and sounds keyed from clips and cutscenes.
- scenario-godot-performance-export: modifier and tree cost on target hardware.
- blender- skills:* Root bone, Rigify Game Friendly export, NLA push-down, loop hints at the source.
Godot 4.7 notes
- IK family since 4.6 on SkeletonModifier3D: TwoBoneIK3D, FABRIK3D, CCDIK3D, JacobianIK3D, SplineIK3D; settings are per index (
set_root_bone_name(i, ...),set_target_node(i, path),set_pole_node,set_pole_direction). SkeletonIK3D is deprecated. - Custom modifiers override
_process_modification_with_delta(delta);_process_modification()is deprecated. Do not applyinfluenceyourself. retarget/rest_fixer/retarget_method(0 None, 1 Overwrite Axis default, 2 Use Retarget Modifier) replaces anyoverwrite_axiskey; RetargetModifier3D exists for runtime retargeting (not run here).tween_await(signal).set_timeout(s)is new in 4.7;tween_subtweensince 4.4.- Animation markers and
play_section_with_markers(4.3+);Animation.lengthis a double in 4.7; animation names are StringName. - AnimationMixer is the base of AnimationPlayer and AnimationTree:
root_motion_track,deterministic(false on the player, true on the tree),callback_mode_process,callback_mode_method. - Observed in 4.7.2 (P6):
priority(lower wins) also decides between auto transitions that are true in the same frame, not onlytravel()paths;travel()called before the firstadvance()starts the machine in that state without error; a transition's defaultadvance_modeis Enabled (travel only). - Observed (P10):
modification_processedreports the modifier's result beforeinfluence;skeleton_updatedgives the blended final pose; outside both, bone poses read as animated. - Observed (P3): a hand-edited
.importis applied by a plain--import; loop-hint suffixes are stripped from clip names, aloopprefix is kept;.in clip names becomes_. - Skeleton3D refuses
:and/in bone names; its error string for that is malformed in 4.7.2 (logged intests/OPEN_ISSUES.md).
References
references/procedures.md(P1 to P16, live results, not yet run),expert-notes.md(attributed claims),critique.md(rubric, scored G5 baseline),gui-paths.md(editor clicks),sources.md.scripts/gd_anim.py: Python helpers (install, run, rig, pose sheets, audit, GLB edits,.importedits, bone sampling).scripts/agentkit/animation/:anim_rig,anim_audit,anim_capture,anim_import,anim_setup,anim_tree,anim_motion,anim_ik,anim_tween,anim_cutscene, plusfoot_placer.gd,locomotion_state.gd,cutscene_director.gd,pose_persist.gd.- Tests:
tests/code/godot-animation/test_anim_live.py(A1 to A9, W) andtest_anim_offline.py.