summerengine/summer/animation-tree

Claude Code and agent skill by summerengine/summer

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Wire AnimationTree state machines and blend trees: locomotion blends, attack interrupts, hit reactions.

Install

npx vskill@latest install summerengine/summer/animation-tree

SKILL.md

AnimationTree — Wire Clips Into Behavior

This skill generates nothing. It composes existing clips on an AnimationPlayer into an AnimationTree with a StateMachine for distinct states (idle / attack / hit / dead) and BlendSpace nodes for continuous parameters (walk-run blend by speed). Wired wrong, you get T-poses, snapping, or clips that cut each other off.

The canonical Summer Engine stack:

Character (CharacterBody3D)
├── Skeleton3D (from the rigged GLB)
├── AnimationPlayer # holds the raw clips (idle, walk, run, attack_sword, ...)
└── AnimationTree # references AnimationPlayer, drives behavior

Before adding the tree, prove direct AnimationPlayer.play() visibly moves the rig. The engine may import animation GLBs as AnimationLibrary resources or as PackedScenes with embedded AnimationPlayers. If a copied clip appears in the list but the mesh T-poses, its tracks probably target a different root/skeleton path. Use the embedded player that came with the matching GLB, or remap/copy tracks only after confirming they target the active Skeleton3D. An inactive AnimationTree is also a no-op, so always set AnimationTree.active = true.

When to use this skill

  • Character has 3+ clips and switching between them via AnimationPlayer.play("walk") is causing pops, T-poses, or "starts halfway through".
  • Need a walk → run blend that's continuous, not boolean.
  • Need attack to interrupt locomotion but only on the upper body, while feet keep walking.
  • Need hit-react one-shot that returns to whatever state was playing before.
  • Building any non-trivial NPC or player character past the prototype phase.

When NOT to use this skill

  • Single-clip looping background prop (banner waving, water surface). Just AnimationPlayer.play("loop") is fine.
  • Cinematic with hand-authored timing. Use AnimationPlayer directly with play_with_capture; AnimationTree is overkill.
  • Character has zero clips — generate first via generate-motion.
  • Procedural-only animation (no clips, all IK). That's procedural-animation.

Canonical state machine

This is the production-default for a humanoid character that can move, attack, take a hit, and die. Start here, prune what you don't need, extend with named states for specials.

┌─────────┐
┌─────►│ Dead │ (terminal — no exit)
│ └─────────┘
│ ▲
│ │ on_death
┌──────────┴───────────┴──────────┐
│ Hit │ (one-shot, returns to prev)
└──────────▲──────────────────────┘
│ on_damaged ┌──────────┐
┌──────────┴──────────┐ │ Attack │ (one-shot)
│ Locomotion │ on_attack ──────►└──────────┘
│ (BlendSpace1D) │ ◄────── on_attack_done
│ idle ↔ walk ↔ run │
└─────────────────────┘
  • Locomotion is a single AnimationNodeBlendSpace1D parameterized by move_speed (0..max). Three points on the line: idle (0), walk (~3), run (~6). Crossfades for free.
  • Attack is an AnimationNodeOneShot wrapping attack_sword (or whichever clip). Fires from the script via parameters/attack/request = ONE_SHOT_REQUEST_FIRE. Auto-returns to Locomotion when the clip ends.
  • Hit same pattern, higher priority — interrupts attack. Fires on damaged signal.
  • Dead is a transition-target with no exit. Trigger via state_machine.travel("dead").

The fast lane: script the wiring (preferred)

Before walking the CRUD steps below, check the ctx lane — one summer_run_script call replaces the add-node/set-prop chain AND the hand-written .tres:

  • Animation-tier engines: ctx.anim_state_machine(character, {states: {...}, transitions: [[from, to, {blend_s: 0.2}], ...], start: "idle"}) gets-or-creates the AnimationTree, wires it to the player, builds the state machine, sets active = true. Unknown clip names come back as a report entry listing the player's REAL clips — never wire against guessed names. Full recipe (inspect clips/bones first, method-track events, root motion, playtest verification): character-animation-wiring.
  • Any engine (raw fallback): the same classes are fully script-bound — AnimationNodeStateMachine.add_node(name, AnimationNodeAnimation), add_transition(from, to, AnimationNodeStateMachineTransition) (one transition resource each, xfade_time on it), then tree.anim_player = tree.get_path_to(player), tree.tree_root = sm, tree.active = true, and ctx.set_owner_recursive(tree). The raw script is quoted in character-animation-wiring.

Scope: anim_state_machine builds clip states + transitions only. The canonical machine below — BlendSpace1D locomotion, OneShot attack/hit — still needs the .tres (or raw-script) lane for those node types. Discrete idle/walk/run backbones don't.

Steps via MCP

1. Inspect the current character
summer_get_scene_tree
summer_inspect_node "./World/Goblin"

Confirm there's a Skeleton3D and an AnimationPlayer with at least idle + walk + run populated. If clips are missing, route to generate-motion.

2. Add the AnimationTree
summer_add_node(scenePath: "res://main.tscn", parent: "./World/Goblin", type: "AnimationTree", name: "AnimationTree")
summer_set_prop(scenePath: "res://main.tscn", path: "./World/Goblin/AnimationTree", key: "anim_player", value: "../AnimationPlayer")
summer_set_prop(scenePath: "res://main.tscn", path: "./World/Goblin/AnimationTree", key: "active", value: "true")

scenePath is a required argument on every scene-mutating tool (summer_add_node, summer_set_prop, summer_set_resource_property, summer_connect_signal, summer_save_scene, summer_instantiate_scene, summer_remove_node, summer_replace_node). Omit it and the call is rejected before it reaches the engine. Get the path from summer_get_project_context.

