Claude
Skills
Sign in
Back

godot-animation-tree-mastery

Included with Lifetime
$97 forever

Expert patterns for AnimationTree including StateMachine transitions, BlendSpace2D for directional movement, BlendTree for layered animations, root motion, transition conditions, advance expressions, and state machine sub-states. Use for complex character animation systems with movement blending and state management. Trigger keywords: AnimationTree, AnimationNodeStateMachine, BlendSpace2D, BlendSpace1D, BlendTree, transition_request, blend_position, advance_expression, AnimationNodeAdd2, AnimationNodeBlend2, root_motion.

Generalscripts

What this skill does


# AnimationTree Mastery

Expert guidance for Godot's advanced animation blending and state machines.

## NEVER Do

- **NEVER call `play()` on AnimationPlayer when using AnimationTree** — AnimationTree controls the player. Directly calling `play()` causes conflicts and jitter. Use `set("parameters/transition_request")` or `travel()` instead [12].
- **NEVER forget to set `active = true`** — AnimationTree is inactive by default. Animations won't play until `$AnimationTree.active = true` [13].
- **NEVER use absolute paths for parameter access** — Use relative paths like `"parameters/StateMachine/transition_request"`. This ensures compatibility when nodes move in the hierarchy [14].
- **NEVER leave `auto_advance` enabled for interactive states** — It causes immediate transitions. Use it only for automated sequences like combo chains or death-to-respawn [15, 121].
- **NEVER use `BlendSpace2D` for 1D blending** — Blending only speed? Use `BlendSpace1D`. Blending only two states? Use `Blend2`. `BlendSpace2D` is specifically for X+Y directional inputs (strafe) [16, 142].
- **NEVER update `AnimationTree` parameters every frame without a guard** — Setting parameters via `set()` every frame regardless of change causes cache invalidation and potential stutter. Check equality first.
- **NEVER use deep, nested `BlendTrees` for simple logic** — Every layer adds CPU overhead. If logic can be handled in a `StateMachine` or a simple script-driven `Blend2`, do it there.
- **NEVER forget to handle `await get_tree().process_frame` when updating parameters synchronously** — Sometimes the tree needs one frame to reconcile state before the next parameter change takes effect.
- **NEVER rely on `auto_advance` for long cutscenes** — If an animation is interrupted, `auto_advance` can put the character in a broken state. Use `Method Tracks` to signal state completion instead.
- **NEVER use `Sync` groups for animations with wildly different lengths** — It forces one animation to play at an extreme speed. Use `TimeScale` or separate layers for mismatching cycles.

---

## Available Scripts

> **MANDATORY**: Read the appropriate script before implementing the corresponding pattern.

### [sync_parameter_manager.gd](scripts/sync_parameter_manager.gd)
Expert management of `AnimationTree` parameters with guards to prevent redundant updates and GPU cache churn.

### [reactive_oneshot_vfx.gd](scripts/reactive_oneshot_vfx.gd)
Using `AnimationNodeOneShot` for high-priority reactive animations like recoil, blinks, and hit reactions.

### [dynamic_timescale_control.gd](scripts/dynamic_timescale_control.gd)
Runtime manipulation of playback speed for bullet-time effects or movement haste multipliers.

### [advanced_transition_masking.gd](scripts/advanced_transition_masking.gd)
Procedural bone filtering (masking) for nodes like `Add2` to separate upper/lower body animations.

### [statemachine_travel_code.gd](scripts/statemachine_travel_code.gd)
Programmatic control of `AnimationNodeStateMachinePlayback` using `travel()` and `start()`.

### [blendtree_logic_mixing.gd](scripts/blendtree_logic_mixing.gd)
Complex mixing patterns for `BlendTree` nodes to create interactive combat layers.

### [root_motion_animtree_sync.gd](scripts/root_motion_animtree_sync.gd)
Expert 3D CharacterBody motion extraction optimized specifically for `AnimationTree` nodes.

### [sync_group_layering.gd](scripts/sync_group_layering.gd)
Using Sync Groups to keep multi-layered animations (e.g. walk and reload) perfectly aligned.

### [nested_tree_architecture.gd](scripts/nested_tree_architecture.gd)
Pattern for managing hierarchical State Machines and nested node parameter paths.

### [runtime_tree_debugging.gd](scripts/runtime_tree_debugging.gd)
Interactive tool for visualizing current states, transition paths, and blend values in real-time.

---

## Core Concepts

