godot-animation-tree-mastery

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

AnimationTree Mastery

AnimationTree 精通指南

Expert guidance for Godot's advanced animation blending and state machines.
Godot高级动画混合与状态机的进阶指南。

NEVER Do

绝对不要做这些

  • NEVER call
    play()
    on AnimationPlayer when using AnimationTree
    — AnimationTree controls the player. Directly calling
    play()
    causes conflicts. Use
    set("parameters/transition_request")
    instead.
  • NEVER forget to set
    active = true
    — AnimationTree is inactive by default. Animations won't play until
    $AnimationTree.active = true
    .
  • NEVER use absolute paths for transition_request — Use relative paths. "parameters/StateMachine/transition_request", not "/root/.../transition_request".
  • NEVER leave auto_advance enabled unintentionally — Auto-advance transitions fire immediately without conditions. Useful for combo chains, but deadly for idle→walk.
  • NEVER use BlendSpace2D for non-directional blending — Use BlendSpace1D for speed (walk→run) or Blend2 for simple tweens. BlendSpace2D is for X+Y axes (strafe animations).

  • 使用AnimationTree时,绝对不要在AnimationPlayer上调用
    play()
    —— AnimationTree会控制播放器,直接调用
    play()
    会导致冲突,请改用
    set("parameters/transition_request")
  • 绝对不要忘记设置
    active = true
    —— AnimationTree默认处于非激活状态,必须设置
    $AnimationTree.active = true
    后动画才会播放。
  • 绝对不要为transition_request使用绝对路径 —— 请使用相对路径,例如"parameters/StateMachine/transition_request",而非"/root/.../transition_request"。
  • 绝对不要无意开启auto_advance —— 自动过渡会在无任何条件的情况下立即触发,适用于连招序列,但在 idle→walk 这类场景中会引发问题。
  • 绝对不要将BlendSpace2D用于非定向混合 —— 速度混合(walk→run)请使用BlendSpace1D,简单补间请使用Blend2。BlendSpace2D仅适用于X+Y轴混合(例如 Strafing 动画)。

Available Scripts

可用脚本

MANDATORY: Read the appropriate script before implementing the corresponding pattern.
强制要求:在实现对应模式前,请先阅读相应脚本。

nested_state_machine.gd

nested_state_machine.gd

Hierarchical state machine pattern. Shows travel() between sub-states and deep parameter paths (StateMachine/BlendSpace2D/blend_position).
分层状态机模式,展示了子状态间的travel()调用以及深层参数路径(StateMachine/BlendSpace2D/blend_position)的使用。

skeleton_ik_lookat.gd

skeleton_ik_lookat.gd

Procedural IK for head-tracking. Drives SkeletonModifier3D look-at parameters from AnimationTree with smooth weight blending.

用于头部追踪的程序化IK,通过AnimationTree驱动SkeletonModifier3D的看向参数,并实现平滑权重混合。

Core Concepts

核心概念

AnimationTree Structure

AnimationTree 结构

AnimationTree (node)
  ├─ Root (assigned in editor)
  │   ├─ StateMachine (common)
  │   ├─ BlendTree (layering)
  │   └─ BlendSpace (directional)
  └─ anim_player: NodePath → points to AnimationPlayer
AnimationTree (节点)
  ├─ Root (在编辑器中指定)
  │   ├─ StateMachine (常用)
  │   ├─ BlendTree (分层动画)
  │   └─ BlendSpace (定向混合)
  └─ anim_player: NodePath → 指向AnimationPlayer

Parameter Access

参数访问

gdscript
undefined
gdscript
undefined

Set parameters using string paths

使用字符串路径设置参数

$AnimationTree.set("parameters/StateMachine/transition_request", "run") $AnimationTree.set("parameters/Movement/blend_position", Vector2(1, 0))
$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")

---
var current_state = $AnimationTree.get("parameters/StateMachine/current_state")

---

StateMachine Pattern

状态机模式

Basic Setup

基础设置

gdscript
undefined
gdscript
undefined

Scene structure:

场景结构:

CharacterBody2D

CharacterBody2D

├─ AnimationPlayer (has: idle, walk, run, jump, land)

├─ AnimationPlayer (包含: idle, walk, run, jump, land)

└─ AnimationTree

└─ AnimationTree

└─ Root: AnimationNodeStateMachine

└─ Root: AnimationNodeStateMachine

StateMachine nodes (created in AnimationTree editor):

