Машины состояний 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 3 против Godot 4

В Godot 4 свойство AnimationTree.animation_player было заменено на AnimationTree.anim_player. Путь к параметру воспроизведения также изменился. Если вы переходите с Godot 3, ознакомьтесь с официальным руководством по миграции.

3. Базовая настройка

Настройка AnimationTree выполняется в четыре шага:

  1. Добавьте узел AnimationTree — Добавьте его как дочерний узел вашего персонажа (например, CharacterBody2D или CharacterBody3D) рядом с вашим AnimationPlayer.
  2. Задайте anim_player — В инспекторе укажите в свойстве «Anim Player» ваш узел AnimationPlayer.
  3. Задайте tree_root — Нажмите на свойство «Tree Root» в инспекторе и создайте новый AnimationNodeStateMachine.
  4. Установите 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, дважды щёлкните по нему в инспекторе, чтобы открыть редактор машины состояний:

  1. Щёлкните правой кнопкой мыши в области графа и выберите Add Animation
  2. Выберите анимацию из вашего AnimationPlayer (Idle, Walk, Run, Jump и т. д.)
  3. Повторите для каждого нужного вам состояния анимации
Особые состояния

Start — это точка входа; первый переход всегда начинается здесь. End необязателен и сигнализирует о завершении работы машины состояний (полезно для вложенных машин состояний).

Добавление переходов

Чтобы создать переход между двумя состояниями:

  1. Нажмите на узел исходного состояния
  2. Перетащите к целевому состоянию, чтобы создать стрелку перехода
  3. Нажмите на стрелку перехода, чтобы настроить его в инспекторе

Свойства перехода

Свойство Описание
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() (ручные переходы). Вы можете сочетать оба подхода.

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> для условий, заданных на переходах. Имя условия должно совпадать с 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.

Запуск из кода

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 Начать воспроизведение анимации 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.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 (с состояниями Idle/Walk/Run/Jump/Fall), соединённый с узлом OneShot («AttackOneShot»), который, в свою очередь, соединён с Output. Анимация атаки соединена со входом «shot» узла OneShot.

11. Устранение неполадок

«Анимация не воспроизводится»

Проверьте, что на AnimationTree установлено active = true и что свойство anim_player указывает на действительный AnimationPlayer. Также убедитесь, что AnimationPlayer действительно содержит анимации с теми именами, на которые вы ссылаетесь.

«travel() ничего не делает»

Убедитесь, что между текущим состоянием и целевым состоянием существует допустимый путь перехода. travel() тихо завершится неудачей, если пути не существует. Используйте state_machine.get_current_node(), чтобы отладить, в каком состоянии вы на самом деле находитесь.

«Смешивание не работает»

Проверьте, что значение blend_position попадает в диапазон точек вашего blend space. Если ваши точки находятся на 0.0 и 1.0, значение 5.0 не будет работать как ожидается. Используйте clamp().

«Warning: AnimationTree is not active»

Установите 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
Получить Godot MCP Pro — $15