AnimationTree 狀態機
於 Godot 4

關於狀態機、轉場、Blend Tree、OneShot、travel() 與條件取捨的完整指南 — 附真實 GDScript 程式碼範例。

1. 簡介

AnimationTree 是 Godot 4 用於複雜動畫混合與狀態轉換的系統。如果你曾經試著用散落在整份程式碼中的 AnimationPlayer.play() 呼叫來管理多個動畫,你就會知道那有多快變得難以維護。AnimationTree 透過一張由狀態與轉場組成的視覺化圖表解決了這個問題。

儘管 AnimationTree 功能極為強大,官方文件卻相當單薄,常常讓開發者摸不著頭緒。本指南會帶你走過一切 – 從基本設定到進階的 Blend Tree – 並附上可直接複製到你專案中的真實 GDScript 程式碼。

你將學到什麼

狀態機、轉場、Blend Space(1D 與 2D)、用於攻擊的 OneShot、travel() 與條件的取捨,以及一套完整的角色控制器模式。

2. 前置需求

在設定 AnimationTree 之前,你需要:

  • 一個至少已建立 Idle、Walk、Run 與 Jump 動畫的 AnimationPlayer 節點
  • AnimationPlayer 必須是你新增 AnimationTree 的那個節點的同層或子節點(通常兩者都是角色根節點的子節點)
  • Godot 4.x(本指南使用 Godot 4 的 API — AnimationTree 的 API 相較於 Godot 3 有大幅變動)
Godot 3 與 Godot 4 的差異

在 Godot 4 中,AnimationTree.animation_player 已被 AnimationTree.anim_player 取代。播放的參數路徑也隨之改變。如果你正從 Godot 3 遷移,請參閱官方遷移指南

3. 基本設定

設定一個 AnimationTree 分為四個步驟:

  1. 新增一個 AnimationTree 節點 — 將它與 AnimationPlayer 並列,新增為角色(例如 CharacterBody2DCharacterBody3D)的子節點。
  2. 設定 anim_player — 在 Inspector 中將「Anim Player」屬性指向你的 AnimationPlayer 節點。
  3. 設定 tree_root — 在 Inspector 中點按「Tree Root」屬性,並建立一個新的 AnimationNodeStateMachine
  4. 設定 active = true — 在 Inspector 中勾選「Active」核取方塊,或於程式碼中設定。

你的場景樹應該長這樣:

CharacterBody2D (or CharacterBody3D)
  +-- Sprite2D (or Sprite3D)
  +-- CollisionShape2D
  +-- AnimationPlayer      <-- has Idle, Walk, Run, Jump animations
  +-- AnimationTree         <-- points to AnimationPlayer above
GDScript
# Minimal code setup (usually done via the editor instead):
@onready var anim_tree: AnimationTree = $AnimationTree

func _ready() -> void:
    # If you set these in the Inspector, you don't need this code
    anim_tree.anim_player = ^"../AnimationPlayer"
    anim_tree.tree_root = AnimationNodeStateMachine.new()
    anim_tree.active = true
編輯器 vs 程式碼

實務上,你幾乎總是在編輯器中設定 AnimationTree。上面的程式碼只是為了完整性而附上,但通常你的腳本中只需要 anim_tree.active = true(甚至這一項也能在編輯器中設定)。

4. 狀態機基礎

AnimationTree 中的狀態機建立在一個簡單的概念上:狀態(state)代表動畫,而轉場(transition)則定義在狀態間切換的條件。

新增狀態

一旦你的 AnimationTree 的 tree_root 是 AnimationNodeStateMachine,就在 Inspector 中對它按兩下以開啟狀態機編輯器:

  1. 在圖表區域按右鍵,選擇 Add Animation
  2. 從你的 AnimationPlayer 中選擇一個動畫(Idle、Walk、Run、Jump 等)
  3. 對你需要的每個動畫狀態重複此步驟
特殊狀態

Start 是進入點 — 第一個轉場永遠從這裡開始。End 是選用的,用來表示狀態機已結束(對巢狀狀態機很有用)。

新增轉場

