AnimationTree State Machine
ใน Godot 4
คู่มือฉบับสมบูรณ์สำหรับ state machine, transition, blend tree, OneShot, travel() กับ condition — พร้อมตัวอย่างโค้ด GDScript จริง
สารบัญ
1. บทนำ
AnimationTree คือระบบของ Godot 4 สำหรับการเบลนด์อนิเมชันที่ซับซ้อนและการเปลี่ยน state หากคุณเคยพยายามจัดการอนิเมชันหลายตัวด้วยการเรียก AnimationPlayer.play() กระจายอยู่ทั่วโค้ด คุณคงรู้ดีว่ามันเริ่มควบคุมไม่ได้เร็วแค่ไหน AnimationTree แก้ปัญหานี้ด้วยกราฟภาพของ state และ transition
แม้จะทรงพลังอย่างเหลือเชื่อ แต่เอกสารทางการของ AnimationTree กลับมีน้อยและมักทำให้นักพัฒนาต้องเดาเอาเอง คู่มือนี้จะพาคุณไปตั้งแต่การตั้งค่าพื้นฐานไปจนถึง blend tree ขั้นสูง พร้อมโค้ด GDScript จริงที่คุณคัดลอกไปใช้ในโปรเจกต์ได้ทันที
State machine, transition, blend space (1D และ 2D), OneShot สำหรับการโจมตี, travel() กับ condition และรูปแบบ character controller ที่สมบูรณ์
2. สิ่งที่ต้องมีก่อน
ก่อนตั้งค่า AnimationTree คุณต้องมี:
- โหนด
AnimationPlayerที่มีอนิเมชันอย่างน้อย Idle, Walk, Run และ Jump สร้างไว้แล้ว - AnimationPlayer ต้องเป็นโหนดพี่น้องหรือลูกของโหนดที่คุณเพิ่ม AnimationTree เข้าไป (โดยทั่วไปทั้งสองเป็นลูกของโหนดรากตัวละครของคุณ)
- Godot 4.x (คู่มือนี้ใช้ API ของ Godot 4 — API ของ AnimationTree เปลี่ยนไปมากจาก Godot 3)
ใน Godot 4 พรอเพอร์ตี AnimationTree.animation_player ถูกแทนที่ด้วย AnimationTree.anim_player และเส้นทางพารามิเตอร์ playback ก็เปลี่ยนไปด้วย หากคุณกำลังย้ายจาก Godot 3 ให้ตรวจสอบคู่มือการย้ายอย่างเป็นทางการ
3. การตั้งค่าพื้นฐาน
การตั้งค่า AnimationTree มีสี่ขั้นตอน:
-
เพิ่มโหนด AnimationTree
— เพิ่มเป็นลูกของตัวละครของคุณ (เช่น
CharacterBody2DหรือCharacterBody3D) วางไว้ข้าง ๆ AnimationPlayer -
ตั้งค่า
anim_player— ใน Inspector ให้ชี้พรอเพอร์ตี "Anim Player" ไปยังโหนด AnimationPlayer ของคุณ -
ตั้งค่า
tree_root— คลิกพรอเพอร์ตี "Tree Root" ใน Inspector แล้วสร้างAnimationNodeStateMachineใหม่ -
ตั้งค่า
active = true— ติ๊กช่อง "Active" ใน Inspector หรือตั้งค่าในโค้ด
โครงสร้างซีน (scene tree) ของคุณควรมีลักษณะแบบนี้:
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
ในทางปฏิบัติ คุณมักจะตั้งค่า AnimationTree ในเอดิเตอร์เกือบทุกครั้ง โค้ดด้านบนแสดงไว้เพื่อความครบถ้วน แต่โดยทั่วไปคุณต้องการเพียง anim_tree.active = true ในสคริปต์ของคุณ (และแม้แต่บรรทัดนี้ก็ตั้งค่าในเอดิเตอร์ได้)
4. พื้นฐาน State Machine
State machine ใน AnimationTree ทำงานบนแนวคิดง่าย ๆ: state แทนอนิเมชัน และ transition กำหนดเงื่อนไขในการสลับระหว่างกัน
การเพิ่ม State
เมื่อ tree_root ของ AnimationTree เป็น AnimationNodeStateMachine แล้ว ให้ดับเบิลคลิกที่มันใน Inspector เพื่อเปิดเอดิเตอร์ของ state machine:
- คลิกขวาในพื้นที่กราฟแล้วเลือก Add Animation
- เลือกอนิเมชันจาก AnimationPlayer ของคุณ (Idle, Walk, Run, Jump ฯลฯ)
- ทำซ้ำสำหรับแต่ละ state ของอนิเมชันที่คุณต้องการ
Start คือจุดเข้า — transition แรกจะเริ่มจากที่นี่เสมอ ส่วน End เป็นตัวเลือกและใช้ส่งสัญญาณว่า state machine ทำงานเสร็จแล้ว (มีประโยชน์สำหรับ state machine ที่ซ้อนกัน)
การเพิ่ม Transition
ในการสร้าง transition ระหว่างสอง state:
- คลิกที่โหนด state ต้นทาง
- ลากไปยัง state ปลายทางเพื่อสร้างลูกศร transition
- คลิกที่ลูกศร transition เพื่อตั้งค่าใน Inspector
พรอเพอร์ตีของ Transition
| พรอเพอร์ตี | คำอธิบาย |
|---|---|
advance_mode |
Auto — ทำงานเมื่อ condition เป็น true Enabled — พร้อมใช้กับ travel() เสมอ Disabled — ถูกบล็อก |
advance_condition |
ชื่อของพารามิเตอร์แบบ boolean (เช่น is_moving) เมื่อเป็น true transition จะทำงานโดยอัตโนมัติ |
xfade_time |
ระยะเวลา crossfade เป็นวินาที เบลนด์อนิเมชันให้ราบรื่น ค่าทั่วไป: 0.1 – 0.3 วินาที |
switch_mode |
Immediate — สลับทันที Sync — ให้ตรงกับตำแหน่ง playback 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. ควบคุม State Machine จากโค้ด
มีสองวิธีหลักในการสั่งงาน state machine: การตั้งค่าพารามิเตอร์ condition (transition อัตโนมัติ) และการเรียก travel() (transition แบบสั่งเอง) คุณสามารถผสมทั้งสองแนวทางเข้าด้วยกันได้
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> สำหรับ condition ที่ตั้งไว้บน transition ชื่อ condition ต้องตรงกับ advance_condition ที่คุณตั้งไว้บน transition ในเอดิเตอร์
6. travel() กับ Condition
แบบอิง Condition (แนะนำสำหรับการเคลื่อนที่)
ตั้งค่าพารามิเตอร์ boolean ทุกเฟรมและปล่อยให้ transition ทำงานโดยอัตโนมัติ วิธีนี้เป็นเชิงประกาศ (declarative) มากกว่าและทำให้โค้ดของคุณสะอาด State machine จะจัดการตรรกะ transition, crossfade และกรณีขอบเขตต่าง ๆ ให้คุณ
# 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() ร้องขอการเปลี่ยน state โดยเคารพกฎของ transition — หากไม่มีเส้นทางที่ถูกต้องจาก state ปัจจุบันไปยังเป้าหมาย การเรียกจะถูกละเว้น ทำให้เรียกซ้ำได้อย่างปลอดภัย ใช้กับทริกเกอร์ครั้งเดียว เช่น การโจมตี, อีโมต หรืออนิเมชันคัตซีน
# 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)
Condition สำหรับ state ต่อเนื่อง (idle, walk, run, fall) travel() สำหรับ state ที่เกิดจากอีเวนต์ (attack, dodge, interact) หลายโปรเจกต์ใช้ทั้งสองอย่าง: condition สำหรับการเคลื่อนที่, travel() สำหรับแอ็กชันการต่อสู้
7. Blend Tree
Blend tree ช่วยให้คุณแทรกค่าระหว่างอนิเมชันหลายตัวได้อย่างราบรื่นตามค่าต่อเนื่อง แทนที่จะสลับ state แบบตายตัว เหมาะอย่างยิ่งสำหรับการเบลนด์ความเร็ว walk/run และการเคลื่อนที่ตามทิศทาง
BlendSpace1D
การเบลนด์แบบ 1 มิติระหว่างอนิเมชันสองตัวขึ้นไปตามแกนเดียว การใช้งานที่พบบ่อย: เบลนด์ Walk และ Run ตามความเร็วการเคลื่อนที่
ในเอดิเตอร์ ให้สร้างโหนด BlendSpace1D ภายใน state machine ของคุณ (หรือเป็น 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
การเบลนด์แบบ 2 มิติโดยใช้สองแกน เหมาะอย่างยิ่งสำหรับการเคลื่อนที่ 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 รองรับหลายโหมดการเบลนด์: การทำ triangulation แบบเริ่มต้นใช้ได้ดีกับกรณีส่วนใหญ่ คุณยังเลือกโหมด discrete (สแนปไปยังจุดที่ใกล้ที่สุด) ได้ หากต้องการอนิเมชันสไตล์พิกเซลอาร์ตโดยไม่มีการแทรกค่า
8. ประเภทโหนดที่พบบ่อย
AnimationTree รองรับโหนดหลายประเภทที่สามารถนำมาผสมกันเพื่อสร้างพฤติกรรมอนิเมชันที่ซับซ้อน:
| ประเภทโหนด | กรณีใช้งาน |
|---|---|
AnimationNodeStateMachine |
State machine ที่มี transition โหนดรากที่พบบ่อยที่สุด state เป็นได้ทั้งอนิเมชันหรือ state machine ที่ซ้อนกัน |
AnimationNodeBlendSpace1D |
เบลนด์ 1 มิติตามแกนเดียว ความเร็ว walk/run, มุมเล็ง ฯลฯ |
AnimationNodeBlendSpace2D |
เบลนด์ 2 มิติโดยใช้สองแกน การเคลื่อนที่ตามทิศทาง, การเบลนด์การเดินด้านข้าง |
AnimationNodeBlendTree |
กราฟของการดำเนินการเบลนด์ ผสมโหนดเบลนด์หลายตัวด้วยตรรกะที่กำหนดเอง |
AnimationNodeAdd2 |
การเบลนด์แบบบวก (additive) วางอนิเมชันซ้อนบนอีกตัวหนึ่ง (เช่น aim offset ทับบนการเดิน) |
AnimationNodeTimeScale |
ควบคุมความเร็ว ทำให้อนิเมชันเล่นเร็วขึ้นหรือช้าลงขณะรันไทม์ |
AnimationNodeOneShot |
โอเวอร์เลย์อนิเมชันแบบครั้งเดียว เหมาะสำหรับการโจมตี, อีโมต, ปฏิกิริยาการโดนโจมตี |
AnimationNodeTransition |
สลับระหว่างอินพุตหลายตัวพร้อม crossfade เป็นทางเลือกแทน state machine สำหรับการตั้งค่าที่เรียบง่ายกว่า |
9. รูปแบบ OneShot (การโจมตี, อีโมต)
โหนด OneShot เป็นหนึ่งในรูปแบบที่มีประโยชน์ที่สุดใน AnimationTree มันเล่นอนิเมชันครั้งเดียวทับบนอนิเมชันฐานของคุณ (เช่นเล่นการเหวี่ยงโจมตีขณะเดิน) แล้วกลับสู่อนิเมชันฐานโดยอัตโนมัติ
การตั้งค่าใน BlendTree
ในการใช้ OneShot ราก AnimationTree ของคุณ (หรือ state ภายในนั้น) ต้องเป็น 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.
การทริกเกอร์จากโค้ด
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
ค่าคงที่ของคำขอ OneShot
| ค่าคงที่ | ผลลัพธ์ |
|---|---|
ONE_SHOT_REQUEST_FIRE |
เริ่มเล่นอนิเมชันแบบครั้งเดียว |
ONE_SHOT_REQUEST_ABORT |
ยกเลิก one-shot และกลับสู่ฐานทันที |
ONE_SHOT_REQUEST_FADE_OUT |
เฟดเอาต์ one-shot (ใช้พรอเพอร์ตี fadeout_time) |
สำหรับระบบคอมโบ ให้ใช้โหนด OneShot หลายตัวเรียงต่อกัน หรือ state machine ที่ซ้อนอยู่ภายในอินพุต shot ของ OneShot โดยมี state Attack1 → Attack2 → Attack3
10. ตัวอย่างใช้งานจริง: Character Controller แบบสมบูรณ์
นี่คือสคริปต์ตัวละครแพลตฟอร์เมอร์ 2D ที่สมบูรณ์ซึ่งผสานการเคลื่อนที่ด้วย state machine เข้ากับการโจมตีแบบ OneShot เป็นรูปแบบที่พร้อมใช้งานจริงและปรับใช้กับโปรเจกต์ของคุณเองได้
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 ภายในมีโหนด StateMachine (ที่มี state Idle/Walk/Run/Jump/Fall) เชื่อมต่อไปยังโหนด OneShot ("AttackOneShot") ซึ่งเชื่อมไปยัง Output อนิเมชันการโจมตีเชื่อมต่อกับอินพุต "shot" ของ OneShot
11. การแก้ปัญหา
ตรวจสอบว่า active = true บน AnimationTree และพรอเพอร์ตี anim_player ชี้ไปยัง AnimationPlayer ที่ถูกต้อง อีกทั้งตรวจสอบว่า AnimationPlayer มีอนิเมชันตามชื่อที่คุณอ้างอิงอยู่จริง
ตรวจให้แน่ใจว่ามีเส้นทาง transition ที่ถูกต้องระหว่าง state ปัจจุบันกับ state เป้าหมาย travel() จะล้มเหลวเงียบ ๆ หากไม่มีเส้นทาง ใช้ state_machine.get_current_node() เพื่อดีบักว่าคุณอยู่ใน state ใดจริง ๆ
ตรวจสอบว่าค่า blend_position ของคุณอยู่ในช่วงของจุด blend space ของคุณ หากจุดของคุณอยู่ที่ 0.0 และ 1.0 ค่า 5.0 จะไม่ทำงานตามที่คาดหวัง ให้ใช้ clamp()
ตั้งค่า active = true ไม่ว่าจะในเอดิเตอร์ (ช่องติ๊กใน Inspector) หรือในฟังก์ชัน _ready() ของคุณ AnimationTree จะไม่ทำอะไรจนกว่าจะถูกเปิดใช้งาน
ตรวจสอบซ้ำว่า: (1) advance_mode ของ transition ถูกตั้งเป็น Auto, (2) ชื่อ advance_condition ตรงกับที่คุณตั้งในโค้ดทุกประการ (แยกตัวพิมพ์เล็ก-ใหญ่) และ (3) คุณตั้งค่าพารามิเตอร์ทุกเฟรมใน _physics_process()
AnimationTree จัดการเฉพาะการเล่นอนิเมชันเท่านั้น ตรรกะการเคลื่อนที่ (velocity, move_and_slide()) แยกออกไปต่างหากและต้องถูกเขียนไว้ใน _physics_process() ของสคริปต์คุณ
อยากให้ AI สร้าง AnimationTree ให้คุณไหม?
Godot MCP Pro สามารถสร้าง state machine, เพิ่ม state และ transition, ตั้งค่า 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