GodotAnimationTree精通Skill godot-animation-tree-mastery

掌握 Godot 游戏引擎中 AnimationTree 的高级使用技巧,包括状态机、混合空间、根运动等,用于开发复杂角色动画系统。关键词:Godot, AnimationTree, 动画, 游戏开发, 状态机, 混合动画。

游戏开发 0 次安装 0 次浏览 更新于 3/23/2026

名称: godot-animation-tree-mastery 描述: “AnimationTree 的专家模式,包括状态机过渡、用于定向移动的 BlendSpace2D、用于分层动画的 BlendTree、根运动、过渡条件、高级表达式和状态机子状态。用于具有运动混合和状态管理的复杂角色动画系统。触发关键词:AnimationTree, AnimationNodeStateMachine, BlendSpace2D, BlendSpace1D, BlendTree, transition_request, blend_position, advance_expression, AnimationNodeAdd2, AnimationNodeBlend2, root_motion。”

AnimationTree 精通

Godot 高级动画混合和状态机的专家指导。

绝对不要做

  • 绝对不要在启用 AnimationTree 时调用 play() — AnimationTree 控制播放器。直接调用 play() 会导致冲突。应使用 set("parameters/transition_request")
  • 绝对不要忘记设置 active = true — AnimationTree 默认不活动。动画不会播放,除非设置 $AnimationTree.active = true
  • 绝对不要使用绝对路径进行 transition_request — 使用相对路径。“parameters/StateMachine/transition_request”,而不是 “/root/…/transition_request”。
  • 绝对不要无意中启用 auto_advance — 自动前进过渡会在没有条件时立即触发。适用于连击链,但对于空闲→行走是致命的。
  • 绝对不要将 BlendSpace2D 用于非定向混合 — 使用 BlendSpace1D 用于速度(行走→奔跑)或 Blend2 用于简单补间。BlendSpace2D 适用于 X+Y 轴(侧移动画)。

可用脚本

必需:在实现相应模式前阅读相关脚本。

nested_state_machine.gd

分层状态机模式。展示 travel() 在子状态之间的切换以及深层参数路径(StateMachine/BlendSpace2D/blend_position)。

skeleton_ik_lookat.gd

用于头部跟踪的程序化 IK。通过平滑权重混合,从 AnimationTree 驱动 SkeletonModifier3D 的注视参数。


核心概念

AnimationTree 结构

AnimationTree (节点)
  ├─ 根 (在编辑器中指定)
  │   ├─ StateMachine (常见)
  │   ├─ BlendTree (分层)
  │   └─ BlendSpace (定向)
  └─ anim_player: NodePath → 指向 AnimationPlayer

参数访问

# 使用字符串路径设置参数
$AnimationTree.set("parameters/StateMachine/transition_request", "run")
$AnimationTree.set("parameters/Movement/blend_position", Vector2(1, 0))

# 获取当前状态
var current_state = $AnimationTree.get("parameters/StateMachine/current_state")

状态机模式

基本设置

# 场景结构:
# CharacterBody2D
#   ├─ AnimationPlayer (包含:空闲、行走、奔跑、跳跃、着陆)
#   └─ AnimationTree
#       └─ 根:AnimationNodeStateMachine

# 状态机节点(在 AnimationTree 编辑器中创建):
# - 空闲 (AnimationNode 引用 "空闲")
# - 行走 (AnimationNode 引用 "行走")
# - 奔跑 (AnimationNode 引用 "奔跑")
# - 跳跃 (AnimationNode 引用 "跳跃")
# - 着陆 (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()
    
    # 基于游戏状态进行过渡
    if is_on_floor():
        if velocity.length() < 10:
            state_machine.travel("空闲")
        elif velocity.length() < 200:
            state_machine.travel("行走")
        else:
            state_machine.travel("奔跑")
    else:
        if velocity.y < 0:  # 上升
            state_machine.travel("跳跃")
        else:  # 下降
            state_machine.travel("着陆")

过渡条件(高级表达式)

# 在 AnimationTree 编辑器中:
# 添加从 空闲 → 行走 的过渡
# 设置 "前进条件" 为 "is_walking"

# 在代码中:
anim_tree.set("parameters/conditions/is_walking", true)

# 当条件变为 true 时,过渡自动触发
# 适用于事件驱动的过渡(受伤、死亡等)

# 示例:伤害过渡
anim_tree.set("parameters/conditions/is_damaged", false)  # 每帧重置

func take_damage() -> void:
    anim_tree.set("parameters/conditions/is_damaged", true)
    # 立即触发到 "受伤" 状态的过渡

自动前进(连击链)

# 在 AnimationTree 编辑器中:
# 从 攻击1 → 攻击2 的过渡
# 启用 "自动前进"(无需条件)

# 代码:
state_machine.travel("攻击1")
# 攻击1 动画播放
# 当攻击1 完成时,自动过渡到攻击2
# 当攻击2 完成时,过渡到空闲(下一个自动前进)

# 适用于:
# - 攻击连击
# - 死亡 → 重生
# - 过场动画序列

BlendSpace2D(定向移动)

8 方向移动