要在兩個狀態之間建立轉場:

  1. 點按來源狀態節點
  2. 拖曳到目標狀態,以建立一個轉場箭頭
  3. 點按該轉場箭頭,在 Inspector 中設定它

轉場屬性

屬性 說明
advance_mode Auto — 條件為真時觸發。Enabled — 始終可供 travel() 使用。Disabled — 封鎖。
advance_condition 一個布林參數的名稱(例如 is_moving)。為真時,轉場會自動觸發。
xfade_time 交叉淡化的秒數。用於動畫之間的平滑混合。典型值:0.1 – 0.3 秒。
switch_mode Immediate — 立即切換。Sync — 對齊播放位置。AtEnd — 等待目前動畫結束。

平台跳躍類角色的典型設定:

Start --> Idle
Idle  --> Walk   (condition: is_moving)
Walk  --> Idle   (condition: is_idle)
Idle  --> Jump   (condition: is_jumping)
Walk  --> Jump   (condition: is_jumping)
Jump  --> Fall   (condition: is_falling)
Fall  --> Idle   (condition: is_on_floor, switch_mode: Immediate)

5. 從程式碼控制狀態機

驅動狀態機主要有兩種方式:設定條件參數(自動轉場)與呼叫 travel()(手動轉場)。你可以混用這兩種做法。

GDScript
extends CharacterBody2D

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

func _physics_process(delta: float) -> void:
    # ... movement logic here ...
    move_and_slide()
    _update_animation_parameters()

func _update_animation_parameters() -> void:
    # Approach 1: Set condition parameters — transitions fire automatically
    anim_tree.set("parameters/conditions/is_moving", velocity.length() > 10.0)
    anim_tree.set("parameters/conditions/is_idle", velocity.length() <= 10.0)
    anim_tree.set("parameters/conditions/is_on_floor", is_on_floor())
    anim_tree.set("parameters/conditions/is_jumping", velocity.y < 0 and not is_on_floor())
    anim_tree.set("parameters/conditions/is_falling", velocity.y > 0 and not is_on_floor())

    # Approach 2: Use travel() for direct control
    # if velocity.length() > 10.0:
    #     state_machine.travel("Walk")
    # else:
    #     state_machine.travel("Idle")
參數路徑

對於設定在轉場上的條件,參數遵循 parameters/conditions/<condition_name> 的模式。條件名稱必須與你在編輯器中為該轉場設定的 advance_condition 相符。

6. travel() 與條件的取捨

條件式(推薦用於移動)

每一幀設定布林參數,讓轉場自動觸發。這種做法更為宣告式,並讓你的程式碼保持乾淨。狀態機會替你處理轉場邏輯、交叉淡化與各種邊界情況。

# Declarative: just describe the current state of the world
anim_tree.set("parameters/conditions/is_moving", velocity.length() > 10.0)
anim_tree.set("parameters/conditions/is_on_floor", is_on_floor())

travel()(推薦用於單次動作)

travel() 會請求一次狀態轉換。它會遵守轉場規則 — 如果從目前狀態到目標之間沒有有效路徑,該呼叫就會被忽略。這使得反覆呼叫它是安全的。請把它用於一次性觸發,例如攻擊、表情動作或過場動畫。

# travel() — requests a transition (respects transition rules)
state_machine.travel("Jump")

# Get current state name
var current: StringName = state_machine.get_current_node()
print(current)  # "Idle", "Walk", etc.

# Check if travel is possible
var is_playing: bool = state_machine.is_playing()
print(is_playing)
該用哪一種?

條件用於持續性的狀態(Idle、Walk、Run、Fall)。travel() 用於由事件觸發的狀態(攻擊、閃避、互動)。許多專案兩者並用:移動用條件,戰鬥動作用 travel()。

7. Blend Tree

Blend Tree 讓你能依據一個連續數值在多個動畫之間平滑地內插,而不是在離散狀態間硬切換。這非常適合用於走路/奔跑速度的混合,以及具方向性的移動。

BlendSpace1D

沿單一軸在兩個或更多動畫之間的 1D 混合。常見用途:依據移動速度混合 Walk 與 Run。

在編輯器中,於你的狀態機內建立一個 BlendSpace1D 節點(或作為獨立的 tree root)。加入動畫點:

# BlendSpace1D setup (in editor):
# Point 0.0 = Walk animation
# Point 1.0 = Run animation

# Control from code:
var speed_factor: float = clamp(velocity.length() / max_speed, 0.0, 1.0)
anim_tree.set("parameters/WalkRun/blend_position", speed_factor)

BlendSpace2D

使用兩個軸的 2D 混合。非常適合 8 方向移動,或角色可朝任意方向移動的俯視角遊戲。

# BlendSpace2D setup (in editor):
# Place animations at positions:
#   Idle at (0, 0)
#   WalkRight at (1, 0), WalkLeft at (-1, 0)
#   WalkUp at (0, -1), WalkDown at (0, 1)
#   Diagonals at corners

# Control from code:
var input_dir := Input.get_vector("move_left", "move_right", "move_up", "move_down")
anim_tree.set("parameters/Movement/blend_position", input_dir)
混合模式

BlendSpace2D 支援多種混合模式:預設的三角剖分在大多數情況下都運作良好。如果你想要不含內插的像素藝術風格動畫,也可以選擇離散模式(吸附到最近的點)。

8. 常見的節點類型

AnimationTree 支援多種可組合的節點類型,用以打造複雜的動畫行為:

節點類型 使用情境
AnimationNodeStateMachine 帶有轉場的狀態機。最常見的根節點。狀態可以是動畫,也可以是巢狀狀態機。
AnimationNodeBlendSpace1D 沿單一軸的 1D 混合。走路/奔跑速度、瞄準角度等。
AnimationNodeBlendSpace2D 使用兩個軸的 2D 混合。方向性移動、橫向平移(strafe)混合。
AnimationNodeBlendTree 一張由混合運算組成的圖表。以自訂邏輯組合多個混合節點。
AnimationNodeAdd2 加成式混合。將一個動畫疊加在另一個之上(例如把瞄準偏移疊在走路之上)。
AnimationNodeTimeScale 速度控制。讓動畫在執行時播放得更快或更慢。
AnimationNodeOneShot 單次動畫疊加。非常適合攻擊、表情動作、受擊反應。
AnimationNodeTransition 以交叉淡化在多個輸入之間切換。對較簡單的設定而言是狀態機的替代方案。

9. OneShot 模式(攻擊、表情動作)

OneShot 節點是 AnimationTree 中最實用的模式之一。它會在你的基礎動畫之上播放一次性動畫(例如在走路時播放一記攻擊揮擊),接著自動回到基礎動畫。

在 BlendTree 中設定

要使用 OneShot,你的 AnimationTree 根節點(或其中的某個狀態)必須是 BlendTree

# BlendTree graph setup:
#
#   [StateMachine] ---> [OneShot "AttackOneShot"] ---> [Output]
#   (base locomotion)      ^
#                          |
#                   [Animation "Attack"]
#                   (shot input)
#
# The StateMachine provides the base (idle/walk/run).
# The Attack animation is connected to the OneShot's "shot" input.

從程式碼觸發

GDScript
extends CharacterBody2D

@onready var anim_tree: AnimationTree = $AnimationTree

func _input(event: InputEvent) -> void:
    if event.is_action_pressed("attack"):
        _play_attack()

func _play_attack() -> void:
    # Fire the one-shot animation
    anim_tree.set(
        "parameters/AttackOneShot/request",
        AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE
    )

func _process(delta: float) -> void:
    # Check if the one-shot is currently active
    var is_attacking: bool = anim_tree.get("parameters/AttackOneShot/active")
    if is_attacking:
        # Optionally disable movement during attack
        pass

OneShot 請求常數

常數 作用
ONE_SHOT_REQUEST_FIRE 開始播放 OneShot 動畫
ONE_SHOT_REQUEST_ABORT 中止 OneShot 並立即回到基礎動畫
ONE_SHOT_REQUEST_FADE_OUT 淡出 OneShot(使用 fadeout_time 屬性)
多段攻擊

對於連段系統,可依序使用多個 OneShot 節點,或在 OneShot 的「shot」輸入中放入一個帶有 Attack1 → Attack2 → Attack3 狀態的巢狀狀態機。

10. 實戰範例:完整角色控制器

這裡是一份完整的 2D 平台跳躍角色腳本,結合了狀態機移動與一記 OneShot 攻擊。這是一套可用於正式產品的模式,你能依自己的專案加以調整。

GDScript — player.gd
extends CharacterBody2D

const SPEED := 200.0
const JUMP_VELOCITY := -350.0
const SPRINT_MULTIPLIER := 1.6

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

var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")

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

func _physics_process(delta: float) -> void:
    _apply_gravity(delta)
    _handle_jump()
    _handle_movement()
    move_and_slide()
    _update_animation()

func _apply_gravity(delta: float) -> void:
    if not is_on_floor():
        velocity.y += gravity * delta

func _handle_jump() -> void:
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = JUMP_VELOCITY

func _handle_movement() -> void:
    var direction := Input.get_axis("move_left", "move_right")
    var is_sprinting := Input.is_action_pressed("sprint")
    var current_speed := SPEED * (SPRINT_MULTIPLIER if is_sprinting else 1.0)

    if direction != 0.0:
        velocity.x = direction * current_speed
        sprite.flip_h = direction < 0.0
    else:
        velocity.x = move_toward(velocity.x, 0.0, SPEED)

func _update_animation() -> void:
    # Skip animation updates during attack
    var is_attacking: bool = anim_tree.get("parameters/AttackOneShot/active")
    if is_attacking:
        return

    if not is_on_floor():
        if velocity.y < 0:
            playback.travel("Jump")
        else:
            playback.travel("Fall")
    elif abs(velocity.x) > 10.0:
        if Input.is_action_pressed("sprint"):
            playback.travel("Run")
        else:
            playback.travel("Walk")
    else:
        playback.travel("Idle")

func _input(event: InputEvent) -> void:
    if event.is_action_pressed("attack") and is_on_floor():
        anim_tree.set(
            "parameters/AttackOneShot/request",
            AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE
        )
此範例的 AnimationTree 結構

Root = BlendTree。其中:一個 StateMachine 節點(帶有 Idle/Walk/Run/Jump/Fall 狀態)連到一個 OneShot 節點("AttackOneShot"),後者再連到 Output。攻擊動畫則連到 OneShot 的「shot」輸入。

11. 疑難排解

「動畫沒有播放」

請檢查 AnimationTree 上是否設定了 active = true,且 anim_player 屬性指向一個有效的 AnimationPlayer。此外,也請確認該 AnimationPlayer 確實含有你所引用之名稱的動畫。

「travel() 沒有作用」

請確保目前狀態與目標狀態之間存在一條有效的轉場路徑。若沒有路徑,travel() 會靜默失敗。使用 state_machine.get_current_node() 來除錯,確認你實際上處於哪個狀態。

「混合沒有作用」

請檢查你的 blend_position 值是否落在 blend space 各點的範圍內。若你的點位於 0.0 與 1.0,那麼 5.0 這個值就不會如預期般運作。請使用 clamp()

「Warning: AnimationTree is not active」

請在編輯器中(Inspector 的核取方塊)或你的 _ready() 函式中設定 active = true。AnimationTree 在啟用之前不會做任何事。

「條件式轉場沒有觸發」

請仔細檢查:(1) 該轉場的 advance_mode 設為 Auto,(2) advance_condition 的名稱與你在程式碼中設定的完全相符(區分大小寫),以及 (3) 你在 _physics_process() 中每一幀都有設定該參數。

「動畫有播放但角色不會移動」

AnimationTree 只負責動畫播放。移動邏輯(velocitymove_and_slide())是獨立的,必須在你腳本的 _physics_process() 中實作。

想讓 AI 為你打造 AnimationTree 嗎?

Godot MCP Pro 能建立狀態機、新增狀態與轉場、設定 Blend Tree 並設定參數 — 全部只需一段提示。告訴你的 AI 助理你想要的動畫行為,它就會為你打造整棵 AnimationTree。

  • create_animation_tree
  • add_state_machine_state
  • add_state_machine_transition
  • set_blend_tree_node
  • set_tree_parameter
  • get_animation_tree_structure
取得 Godot MCP Pro — $15