状态机节点(在AnimationTree编辑器中创建):

- Idle (AnimationNode referencing "idle")

- Idle (关联"idle"动画的AnimationNode)

- Walk (AnimationNode referencing "walk")

- Walk (关联"walk"动画的AnimationNode)

- Run (AnimationNode referencing "run")

- Run (关联"run"动画的AnimationNode)

- Jump (AnimationNode referencing "jump")

- Jump (关联"jump"动画的AnimationNode)

- Land (AnimationNode referencing "land")

- Land (关联"land"动画的AnimationNode)

@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")
undefined
@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()
# 基于游戏逻辑的状态过渡
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:  # 上升阶段
        state_machine.travel("Jump")
    else:  # 下落阶段
        state_machine.travel("Land")
undefined

Transition Conditions (Advance Expressions)

过渡条件(高级表达式)

gdscript
undefined
gdscript
undefined

In AnimationTree editor:

在AnimationTree编辑器中:

Add transition from Idle → Walk

添加从Idle → Walk的过渡

Set "Advance Condition" to "is_walking"

将"Advance Condition"设置为"is_walking"

In code:

代码中:

anim_tree.set("parameters/conditions/is_walking", true)
anim_tree.set("parameters/conditions/is_walking", true)

Transition fires automatically when condition becomes true

当条件变为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
undefined
anim_tree.set("parameters/conditions/is_damaged", false) # 每帧重置
func take_damage() -> void: anim_tree.set("parameters/conditions/is_damaged", true) # 会立即触发向"Hurt"状态的过渡
undefined

Auto-Advance (Combo Chains)

自动过渡(连招序列)

gdscript
undefined
gdscript
undefined

In AnimationTree editor:

在AnimationTree编辑器中:

Transition from Attack1 → Attack2

添加从Attack1 → Attack2的过渡

Enable "Auto Advance" (no condition needed)

开启"Auto Advance"(无需设置条件)

Code:

代码:

state_machine.travel("Attack1")
state_machine.travel("Attack1")

Attack1 animation plays

播放Attack1动画

When Attack1 finishes, automatically transitions to Attack2

Attack1播放完毕后,自动过渡到Attack2

When Attack2 finishes, transitions to Idle (next auto-advance)

Attack2播放完毕后,自动过渡到Idle(下一个自动过渡)

Useful for:

适用场景:

- Attack combos

- 攻击连招

- Death → Respawn

- 死亡→重生

- Cutscene sequences

- 过场动画序列


---

---

BlendSpace2D (Directional Movement)

BlendSpace2D(定向移动)

8-Way Movement

八方向移动

gdscript
undefined
gdscript
undefined

Create BlendSpace2D in AnimationTree editor:

在AnimationTree编辑器中创建BlendSpace2D:

- Add animations at positions:

- 在以下位置添加动画:

- (0, -1): walk_up

- (0, -1): walk_up

- (0, 1): walk_down

- (0, 1): walk_down

- (-1, 0): walk_left

- (-1, 0): walk_left

- (1, 0): walk_right

- (1, 0): walk_right

- (-1, -1): walk_up_left

- (-1, -1): walk_up_left

- (1, -1): walk_up_right

- (1, -1): walk_up_right

- (-1, 1): walk_down_left

- (-1, 1): walk_down_left

- (1, 1): walk_down_right

- (1, 1): walk_down_right

- (0, 0): idle (center)

- (0, 0): idle(中心位置)

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
undefined
func _physics_process(delta: float) -> void: var input := Input.get_vector("left", "right", "up", "down")
# 设置混合位置(AnimationTree会自动在动画间插值)
anim_tree.set("parameters/Movement/blend_position", input)

# BlendSpace2D会根据输入自动混合动画
# input = (0.5, -0.5) → 混合walk_right与walk_up动画
undefined

BlendSpace1D (Speed Blending)

BlendSpace1D(速度混合)

gdscript
undefined
gdscript
undefined

For walk → run transitions

用于walk → run的过渡

Create BlendSpace1D:

创建BlendSpace1D:

- Position 0.0: walk

- 位置0.0: walk

- Position 1.0: run

- 位置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

---
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)
# 随着速度提升,平滑从walk过渡到run

---

BlendTree (Layered Animations)

BlendTree(分层动画)

Add Upper Body Animation

叠加上半身动画

gdscript
undefined
gdscript
undefined

Problem: Want to aim gun while walking

需求:在行走时同时实现持枪瞄准

Solution: Blend upper body (aim) with lower body (walk)

解决方案:将上半身(瞄准)与下半身(行走)动画混合

In AnimationTree editor:

在AnimationTree编辑器中:

Root → BlendTree

Root → BlendTree

├─ Walk (lower body animation)

├─ Walk(下半身动画)

├─ Aim (upper body animation)

├─ Aim(上半身动画)

└─ Add2 node (combines them)

└─ Add2节点(将两者组合)

- Inputs: Walk, Aim

- 输入: Walk, Aim

- filter_enabled: true

- filter_enabled: true

- Filters: Only enable upper body bones for Aim

- 过滤规则: 仅启用Aim动画的上半身骨骼

Code:

代码:

No code needed! BlendTree auto-combines

无需额外代码!BlendTree会自动组合动画

Just ensure animations are assigned

只需确保已正确分配动画即可

undefined
undefined

Blend2 (Crossfade)

Blend2(交叉淡入淡出)

gdscript
undefined
gdscript
undefined

Blend between two animations dynamically

动态混合两个动画

Root → BlendTree

Root → BlendTree

└─ Blend2

└─ Blend2

├─ Input A: idle

├─ 输入A: idle

└─ Input B: attack

└─ 输入B: attack

Code:

代码:

var blend_amount := 0.0
func _process(delta: float) -> void: # Gradually blend from idle → attack blend_amount += delta blend_amount = clamp(blend_amount, 0.0, 1.0)
anim_tree.set("parameters/IdleAttackBlend/blend_amount", blend_amount)
# 0.0 = 100% idle
# 0.5 = 50% idle, 50% attack
# 1.0 = 100% attack

---
var blend_amount := 0.0
func _process(delta: float) -> void: # 逐渐从idle过渡到attack blend_amount += delta blend_amount = clamp(blend_amount, 0.0, 1.0)
anim_tree.set("parameters/IdleAttackBlend/blend_amount", blend_amount)
# 0.0 = 100% idle
# 0.5 = 50% idle, 50% attack
# 1.0 = 100% attack

---

Root Motion with AnimationTree

AnimationTree 根运动

gdscript
undefined
gdscript
undefined

Enable in AnimationTree

在AnimationTree中启用

anim_tree.root_motion_track = NodePath("CharacterBody3D/Skeleton3D:Root")
func _physics_process(delta: float) -> void: # Get root motion var root_motion := anim_tree.get_root_motion_position()
# Apply to character (not velocity!)
global_position += root_motion.rotated(rotation.y)

# For CharacterBody3D with move_and_slide:
velocity = root_motion / delta
move_and_slide()

---
anim_tree.root_motion_track = NodePath("CharacterBody3D/Skeleton3D:Root")
func _physics_process(delta: float) -> void: # 获取根运动数据 var root_motion := anim_tree.get_root_motion_position()
# 应用到角色(不要与velocity混用!)
global_position += root_motion.rotated(rotation.y)

# 对于使用move_and_slide的CharacterBody3D:
velocity = root_motion / delta
move_and_slide()

---

Advanced Patterns

进阶模式

Sub-StateMachines

子状态机

gdscript
undefined
gdscript
undefined

Nested state machines for complex behavior

用于复杂行为的嵌套状态机

Root → StateMachine

Root → StateMachine

├─ Grounded (Sub-StateMachine)

├─ Grounded(子状态机)

│ ├─ Idle

│ ├─ Idle

│ ├─ Walk

│ ├─ Walk

│ └─ Run

│ └─ Run

└─ Airborne (Sub-StateMachine)

└─ Airborne(子状态机)

├─ Jump

├─ Jump

├─ Fall

├─ Fall

└─ Glide

└─ Glide

Access nested states:

访问嵌套状态:

var sub_state = anim_tree.get("parameters/Grounded/playback") sub_state.travel("Run")
undefined
var sub_state = anim_tree.get("parameters/Grounded/playback") sub_state.travel("Run")
undefined

Time Scale (Slow Motion)

时间缩放(慢动作)

gdscript
undefined
gdscript
undefined

Slow down specific animation without affecting others

单独放慢指定动画,不影响其他动画

anim_tree.set("parameters/TimeScale/scale", 0.5) # 50% speed
anim_tree.set("parameters/TimeScale/scale", 0.5) # 50%速度

Useful for:

适用场景:

- Bullet time

