AnimationTree State Machine
trong Godot 4

Hướng dẫn đầy đủ về state machine, transition, blend tree, OneShot, travel() so với điều kiện — kèm các ví dụ mã GDScript thực tế.

1. Giới thiệu

AnimationTree là hệ thống của Godot 4 dùng cho việc trộn animation phức tạp và chuyển đổi trạng thái. Nếu bạn từng thử quản lý nhiều animation bằng các lệnh gọi AnimationPlayer.play() rải rác khắp mã của mình, hẳn bạn biết nó nhanh chóng trở nên khó kiểm soát đến mức nào. AnimationTree giải quyết vấn đề này bằng một đồ thị trực quan gồm các trạng thái và transition.

Dù cực kỳ mạnh mẽ, tài liệu chính thức của AnimationTree lại sơ sài và thường khiến các nhà phát triển phải mò mẫm. Hướng dẫn này đi qua mọi thứ, từ thiết lập cơ bản đến blend tree nâng cao, kèm mã GDScript thực tế mà bạn có thể sao chép vào dự án của mình.

Những gì bạn sẽ học được

State machine, transition, blend space (1D & 2D), OneShot cho đòn tấn công, travel() so với điều kiện, và một mẫu character controller hoàn chỉnh.

2. Điều kiện tiên quyết

Trước khi thiết lập AnimationTree, bạn cần:

  • Một node AnimationPlayer đã có sẵn tối thiểu các animation Idle, Walk, Run và Jump
  • AnimationPlayer phải là node anh em hoặc node con của node nơi bạn thêm AnimationTree (thông thường cả hai đều là node con của node gốc nhân vật)
  • Godot 4.x (hướng dẫn này dùng API của Godot 4 — API của AnimationTree đã thay đổi đáng kể so với Godot 3)
Godot 3 so với Godot 4

Trong Godot 4, AnimationTree.animation_player đã được thay bằng AnimationTree.anim_player. Đường dẫn tham số playback cũng thay đổi. Nếu bạn đang chuyển từ Godot 3, hãy xem hướng dẫn di chuyển chính thức.

3. Thiết lập cơ bản

Thiết lập một AnimationTree gồm bốn bước:

  1. Thêm một node AnimationTree — Thêm nó làm node con của nhân vật (ví dụ CharacterBody2D hoặc CharacterBody3D), đặt cạnh AnimationPlayer của bạn.
  2. Thiết lập anim_player — Trong Inspector, trỏ thuộc tính "Anim Player" tới node AnimationPlayer của bạn.
  3. Thiết lập tree_root — Nhấp vào thuộc tính "Tree Root" trong Inspector và tạo một AnimationNodeStateMachine mới.
  4. Đặt active = true — Tích ô "Active" trong Inspector, hoặc thiết lập bằng mã.

Cây scene của bạn sẽ trông như thế này:

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
Editor so với mã

Trên thực tế, bạn gần như luôn cấu hình AnimationTree trong editor. Đoạn mã trên được trình bày cho đầy đủ, nhưng thông thường bạn chỉ cần anim_tree.active = true trong script của mình (và ngay cả điều đó cũng có thể thiết lập trong editor).

4. Kiến thức cơ bản về State Machine

State machine trong AnimationTree hoạt động dựa trên một khái niệm đơn giản: các trạng thái (state) đại diện cho các animation, còn các transition định nghĩa điều kiện để chuyển đổi giữa chúng.

Thêm trạng thái

Một khi tree_root của AnimationTree là một AnimationNodeStateMachine, hãy nhấp đúp vào nó trong Inspector để mở trình chỉnh sửa state machine:

  1. Nhấp chuột phải vào vùng đồ thị và chọn Add Animation
  2. Chọn một animation từ AnimationPlayer của bạn (Idle, Walk, Run, Jump, v.v.)
  3. Lặp lại cho mỗi trạng thái animation bạn cần
Các trạng thái đặc biệt

Start là điểm vào — transition đầu tiên luôn bắt đầu từ đây. End là tùy chọn và báo hiệu rằng state machine đã kết thúc (hữu ích cho các state machine lồng nhau).

Thêm transition

Để tạo một transition giữa hai trạng thái:

  1. Nhấp vào node trạng thái nguồn
  2. Kéo tới trạng thái đích để tạo mũi tên transition
  3. Nhấp vào mũi tên transition để cấu hình nó trong Inspector

Thuộc tính của transition

Thuộc tính Mô tả
advance_mode Auto — kích hoạt khi điều kiện của nó đúng. Enabled — luôn khả dụng cho travel(). Disabled — bị chặn.
advance_condition Tên của một tham số boolean (ví dụ is_moving). Khi đúng, transition tự động kích hoạt.
xfade_time Thời lượng crossfade tính bằng giây. Trộn mượt giữa các animation. Điển hình: 0.1 – 0.3s.
switch_mode Immediate — chuyển ngay. Sync — khớp vị trí playback. AtEnd — đợi animation hiện tại kết thúc.

Một thiết lập điển hình cho nhân vật platformer:

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. Điều khiển State Machine từ mã

Có hai cách chính để điều khiển state machine: thiết lập các tham số điều kiện (transition tự động) và gọi travel() (transition thủ công). Bạn có thể kết hợp cả hai cách tiếp cận.

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")
Đường dẫn tham số

Các tham số tuân theo mẫu parameters/conditions/<condition_name> đối với các điều kiện được đặt trên transition. Tên điều kiện phải khớp với advance_condition mà bạn đã thiết lập cho transition trong editor.

6. travel() so với điều kiện

Dựa trên điều kiện (khuyến nghị cho di chuyển)

Thiết lập các tham số boolean mỗi khung hình và để các transition tự động kích hoạt. Cách này mang tính khai báo hơn và giữ cho mã của bạn gọn gàng. State machine sẽ xử lý logic transition, crossfade và các trường hợp biên giúp bạn.

# 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() (khuyến nghị cho các hành động đơn lẻ)

travel() yêu cầu một chuyển đổi trạng thái. Nó tôn trọng các quy tắc transition — nếu không tồn tại đường đi hợp lệ từ trạng thái hiện tại tới trạng thái đích, lệnh gọi sẽ bị bỏ qua. Điều này khiến việc gọi lặp lại là an toàn. Hãy dùng nó cho các trigger đơn lẻ như tấn công, emote, hoặc animation cutscene.

# 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)
Khi nào dùng cái nào?

Điều kiện cho các trạng thái liên tục (idle, walk, run, fall). travel() cho các trạng thái được kích hoạt bởi sự kiện (attack, dodge, interact). Nhiều dự án dùng cả hai: điều kiện cho di chuyển, travel() cho các hành động chiến đấu.

7. Blend Tree

Blend tree cho phép bạn nội suy mượt mà giữa nhiều animation dựa trên một giá trị liên tục, thay vì chuyển đổi cứng giữa các trạng thái rời rạc. Điều này hoàn hảo cho việc trộn tốc độ đi/chạy và di chuyển theo hướng.

BlendSpace1D

Một phép trộn 1D giữa hai hoặc nhiều animation dọc theo một trục duy nhất. Cách dùng phổ biến: trộn Walk và Run dựa trên tốc độ di chuyển.

Trong editor, tạo một node BlendSpace1D bên trong state machine của bạn (hoặc làm tree root độc lập). Thêm các điểm animation:

# 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

Một phép trộn 2D dùng hai trục. Hoàn hảo cho di chuyển 8 hướng hoặc game top-down nơi nhân vật có thể di chuyển theo bất kỳ hướng nào.

# 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)
Chế độ trộn

BlendSpace2D hỗ trợ nhiều chế độ trộn: phép tam giác hóa mặc định hoạt động tốt trong hầu hết trường hợp. Bạn cũng có thể chọn chế độ rời rạc (bám vào điểm gần nhất) nếu muốn animation kiểu pixel-art mà không nội suy.

8. Các loại Node thông dụng

AnimationTree hỗ trợ một số loại node có thể kết hợp để tạo hành vi animation phức tạp:

Loại Node Trường hợp sử dụng
AnimationNodeStateMachine State machine có transition. Node gốc phổ biến nhất. Trạng thái có thể là animation hoặc state machine lồng nhau.
AnimationNodeBlendSpace1D Trộn 1D dọc theo một trục. Tốc độ đi/chạy, góc ngắm, v.v.
AnimationNodeBlendSpace2D Trộn 2D dùng hai trục. Di chuyển theo hướng, trộn strafe.
AnimationNodeBlendTree Một đồ thị các phép trộn. Kết hợp nhiều node trộn với logic tùy chỉnh.
AnimationNodeAdd2 Trộn cộng dồn. Xếp một animation lên trên animation khác (ví dụ độ lệch ngắm bắn lên trên đi bộ).
AnimationNodeTimeScale Điều khiển tốc độ. Cho một animation chạy nhanh hơn hoặc chậm hơn lúc chạy game.
AnimationNodeOneShot Lớp phủ animation một lần. Hoàn hảo cho tấn công, emote, phản ứng khi trúng đòn.
AnimationNodeTransition Chuyển đổi giữa nhiều đầu vào có crossfade. Giải pháp thay thế cho state machine trong các thiết lập đơn giản hơn.

9. Mẫu OneShot (Tấn công, Emote)

Node OneShot là một trong những mẫu hữu ích nhất trong AnimationTree. Nó phát một animation một lần lên trên animation nền của bạn (như phát đòn vung tấn công trong khi đang đi bộ), rồi tự động trở về animation nền.

Thiết lập trong BlendTree

Để dùng OneShot, gốc AnimationTree của bạn (hoặc một trạng thái bên trong nó) cần phải là một 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.

Kích hoạt từ mã

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

Các hằng số Request của OneShot

Hằng số Tác dụng
ONE_SHOT_REQUEST_FIRE Bắt đầu phát animation one-shot
ONE_SHOT_REQUEST_ABORT Hủy one-shot và trở về nền ngay lập tức
ONE_SHOT_REQUEST_FADE_OUT Làm mờ dần one-shot (dùng thuộc tính fadeout_time)
Nhiều đòn tấn công

Với hệ thống combo, hãy dùng nhiều node OneShot nối tiếp nhau hoặc một state machine lồng bên trong đầu vào shot của OneShot với các trạng thái Attack1 → Attack2 → Attack3.

10. Ví dụ thực tế: Character Controller đầy đủ

Dưới đây là một script nhân vật platformer 2D hoàn chỉnh, kết hợp di chuyển bằng state machine với đòn tấn công OneShot. Đây là một mẫu sẵn sàng cho sản phẩm mà bạn có thể điều chỉnh cho dự án của mình.

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
        )
Cấu trúc AnimationTree cho ví dụ này

Root = BlendTree. Bên trong: một node StateMachine (với các trạng thái Idle/Walk/Run/Jump/Fall) nối tới một node OneShot ("AttackOneShot"), node này nối tới Output. Animation tấn công nối tới đầu vào "shot" của OneShot.

11. Khắc phục sự cố

"Animation không phát"

Hãy kiểm tra rằng active = true trên AnimationTree và thuộc tính anim_player trỏ tới một AnimationPlayer hợp lệ. Cũng hãy xác nhận AnimationPlayer thực sự có các animation với đúng tên mà bạn đang tham chiếu.

"travel() không làm gì cả"

Hãy đảm bảo tồn tại một đường transition hợp lệ giữa trạng thái hiện tại và trạng thái đích. travel() sẽ thất bại âm thầm nếu không có đường đi. Dùng state_machine.get_current_node() để gỡ lỗi xem bạn thực sự đang ở trạng thái nào.

"Blend không hoạt động"

Hãy kiểm tra rằng giá trị blend_position của bạn nằm trong phạm vi của các điểm blend space. Nếu các điểm của bạn ở 0.0 và 1.0, thì giá trị 5.0 sẽ không hoạt động như mong đợi. Hãy dùng clamp().

"Warning: AnimationTree is not active"

Hãy đặt active = true trong editor (ô tích Inspector) hoặc trong hàm _ready() của bạn. AnimationTree không làm gì cho tới khi được kích hoạt.

"Transition dựa trên điều kiện không kích hoạt"

Hãy kiểm tra kỹ rằng: (1) advance_mode của transition được đặt thành Auto, (2) tên advance_condition khớp chính xác với tên bạn đặt trong mã (phân biệt hoa thường), và (3) bạn đang thiết lập tham số đó mỗi khung hình trong _physics_process().

"Animation phát nhưng nhân vật không di chuyển"

AnimationTree chỉ xử lý việc phát animation. Logic di chuyển (velocity, move_and_slide()) là tách biệt và phải được triển khai trong _physics_process() của script của bạn.

Muốn AI xây dựng AnimationTree cho bạn?

Godot MCP Pro có thể tạo state machine, thêm trạng thái và transition, cấu hình blend tree, và thiết lập tham số — tất cả chỉ từ một prompt duy nhất. Hãy nói cho trợ lý AI của bạn biết bạn muốn hành vi animation nào, và nó sẽ xây dựng toàn bộ AnimationTree cho bạn.

  • create_animation_tree
  • add_state_machine_state
  • add_state_machine_transition
  • set_blend_tree_node
  • set_tree_parameter
  • get_animation_tree_structure
Nhận Godot MCP Pro — $15