Машины состояний AnimationTree
в Godot 4
Полное руководство по машинам состояний, переходам, blend tree, OneShot, travel() против условий — с реальными примерами кода на GDScript.
Содержание
1. Введение
AnimationTree — это система Godot 4 для сложного смешивания анимаций и переходов между состояниями. Если вы когда-либо пытались управлять несколькими анимациями с помощью вызовов AnimationPlayer.play(), разбросанных по всему коду, вы знаете, как быстро это становится неуправляемым. AnimationTree решает эту проблему с помощью визуального графа состояний и переходов.
Несмотря на невероятную мощь, официальная документация по AnimationTree скудна и часто оставляет разработчиков в догадках. Это руководство проведёт вас через всё — от базовой настройки до продвинутых blend tree — с реальным кодом на GDScript, который вы можете скопировать прямо в свой проект.
Машины состояний, переходы, blend space (1D и 2D), OneShot для атак, travel() против условий и полный паттерн контроллера персонажа.
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. Путь к параметру воспроизведения также изменился. Если вы переходите с Godot 3, ознакомьтесь с официальным руководством по миграции.
3. Базовая настройка
Настройка AnimationTree выполняется в четыре шага:
-
Добавьте узел AnimationTree
— Добавьте его как дочерний узел вашего персонажа (например,
CharacterBody2DилиCharacterBody3D) рядом с вашим AnimationPlayer. -
Задайте
anim_player— В инспекторе укажите в свойстве «Anim Player» ваш узел AnimationPlayer. -
Задайте
tree_root— Нажмите на свойство «Tree Root» в инспекторе и создайте новыйAnimationNodeStateMachine. -
Установите
active = true— Установите флажок «Active» в инспекторе или задайте это в коде.
Дерево вашей сцены должно выглядеть так:
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. Основы машины состояний
Машина состояний в AnimationTree работает на простой концепции: состояния представляют анимации, а переходы определяют условия переключения между ними.
Добавление состояний
Как только tree_root вашего AnimationTree станет AnimationNodeStateMachine, дважды щёлкните по нему в инспекторе, чтобы открыть редактор машины состояний:
- Щёлкните правой кнопкой мыши в области графа и выберите Add Animation
- Выберите анимацию из вашего AnimationPlayer (Idle, Walk, Run, Jump и т. д.)
- Повторите для каждого нужного вам состояния анимации
Start — это точка входа; первый переход всегда начинается здесь. End необязателен и сигнализирует о завершении работы машины состояний (полезно для вложенных машин состояний).
Добавление переходов
Чтобы создать переход между двумя состояниями:
- Нажмите на узел исходного состояния
- Перетащите к целевому состоянию, чтобы создать стрелку перехода
- Нажмите на стрелку перехода, чтобы настроить его в инспекторе
Свойства перехода
| Свойство | Описание |
|---|---|
advance_mode |
Auto — срабатывает, когда его условие истинно. Enabled — всегда доступен для travel(). Disabled — заблокирован. |
advance_condition |
Имя булева параметра (например, is_moving). Когда истинно, переход срабатывает автоматически. |
xfade_time |
Длительность перекрёстного затухания в секундах. Плавное смешивание между анимациями. Типично: 0,1 – 0,3 с. |
switch_mode |
Immediate — переключиться сейчас. Sync — сопоставить позицию воспроизведения. 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. Управление машиной состояний из кода
Есть два основных способа управлять машиной состояний: установка параметров-условий (автоматические переходы) и вызов travel() (ручные переходы). Вы можете сочетать оба подхода.
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> для условий, заданных на переходах. Имя условия должно совпадать с advance_condition, которое вы задали на переходе в редакторе.
6. travel() против условий
На основе условий (рекомендуется для передвижения)
Задавайте булевы параметры каждый кадр и позвольте переходам срабатывать автоматически. Это более декларативно и делает ваш код чище. Машина состояний берёт на себя логику переходов, перекрёстные затухания и граничные случаи.
# 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() запрашивает переход состояния. Он соблюдает правила переходов — если от текущего состояния нет допустимого пути к целевому, вызов игнорируется. Благодаря этому его безопасно вызывать многократно. Используйте его для одноразовых триггеров, таких как атаки, эмоции или анимации кат-сцен.
# 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)
Условия для непрерывных состояний (idle, walk, run, fall). travel() для состояний, запускаемых событиями (атака, уклонение, взаимодействие). Многие проекты используют оба варианта: условия для передвижения, travel() для боевых действий.
7. Blend Tree
Blend tree позволяют плавно интерполировать между несколькими анимациями на основе непрерывного значения, вместо жёсткого переключения между дискретными состояниями. Это идеально подходит для смешивания скорости ходьбы/бега и направленного движения.
BlendSpace1D
1D-смешивание между двумя или более анимациями вдоль одной оси. Частое применение: смешивание Walk и Run на основе скорости движения.
В редакторе создайте узел BlendSpace1D внутри вашей машины состояний (или как самостоятельный 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
2D-смешивание с использованием двух осей. Идеально для 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 поддерживает несколько режимов смешивания: триангуляция по умолчанию хорошо работает в большинстве случаев. Вы также можете выбрать дискретный режим (привязка к ближайшей точке), если хотите анимацию в стиле пиксель-арт без интерполяции.
8. Основные типы узлов
AnimationTree поддерживает несколько типов узлов, которые можно комбинировать для создания сложного анимационного поведения:
| Тип узла | Сценарий использования |
|---|---|
AnimationNodeStateMachine |
Машина состояний с переходами. Самый распространённый корневой узел. Состояния могут быть анимациями или вложенными машинами состояний. |
AnimationNodeBlendSpace1D |
1D-смешивание вдоль одной оси. Скорость ходьбы/бега, угол прицеливания и т. д. |
AnimationNodeBlendSpace2D |
2D-смешивание с использованием двух осей. Направленное движение, смешивание при боковом перемещении. |
AnimationNodeBlendTree |
Граф операций смешивания. Комбинируйте несколько узлов смешивания с собственной логикой. |
AnimationNodeAdd2 |
Аддитивное смешивание. Наложите одну анимацию поверх другой (например, смещение прицела поверх ходьбы). |
AnimationNodeTimeScale |
Управление скоростью. Заставляет анимацию воспроизводиться быстрее или медленнее во время выполнения. |
AnimationNodeOneShot |
Одноразовое наложение анимации. Идеально для атак, эмоций, реакций на попадания. |
AnimationNodeTransition |
Переключение между несколькими входами с перекрёстными затуханиями. Альтернатива машинам состояний для более простых настроек. |
9. Паттерн OneShot (атаки, эмоции)
Узел OneShot — один из самых полезных паттернов в AnimationTree. Он воспроизводит одноразовую анимацию поверх вашей базовой анимации (например, взмах атаки во время ходьбы), а затем автоматически возвращается к базовой анимации.
Настройка в BlendTree
Чтобы использовать OneShot, корень вашего AnimationTree (или состояние внутри него) должен быть 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 |
Начать воспроизведение анимации OneShot |
ONE_SHOT_REQUEST_ABORT |
Отменить OneShot и немедленно вернуться к базовой |
ONE_SHOT_REQUEST_FADE_OUT |
Плавно затухает OneShot (использует свойство fadeout_time) |
Для комбо-систем используйте несколько узлов OneShot последовательно или вложенную машину состояний во входе «shot» узла OneShot с состояниями Attack1 → Attack2 → Attack3.
10. Практический пример: полный контроллер персонажа
Ниже приведён полный скрипт персонажа 2D-платформера, который сочетает передвижение на машине состояний с атакой 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 (с состояниями Idle/Walk/Run/Jump/Fall), соединённый с узлом OneShot («AttackOneShot»), который, в свою очередь, соединён с Output. Анимация атаки соединена со входом «shot» узла OneShot.
11. Устранение неполадок
Проверьте, что на AnimationTree установлено active = true и что свойство anim_player указывает на действительный AnimationPlayer. Также убедитесь, что AnimationPlayer действительно содержит анимации с теми именами, на которые вы ссылаетесь.
Убедитесь, что между текущим состоянием и целевым состоянием существует допустимый путь перехода. travel() тихо завершится неудачей, если пути не существует. Используйте state_machine.get_current_node(), чтобы отладить, в каком состоянии вы на самом деле находитесь.
Проверьте, что значение blend_position попадает в диапазон точек вашего blend space. Если ваши точки находятся на 0.0 и 1.0, значение 5.0 не будет работать как ожидается. Используйте clamp().
Установите active = true либо в редакторе (флажок в инспекторе), либо в вашей функции _ready(). AnimationTree ничего не делает, пока не активирован.
Внимательно проверьте, что: (1) advance_mode перехода установлен в Auto, (2) имя advance_condition в точности совпадает с тем, что вы задали в коде (с учётом регистра), и (3) вы задаёте параметр каждый кадр в _physics_process().
AnimationTree отвечает только за воспроизведение анимации. Логика движения (velocity, move_and_slide()) отделена от неё и должна быть реализована в _physics_process() вашего скрипта.
Хотите, чтобы ИИ построил ваш AnimationTree?
Godot MCP Pro может создавать машины состояний, добавлять состояния и переходы, настраивать blend tree и задавать параметры — всё из одного промпта. Скажите вашему ИИ-ассистенту, какое анимационное поведение вам нужно, и он построит для вас весь AnimationTree.
- create_animation_tree
- add_state_machine_state
- add_state_machine_transition
- set_blend_tree_node
- set_tree_parameter
- get_animation_tree_structure