Хабрахабр

Анимационный граф состояний

Привет! Мы тут в Playrix решили сделать свой Unity3D. А там есть Animator. В этой статье я  расскажу, как мы сделали его у себя и как он работает.

Когда мы начинали проектировать архитектуру своих анимационных графов, мы, конечно же, смотрели на другие аналоги, в частности на Animator от Unity. Однако мы хотели сделать более универсальное решение. В отличие от того же Unity у нас есть кастомизация анимационных состояний через интерфейс контроллеров. Но для начала стоит разобраться, что такое анимационный граф состояний. Если вы уже с этим сталкивались, имеет смысл пропустить вводную часть и перейти к особенностям реализации.

Итак, что же это такое — анимационный граф состояний?

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

Возьмем, например, анимацию персонажа:

У нас есть трехмерная модель человечка и есть несколько его анимаций: 

  • idle — стоит на месте;
  • walk — идет вперед;
  • sitting — сидит;
  • hello — машет рукой.

Классический подход управления анимациями таков: если нужно, чтобы объект стоял — включаем idle, ходил — walk, сидел — sitting. Но с этим есть определенные сложности.

Например, чтобы человек сел, сначала нужно проиграть анимацию, как он садится, а потом начать играть зацикленную анимацию, где человек уже сидит. Во-первых, нужно вручную управлять длительностью и последовательностью анимаций. Подгонять в коде стыки этих анимаций сложно и неудобно.

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

С ним вам не нужно оперировать анимациями вручную, теперь вы оперируете состояниями. Анимационный граф предназначен для решения этих вопросов. Теперь программист не задумывается о таймингах и последовательности анимации, он просто указывает, в какое состояние должен перейти объект. Как объект будет анимироваться для достижения этого состояния — это работа аниматоров и дизайнеров.

При переходе между состояниями мы можем сделать плавный переход одной анимации к другой. Также с анимационным графом отпадает проблема стыковки анимаций. Вес — это коэффициент смешивания от 0 до 1, где 0 означает, что анимация никак не влияет на объект, а 1 — полностью влияет. Это делается с помощью весов.

В любой момент анимации ходьбы персонаж может остановиться. Например, переход между ходьбой (walk) и стоянием (idle) очень требователен к настройке процесса. В это время вес ходьбы убывает от 1 до 0, а вес стояния увеличивается от 0 до 1. Поэтому переход осуществляется не мгновенно, а за какой-то небольшой промежуток времени. Важно, чтобы сумма весов была равна единице, иначе могут появиться артефакты.

Как это все работает?

Граф состоит из состояний и переходов. Состояние — это набор анимационных контроллеров, каждый из которых может проигрывать какую-то анимацию на объекте или выполнять какую-то логику. Контроллер имеет точки входа и выхода — это те моменты, когда граф включает состояние с этим контроллером и выключает соответственно. Также контроллер имеет функцию обновления, где, помимо промежутка времени с прошлого кадра, приходит вес перехода. Для смешивания анимаций его нужно обязательно учитывать.

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

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

Множество переходов может приходить в состояние и точно так же неограниченно выходить. Переходов может быть сколько угодно. Например, если между состояниями A и F нет перехода напрямую, но есть цепочка A→B→C→D→E→F, то при запросе перехода из А в F граф сам поймет, что ему нужно пройти промежуточные состояния B, C, D, и E. Переходы определяют возможность достижения состояний.

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

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

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

Анимационный граф делает всю сопутствующую работу:

  • планирует путь до необходимого состояния;
  • обновляет работающие в данный момент состояния;
  • осуществляет плавный переход между состояниями;
  • регулирует веса в них.

Интересные возможности

В движке Playrix есть много разных типов анимаций: 3Dмодели, Spine, Flash, эффекты частиц, скелетная анимация. На каждый тип существует определенный контроллер.

Например, рандомизированный контроллер. Кроме простых анимационных контроллеров у нас есть несколько вспомогательных. Каждый раз, когда объект переходит в состояние с таким рандомизированным контроллером, происходит случайный выбор с учетом вероятностей, и начинает функционировать выбранный контроллер. Он может включать в себя список других контроллеров и вероятности их выбора. Остальные спят и бездействуют, дожидаясь своего момента.

Например, если у нескольких персонажей один и тот же граф, и у всех есть какая-то анимация действия. Но иногда на одном состоянии нам нужно переключать анимации. Для таких ситуаций есть специальный контроллер, который тоже содержит в себе список контроллеров, но, в отличие от рандомизированного, здесь он выбирает контроллер в зависимости от переменной. Один персонаж должен достать метлу и начать мести дорогу, другой достать фотоаппарат и начать фотографировать, третий ест мороженое.

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

Например, можно смешивать анимацию ходьбы влево, вправо и вперед. Еще у нас есть контроллер, который может смешивать несколько анимаций. Тем самым при поворотах можно регулировать веса между ними так, чтобы ноги персонажа не проскальзывали и ходьба выглядела естественно.

We need to go deeper

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

У нас есть интерфейс контроллера, есть несколько контроллеров «из коробки» и есть возможность имплементировать интерфейс и сделать в нем все, что угодно (и необязательно это будет анимация):

  • изменить текст на кнопке;
  • взаимодействовать с другими объектами в иерархии;
  • и даже поуправлять другим графом анимаций.

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

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

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

Но с двумя графами все гораздо проще. Данную логику можно было бы поместить и на первом графе, но тогда бы анимации дублировались множество раз. Управляющий граф содержит в себе цепочки состояний, включающие состояния из первого графа, который работает параллельно.

Что дальше?

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть