Использование AnimationTree

Введение

Благодаря AnimationPlayer Godot имеет одну из самых гибких систем анимации, которые можно встретить в различных игровых движках. Возможность анимировать практически любое свойство в любом узле или ресурсе, а также наличие выделенных дорожек преобразования, кривые Безье, вызовы функций, аудио и субанимации довольно уникальна.

Однако поддержка смешивания этих анимаций с помощью AnimationPlayer относительно ограничена, так как может быть установлено только фиксированное время между плавными переходами.

Создание AnimationTree

Перед началом необходимо прояснить, что узел AnimationTree не содержит собственных анимаций. Вместо этого используется анимация, содержащаяся в узле AnimationPlayer. Таким образом, можно редактировать анимации (или импортировать их из сцены 3D) как обычно, а затем использовать этот дополнительный узел для управления воспроизведением.

Наиболее распространено использование AnimationTree в 3D-сценах. При импорте сцен из формата обмена 3D они, обычно, поставляются со встроенными анимациями (либо несколькими, либо разделенными из одной большой при импорте). В конце импортированная сцена Godot будет содержать анимации в узле AnimationPlayer.

Поскольку в Godot импортированные сцены редко используются непосредственно (они либо созданы, либо унаследованы), можно поместить узел AnimationTree в новую сцену, содержащую импортированную. После этого укажите узел AnimationTree на AnimationPlayer, который был создан в импортированной сцене.

Вот как это делается в Демо Шутер от третьего лица, для примера:

../../_images/animtree1.png

Для игрока была создана новая сцена с CharacterBody3D в качестве корня. Внутри этой сцены был создан исходный файл .dae (Collada) и создан узел AnimationTree.

Создание дерева

В AnimationTree можно использовать три основных типа узлов:

  1. Узлы анимации, которые ссылаются на анимацию из связанного AnimationPlayer.

  2. Корневые узлы анимации, которые используются для смешения подузлов.

  3. Узлы смешивания анимаций, которые используются в AnimationNodeBlendTree как смешение на одном и том же графике через несколько входных портов.

Чтобы задать корневой узел в AnimationTree, доступно несколько типов:

../../_images/animtree2.png
  • AnimationNodeAnimation: выбирает анимацию из списка и воспроизводит ее. Это простейший корневой узел, который обычно не используется непосредственно в качестве корневого.

  • AnimationNodeBlendTree: Содержит множество узлов типа blend, таких как mix, blend2, blend3, one shot и т. д. Это один из наиболее часто используемых корней.

  • AnimationNodeStateMachine: содержит несколько корневых узлов в качестве дочерних в графе. Каждый узел используется как state и предоставляет несколько функций для переходов между состояниями.

  • AnimationNodeBlendSpace2D: позволяет размещать корневые узлы в 2D пространстве смешения. Управляет смешением положений в 2D для смешивания нескольких анимаций.

  • AnimationNodeBlendSpace1D: Упрощенная версия вышеприведенного (1D).

Дерево смешения

AnimationNaseBlendTree может содержать как корневые, так и обычные узлы, используемые для смешения. Узлы добавляются в граф из меню:

../../_images/animtree3.webp

Все деревья смешения по умолчанию содержат узел «Output», и для воспроизведения анимации к нему необходимо что-то подключить.

Самый простой способ проверить эту функциональность — подключить к нему непосредственно узел Animation:

../../_images/animtree4.png

Это просто воспроизводит анимацию. Убедитесь, что AnimationTree активен, чтобы что-то действительно произошло.

Ниже приведено краткое описание доступных узлов:

Blend2 / Blеnd3

Эти узлы будут смешиваться между двумя или тремя входами по указанному пользователем значению смешения:

../../_images/animtree5.gif

Для более сложного смешения рекомендуется использовать пространства смешения.

При смешении также могут использоваться фильтры, т.е. можно по отдельности управлять тем, какие дорожки проходят через функцию смешения. Это очень полезно для наложения анимаций друг на друга.

