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ế.
Mục lục
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.
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)
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:
-
Thêm một node AnimationTree
— Thêm nó làm node con của nhân vật (ví dụ
CharacterBody2DhoặcCharacterBody3D), đặt cạnh AnimationPlayer của bạn. -
Thiết lập
anim_player— Trong Inspector, trỏ thuộc tính "Anim Player" tới node AnimationPlayer của bạn. -
Thiết lập
tree_root— Nhấp vào thuộc tính "Tree Root" trong Inspector và tạo mộtAnimationNodeStateMachinemới. -
Đặ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
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:
- Nhấp chuột phải vào vùng đồ thị và chọn Add Animation
- Chọn một animation từ AnimationPlayer của bạn (Idle, Walk, Run, Jump, v.v.)
- Lặp lại cho mỗi trạng thái animation bạn cần
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:
- Nhấp vào node trạng thái nguồn
- Kéo tới trạng thái đích để tạo mũi tên transition
- 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.
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")
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)
Đ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)
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ã
GDScriptextends 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) |
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.gdextends 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
)
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ố
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.
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.
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().
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.
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().
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