Claude-skill-registry godot-setup-animationtree

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/godot-setup-animationtree" ~/.claude/skills/majiayu000-claude-skill-registry-godot-setup-animationtree && rm -rf "$T"
manifest: skills/data/godot-setup-animationtree/SKILL.md
source content

Setup AnimationTree State Machine

Core Principle

Animation is state-driven, not frame-driven. Use AnimationTree to manage animation states, BlendSpaces for smooth parameter blending, and transitions for state changes. Avoid direct AnimationPlayer playback in gameplay code.

What This Skill Does

Sets up complete AnimationTree systems:

  1. AnimationTree Node Structure - Creates the tree node and animation player binding
  2. AnimationNodeStateMachine - Builds state machine graphs with entry/exit states
  3. BlendSpace2D/3D - Configures locomotion blending based on velocity/input
  4. State Transitions - Defines conditions (bool, expression, time-based) for state changes
  5. Blend Trees - Creates complex animation mixing with OneShot, Add2, Blend2 nodes

AnimationTree Setup

Basic AnimationTree Configuration

Before (Direct AnimationPlayer):

# player_animation.gd - Manual animation management
extends CharacterBody2D

@onready var anim_player: AnimationPlayer = $AnimationPlayer

func _physics_process(delta):
    var velocity = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    
    if velocity.length() > 0:
        if not anim_player.current_animation == "run":
            anim_player.play("run")
    else:
        if not anim_player.current_animation == "idle":
            anim_player.play("idle")

After (AnimationTree):

# player_animation.gd - State-driven animation
extends CharacterBody2D

@onready var animation_tree: AnimationTree = $AnimationTree
@onready var playback: AnimationNodeStateMachinePlayback

func _ready():
    animation_tree.active = true
    playback = animation_tree.get("parameters/playback")

func _physics_process(delta):
    var input_dir = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    
    # Update blend position for BlendSpace2D
    animation_tree.set("parameters/blend_position", input_dir)
    
    # Transition states
    if input_dir.length() > 0.1:
        playback.travel("Locomotion")
    else:
        playback.travel("Idle")

Generated Scene:

# player.tscn
[gd_scene load_steps=6 format=3]

[ext_resource type="Script" path="res://player_animation.gd" id="1_abc123"]