../../_images/animtree6.png

OneShоt

Этот узел выполнит субанимацию и вернется после ее завершения. Время смешения для плавного замедления и ускорения можно настроить, так же как и фильтры.

../../_images/animtree6b.gif

После установки запроса и изменения режима воспроизведения анимации узел one-shot автоматически удаляет запрос на следующем кадре обработки, установив для параметра request значение AnimationNodeOneShot".ONE_SHOT_REQUEST_NONE.

# Play child animation connected to "shot" port.
animation_tree.set("parameters/OneShot/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE)
# Alternative syntax (same result as above).
animation_tree["parameters/OneShot/request"] = AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE

# Abort child animation connected to "shot" port.
animation_tree.set("parameters/OneShot/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_ABORT)
# Alternative syntax (same result as above).
animation_tree["parameters/OneShot/request"] = AnimationNodeOneShot.ONE_SHOT_REQUEST_ABORT

# Get current state (read-only).
animation_tree.get("parameters/OneShot/active"))
# Alternative syntax (same result as above).
animation_tree["parameters/OneShot/active"]

TimeSeek

Этот узел может использоваться для выполнения команды seek с любыми нижестоящими элементами графа анимации. Этот тип узла используется для воспроизведения Animation с начальной или любой другой позиции воспроизведения внутри AnimationNaseBlendTree.

После установки времени и изменения режима воспроизведения анимации узел поиска автоматически переходит в спящий режим на следующем кадре процесса, установив для него значение seek_request равным -1.0.

# Play child animation from the start.
animation_tree.set("parameters/TimeSeek/seek_request", 0.0)
# Alternative syntax (same result as above).
animation_tree["parameters/TimeSeek/seek_request"] = 0.0

# Play child animation from 12 second timestamp.
animation_tree.set("parameters/TimeSeek/seek_request", 12.0)
# Alternative syntax (same result as above).
animation_tree["parameters/TimeSeek/seek_request"] = 12.0

TimeScаle

Позволяет изменять скорость анимации (или изменять ее в обратном направлении), подключенную к входу in, с помощью параметра scale. Установка значения scale равным 0 приведет к приостановке анимации.

Переход

Очень простой конечный автомат (если вы не хотите иметь дело с узлом StateMachine). К выходам можно подключить анимацию и указать время перехода. После установки запроса и изменения режима воспроизведения анимации узел перехода автоматически удаляет запрос в следующем кадре процесса, устанавливая для него значение transition_request в пустую строку ("").

# Play child animation connected to "state_2" port.
animation_tree.set("parameters/Transition/transition_request", "state_2")
# Alternative syntax (same result as above).
animation_tree["parameters/Transition/transition_request"] = "state_2"

# Get current state name (read-only).
animation_tree.get("parameters/Transition/current_state")
# Alternative syntax (same result as above).
animation_tree["parameters/Transition/current_state"]

# Get current state index (read-only).
animation_tree.get("parameters/Transition/current_index"))
# Alternative syntax (same result as above).
animation_tree["parameters/Transition/current_index"]

BlendSpacе2D

BlendSpace2D — это узел, выполняющий продвинутое смешение в двух измерениях. В двумерное пространство добавляются точки, и затем можно управлять их положением для определения смешения:

../../_images/animtree7.gif

Диапазоны в координатах X и Y можно регулировать (и помечать для удобства). По умолчанию точки можно размещать в любом месте (щелкните правой кнопкой мыши на системе координат или используйте кнопку Добавить точку), а треугольники будут автоматически сгенерированы с помощью Delaunay.

../../_images/animtree8.gif

Можно также нарисовать треугольники вручную, отключив опцию auto triangle, хотя это требуется редко:

../../_images/animtree9.png