### AnimationTree Structure

```
AnimationTree (node)
  ├─ Root (assigned in editor)
  │   ├─ StateMachine (common)
  │   ├─ BlendTree (layering)
  │   └─ BlendSpace (directional)
  └─ anim_player: NodePath → points to AnimationPlayer
```

### Parameter Access

```gdscript
# Set parameters using string paths
$AnimationTree.set("parameters/StateMachine/transition_request", "run")
$AnimationTree.set("parameters/Movement/blend_position", Vector2(1, 0))

# Get current state
var current_state = $AnimationTree.get("parameters/StateMachine/current_state")
```

---

## StateMachine Pattern

### Basic Setup

```gdscript
# Scene structure:
# CharacterBody2D
#   ├─ AnimationPlayer (has: idle, walk, run, jump, land)
#   └─ AnimationTree
#       └─ Root: AnimationNodeStateMachine

# StateMachine nodes (created in AnimationTree editor):
# - Idle (AnimationNode referencing "idle")
# - Walk (AnimationNode referencing "walk")
# - Run (AnimationNode referencing "run")
# - Jump (AnimationNode referencing "jump")
# - Land (AnimationNode referencing "land")

@onready var anim_tree: AnimationTree = $AnimationTree
@onready var state_machine: AnimationNodeStateMachinePlayback = anim_tree.get("parameters/StateMachine/playback")

func _ready() -> void:
    anim_tree.active = true

func _physics_process(delta: float) -> void:
    var velocity := get_velocity()
    
    # State transitions based on gameplay
    if is_on_floor():
        if velocity.length() < 10:
            state_machine.travel("Idle")
        elif velocity.length() < 200:
            state_machine.travel("Walk")
        else:
            state_machine.travel("Run")
    else:
        if velocity.y < 0:  # Rising
            state_machine.travel("Jump")
        else:  # Falling
            state_machine.travel("Land")
```

### Transition Conditions (Advance Expressions)

```gdscript
# In AnimationTree editor:
# Add transition from Idle → Walk
# Set "Advance Condition" to "is_walking"

# In code:
anim_tree.set("parameters/conditions/is_walking", true)

# Transition fires automatically when condition becomes true
# Useful for event-driven transitions (hurt, dead, etc.)

# Example: Damage transition
anim_tree.set("parameters/conditions/is_damaged", false)  # Reset each frame

func take_damage() -> void:
    anim_tree.set("parameters/conditions/is_damaged", true)
    # Transition to "Hurt" state fires immediately
```

### Auto-Advance (Combo Chains)

```gdscript
# In AnimationTree editor:
# Transition from Attack1 → Attack2
# Enable "Auto Advance" (no condition needed)

# Code:
state_machine.travel("Attack1")
# Attack1 animation plays
# When Attack1 finishes, automatically transitions to Attack2
# When Attack2 finishes, transitions to Idle (next auto-advance)

# Useful for:
# - Attack combos
# - Death → Respawn
# - Cutscene sequences
```

---

## BlendSpace2D (Directional Movement)

### 8-Way Movement

```gdscript
# Create BlendSpace2D in AnimationTree editor:
# - Add animations at positions:
#   - (0, -1): walk_up
#   - (0, 1): walk_down
#   - (-1, 0): walk_left
#   - (1, 0): walk_right
#   - (-1, -1): walk_up_left
#   - (1, -1): walk_up_right
#   - (-1, 1): walk_down_left
#   - (1, 1): walk_down_right
#   - (0, 0): idle (center)

# In code:
func _physics_process(delta: float) -> void:
    var input := Input.get_vector("left", "right", "up", "down")
    
    # Set blend position (AnimationTree interpolates between animations)
    anim_tree.set("parameters/Movement/blend_position", input)
    
    # BlendSpace2D automatically blends animations based on input
    # input = (0.5, -0.5) → blends walk_right and walk_up
```

### BlendSpace1D (Speed Blending)

```gdscript
# For walk → run transitions
# Create BlendSpace1D:
#   - Position 0.0: walk
#   - Position 1.0: run

func _physics_process(delta: float) -> void:
    var speed := velocity.length()
    var max_speed := 400.0
    var blend_value := clamp(speed / max_speed, 0.0, 1.0)
    
    anim_tree.set("parameters/SpeedBlend/blend_position", blend_value)
    # Smoothly blends from walk → run as speed increases
```

---

## BlendTree (Layered Animations)

### Add Upper Body Anim

Related in General