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 3 กับ Godot 4

ใน Godot 4 พรอเพอร์ตี AnimationTree.animation_player ถูกแทนที่ด้วย AnimationTree.anim_player และเส้นทางพารามิเตอร์ playback ก็เปลี่ยนไปด้วย หากคุณกำลังย้ายจาก Godot 3 ให้ตรวจสอบคู่มือการย้ายอย่างเป็นทางการ

3. การตั้งค่าพื้นฐาน

การตั้งค่า AnimationTree มีสี่ขั้นตอน:

  1. เพิ่มโหนด AnimationTree — เพิ่มเป็นลูกของตัวละครของคุณ (เช่น CharacterBody2D หรือ CharacterBody3D) วางไว้ข้าง ๆ AnimationPlayer
  2. ตั้งค่า anim_player — ใน Inspector ให้ชี้พรอเพอร์ตี "Anim Player" ไปยังโหนด AnimationPlayer ของคุณ
  3. ตั้งค่า tree_root — คลิกพรอเพอร์ตี "Tree Root" ใน Inspector แล้วสร้าง AnimationNodeStateMachine ใหม่
  4. ตั้งค่า 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:

  1. คลิกขวาในพื้นที่กราฟแล้วเลือก Add Animation
  2. เลือกอนิเมชันจาก AnimationPlayer ของคุณ (Idle, Walk, Run, Jump ฯลฯ)
  3. ทำซ้ำสำหรับแต่ละ state ของอนิเมชันที่คุณต้องการ
State พิเศษ

Start คือจุดเข้า — transition แรกจะเริ่มจากที่นี่เสมอ ส่วน End เป็นตัวเลือกและใช้ส่งสัญญาณว่า state machine ทำงานเสร็จแล้ว (มีประโยชน์สำหรับ state machine ที่ซ้อนกัน)

การเพิ่ม Transition

ในการสร้าง transition ระหว่างสอง state:

  1. คลิกที่โหนด state ต้นทาง
  2. ลากไปยัง state ปลายทางเพื่อสร้างลูกศร transition
  3. คลิกที่ลูกศร 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 แบบสั่งเอง) คุณสามารถผสมทั้งสองแนวทางเข้าด้วยกันได้

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> สำหรับ 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.

การทริกเกอร์จากโค้ด

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 เริ่มเล่นอนิเมชันแบบครั้งเดียว
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.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 (ที่มี state Idle/Walk/Run/Jump/Fall) เชื่อมต่อไปยังโหนด OneShot ("AttackOneShot") ซึ่งเชื่อมไปยัง Output อนิเมชันการโจมตีเชื่อมต่อกับอินพุต "shot" ของ OneShot

11. การแก้ปัญหา

"อนิเมชันไม่เล่น"

ตรวจสอบว่า active = true บน AnimationTree และพรอเพอร์ตี anim_player ชี้ไปยัง AnimationPlayer ที่ถูกต้อง อีกทั้งตรวจสอบว่า AnimationPlayer มีอนิเมชันตามชื่อที่คุณอ้างอิงอยู่จริง

"travel() ไม่ทำอะไรเลย"

ตรวจให้แน่ใจว่ามีเส้นทาง transition ที่ถูกต้องระหว่าง state ปัจจุบันกับ state เป้าหมาย travel() จะล้มเหลวเงียบ ๆ หากไม่มีเส้นทาง ใช้ state_machine.get_current_node() เพื่อดีบักว่าคุณอยู่ใน state ใดจริง ๆ

"การเบลนด์ไม่ทำงาน"

ตรวจสอบว่าค่า blend_position ของคุณอยู่ในช่วงของจุด blend space ของคุณ หากจุดของคุณอยู่ที่ 0.0 และ 1.0 ค่า 5.0 จะไม่ทำงานตามที่คาดหวัง ให้ใช้ clamp()

"Warning: AnimationTree is not active"

ตั้งค่า active = true ไม่ว่าจะในเอดิเตอร์ (ช่องติ๊กใน Inspector) หรือในฟังก์ชัน _ready() ของคุณ AnimationTree จะไม่ทำอะไรจนกว่าจะถูกเปิดใช้งาน

"transition แบบอิง condition ไม่ทำงาน"

ตรวจสอบซ้ำว่า: (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
รับ Godot MCP Pro — $15