Наконец, можно изменить режим смешения. По умолчанию смешение происходит путем интерполяции точек внутри ближайшего треугольника. При работе с 2D анимациями (покадрово) можно переключиться в режим Дискретный. Кроме того, если требуется сохранить текущее положение воспроизведения при переключении между дискретными анимациями, используется режим Перенос. Этот режим можно изменить в меню Смешение:

../../_images/animtree10.png

BlendSpacе1D

Это похоже на 2D пространства смешивания, но в одном измерении (поэтому треугольники не нужны).

StateMachinе (машина состояний)

Этот узел действует как конечный автомат с корневыми узлами в качестве состояний. Корневые узлы могут быть созданы и соединены с помощью линий. Состояния соединяются с помощью Переходов, которые являются соединениями со специальными свойствами. Переходы являются однонаправленными, но для соединения в обоих направлениях можно использовать два.

../../_images/animtree11.gif

Существует много типов перехода:

../../_images/animtree12.png
  • Immediate: Немедленно перейдет в следующее состояние. Текущее состояние завершается и происходит переход в новое состояние.

  • Sync: Немедленно перейдет в следующее состояние, но будет установлено в позицию воспроизведения старого состояния.

  • At End: Дождется окончания воспроизведения текущего состояния, а затем переключится в начало анимации следующего состояния.

Переходы также имеют несколько свойств. Щелкните любой переход и он будет отображаться в доке инспектора:

../../_images/animtree13.png
  • Switch Mode — тип перехода (см. выше), его можно изменить после создания здесь.

  • Auto Advance включит переход автоматически при достижении этого состояния. Это лучше всего подходит для режима переключения At End.

  • Advance Condition включит автоматический шаг при установке этого условия. Это настраиваемое текстовое поле, которое может быть заполнено именем переменной. Переменная может быть изменена из кода (подробнее об этом ниже).

  • Xfade Time - время перекрестного затухания между этим состоянием и следующим.

  • Приоритет используется совместно с функцией travel() из кода (подробнее об этом позже). При перемещении по дереву предпочтительны переходы с более низким приоритетом.

  • Отключено позволяет отключить этот переход (если он отключен, он не будет использоваться во время путешествия или автоматического продвижения вперед).

Для лучшего смешивания

В Godot 4.0+, чтобы результаты смешивания были детерминированными (воспроизводимыми и всегда согласованными), значения свойств смешивания должны иметь определенное начальное значение. Например, в случае смешивания двух анимаций, если одна анимация имеет дорожку свойств, а другая - нет, смешанная анимация вычисляется так, как если бы последняя анимация обладала дорожкой свойств с начальным значением первой анимации.

При использовании Дорожки Позиционирования/Вращения/Масштабирования для костей Skeleton3D начальным значением является Bone Rest. Для других свойств начальными значениями являются 0, и если дорожка присутствует в анимации RESET, вместо него используется значение первого ключевого кадра этой дорожки.

Например, в AnimationPlayer, представленном ниже, есть две анимации, но в одной из них отсутствует дорожка свойств для позиционирования.

../../_images/blending1.webp

Это означает, что анимация, в которой отсутствует это, будет обрабатывать эти позиции как Vector2(0, 0).

../../_images/blending2.webp

Эту проблему можно решить, добавив Дорожку свойств для позиционирования в качестве начального значения в анимацию RESET.

../../_images/blending3.webp ../../_images/blending4.webp

Примечание

Имейте в виду, что анимация RESET предназначена для определения позы по умолчанию при первоначальной загрузке объекта. Предполагается, что она содержит только один кадр и не будет воспроизводиться с использованием временной шкалы.

Также имейте в виду, что Дорожка Вращения 3D и дорожки свойств для 2D-поворота с типом интерполяции, установленным на Линейный угол или Кубический угол, не позволят поворачивать более чем на 180 градусов от исходного значения при смешанной анимации.

Это может быть полезно для Skeleton3Ds, чтобы предотвратить проникновение костей в тело при смешивании анимаций. Поэтому значения Bone Rest в Skeleton3D должны быть как можно ближе к середине диапазона перемещения. Это означает, что для моделей гуманоидов предпочтительнее импортировать их в Т-образной позе.