- 子弹时间

- Hurt/stun effects

- 受伤/眩晕效果

- Charge-up animations

- 蓄力动画

undefined
undefined

Sync Between Animations

动画同步

gdscript
undefined
gdscript
undefined

Problem: Switching from walk → run causes foot slide

问题:从walk切换到run时会出现脚部滑动

Solution: Use "Sync" on transition

解决方案:在过渡中启用"Sync"

In AnimationTree editor:

在AnimationTree编辑器中:

Transition: Walk → Run

过渡: Walk → Run

Enable "Sync" checkbox

勾选"Sync"复选框

Godot automatically syncs animation playback positions

Godot会自动同步动画播放位置

Feet stay grounded during transition

过渡过程中脚部会保持着地状态


---

---

Debugging AnimationTree

AnimationTree 调试

Print Current State

打印当前状态

gdscript
func _process(delta: float) -> void:
    var current_state = anim_tree.get("parameters/StateMachine/current_state")
    print("Current state: ", current_state)
    
    # Print blend position
    var blend_pos = anim_tree.get("parameters/Movement/blend_position")
    print("Blend position: ", blend_pos)
gdscript
func _process(delta: float) -> void:
    var current_state = anim_tree.get("parameters/StateMachine/current_state")
    print("当前状态: ", current_state)
    
    # 打印混合位置
    var blend_pos = anim_tree.get("parameters/Movement/blend_position")
    print("混合位置: ", blend_pos)

Common Issues

常见问题

gdscript
undefined
gdscript
undefined

Issue: Animation not playing

问题:动画无法播放

Solution:

解决方案:

if not anim_tree.active: anim_tree.active = true
if not anim_tree.active: anim_tree.active = true

Issue: Transition not working

问题:过渡不生效

Check:

检查:

1. Is advance_condition set?

1. 是否设置了advance_condition?

2. Is transition priority correct?

2. 过渡优先级是否正确?

3. Is auto_advance enabled unintentionally?

3. 是否无意开启了auto_advance?

Issue: Blend not smooth

问题:混合效果不流畅

Solution: Increase transition xfade_time (0.1 - 0.3s)

解决方案:增加过渡的xfade_time(0.1-0.3秒)


---

---

Performance Optimization

性能优化

Disable When Not Needed

无需使用时禁用

gdscript
undefined
gdscript
undefined

AnimationTree is expensive

AnimationTree性能开销较大

Disable for off-screen entities

对屏幕外的实体禁用动画

extends VisibleOnScreenNotifier3D
func _ready() -> void: screen_exited.connect(_on_screen_exited) screen_entered.connect(_on_screen_entered)
func _on_screen_exited() -> void: $AnimationTree.active = false
func _on_screen_entered() -> void: $AnimationTree.active = true

---
extends VisibleOnScreenNotifier3D
func _ready() -> void: screen_exited.connect(_on_screen_exited) screen_entered.connect(_on_screen_entered)
func _on_screen_exited() -> void: $AnimationTree.active = false
func _on_screen_entered() -> void: $AnimationTree.active = true

---

Decision Tree: When to Use AnimationTree

决策树:何时使用AnimationTree

FeatureAnimationPlayer OnlyAnimationTree
Simple state swap✅ play("idle")❌ Overkill
Directional movement❌ Complex✅ BlendSpace2D
State machine (5+ states)❌ Messy code✅ StateMachine
Layered animations❌ Manual blending✅ BlendTree
Root motion✅ Possible✅ Built-in
Transition blending❌ Manual✅ Auto
Use AnimationTree for: Complex characters with 5+ states, directional movement, layered animations Use AnimationPlayer for: Simple animations, UI, cutscenes, props
特性仅使用AnimationPlayer使用AnimationTree
简单状态切换✅ 使用play("idle")❌ 过于冗余
定向移动❌ 实现复杂✅ 推荐使用BlendSpace2D
状态机(5个以上状态)❌ 代码混乱✅ 推荐使用StateMachine
分层动画❌ 需手动混合✅ 推荐使用BlendTree
根运动✅ 可实现✅ 原生支持
过渡混合❌ 需手动实现✅ 自动支持
推荐使用AnimationTree的场景:包含5个以上状态的复杂角色、定向移动、分层动画 推荐使用AnimationPlayer的场景:简单动画、UI、过场动画、道具

Reference

参考

  • Master Skill: godot-master
  • 核心技能:godot-master