Руководство по анимациям в Unreal для программистов
Основы
В Unreal Engine 5 существует обширный набор инструментов и систем для анимации. В этом небольшом руководстве я постараюсь дать общий обзор на то, какие есть системы и как их использовать и откуда начинать копать код в случае багов и расширений. Это руководство скорее пригодится программистам и техническим аниматорам, но и остальные разработчики могут найти его полезным.
В unreal система координат достаточно уникальная: левая система координат с Z вверх.
Эта особенность вызывает некоторые сложности при экспорте анимаций и скелетов. В большей части программ для анимаций это уже учтено, и достаточно включить галочку “Unreal Export” или похожую, и проблем не будет, но если скелет выглядит странно и кости вывернуты - это первое, что стоит проверять.
В Unreal существуют два основных ассета. связанных со скелетной анимацией: непосредственно скелет (Skeleton) и скелетный меш (Skeletal Mesh).
Skeleton (USkeleton)
Основой всей анимационной логики Unreal служит skeleton ассет, содержащий иерархию костей и их расположение в базовой (неанимированной) позе. Чаще всего в Unreal используется A поза, но в большинстве случаев базовая поза не так важна, так как большая часть инструментов, для которых она используется (например аддитивные анимации), позволяет указывать базовую позу напрямую, например кадр из определенной анимации.
В скелете определены несколько важных дополнительных сущностей. важных для анимационной системы:
- Curve (Кривые) - это целочисленные значения, которые можно задавать внутри анимации и монтажей, и которые можно считывать в любом месте анимационной системы. Кривые позволяют переключать разные опции, менять параметры материалов и управлять анимационными графами. Если кривая помечена как material curve, её значение будет автоматически передаваться во все материалы, которые используется skeletal mesh. (подробнее о кривых можно почитать тут)
- Animation Notifies - набор стандартных событий для этого скелета. Они делятся на два типа Notify (буквально событие, на которое можно отреагировать в BP) и SyncMarker - точки синхронизации, использующиеся для бленда между анимациями.
- Blend Profiles - это маска, которая показывает какие кости и насколько активны в этом профиле. С помощью них можно проигрывать анимации только на определенных костях или конечностях.
- Retargeting – параметры ретаргетинга. Поскольку большинство анимационных ассетов привязано к конкретному скелету, для использования анимаций на совместимых скелетах необходимо указать их соответствие. Здесь задаются как явно совместимые скелеты, так и правила Retargetting.
- Socket - это дополнительные кости, созданые внутри Unreal. Чаще всего они используются для присоединения дополнительных компонентов (например другого меша или VFX), либо для определения положения некоторой части скелета в пространстве (например - куда смотрит голова персонажа) (подробнее о Socket можно почитать тут)
Если вы не можете найти какие-то настройки скелета, обратите внимание на меню Window. в котором могут быть нужные вам окна настроек. Подробное описание скелетов можно найти тут
Skeletal Mesh (USkeletalMesh)
Skeleton Mesh - это уже непосредственно меш с треугольниками, материалами и скинингом, который использует определенный Skeleton для анимации. В основном тут живут настройки, связанные с визуальной функцией модели, но есть и несколько анимационных:
- Curve (Кривые) - те же кривые,. что и для скелета, но существуют только для этого конкретного меша.
- Morph Target - настройки, позволяющие деформировать меш.
- Cloth Settings - настройки ткани.
При работе с анимацией основное внимание обычно уделяется скелету, а не мешу.
Skeletal Mesh Component (USkeletalMeshComponent)
Поскольку основой всех игровых объектов в Unreal является Actor, чтобы добавить Skeleton Mesh на Actor используется Skeleton Mesh Component. В этом компоненте соединяются все настройки. необходимые для правильного функционирования SkeletalMesh. Их достаточно много и большая их часть имеет описание, или можно понять их функции из исходников, но часть стоит упомянуть отдельно.
- ComponentTick - настройка того, когда и как тикает наш компонент. Она стандартная для ActorComponent, но все равно важная для SkeletalMesh. В ней можно указать, нужно ли мешу обновляться на сервере (в некоторых играх - да, в некоторых - нет), когда тикать (чаще всего до физики, но меши, которые присоединены к другому компоненту, должны тикать в конце, чтобы не отставать на кадр от своего родителя)
- Visibility Based Anim Tick Option позволяет настраивать то, будет ли обновляться поза в зависимости от видимости скелета. Очень важная настройка для оптимизации, так как тяжеловесные анимационные графы очень сильно бьют по производительности проекта.
- Animation Mode отвечает за то, как анимирован компонент с помощью Blueprint (о которых пойдет речь дальше), или с помощью простой анимации, или вообще каким-нибудь необычным образом из кода (например копированием поз в Animation Sharing).
Этот компонент еще интересен тем, что, не смотря на режим анимации, этот компонент все равно отвечает за финальное обновление позы и Transform конкретного меша. Даже при многопоточной логике обновления анимационных графов, о которых мы поговорим позже, финальное положение костей все равно обновляется в этом компоненте, что делает его идеальной точкой старта для поиска багов. Сам компонент унаследован от USkinnedMeshComponent, которйы в свою очередь унаследован от UMeshComponent. Эти два класса и сам USkeletalMeshComponent позволяют достаточно быстро определить стандартные проблемы, связанные с анимациями. Например: передается ли кривая в материалы (USkeletalMeshComponent::ApplyAnimationCurvesToComponent), или обновляется ли поза (USkinnedMeshComponent::ShouldTickPose и USkinnedMeshComponent::TickPose), или вообще тикает ли наш скелет (USkeletalMeshComponent::TickComponent).
Анимации
Базовым абстрактным ассетом, содержащим анимационную информацию является UAnimationAsset. От него унаследованы все остальные ассеты, которые отвечают за анимации.
Animation Sequence (UAnimSequence)
Каждая анимация представлена в Unreal как Animation Sequence. Она жестко привязана к Skeleton и не может существовать без конкретного скелета. Тут живут все движения костей, которые запекались в анимационной программе, также тут можно добавлять анимации кривых и аддитивных треков. Кроме того тут определены настройки того как эту анимацию воспринимать остальной анимационной системе: аддитивная ли это анимация или обычная (Additive Anim Type), есть ли у этой анимации Root Motion, и если есть, то от какой позы он считается (Все эти настройки расположены в секции Root Motion) . В анимации можно добавлять нотификаторы: как определенные раньше в Skeleton, так и любые доступные. Нотификаторы могут варьироваться от событий до полноценных блюпринтов, выполняющих некоторую игровую логику или вызывающих выполнение такой логики в других системах. (Подробнее про нотификаторы будет ниже)
Обычно этот класс не расширяют в c++, а просто создают ассеты из него, но часто бывает так, что необходимо добавить какую-нибудь важную информацию в анимацию. Для этого в Unreal UAssetUserData и UAnimMetaData можно заводить дополнительные типы данных и хранить их прямо в анимации. Затем их может использовать либо игровая логика, либо узлы AnimationBlueprint. Эти данные присутствуют у всех анимационных ассетов, унаследованных от UAnimationAsset, в том числе и у UAnimSequence.
Подробнее про анимации можно почитать тут
Animation Montage (UAnimMontage)
Animation Montage - это склейки и комбинации нескольких анимаций. Они используются для того, чтобы комбинировать анимации. С помощью монтажей можо легко реплицировать RootMotion и проигрывание анимаций по сети (Такие плагины как Game Ability System очень сильно зависят от монтажей и вокруг них строят свою систему репликации анимаций).
Монтаж разбит на слоты (в дальнейшем они будут полезны при проигрывании разных анимаций на разных частях скелета или Animation Blueprint). Каждый слот состоит из последовательности анимаций.
Монтажи разбиты на секции. Секции можно настраивать, чтобы добиться циклов или переходов в зависимости от команд из игровой логики. В монтажи также можно добавлять нотификаторы и кривые.
Настройки BlendIn и BlendOut позволяют настраивать бленды при старте монтажа и его окончания. Чаще всего монтажи играют роль некоего действия, которое совершает персонаж. Такое действие должно начать проигрываться сразу и желательно реплицироваться по сети. Кроме того, как будет видно ниже, в AnimationBlueprint монтажи живут обособленно в анимационном графе и чаще всего нет отдельного перехода в монтаж, поэтому. чтобы позы не переключались резко, логика проигрывания монтажа берет на себя контроль за блендом поз.
Подробнее про монтажи можно почитать тут
Blend Space(UBlendSpace)
Blend Space позволяет смешивать разные анимации в зависимости от двух параметров: горизонтальная и вертикальная оси. Стандартным примером использования является создание красивой анимации передвижения, где по горизонтальной оси откладывают направление перемещения (угол относительно прямого направления), а по вертикальной оси - скорость передвижения. Анимации располагают в разных точках образовавшейся плоскости. Когда надо выбрать позу, BlendSpace по входящим параметрам определяет точку на плоскости, находит ближайшие анимации и. в зависимости от расстояния от них до текущей точки, смешивает их в одну позу.
Подробнее про Blend Space можно почитать тут
Anim Notify
Нотификаторы (наследники класса UAnimNotify и UAnimNotifyState) - главный механизм передачи информации из анимационной системы в остальные подсистемы игры. Нотификаторы делятся на два типа:
- Наследники UAnimNotify - единоразовое событие. Если при проигрывании анимации или монтажа встречается нотификатор, то вызывается функция UAnimNotify::Notify.
- Наследники UAnimNotifyState - нотификатор с длительностью, активный на протяжение определенного числа кадров. Анимационная система вызывает UAnimNotifyState::NotifyBegin, UAnimNotifyState::NotifyEnd, UAnimNotifyState::NotifyTick на старте, при завершении и тике нотификатора соответственно.
Нотификаторы удобно использовать для вызова геймплейных событий, проигрывания звуков, или визуальных эффектов непосредственно из анимации.
Подробнее про нотификаторы можно почитать тут
Animation Blueprint (ABP)
Анимационный блюприт (иногда анимационный граф) является основным и стандартным способом управления анимациями в Unreal. С точки зрения редактора, ABP привязан к конкретному скелету и состоит из двух частей. Первая часть - это стандартный для любого Blueprint EventGraph: здесь можно создавать логику, заполнять некоторые важные флаги и параметры для работы ABP. Вторая часть - это AnimationGraph (анимационный граф), роль которого собрать финальную позу скелета со всеми кривыми, морфами и прочими метаданными.
Animation Graph
Анимационный граф состоит из последовательности AnimNode (FAnimNode_Base) (анимационных узлов), которые выполняют логику, чтобы получить анимационную позу, повлиять на метаданные, либо переключить между разными частями графа.
У каждого AnimNode есть один выход, в который передается результат работы узла, но может быть несколько входов. Какой из них использовать определяет сам узел на основе своей внутренней логики.
В Unreal создано очень много готовых узлов, с помощью которых можно создать почти любое поведение. Я остановлюсь на нескольких самых важных и часто используемых.
Sequence Player (FAnimNode_SequencePlayerBase)
Узел, который проигрывает Animation Sequence. Самый простой и атомарный элементы любого ABP. В этом узле можно задать какую анимацию надо играть и указать настройки того, как надо играть её.
State Machine (FAnimNode_StateMachine)
Стандартный конечный автомат для описания конкретных анимационных состояний. Каждое состояние представляет из себя Animation Graph, что позволяет вкладывать в состояние еще один State Machine. Переходы между состояниями осуществляются с помощью условий или событий. У состояний существует набор событий, на которые можно повесить функции или события из EventGraph
Побольше почитать про State Machine можно тут
Slot (FAnimNode_Slot)
Узел, который играет слот из монтажа. ABP позволяет играть неограниченное количество монтажей параллельно, но с точки зрения Анимационного графа это не значит ничего для финальной позы, монтаж просто помещается в ABP, начинается, проигрывается и заканчивается. Для того, чтобы анимация из него попала в финальную позу, как раз и существует узел Slot. Этот узел использует позу одного из слотов активного сейчас монтажа, и если монтажа нет, то он просто прокидывает входящую позу без изменений.
Слоты разбиты на группы и редактируются для конкретного скелета в Animation Slot Manager, который можно открыть с помощью меню Window.
BlendByBool (FAnimNode_BlendListByBool)
Узел, который выбирает либо одну входящую позу, либо вторую, в зависимости от булевого флага. По аналогии работают и остальные BlendBy узлы
Apply Additive (FAnimNode_ApplyAdditive)
Добавляет в позу аддитивную позу. Обычно используется для проигрывания дополнительных небольших анимаций или поз смещения, например анимации реакции на удар или blendspace поворота головы за целью.
Layered blend per bone (FAnimNode_LayeredBoneBlend)
Позволяет разбить скелет на части и указать для каждой части позу, которую надо будет использовать. Чаще всего используется для анимаций верхней части тела (например анимации стрельбы, которая играется независимо от анимации ног). Для разбиения скелета на части можно указывать непосредственно кости, либо BlendProfile, заданные в Skeleton.
Подробнее про узлы можно почитать тут
Многопоточность
ABP в Unreal может обновлять свои узлы и позы многопоточно, что очень положительно влияет на производительность графа и игры в целом. Для того, чтобы ваш граф использовал многопоточность, в ABP стоит использовать Blueprint Thread Safe Update Animation и только Thread Safe функции.
Fast Update
Как видно из скришнотов выше, некоторые узлы отмечены белой молнией. Эти узлы помечены для быстрой обработки. Такие узлы позволяют компилятору blueprint копировать значения переменных и тем самым избегать вызовов блюпринтовых методов и функций. Это улучшает производительность всего ABP. Для этого ни один пин узла не должен содержать никаких вычислений кроме как чтения переменных( а лучше всего вообще bind переменные в параметры узла).
Подробнее про многопоточность и Fast Update можно почитать тут
FAnimNode_Base
В определенный момент разработки может наступить момент, когда уже существующих AnimNode недостаточно и нужно реализовать новую логику. Для этого нужно создать два класса: класс, содержащий непосредственно логику изменения позы, и класс, необходимый для редактора графов.
- Runtime узел - наследуется от FAnimNode_Base, и должен существовать в c++ проекте с типом "Runtime". Этот класс будет использоваться во время игры и выполнять нужную логику.
- Узел редактора - наследуется от UAnimGraphNode_Base., он содержит необходимые для редактора настройки и логики нового узла, и он должен существовать в c++ проекте с типом "UncookedOnly".
Выполнение узла разбито в ABP на несколько этапов, и для них всех в FAnimNode_Base существуют виртуальные функции. Из интересных стоит отметить следующие:
- Initialize_AnyThread функция для инициализации вашего узла
- Update_AnyThread функция для обновления логики узла.
- PreUpdate функция, которая выполняется до вызова Update в GameThread; позволяет сохранить данные, которые могут быть не threadsafe.
- Evaluate_AnyThread функция для обновления и создания финальной позы.
Чтобы добавить вход в новый узел, ему надо задать поле следующего вида
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Links) FPoseLink BasePose;
При этом в Update_AnyThread стоит вызвать GetEvaluateGraphExposedInputs().Execute(Context);. чтобы входящие узлы тоже получили шанс выполнить свою логику, и вызвать FPoseLinkBase::Update(const FAnimationUpdateContext& InContext) на самой BasePose, передав соответствующий контекст. Примером того, как работать с входящими позами, может служить класс FAnimNode_BlendListBase, который блендит список входящих поз с разными весами. Создавая новый узел, полезно поглядеть что и как делается в этом классе.
UAnimInstance и FAnimInstanceProxy
Программно Animation Blueprint разделён на две составляющие:
- UAnimInstance – базовый класс экземпляра ABP. Может быть расширен в Blueprint или C++.
- FAnimInstanceProxy – прослойка, обеспечивающая потокобезопасный доступ к данным во время обновления графа и скелета. Именно с ней взаимодействуют FAnimNode. Результирующая поза и метаданные (например, Root Motion) сохраняются в прокси, затем считываются UAnimInstance и передаются в USkeletalMeshComponent.
Debug
В Unreal есть несколько инструментов для дебага анимаций и скелетов.
Существуют флаги, позволяющие рендерить кости и их положение в пространстве: консольная команда Show Bones. Если открыть окно редактирования какого-нибудь ABP, то, как и с любым Bluerprint, если у вас запущена PIE, вы можете выбрать экземпляр работающего в мире ABP и посмотреть, что с ним происходит с помощью редактора.
Основным инструментом является конечно же Rewinder Debbuger, который записывает все события, происходящие с актором и с ABP, и позволяет, используя timeline, подробно кадр за кадром просматривать, что происходит. Для того. чтобы открыть Rewinder, нужно воспользоваться меню Tools -> Debug
После этого выбрать нужного актора и нажать запись.
N.B. я не уверен, насколько эта проблема зависит от версии Unreal или от сборки, но у меня Rewinder работает только в netmode PIE Play As Standalone. После этого вы можете спокойно анализировать, что происходит с мешем или ABP