[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_idle"]
animation = &"idle"

[sub_resource type="AnimationNodeBlendSpace2D" id="AnimationNodeBlendSpace2D_loco"]
blend_point_0/node = SubResource("AnimationNodeAnimation_idle")
blend_point_0/pos = Vector2(0, 0)
blend_point_1/node = SubResource("AnimationNodeAnimation_walk")
blend_point_1/pos = Vector2(0, 1)
blend_point_2/node = SubResource("AnimationNodeAnimation_run")
blend_point_2/pos = Vector2(0, 1.5)

[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_main"]
states/Idle/node = SubResource("AnimationNodeAnimation_idle")
states/Idle/position = Vector2(300, 100)
states/Locomotion/node = SubResource("AnimationNodeBlendSpace2D_loco")
states/Locomotion/position = Vector2(500, 100)

[sub_resource type="AnimationNodeStateMachinePlayback" id="AnimationNodeStateMachinePlayback_main"]

[node name="Player" type="CharacterBody2D"]
script = ExtResource("1_abc123")

[node name="AnimationPlayer" type="AnimationPlayer" parent="."]

[node name="AnimationTree" type="AnimationTree" parent="."]
anim_player = NodePath("../AnimationPlayer")
tree_root = SubResource("AnimationNodeStateMachine_main")
parameters/playback = SubResource("AnimationNodeStateMachinePlayback_main")
parameters/blend_position = Vector2(0, 0)

AnimationTree Activation

# Essential setup for AnimationTree
func _ready():
    # Activate the tree (must be done after node is ready)
    animation_tree.active = true
    
    # Get reference to state machine playback
    playback = animation_tree.get("parameters/playback")
    
    # Optional: Set initial state
    playback.start("Idle")

AnimationNodeStateMachine

State Machine Structure

State Machine Graph:

# State machine nodes in .tscn format
[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_character"]
states/Start/position = Vector2(150, 100)
states/End/position = Vector2(800, 100)

# Idle state (single animation)
states/Idle/node = SubResource("AnimationNodeAnimation_idle")
states/Idle/position = Vector2(300, 100)

# Locomotion (blend space)
states/Locomotion/node = SubResource("AnimationNodeBlendSpace2D_loco")
states/Locomotion/position = Vector2(500, 100)

# Attack (one-shot animation)
states/Attack/node = SubResource("AnimationNodeOneShot_attack")
states/Attack/position = Vector2(500, 300)

# Death state
states/Death/node = SubResource("AnimationNodeAnimation_death")
states/Death/position = Vector2(700, 100)

State Transitions

Transition Configuration:

# Transitions between states
[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_character"]

# Idle -> Locomotion (auto on bool parameter)
transitions = ["Idle", "Locomotion", SubResource("AnimationNodeStateMachineTransition_idle_to_loco")]

# Locomotion -> Idle
transitions = ["Locomotion", "Idle", SubResource("AnimationNodeStateMachineTransition_loco_to_idle")]

# Any State -> Attack (using Attack trigger)
transitions = ["Start", "Attack", SubResource("AnimationNodeStateMachineTransition_attack")]

# Any State -> Death
transitions = ["Start", "Death", SubResource("AnimationNodeStateMachineTransition_death")]

Transition Resource Definitions:

# Transition with condition
var transition = AnimationNodeStateMachineTransition.new()
transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_IMMEDIATE
transition.advance_mode = AnimationNodeStateMachineTransition.ADVANCE_MODE_AUTO
transition.advance_condition = "is_moving"

# Transition with expression (Godot 4.1+)
transition.advance_expression = "velocity.length() > 0.1"

# Transition with time condition
transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_AT_END

Playback Control

# State machine playback script
extends CharacterBody2D

@onready var animation_tree: AnimationTree = $AnimationTree
var playback: AnimationNodeStateMachinePlayback

func _ready():
    animation_tree.active = true
    playback = animation_tree.get("parameters/playback")

func _physics_process(delta):
    # Transition to locomotion state
    if velocity.length() > 0.1:
        playback.travel("Locomotion")
    else:
        playback.travel("Idle")
    
    # Trigger attack (OneShot)
    if Input.is_action_just_pressed("attack"):
        playback.travel("Attack")
    
    # Trigger death (immediate)
    if health <= 0:
        playback.travel("Death")

func stop_movement():
    # Stop at current state
    playback.stop()
    
func reset_to_idle():
    # Start over from Start node
    playback.start("Idle")

BlendSpace2D Configuration

Locomotion Blend Space

Before (No Blending):

# Discrete animations - jarring transitions
func update_animation():
    if velocity.length() < 0.1:
        anim_player.play("idle")
    elif velocity.length() < 100:
        anim_player.play("walk")
    else:
        anim_player.play("run")

After (BlendSpace2D):

# Smooth blending between all animations
func _physics_process(delta):
    # Normalize velocity for blend position (-1 to 1)
    var blend_pos = velocity / max_speed
    animation_tree.set("parameters/Locomotion/blend_position", blend_pos)

Generated BlendSpace2D:

# BlendSpace2D resource
[sub_resource type="AnimationNodeBlendSpace2D" id="AnimationNodeBlendSpace2D_loco"]

# Center - Idle
blend_point_0/node = SubResource("AnimationNodeAnimation_idle")
blend_point_0/pos = Vector2(0, 0)

# Up - Walk North
blend_point_1/node = SubResource("AnimationNodeAnimation_walk_north")
blend_point_1/pos = Vector2(0, -1)

# Down - Walk South
blend_point_2/node = SubResource("AnimationNodeAnimation_walk_south")
blend_point_2/pos = Vector2(0, 1)

# Left - Walk West
blend_point_3/node = SubResource("AnimationNodeAnimation_walk_west")
blend_point_3/pos = Vector2(-1, 0)

# Right - Walk East
blend_point_4/node = SubResource("AnimationNodeAnimation_walk_east")
blend_point_4/pos = Vector2(1, 0)

# Diagonal blends (automatically interpolated)
blend_point_5/node = SubResource("AnimationNodeAnimation_walk_northeast")
blend_point_5/pos = Vector2(0.707, -0.707)

# Blend mode
blend_mode = 1  # BLEND_MODE_INTERPOLATED
min_space = Vector2(-1.5, -1.5)
max_space = Vector2(1.5, 1.5)

BlendSpace2D Setup Script

# Programmatically create BlendSpace2D
func create_locomotion_blend_space() -> AnimationNodeBlendSpace2D:
    var blend_space = AnimationNodeBlendSpace2D.new()
    
    # Add idle animation at center
    var idle_node = AnimationNodeAnimation.new()
    idle_node.animation = "idle"
    blend_space.add_blend_point(idle_node, Vector2.ZERO)
    
    # Add directional walks
    var walk_north = AnimationNodeAnimation.new()
    walk_north.animation = "walk_north"
    blend_space.add_blend_point(walk_north, Vector2(0, -1))
    
    var walk_south = AnimationNodeAnimation.new()
    walk_south.animation = "walk_south"
    blend_space.add_blend_point(walk_south, Vector2(0, 1))
    
    var walk_east = AnimationNodeAnimation.new()
    walk_east.animation = "walk_east"
    blend_space.add_blend_point(walk_east, Vector2(1, 0))
    
    var walk_west = AnimationNodeAnimation.new()
    walk_west.animation = "walk_west"
    blend_space.add_blend_point(walk_west, Vector2(-1, 0))
    
    # Configure blend triangles for proper interpolation
    blend_space.add_triangle(0, 1, 4)  # Idle, North, East
    blend_space.add_triangle(0, 4, 2)  # Idle, East, South
    blend_space.add_triangle(0, 2, 3)  # Idle, South, West
    blend_space.add_triangle(0, 3, 1)  # Idle, West, North
    
    return blend_space

Parameter-Driven Blend Space

# Update blend space based on input/velocity
func update_locomotion_blend():
    var input_dir = Input.get_vector("move_left", "move_right", "move_up", "move_down")
    
    # Calculate blend position based on input
    var blend_position = input_dir
    
    # Apply to AnimationTree
    animation_tree.set("parameters/Locomotion/blend_position", blend_position)
    
    # Also update speed scale for walk/run distinction
    var speed = velocity.length()
    var speed_scale = clamp(speed / base_speed, 0.5, 2.0)
    animation_tree.set("parameters/Locomotion/speed_scale", speed_scale)

BlendSpace3D Configuration

3D Locomotion Blend Space

# BlendSpace3D for 3D characters
[sub_resource type="AnimationNodeBlendSpace3D" id="AnimationNodeBlendSpace3D_loco3d"]

# Idle at center
blend_point_0/node = SubResource("AnimationNodeAnimation_idle_3d")
blend_point_0/pos = Vector3(0, 0, 0)

# Cardinal directions
blend_point_1/node = SubResource("AnimationNodeAnimation_walk_forward")
blend_point_1/pos = Vector3(0, 0, 1)

blend_point_2/node = SubResource("AnimationNodeAnimation_walk_backward")
blend_point_2/pos = Vector3(0, 0, -1)

blend_point_3/node = SubResource("AnimationNodeAnimation_strafe_left")
blend_point_3/pos = Vector3(-1, 0, 0)

blend_point_4/node = SubResource("AnimationNodeAnimation_strafe_right")
blend_point_4/pos = Vector3(1, 0, 0)

# Run variants at higher magnitude
blend_point_5/node = SubResource("AnimationNodeAnimation_run_forward")
blend_point_5/pos = Vector3(0, 0, 1.5)

3D Character Animation Script:

# character_3d.gd
extends CharacterBody3D

@onready var animation_tree: AnimationTree = $AnimationTree

func _physics_process(delta):
    # Get local velocity relative to character rotation
    var local_velocity = transform.basis.inverse() * velocity
    
    # Normalize for blend position
    var blend_pos = Vector3(
        clamp(local_velocity.x / max_speed, -1, 1),
        0,
        clamp(local_velocity.z / max_speed, -1, 1)
    )
    
    # Update blend space
    animation_tree.set("parameters/Locomotion3D/blend_position", blend_pos)
    
    # Update animation speed based on actual velocity
    var speed_factor = velocity.length() / max_speed
    animation_tree.set("parameters/Locomotion3D/speed_scale", clamp(speed_factor, 0.5, 1.5))

State Transitions

Boolean Condition Transitions

# Setup boolean condition in AnimationTree
func setup_conditions():
    # Set condition values
    animation_tree.set("parameters/conditions/is_moving", velocity.length() > 0.1)
    animation_tree.set("parameters/conditions/is_attacking", Input.is_action_pressed("attack"))
    animation_tree.set("parameters/conditions/is_grounded", is_on_floor())
    animation_tree.set("parameters/conditions/is_dead", health <= 0)

Corresponding Scene Configuration:

[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_idle_to_move"]
advance_mode = 1  # ADVANCE_MODE_AUTO
advance_condition = "is_moving"

[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_move_to_idle"]
advance_mode = 1
advance_condition = "is_moving"
negated = true

[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_attack"]
advance_mode = 1
advance_condition = "is_attacking"

Expression-Based Transitions

# Godot 4.1+ expression transitions
# No need for manual condition setting

# Transition expression examples:
# "velocity.length() > 0.1"
# "health <= 0"
# "is_on_floor() and Input.is_action_pressed(\"jump\")"
# "anim_time >= 0.8" (requires anim_time parameter tracking)

Setting up Expression Transitions:

func create_expression_transition(expression: String) -> AnimationNodeStateMachineTransition:
    var transition = AnimationNodeStateMachineTransition.new()
    transition.advance_mode = AnimationNodeStateMachineTransition.ADVANCE_MODE_AUTO
    transition.advance_expression = expression
    return transition

# Usage
var jump_transition = create_expression_transition("is_on_floor() and Input.is_action_pressed('jump')")
state_machine.add_transition("Idle", "Jump", jump_transition)

Time-Based Transitions

# Transition at end of animation
var end_transition = AnimationNodeStateMachineTransition.new()
end_transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_AT_END
end_transition.advance_mode = AnimationNodeStateMachineTransition.ADVANCE_MODE_AUTO

# Transition after specific time
var time_transition = AnimationNodeStateMachineTransition.new()
time_transition.switch_mode = AnimationNodeStateMachineTransition.SWITCH_MODE_AT_END
time_transition.advance_mode = AnimationNodeStateMachineTransition.ADVANCE_MODE_AUTO
# Add custom wait time via expression

Blend Trees

Complex Animation Mixing

OneShot for Actions:

[sub_resource type="AnimationNodeOneShot" id="AnimationNodeOneShot_attack"]
animation = SubResource("AnimationNodeAnimation_attack")
fadein_time = 0.1
fadeout_time = 0.15
mix_mode = 0  # ONE_SHOT_MIX_MODE_BLEND

Blend2 for Smooth Transitions:

[sub_resource type="AnimationNodeBlend2" id="AnimationNodeBlend2_action"]

Add2 for Layering:

[sub_resource type="AnimationNodeAdd2" id="AnimationNodeAdd2_recoil"]
# Adds recoil on top of base animation

TimeScale for Speed Control:

[sub_resource type="AnimationNodeTimeScale" id="AnimationNodeTimeScale_run"]
scale = 1.0  # Modified at runtime

Complete Blend Tree Example

# Complex blend tree with layering
[sub_resource type="AnimationNodeBlendTree" id="AnimationNodeBlendTree_complex"]

# Input animations
nodes/Animation/node = SubResource("AnimationNodeAnimation_idle")
nodes/Animation/position = Vector2(100, 100)

# Time scale for speed control
nodes/TimeScale/node = SubResource("AnimationNodeTimeScale_var")
nodes/TimeScale/position = Vector2(300, 100)
nodes/TimeScale/input_0 = SubResource("AnimationNodeAnimation_idle")

# OneShot for hit reaction (layered on top)
nodes/HitReaction/node = SubResource("AnimationNodeOneShot_hit")
nodes/HitReaction/position = Vector2(500, 100)
nodes/HitReaction/input_0 = SubResource("AnimationNodeTimeScale_var")

# Add2 for weapon sway
nodes/WeaponSway/node = SubResource("AnimationNodeAdd2_sway")
nodes/WeaponSway/position = Vector2(700, 100)
nodes/WeaponSway/input_0 = SubResource("AnimationNodeOneShot_hit")
nodes/WeaponSway/input_1 = SubResource("AnimationNodeAnimation_sway")

# Output
nodes/Output/position = Vector2(900, 100)
node_connections = [&"output", 0, &"WeaponSway"]

Blend Tree Script Control:

# Control blend tree parameters
func update_blend_tree():
    # Update time scale based on movement speed
    var speed = velocity.length()
    var time_scale = clamp(speed / base_speed, 0.5, 2.0)
    animation_tree.set("parameters/TimeScale/scale", time_scale)
    
    # Trigger OneShot
    if is_hit:
        animation_tree.set("parameters/HitReaction/active", true)
        animation_tree.set("parameters/HitReaction/internal_active", true)
    
    # Control Add2 amount (0.0 = no sway, 1.0 = full sway)
    var sway_amount = clamp(speed / max_speed, 0.0, 1.0)
    animation_tree.set("parameters/WeaponSway/add_amount", sway_amount)

Examples

2D Character Animation System

Complete Setup:

# character_animator.gd
extends CharacterBody2D

@onready var animation_tree: AnimationTree = $AnimationTree
@onready var playback: AnimationNodeStateMachinePlayback

@export var max_speed: float = 200.0
@export var blend_smoothness: float = 5.0

var target_blend_position: Vector2 = Vector2.ZERO
var current_blend_position: Vector2 = Vector2.ZERO

func _ready():
    animation_tree.active = true
    playback = animation_tree.get("parameters/playback")

func _physics_process(delta):
    # Get input
    var input_dir = Input.get_vector("move_left", "move_right", "move_up", "move_down")
    
    # Calculate target blend position
    if input_dir.length() > 0.1:
        target_blend_position = input_dir
        playback.travel("Locomotion")
    else:
        target_blend_position = Vector2.ZERO
        playback.travel("Idle")
    
    # Smooth blend position transition
    current_blend_position = current_blend_position.lerp(target_blend_position, blend_smoothness * delta)
    animation_tree.set("parameters/Locomotion/blend_position", current_blend_position)
    
    # Handle actions
    if Input.is_action_just_pressed("attack"):
        playback.travel("Attack")
    
    if Input.is_action_just_pressed("interact"):
        playback.travel("Interact")

func take_damage():
    playback.travel("Hit")

func die():
    playback.travel("Death")

Generated Scene:

# animated_character.tscn
[gd_scene load_steps=10 format=3]

[ext_resource type="Script" path="res://character_animator.gd" id="1_anim123"]

# Animation nodes
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_idle"]
animation = &"idle"

[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_attack"]
animation = &"attack"

[sub_resource type="AnimationNodeBlendSpace2D" id="AnimationNodeBlendSpace2D_loco"]
blend_point_0/node = SubResource("AnimationNodeAnimation_idle")
blend_point_0/pos = Vector2(0, 0)
# ... more blend points

# State machine
[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_main"]
states/Start/position = Vector2(150, 100)
states/Idle/node = SubResource("AnimationNodeAnimation_idle")
states/Idle/position = Vector2(300, 100)
states/Locomotion/node = SubResource("AnimationNodeBlendSpace2D_loco")
states/Locomotion/position = Vector2(500, 100)
states/Attack/node = SubResource("AnimationNodeAnimation_attack")
states/Attack/position = Vector2(500, 300)
states/End/position = Vector2(700, 100)

# Transitions
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_to_loco"]
advance_condition = "is_moving"

[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_to_idle"]
advance_condition = "is_moving"
negated = true

[node name="AnimatedCharacter" type="CharacterBody2D"]
script = ExtResource("1_anim123")

[node name="Sprite2D" type="Sprite2D" parent="."]

[node name="AnimationPlayer" type="AnimationPlayer" parent="."]

[node name="AnimationTree" type="AnimationTree" parent="."]
anim_player = NodePath("../AnimationPlayer")
tree_root = SubResource("AnimationNodeStateMachine_main")
parameters/playback = SubResource("AnimationNodeStateMachinePlayback_main")
parameters/Locomotion/blend_position = Vector2(0, 0)
parameters/conditions/is_moving = false

Combat Animation System

Attack Combo System:

# combat_animator.gd
extends CharacterBody2D

@onready var animation_tree: AnimationTree = $AnimationTree
@onready var playback: AnimationNodeStateMachinePlayback

var combo_count: int = 0
var max_combo: int = 3
var combo_window: float = 0.5
var combo_timer: float = 0.0

func _ready():
    animation_tree.active = true
    playback = animation_tree.get("parameters/playback")

func _physics_process(delta):
    # Update combo timer
    if combo_timer > 0:
        combo_timer -= delta
        if combo_timer <= 0:
            combo_count = 0
    
    # Handle attack input
    if Input.is_action_just_pressed("attack"):
        perform_attack()
    
    # Update animation conditions
    animation_tree.set("parameters/conditions/in_combo", combo_count > 0)

func perform_attack():
    match combo_count:
        0:
            playback.travel("Attack1")
        1:
            playback.travel("Attack2")
        2:
            playback.travel("Attack3")
        _:
            combo_count = 0
            playback.travel("Attack1")
    
    combo_count = (combo_count + 1) % max_combo
    combo_timer = combo_window

func reset_combo():
    combo_count = 0
    combo_timer = 0.0

State Machine for Combos:

[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_combat"]
states/Idle/position = Vector2(300, 100)
states/Attack1/position = Vector2(500, 100)
states/Attack2/position = Vector2(500, 200)
states/Attack3/position = Vector2(500, 300)

# Attack1 -> Attack2 transition
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_a1_a2"]
switch_mode = 1  # SWITCH_MODE_AT_END
advance_mode = 1
advance_condition = "in_combo"

# Attack2 -> Attack3 transition
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_a2_a3"]
switch_mode = 1
advance_mode = 1
advance_condition = "in_combo"

# All attacks -> Idle (when combo ends)
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_end"]
switch_mode = 1
advance_mode = 1
negated = true
advance_condition = "in_combo"

NPC Animation with Random Idle Variations

# npc_animator.gd
extends CharacterBody2D

@onready var animation_tree: AnimationTree = $AnimationTree
@onready var playback: AnimationNodeStateMachinePlayback

var idle_variations: Array[String] = ["Idle", "Idle2", "Idle3"]
var idle_timer: float = 0.0
var next_idle_change: float = 5.0

func _ready():
    animation_tree.active = true
    playback = animation_tree.get("parameters/playback")
    pick_random_idle()

func _physics_process(delta):
    # Random idle variation
    idle_timer += delta
    if idle_timer >= next_idle_change and velocity.length() < 0.1:
        idle_timer = 0.0
        next_idle_change = randf_range(3.0, 8.0)
        pick_random_idle()
    
    # Movement
    if velocity.length() > 0.1:
        playback.travel("Walk")

func pick_random_idle():
    var random_idle = idle_variations[randi() % idle_variations.size()]
    playback.travel(random_idle)

Common Patterns

Animation Event Integration

# Connect animation events to gameplay
func _ready():
    animation_tree.animation_started.connect(_on_animation_started)
    animation_tree.animation_finished.connect(_on_animation_finished)

func _on_animation_started(anim_name: StringName):
    match anim_name:
        "attack":
            # Disable movement during attack
            can_move = false
        "dash":
            # Make invincible
            is_invincible = true

func _on_animation_finished(anim_name: StringName):
    match anim_name:
        "attack":
            can_move = true
        "dash":
            is_invincible = false

Animation-Driven Movement

# Root motion implementation
func _on_animation_tree_animation_started(anim_name: StringName):
    if anim_name == &"attack":
        # Enable root motion tracking
        animation_tree.set("parameters/Attack/active", true)

func _physics_process(delta):
    # Apply root motion from animation
    var root_motion: Transform3D = animation_tree.get_root_motion()
    global_transform *= root_motion

Smooth State Transitions

# Crossfade duration configuration
var transition = AnimationNodeStateMachineTransition.new()
transition.fade_duration = 0.2  # 200ms crossfade

Safety

  • Always check if AnimationTree is active before accessing parameters
  • Verify AnimationPlayer has all required animations before setting up tree
  • Handle missing blend positions gracefully (default to center)
  • Reset AnimationTree when reparenting or changing scenes
  • Disconnect signals before queue_free()

When NOT to Use

Don't use AnimationTree when:

  • Only 2-3 simple animations (overkill)
  • Animation logic is extremely simple (just play/stop)
  • Performance is critical on low-end devices
  • You need direct frame-by-frame control

Use direct AnimationPlayer instead:

# Simple animation control
func _physics_process(delta):
    if is_moving:
        anim_player.play("walk")
    else:
        anim_player.play("idle")

Integration

Works with:

  • godot-add-signals - Connect animation events to gameplay systems
  • godot-extract-to-scenes - Create reusable animated character scenes
  • godot-setup-navigation - Navigation agents with movement animations
  • godot-migrate-tilemap - Animated tilemap characters

Performance Tips

  1. Limit Blend Points - 4-8 points is usually enough
  2. Cache Parameters - Store parameter paths as constants
  3. Disable Unused Trees - Set
    active = false
    when off-screen
  4. Use TimeScale - Instead of changing animation speeds
  5. Batch Transitions - Group state changes when possible