anim_player must be a relative NodePath; ../AnimationPlayer from a sibling. Don't use absolute paths — breaks scene reuse.

3. Build the tree resource

The tree_root is a single AnimationNodeStateMachine. Inside it, a Locomotion node which is itself an AnimationNodeBlendSpace1D, plus Attack, Hit, Dead nodes.

Three ways to build it, in preference order: (1) a summer_run_script GDScript constructing the node graph via the script-bound API — transactional, no hand-parsed text format (see the fast-lane section above; BlendSpace1D and OneShot nodes are AnimationNodeBlendSpace1D.new() + add_blend_point(...) and AnimationNodeOneShot.new() with fadein_time/fadeout_time, composed with the same add_node/add_transition calls); (2) hand-writing the .tres below and importing it; (3) summer_set_resource_property node-by-node — the most verbose, use only for touch-ups. The .tres skeleton, for the read/write fallback lane:

[gd_resource type="AnimationNodeStateMachine" load_steps=8 format=3]
[sub_resource type="AnimationNodeBlendSpace1D" id="locomotion"]
blend_point_0/node = SubResource("idle_clip")
blend_point_0/pos = 0.0
blend_point_1/node = SubResource("walk_clip")
blend_point_1/pos = 3.0
blend_point_2/node = SubResource("run_clip")
blend_point_2/pos = 6.0
min_space = 0.0
max_space = 6.0
[sub_resource type="AnimationNodeOneShot" id="attack_oneshot"]
fadein_time = 0.05
fadeout_time = 0.10
mix_mode = 0 # 0=BLEND, 1=ADD
[sub_resource type="AnimationNodeOneShot" id="hit_oneshot"]
fadein_time = 0.0 # snap in — hit reactions are immediate
fadeout_time = 0.15
# ... AnimationNodeAnimation sub_resources point at clip names ...
[resource]
states/Locomotion/node = SubResource("locomotion")
states/Attack/node = SubResource("attack_oneshot")
states/Hit/node = SubResource("hit_oneshot")
states/Dead/node = SubResource("dead_clip")
transitions = [
"Locomotion", "Attack", SubResource("trans_to_attack"),
"Attack", "Locomotion", SubResource("trans_back"),
"Locomotion", "Hit", SubResource("trans_to_hit"),
"Attack", "Hit", SubResource("trans_to_hit"),
"Hit", "Locomotion", SubResource("trans_back"),
"Locomotion", "Dead", SubResource("trans_to_dead"),
"Attack", "Dead", SubResource("trans_to_dead"),
"Hit", "Dead", SubResource("trans_to_dead")
]
graph_offset = Vector2(0, 0)

Each AnimationNodeStateMachineTransition carries xfade_time (0.15 default), switch_mode (Immediate / Sync / At End), and advance_mode (Auto / Enabled / Disabled).

Ask before writing the .tres: "May I write res://characters/goblin/animation_tree.tres with the canonical 4-state machine + Locomotion blendspace?"

4. Drive it from the controller script
gdscript
@onready var tree: AnimationTree = $AnimationTree
@onready var sm: AnimationNodeStateMachinePlayback = tree["parameters/playback"]
func _physics_process(delta: float) -> void:
var horiz_speed: float = Vector2(velocity.x, velocity.z).length()
tree.set("parameters/Locomotion/blend_position", horiz_speed)
func attack() -> void:
tree.set("parameters/Attack/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE)
func take_hit() -> void:
tree.set("parameters/Hit/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE)
func die() -> void:
sm.travel("Dead")

tree.set(...) writes a parameter path. sm.travel(...) switches state.

5. Connect signals
summer_connect_signal(
scenePath: "res://main.tscn",
emitter: "./World/Goblin",
signal: "damaged",
receiver: "./World/Goblin",
method: "take_hit"
)

The parameters are emitter and receiver — not source/target, not from/to. Those spellings fail schema validation.

If the goblin has an HP component emitting died, connect that to die.

6. Save & verify
summer_save_scene(scenePath: "res://main.tscn")
summer_get_script_errors(path: "res://scripts/goblin.gd")

summer_get_script_errors checks one file and requires path; there is no "check everything" form. For a project-wide sweep use summer_get_diagnostics.

Then summer_play and watch — the goblin should idle in place, blend up to walk/run as it moves, fire attack on input/AI, snap to hit on damage, end in Dead.

Confirmation gates

  • Before writing the `.tres`: show the state list and transition table.
  • Before connecting signals: show source → target → method.
  • Before pressing play: state which behaviors should be visible (idle plays, walk crossfades, attack interrupts).

Reference card

Parameter paths cheat-sheet
GoalPath
Set blendspace1D positionparameters/Locomotion/blend_position
Fire a OneShotparameters/<NodeName>/request (set to ONE_SHOT_REQUEST_FIRE)
Abort a OneShotparameters/<NodeName>/request = ONE_SHOT_REQUEST_ABORT
Read OneShot active stateparameters/<NodeName>/active
Get StateMachine playbackparameters/playback (returns AnimationNodeStateMachinePlayback)
Switch statestate_machine.travel("StateName")
Read current statestate_machine.get_current_node()
Force state without transitionstate_machine.start("StateName")
Transition switch_mode decision
  • Immediate (0): snap to new state at xfade. Use for hit reactions, deaths.
  • Sync (1): match current play position. Use for walk ↔ run (avoids foot-pop mid-stride).
  • At End (2): finish current clip first. Use for attack-string combos (attack_1 → attack_2 only at end of attack_1).
Pitfalls

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
85
Repository forks
7
7-day trend score
21

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/summerengine/summer/animation-tree/badge)](https://verified-skill.com/skills/summerengine/summer/animation-tree)

Something wrong with this skill or its page? Tell us.