Maszyny stanów AnimationTree
w Godot 4
Kompletny przewodnik po maszynach stanów, przejściach, drzewach mieszania, OneShot, travel() vs warunki — z prawdziwymi przykładami kodu GDScript.
Spis treści
- Wprowadzenie
- Wymagania wstępne
- Podstawowa konfiguracja
- Podstawy maszyny stanów
- Sterowanie maszyną stanów z kodu
- travel() vs warunki
- Drzewa mieszania (Blend Trees)
- Popularne typy węzłów
- Wzorzec OneShot (ataki, emotki)
- Przykład praktyczny: pełny kontroler postaci
- Rozwiązywanie problemów
- Automatyzuj z Godot MCP Pro
1. Wprowadzenie
AnimationTree to system Godot 4 do złożonego mieszania animacji i przełączania stanów. Jeśli kiedykolwiek próbowałeś zarządzać wieloma animacjami za pomocą wywołań AnimationPlayer.play() rozsianych po całym kodzie, wiesz, jak szybko staje się to niemożliwe do opanowania. AnimationTree rozwiązuje ten problem za pomocą wizualnego grafu stanów i przejść.
Mimo że AnimationTree jest niezwykle potężny, jego oficjalna dokumentacja jest uboga i często pozostawia programistów bez odpowiedzi. Ten przewodnik prowadzi przez wszystko – od podstawowej konfiguracji po zaawansowane drzewa mieszania – z prawdziwym kodem GDScript, który możesz skopiować bezpośrednio do swojego projektu.
Maszyny stanów, przejścia, przestrzenie mieszania (1D i 2D), OneShot do ataków, travel() vs warunki oraz kompletny wzorzec kontrolera postaci.
2. Wymagania wstępne
Zanim skonfigurujesz swój AnimationTree, potrzebujesz:
- Węzła
AnimationPlayerz już utworzonymi co najmniej animacjami Idle, Walk, Run i Jump - AnimationPlayer musi być rodzeństwem lub dzieckiem węzła, do którego dodajesz AnimationTree (zwykle oba są dziećmi głównego węzła postaci)
- Godot 4.x (ten przewodnik korzysta z API Godot 4 — API AnimationTree znacząco zmieniło się względem Godot 3)
W Godot 4 AnimationTree.animation_player został zastąpiony przez AnimationTree.anim_player. Zmieniła się także ścieżka parametru odtwarzania. Jeśli migrujesz z Godot 3, sprawdź oficjalny przewodnik migracji.
3. Podstawowa konfiguracja
Konfiguracja AnimationTree wymaga czterech kroków:
-
Dodaj węzeł AnimationTree
— Dodaj go jako dziecko swojej postaci (np.
CharacterBody2DlubCharacterBody3D), obok AnimationPlayer. -
Ustaw
anim_player— W Inspektorze wskaż właściwością „Anim Player” swój węzeł AnimationPlayer. -
Ustaw
tree_root— Kliknij właściwość „Tree Root” w Inspektorze i utwórz nowąAnimationNodeStateMachine. -
Ustaw
active = true— Zaznacz pole „Active” w Inspektorze albo ustaw to w kodzie.
Twoje drzewo sceny powinno wyglądać tak:
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
W praktyce niemal zawsze konfigurujesz AnimationTree w edytorze. Powyższy kod pokazano dla kompletności, ale zwykle w skrypcie potrzebujesz jedynie anim_tree.active = true (a i to można ustawić w edytorze).
4. Podstawy maszyny stanów
Maszyna stanów w AnimationTree działa według prostej koncepcji: stany reprezentują animacje, a przejścia definiują warunki przełączania między nimi.
Dodawanie stanów
Gdy tree_root Twojego AnimationTree jest już typu AnimationNodeStateMachine, kliknij go dwukrotnie w Inspektorze, aby otworzyć edytor maszyny stanów:
- Kliknij prawym przyciskiem myszy w obszarze grafu i wybierz Add Animation
- Wybierz animację ze swojego AnimationPlayer (Idle, Walk, Run, Jump itd.)
- Powtórz dla każdego potrzebnego stanu animacji
Start to punkt wejścia — pierwsze przejście zawsze zaczyna się tutaj. End jest opcjonalny i sygnalizuje, że maszyna stanów zakończyła działanie (przydatne przy zagnieżdżonych maszynach stanów).
Dodawanie przejść
Aby utworzyć przejście między dwoma stanami:
- Kliknij węzeł stanu źródłowego
- Przeciągnij do stanu docelowego, aby utworzyć strzałkę przejścia
- Kliknij strzałkę przejścia, aby skonfigurować ją w Inspektorze
Właściwości przejścia
| Właściwość | Opis |
|---|---|
advance_mode |
Auto — wyzwala się, gdy warunek jest prawdziwy. Enabled — zawsze dostępne dla travel(). Disabled — zablokowane. |
advance_condition |
Nazwa parametru logicznego (np. is_moving). Gdy jest prawdziwy, przejście wyzwala się automatycznie. |
xfade_time |
Czas trwania crossfade w sekundach. Płynne mieszanie między animacjami. Typowo: 0,1 – 0,3 s. |
switch_mode |
Immediate — przełącz natychmiast. Sync — dopasuj pozycję odtwarzania. AtEnd — poczekaj, aż bieżąca animacja się zakończy. |
Typowa konfiguracja dla postaci z platformówki:
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. Sterowanie maszyną stanów z kodu
Istnieją dwa główne sposoby sterowania maszyną stanów: ustawianie parametrów warunków (automatyczne przejścia) oraz wywoływanie travel() (przejścia ręczne). Oba podejścia można łączyć.
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")
Parametry stosują wzorzec parameters/conditions/<condition_name> dla warunków ustawionych na przejściach. Nazwa warunku musi być zgodna z advance_condition, którą ustawiłeś na przejściu w edytorze.
6. travel() vs warunki
Oparte na warunkach (zalecane dla poruszania się)
W każdej klatce ustawiaj parametry logiczne i pozwól, aby przejścia wyzwalały się automatycznie. To bardziej deklaratywne i utrzymuje kod w czystości. Maszyna stanów sama zajmuje się logiką przejść, crossfade'ami i przypadkami brzegowymi.
# 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() (zalecane dla akcji jednorazowych)
travel() żąda przejścia stanu. Respektuje reguły przejść — jeśli z bieżącego stanu nie istnieje prawidłowa ścieżka do celu, wywołanie jest ignorowane. Dzięki temu można je bezpiecznie wywoływać wielokrotnie. Używaj go do jednorazowych wyzwalaczy, takich jak ataki, emotki czy animacje przerywników.
# 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)
Warunki dla stanów ciągłych (bezczynność, chodzenie, bieganie, opadanie). travel() dla stanów wyzwalanych zdarzeniami (atak, unik, interakcja). Wiele projektów używa obu: warunków do poruszania się, travel() do akcji bojowych.
7. Drzewa mieszania (Blend Trees)
Drzewa mieszania pozwalają płynnie interpolować między wieloma animacjami na podstawie wartości ciągłej, zamiast twardo przełączać między dyskretnymi stanami. To idealne rozwiązanie do mieszania prędkości chodu/biegu oraz ruchu kierunkowego.
BlendSpace1D
Mieszanie 1D między dwoma lub więcej animacjami wzdłuż jednej osi. Typowe zastosowanie: mieszanie Walk i Run w zależności od prędkości poruszania się.
W edytorze utwórz węzeł BlendSpace1D wewnątrz swojej maszyny stanów (lub jako samodzielny tree root). Dodaj punkty animacji:
# 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
Mieszanie 2D wykorzystujące dwie osie. Idealne do ruchu 8-kierunkowego lub gier z widokiem z góry, w których postać może poruszać się w dowolnym kierunku.
# 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 obsługuje wiele trybów mieszania: domyślna triangulacja sprawdza się w większości przypadków. Możesz też wybrać tryb dyskretny (przyciąga do najbliższego punktu), jeśli chcesz animację w stylu pixel art bez interpolacji.
8. Popularne typy węzłów
AnimationTree obsługuje kilka typów węzłów, które można łączyć w celu tworzenia złożonego zachowania animacji:
| Typ węzła | Zastosowanie |
|---|---|
AnimationNodeStateMachine |
Maszyna stanów z przejściami. Najczęstszy węzeł główny. Stany mogą być animacjami lub zagnieżdżonymi maszynami stanów. |
AnimationNodeBlendSpace1D |
Mieszanie 1D wzdłuż jednej osi. Prędkość chodu/biegu, kąt celowania itd. |
AnimationNodeBlendSpace2D |
Mieszanie 2D z dwiema osiami. Ruch kierunkowy, mieszanie ruchu bocznego (strafe). |
AnimationNodeBlendTree |
Graf operacji mieszania. Łącz wiele węzłów mieszania z własną logiką. |
AnimationNodeAdd2 |
Mieszanie addytywne. Nakładaj jedną animację na drugą (np. przesunięcie celowania na chodzenie). |
AnimationNodeTimeScale |
Sterowanie prędkością. Sprawia, że animacja odtwarza się szybciej lub wolniej w czasie działania. |
AnimationNodeOneShot |
Jednorazowa nakładka animacji. Idealna do ataków, emotek, reakcji na trafienia. |
AnimationNodeTransition |
Przełączanie między wieloma wejściami z crossfade'ami. Alternatywa dla maszyn stanów w prostszych konfiguracjach. |
9. Wzorzec OneShot (ataki, emotki)
Węzeł OneShot to jeden z najbardziej przydatnych wzorców w AnimationTree. Odtwarza jednorazową animację na wierzchu Twojej animacji bazowej (np. odtwarzanie zamachu atakiem podczas chodzenia), a następnie automatycznie wraca do animacji bazowej.
Konfiguracja w BlendTree
Aby użyć OneShot, root Twojego AnimationTree (lub stan w jego wnętrzu) musi być typu 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.
Wyzwalanie z kodu
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
Stałe żądań OneShot
| Stała | Efekt |
|---|---|
ONE_SHOT_REQUEST_FIRE |
Rozpocznij odtwarzanie animacji OneShot |
ONE_SHOT_REQUEST_ABORT |
Anuluj OneShot i natychmiast wróć do bazy |
ONE_SHOT_REQUEST_FADE_OUT |
Wyciszaj OneShot (używa właściwości fadeout_time) |
W przypadku systemów combo użyj kilku węzłów OneShot w sekwencji lub zagnieżdżonej maszyny stanów w wejściu „shot” węzła OneShot ze stanami Attack1 → Attack2 → Attack3.
10. Przykład praktyczny: pełny kontroler postaci
Oto kompletny skrypt postaci 2D do platformówki, który łączy poruszanie się oparte na maszynie stanów z atakiem OneShot. To gotowy do produkcji wzorzec, który możesz dostosować do własnego projektu.
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. W środku: węzeł StateMachine (ze stanami Idle/Walk/Run/Jump/Fall) połączony z węzłem OneShot („AttackOneShot”), który z kolei łączy się z Output. Animacja ataku łączy się z wejściem „shot” węzła OneShot.
11. Rozwiązywanie problemów
Sprawdź, czy na AnimationTree ustawiono active = true i czy właściwość anim_player wskazuje prawidłowy AnimationPlayer. Upewnij się także, że AnimationPlayer faktycznie zawiera animacje o nazwach, do których się odwołujesz.
Upewnij się, że między bieżącym stanem a stanem docelowym istnieje prawidłowa ścieżka przejścia. travel() cicho zawiedzie, jeśli ścieżka nie istnieje. Użyj state_machine.get_current_node(), aby zdebugować, w jakim stanie faktycznie się znajdujesz.
Sprawdź, czy wartość blend_position mieści się w zakresie punktów Twojej przestrzeni mieszania. Jeśli punkty znajdują się na 0.0 i 1.0, wartość 5.0 nie zadziała zgodnie z oczekiwaniami. Użyj clamp().
Ustaw active = true albo w edytorze (pole wyboru w Inspektorze), albo w funkcji _ready(). AnimationTree nic nie robi, dopóki nie zostanie aktywowany.
Dokładnie sprawdź, czy: (1) advance_mode przejścia jest ustawiony na Auto, (2) nazwa advance_condition dokładnie odpowiada temu, co ustawiłeś w kodzie (z uwzględnieniem wielkości liter), oraz (3) ustawiasz parametr w każdej klatce w _physics_process().
AnimationTree zajmuje się wyłącznie odtwarzaniem animacji. Logika ruchu (velocity, move_and_slide()) jest oddzielna i musi być zaimplementowana w _physics_process() Twojego skryptu.
Chcesz, aby AI zbudowała Twój AnimationTree?
Godot MCP Pro potrafi tworzyć maszyny stanów, dodawać stany i przejścia, konfigurować drzewa mieszania i ustawiać parametry — a wszystko to za pomocą jednego promptu. Powiedz swojemu asystentowi AI, jakiego zachowania animacji oczekujesz, a on zbuduje dla Ciebie cały AnimationTree.
- create_animation_tree
- add_state_machine_state
- add_state_machine_transition
- set_blend_tree_node
- set_tree_parameter
- get_animation_tree_structure