Animation Tree logo

Animation Tree

Organization
SummerEngine
animation-tree

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

Overview

PublisherSummerEngine
Repositorysummer
Skill nameanimation-tree
Stars
91
Forks
8
Bundled files
1
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 1 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by SummerEngine on GitHub. Read the source before you install it.

Installation

Install the Animation Tree AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/SummerEngine/summer/tree/main/library/skills/animation-tree
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/SummerEngine/summer.git /tmp/summer
mkdir -p .claude/skills
cp -r /tmp/summer/library/skills/animation-tree .claude/skills/animation-tree
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Animation Tree in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Animation Tree on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Animation Tree is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

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

  • Character T-poses on play. AnimationTree.active = true was missed. Set it in _ready() or via inspector.
  • Locomotion blendspace stuck on idle even when moving. The blend_position parameter isn't being written — confirm tree.set("parameters/Locomotion/blend_position", speed) runs every frame in _physics_process. Also confirm the path matches your state name exactly (case-sensitive).
  • Walk-to-run pops. Transition switch_mode is Immediate. Set to Sync so the cycle phase matches across the crossfade.
  • Attack one-shot fires every frame as long as input is held. Use Input.is_action_just_pressed, not is_action_pressed. Or store a _can_attack flag with cooldown.
  • Hit reaction interrupts itself when hit twice in 100ms. Set fadein_time = 0.0 and gate from script: if sm.get_current_node() == "Hit": return.
  • Attack plays but locomotion is invisible (lower body T-pose). OneShot's mix_mode is BLEND (full body). For upper-body-only attacks, set mix_mode = ADD and use an additive clip, or drive the lower body from a second AnimationTree branch and blend by bone with an AnimationNodeBlend2 plus filters. SkeletonModification3D is an old upstream class name and ClassDB.class_exists("SkeletonModification3D") returns false on the current Summer build.
  • Dead state replays from start every time HP < 0. Add a guard in the controller: if is_dead: return.

One-shot vs StateMachine — which to use

  • OneShot: the action is a temporary override of an underlying state (attack while running, hit-react while attacking). The base keeps playing.
  • StateMachine state: the action is a replacement (dead, stunned, ragdoll). Nothing else plays.
  • Action combat is usually OneShot for attacks + a StateMachine state for "dead". Don't model attacks as StateMachine states unless you need precise transition control between attack-string variants.

BlendSpace1D vs BlendSpace2D

  • 1D for speed-only locomotion (idle / walk / run).
  • 2D for omnidirectional movement (idle / forward / strafe-left / strafe-right / back). Eight or nine clips arranged on a unit circle. Necessary for top-down or strafing third-person; overkill for FPS hands.

Anti-patterns

  • Skipping AnimationTree and calling AnimationPlayer.play("walk") from script. Works for one clip; breaks the moment you need a crossfade.
  • Modeling every attack as a StateMachine state. The transition matrix explodes (N states × N transitions). Use one OneShot per attack and switch its inner clip via parameters/Attack/animation.
  • Writing the .tres by hand without using sub_resource IDs — the engine loses the references. Always use [sub_resource id="..."] blocks.
  • Driving blend_position from raw input (Input.get_axis(...)). Drive from actual velocity — that way the blend matches the body's real motion, including knockback and slope-slide.

Edge cases

  • Player rotates 180° instantly while sprinting. The blendspace doesn't know about turning — locomotion looks like the character moonwalks for one frame. Add a small additive turn-lean clip layered on top, or add a brief "turn" state with switch_mode=AtEnd.
  • Animation library uses root motion. Set root_motion_track on the AnimationTree (path to the root bone), then in _physics_process use tree.get_root_motion_position() and apply to the CharacterBody3D. Don't combine with input-driven velocity — pick one.
  • Character has no AnimationPlayer. Add one first. AnimationTree without anim_player target is a no-op.

Fallback (no MCP)

Open the scene in Summer Engine, add an AnimationTree node, set Anim Player to the AnimationPlayer, set Active = on, double-click Tree Root, and edit visually. Drag AnimationNodeStateMachine as the root, drop nodes inside, draw transitions by Shift-dragging, and save the scene. The visual editor produces the same .tres; it is slower but discoverable.

Handoff

  • For NPC behavior firing these states (decide-when-to-attack), hand off to design-npc.
  • For first-person hand animations on the player rig, the same pattern applies — hand off to fps-controller for the state inputs.
  • For procedural overlays on top of the tree (look-at the player while idling), hand off to procedural-animation.
  • For lipsync layered on the face during dialogue states, hand off to facial-and-lipsync.

See also

  • character-animation-wiring — the end-to-end rigged-GLB path: inspect real clip/bone names, ctx.anim_state_machine, method tracks, root motion, playtest verification.
  • generate-motion — produce the clips this tree references.
  • retarget — share one tree across many characters.

From a shell

No Summer MCP tools in this session? Run each one as a command; when summer --version is missing or below 4.0, use npx -y summer-engine@latest in place of summer. Fields are flags in kebab-case (--scene-path res://main.tscn) or one --input '<json>', and summer ops describe <op> prints the exact input. The summer-cli skill has the rest.

In this skillCommand
summer_add_nodesummer engine add-node
summer_connect_signalsummer engine connect-signal
summer_get_diagnosticssummer engine get-diagnostics
summer_get_project_contextsummer engine get-project-context
summer_get_scene_treesummer engine get-scene-tree
summer_get_script_errorssummer engine get-script-errors
summer_inspect_nodesummer engine inspect-node
summer_inspect_resourcesummer engine inspect-resource
summer_instantiate_scenesummer engine instantiate-scene
summer_playsummer engine play
summer_remove_nodesummer engine remove-node
summer_replace_nodesummer engine replace-node
summer_run_scriptsummer engine run-script
summer_save_scenesummer engine save-scene
summer_set_propsummer engine set-prop
summer_set_resource_propertysummer engine set-resource-property
summer_stopsummer engine stop

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Animation Tree AI skill do?

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

Why use Animation Tree on TypingMind?

Because you install it once and use it with any model. Animation Tree is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Animation Tree in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/SummerEngine/summer/tree/main/library/skills/animation-tree. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Animation Tree?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Animation Tree?

As many as you like. As long as a model supports skills, you can use Animation Tree with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Animation Tree AI skill free?

Yes. It is published on GitHub by SummerEngine under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