../../_images/blending5.webp

Вы можете видеть, что приоритет отдается кратчайшему пути поворота от Bone Rest, а не кратчайшему пути поворота между анимациями.

Если вам нужно повернуть сам Skeleton3D более чем на 180 градусов, смешав анимацию движения, вы можете использовать Root Motion.

Корневое движение

При работе с 3D-анимацией популярной техникой является использование аниматорами корневой кости скелета для придания движения остальным частям скелета. Это позволяет анимировать персонажей таким образом, что шаги фактически соответствуют полу под ними. Это также позволяет точно взаимодействовать с объектами в кинематографе.

При воспроизведении анимации в Godot можно выбрать эту кость в качестве корневой дорожки движения ** . Это визуально отменит трансформацию кости (анимация останется на месте).

../../_images/animtree14.png

После этого фактическое движение может быть получено через API AnimationTree как преобразование:

# Get the motion delta.
animation_tree.get_root_motion_position()
animation_tree.get_root_motion_rotation()
animation_tree.get_root_motion_scale()

# Get the actual blended value of the animation.
animation_tree.get_root_motion_position_accumulator()
animation_tree.get_root_motion_rotation_accumulator()
animation_tree.get_root_motion_scale_accumulator()

Это может быть передано в такие функции, как CharacterBody3D.move_and_slide для управления перемещением персонажа.

Существует также узел инструментов, RootMotionView, который может быть помещен в сцену и будет действовать как пользовательский пол для вашего персонажа и анимации (этот узел отключен по умолчанию во время игры).

../../_images/animtree15.gif

Контроль из кода

После построения дерева и его предварительного просмотра остается только один вопрос: "Как все это управляется из кода?".

Помните, что узлы анимации - это всего лишь ресурсы, и как таковые они являются общими для всех экземпляров, использующих их. Установка значений в узлах напрямую повлияет на все экземпляры сцены, использующие этот AnimationTree. Это в целом нежелательно, но имеет несколько интересных случаев использования, например, вы можете копировать и вставлять части дерева анимации или повторно использовать узлы со сложной схемой (например, машину состояний или пространство смешивания) в разных деревьях анимации.

Фактические данные анимации содержатся в узле AnimationTree и доступны через свойства. Просмотрите раздел "Параметры" узла AnimationTree, чтобы увидеть все параметры, которые можно изменить в режиме реального времени:

../../_images/animtree16.png

Это удобно, поскольку позволяет анимировать их из AnimationPlayer, или даже из самого AnimationTree, что позволяет реализовать очень сложную логику анимации.

Чтобы изменить эти значения из кода, необходимо получить путь к свойству. Это легко сделать, наведя мышку на любой из параметров:

../../_images/animtree17.png

Что позволяет их устанавливать или читать:

animation_tree.set("parameters/eye_blend/blend_amount", 1.0)
# Simpler alternative form:
animation_tree["parameters/eye_blend/blend_amount"] = 1.0

Путешествие конечного автомата

Одной из приятных особенностей реализации StateMachine Godot является возможность перемещения. Графу можно дать команду перейти из текущего состояния в другое, посетив при этом все промежуточные состояния. Это делается с помощью алгоритма A*. Если нет пути переходов, начинающегося в текущем состоянии и заканчивающегося в конечном состоянии, график телепортируется в конечное состояние.

Чтобы использовать способность путешествия, необходимо сначала получить объект AnimationNodeStateMachinePlayback из узла AnimationTree (он экспортируется как свойство).

var state_machine = animation_tree["parameters/playback"]

После извлечения его можно использовать, вызвав одну из многочисленных функций, которые он предлагает:

state_machine.travel("SomeState")

Государственная машина должна быть запущена, прежде чем вы сможете отправиться в путешествие. Обязательно вызовите start() или выберите узел для Autoplay on Load.

../../_images/animtree18.png