# 在 AnimationTree 编辑器中创建 BlendSpace2D:
# - 在位置添加动画:
#   - (0, -1):向上行走
#   - (0, 1):向下行走
#   - (-1, 0):向左行走
#   - (1, 0):向右行走
#   - (-1, -1):左上行走
#   - (1, -1):右上行走
#   - (-1, 1):左下行走
#   - (1, 1):右下行走
#   - (0, 0):空闲(中心)

# 在代码中:
func _physics_process(delta: float) -> void:
    var input := Input.get_vector("左", "右", "上", "下")
    
    # 设置混合位置(AnimationTree 在动画之间插值)
    anim_tree.set("parameters/Movement/blend_position", input)
    
    # BlendSpace2D 根据输入自动混合动画
    # input = (0.5, -0.5) → 混合向右行走和向上行走

BlendSpace1D(速度混合)

# 用于行走 → 奔跑过渡
# 创建 BlendSpace1D:
#   - 位置 0.0:行走
#   - 位置 1.0:奔跑

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)
    # 随着速度增加,平滑地从行走 → 奔跑混合

BlendTree(分层动画)

添加上半身动画

# 问题:想在行走时瞄准枪支
# 解决方案:混合上半身(瞄准)和下半身(行走)

# 在 AnimationTree 编辑器中:
# 根 → BlendTree
#   ├─ 行走(下半身动画)
#   ├─ 瞄准(上半身动画)
#   └─ Add2 节点(组合它们)
#       - 输入:行走、瞄准
#       - filter_enabled:true
#       - 过滤器:仅启用瞄准的上半身骨骼

# 代码:
# 无需代码!BlendTree 自动组合
# 确保动画已分配

Blend2(淡入淡出)

# 动态混合两个动画
# 根 → BlendTree
#   └─ Blend2
#       ├─ 输入 A:空闲
#       └─ 输入 B:攻击

# 代码:
var blend_amount := 0.0

func _process(delta: float) -> void:
    # 逐渐从空闲 → 攻击混合
    blend_amount += delta
    blend_amount = clamp(blend_amount, 0.0, 1.0)
    
    anim_tree.set("parameters/IdleAttackBlend/blend_amount", blend_amount)
    # 0.0 = 100% 空闲
    # 0.5 = 50% 空闲, 50% 攻击
    # 1.0 = 100% 攻击

使用 AnimationTree 的根运动

# 在 AnimationTree 中启用
anim_tree.root_motion_track = NodePath("CharacterBody3D/Skeleton3D:Root")

func _physics_process(delta: float) -> void:
    # 获取根运动
    var root_motion := anim_tree.get_root_motion_position()
    
    # 应用到角色(非速度!)
    global_position += root_motion.rotated(rotation.y)
    
    # 对于使用 move_and_slide 的 CharacterBody3D:
    velocity = root_motion / delta
    move_and_slide()

高级模式

子状态机

# 嵌套状态机用于复杂行为
# 根 → StateMachine
#   ├─ 地面(子状态机)
#   │   ├─ 空闲
#   │   ├─ 行走
#   │   └─ 奔跑
#   └─ 空中(子状态机)
#       ├─ 跳跃
#       ├─ 坠落
#       └─ 滑翔

# 访问嵌套状态:
var sub_state = anim_tree.get("parameters/Grounded/playback")
sub_state.travel("奔跑")

时间缩放(慢动作)

# 减慢特定动画而不影响其他
anim_tree.set("parameters/TimeScale/scale", 0.5)  # 50% 速度

# 适用于:
# - 子弹时间
# - 受伤/眩晕效果
# - 充能动画

动画间同步

# 问题:从行走 → 奔跑切换导致脚步滑动
# 解决方案:在过渡上使用 "同步"

# 在 AnimationTree 编辑器中:
# 过渡:行走 → 奔跑
# 启用 "同步" 复选框

# Godot 自动同步动画播放位置
# 在过渡期间脚保持接地

调试 AnimationTree

打印当前状态

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)

常见问题

# 问题:动画不播放
# 解决方案:
if not anim_tree.active:
    anim_tree.active = true

# 问题:过渡不工作
# 检查:
# 1. 是否设置了前进条件?
# 2. 过渡优先级是否正确?
# 3. 是否无意中启用了自动前进?

# 问题:混合不流畅
# 解决方案:增加过渡 xfade_time(0.1 - 0.3秒)

性能优化

不需要时禁用

# AnimationTree 开销大
# 对屏幕外实体禁用
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

决策树:何时使用 AnimationTree

特性 仅使用 AnimationPlayer AnimationTree
简单状态切换 ✅ play(“空闲”) ❌ 过度复杂
定向移动 ❌ 复杂 ✅ BlendSpace2D
状态机(5+ 状态) ❌ 代码混乱 ✅ StateMachine
分层动画 ❌ 手动混合 ✅ BlendTree
根运动 ✅ 可能 ✅ 内置
过渡混合 ❌ 手动 ✅ 自动

使用 AnimationTree 用于:具有 5+ 状态的复杂角色、定向移动、分层动画 使用 AnimationPlayer 用于:简单动画、UI、过场动画、道具

参考