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

    Привет! Мы тут в 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. Каждый посетитель имеет два графа: один для анимации модели, другой для анимации поведения. 

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

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

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


    Что дальше?


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

    Комментарии 12

      0
      Жёсткая сетка. Я вот думал о возможной реализации игровых событий и так и не смог прийти к единой структуре. По идее, событие должно быть как-то инициировано, значит нужны начальные условия (временная отметка или привязка с другим событиям). Событие должно быть завершено, значит есть условия завершения и статус завершения. А что ещё?
        0
        Я тоже в своё время задался этим вопросом и в итоге пришёл к реализации на модифицированных маркированных сетях Петри. Точки (маркеры) указывают на текущее состояние объектов, в то время как переходы могут быть условными и включать в себя несколько условий — таймер, например, или наличие определённо состояния в другом объекте. Штука получилась удобная, но оформить это в виде UI пока не могу — не хватает времени. Для себя использую на бумаге.

        Уверен, что у ребят из Playrix тоже есть отличное бомбическое решение для этой задачи и они когда-нибудь им поделятся :)
        0

        Вот, что мне нравится у вас, ребята — то, что к разработке вы подходите качественно: красивая графика, современные технологии, грамотный подход к продвижению кампании(многие ли игровые кампанми имеют блог на хабре, уж не говоря о слове — Интересный?) и многое другое. По статье увы, ничего написать не могу — все в статье и написано. Просто r3sp3c7 за качество, труд.

          0

          Грамотный подход продвижения игр != продвижение игры через Хабр. Тут игроков не так уж и много.

            0
            Мне тоже это нравится в Playrix, всегда приоритет на качество
            0

            Заинтересовал редактор графа. Не могли прокомментировать, используя какие технологии он сделан? Например, могу предположить реализацию с помощью фреймворка Qt или полностью собственная реализация на OpenGL.

              0
              У нас для UI редакторов используется ImGUI, простая и быстрая библиотека
                0

                Спасибо. У него немного другие назначения, нежели чем у Qt.
                Считаю, правильный выбор, если у вас не очень сложная логика между иерархией диалогов или виджетов и все нормально по скорости укладывается ( имею ввиду рендер), т.к. ImGUI постоянно все заново создаёт в процессе рендеринга кадра, даже если объекты уже были созданы и видны в текущем кадре. Как я понимаю, надо самому дополнительно строить некую систему кеширования объектов сцены.

              –2
              Насколько я понял, вы велосипед изобрели. Потому что в Юнити все это реализовано. И плавная смена анимации и прочее.
                0
                По сути да, ничего нового здесь нет… Но зато мы сделали решение для себя, и гораздо более гибкое, чем в unity
                0

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

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

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое