![](https://habrastorage.org/files/bf4/5c3/22a/bf45c322a52d485ca79120f3ba7ca15e.jpg)
Недавно я завершал этап разработки текущего игрового проекта, и мне пришлось исправлять множество небольших недостатков в данных и коде анимаций. Такие типы проблем часто встречались мне и в прошлых проектах. Немного расстроившись, я начал записывать простейшие советы и хитрости, которые я использовал раньше. Они могут быть полезными другим разработчикам, начинающим работу с игровой анимацией.
Отклик на сигналы джойстика только после их стабилизации
У современных геймпадов обычно есть не только цифровые кнопки, но и аналоговые триггеры с джойстиками; каждый из них может выводить практически бесконечное количество значений. Джойстики могут передавать любой угол от 0 до 360 градусов и радиус отклонения от 0 до 1. Триггеры, хотя и похожи на двоичные кнопки, могут выдавать любое нормализованное значение от 0 до 1 — они чувствительны к давлению.
Важно учитывать эту разницу, привязывая аналоговое управление к ключевым моментам действий и анимаций персонажа. Пользователям требуется определённое время для изменения значения любого аналогового элемента управления с 0 на 1. Если в этом диапазоне есть несколько сегментов, используемых для активизации различных типов действий, некоторые промежуточные состояния аналогового управления могут вызывать неожиданные для пользователя действия в игре.
Например, обычно игроки могут поворачиватьтся на 180 градусов с помощью джойстика двумя различными способами (см. рисунок ниже).
Разные способы смены отклонения джойстика на 180 градусов
В обоих случаях может возникнуть проблема. В случае слева это связано с прохождением джойстика через исходное положение. Иногда игры ошибочно интерпретируют это как сигнал для включения анимации остановки персонажа, потому что положение джойстика имеет нулевой радиус. В нашем текущем проекте мы не допускаем прерывания анимации остановки, пока не завершена часть анимации замедления, поэтому пользователь не может немедленно начать двигаться снова, если он собирался отклонить джойстик в другом направлении. В случае справа игра может попытаться выполнить поворот на 90 градусов, если единственным критерием для джойстика является нахождение в определённом сегменте круга.
Разумеется, если сработавшее действие или анимацию можно сразу же прекратить, то оно будет заменено после того, как игрок до конца повернёт аналоговый джойстик, и в какой-то мере эту проблему можно игнорировать. В случае, если сработавшее действие не прерывается, игроку придётся ждать завершения предыдущего действия, чтобы получить ожидаемую реакцию. Это даже может привести полному игнорированию вводимых им нажатий.
Чтобы предотвратить такие случаи, можно ввести для джойстика или любого другого элемента аналогового ввода критерий стабилизации. Мы можем взять среднее значение из нескольких последних кадров аналогового ввода и сравнить его с текущим значением. Таким образом мы численно определим, стабилизирован ли аналоговый ввод и нужно ли его учитывать. Или же он продолжает быстро изменяться и реакцию на него нужно отложить. Такой подход значительно снижает количество ложноположительных реакций, генерирующих срабатывание действий с помощью аналогового ввода. Главным недостатком здесь становится латентность управления в течение нескольких кадров. Однако при использовании этого принципа во многих движениях персонажа в моём текущем проекте при правильной настройке никто из пользователей не мог заметить разницы.
Достаточное количество кадров анимации, перекрывающее смешивания
Многие персонажи в видеоиграх — настоящие атлеты. Они могут бежать с высокой скоростью, прыгать, снова бежать, маневрировать на бегу, сохраняя большую инерцию. Поэтому в видеоиграх при переходе от одного действия к другому чрезвычайно важно сохранять плавность и правдоподобие переходов и движений персонажа. Один из способов обеспечения плавности — добавление дополнительного времени на смешивание между движениями. Это позволит сохранять плавность переходов от одной анимации к другой. Однако, чтобы это работало хорошо, в процессе смешивания необходимо продолжать обновление завершаемой или зависимой анимации и двигаться соответственно инерции персонажа.
Если движение, оставшееся для завершаемой анимации, короче длительности смешивания между этой анимацией и следующей, то алгоритм смешивания в качестве нижестоящей анимации будет использовать статичную позу без определённой скорости. Часто это интерпретируется игрой как нулевая скорость. В зависимости от веса смешивания, оставшегося для зависимой анимации, такой резкий переход зависимой анимации к нулевой скорости может создать прерывистость смешанной скорости персонажа при переходе.
![](https://habrastorage.org/files/089/1b6/4e9/0891b64e9b3c4d17af577190f8c33127.png)
Изменение скорости персонажа при переходе между анимациями A и B
В нижнем случае смешанная скорость содержит прерывистость, вызванную резким падением до нуля скорости зависимой анимации. В верхнем примере скорость персонажа в процессе смешивания остаётся именно такой, какую мы ожидаем благодаря большому количеству кадров, оставшихся от точки перехода в зависимой анимации.
Старайтесь не терять значимые действия при смешивании с ассетами
Большинство действий, выполняемых персонажем, состоит из подготовки, самого действия и его завершения. Если убрать хотя бы один из этих сегментов, персонаж будет выглядеть неестественно. Например, представьте бейсболиста, пытающегося ударить без замаха и завершения удара. К сожалению, такие неестественные переходы в видеоиграх создавать слишком просто.
Этот эффект очень заметен в играх о баскетболе, где игрок с мячом постоянно ведёт мяч, ударяя им о пол и ловя его на отскоке. Другими словами, он почти всегда или замахивается, чтобы ударить мячом о пол, или завершает движение, ловя его в воздухе. Для завершённости каждый из сегментов удара мячом и его последующей поимки должен проигрываться полностью, иначе дриблинг будет выглядеть неестественно. Нужно быть очень внимательным к переходам в течение каждого из сегментов, чтобы их можно было заменить чем-то, плавно продолжающим процесс подготовки, действия и завершения. Часто возникает проблема при переходе к новому ассету, содержащему сегмент ожидания прямо в начале анимации. Если процесс смешивания перехода слишком длинный, игроки не увидят подготовки к действию в анимации, потому что будет продолжаться предыдущая анимация. Получившееся в игре движение будет выглядеть не очень реалистично.
Существуют способы исправить ситуацию динамическим изменением типа или длины смешивания перехода. Однако лучше всего в этом случае — вообще избегать этих проблем, создавая ассеты таким образом, чтобы в анимации не было никаких обязательных поз, пока текущая анимация не достигла достаточного веса в смешивании. Благодаря этому подготовка, действие и завершении будут чётко видны на экране. Пример: если вы смешиваете анимацию бейсболиста, готового замахнуться битой, и у вас есть долгое смешивание с анимацией, то не заставляйте бейсболиста начинать само движение замаха битой в ассетах анимации слишком рано.
Scaling source motion rather than adding or subtracting
Часто нам нужно добавить к анимации движение, чтобы персонаж достиг определённой точки для взаимодействия. Например, шаг персонажа вперёд, чтобы приблизиться к дверной ручке. Играм часто приходится преобразовывать движение анимации в процессе выполнения, чтобы инверсная кинематика приблизила руку персонажа достаточно близко к ручке. В этом случае не нужно добавлять движение, которое должно происходить с постоянной скоростью в течение всей анимации, Вместо этого стоит заметить, когда анимация движется, и преобразовать скорость движения с учётом необходимой величины дополнительного движения. Другими словами, если персонаж в каких-то сегментах анимации неподвижен, то к этим сегментам не стоит добавлять движение. В противном случае могут возникнуть такая ситуация: персонаж должен стоять, но вместо этого он движется, и его ноги скользят по полу.
![](https://habrastorage.org/files/2dc/aa5/472/2dcaa5472aaf464f924e781db224db3b.png)
Скорость персонажа с добавленными 20% движения. Обеими пунктирными линиями проходится одинаковое расстояние.
На рисунке выше мы берём график скорости анимации, начинающейся с определённой скорости и замедляющейся до состояния покоя. Для игры нам нужно добавить ещё 20% движения персонажа во время анимации. В одном из случаев мы решили добавить дополнительное движение на количество кадров анимации, и равномерно распределить его по каждому карту. В результате персонаж продолжает сохранять ненулевую скорость даже возле завершения, когда находится в состоянии ожидания. Во втором случае мы добавили 20% движения, просто отмасштабировав весь график скорости на 20%. Теперь персонаж двигается быстрее в частях анимации с движением и остаётся неподвижным при указанной в анимации нулевой скорости.
В одной из старых систем, с которой я работал, мы заставили персонажа прыгать вверх на определённую высоту следующим образом: в анимациях отмечалось время отрыва от земли и приземления, а затем алгоритм рассчитывал дополнительную величину смещения, нужного персонажу, чтобы достичь определённой высоты. Затем он равномерно распределял это дополнительное движение между моментами отрыва от земли и приземления. Ни в одном из случаев нам так и не удалось сделать это движение естественным, потому что мы игнорировали слишком многое из базового движения. На следующий год мы переписали систему и она стала масштабировать вертикальную скорость персонажа для правильного соответствия прыжку. Результаты выглядели гораздо более естественно. Так получилось потому, что части прыжка, в которые персонаж ещё не двигался вверх, оставались приблизительно одинаковыми, и мы набирали скорость только когда персонаж уже двигался быстро. Взяв за основу данные о настоящем действии и выполняя изменения для соответствия определённым отметкам, мы достигли гораздо лучших визуальных результатов.
Управление качеством большого количества анимаций
За последние несколько лет у меня была возможность поработать над многими спортивными видеоиграми. В играх такого типа есть интересная сложность: они пытаются воспроизвести точные движения конкретных спортсменов из реального мира. В большинстве других игр существует только одна анимация для одного типа игрового действия. Нам же иногда приходилось создавать десятки разных вариаций, чтобы игровые спортсмены более точно соответствовали своим реальным прототипам. Так появляется большой объём игровых анимаций, и любая операция, которую нужно выполнить для каждой анимации вручную занимает кучу времени разработки. Просто представьте, что для новой функции внезапно требуется вручную добавить новый тэг в сотни различных ассетов: это занимает множество времени!
В подобных ситуациях полезно иметь фреймворк, позволяющий выполнять верификацию данных и создавать исправляющие скрипты для нескольких или всех ассетов анимаций. Такие скрипты могут выполнять верификацию по определённому набору правил, создавая список ассетов, требующих ручной проверки, автоматически устранять проблемы или даже добавлять метаданные в определённые моменты нужных ассетов.
Обычно лучше исправлять такие проблемы с данными заранее. Иначе устранение ошибок в процессе их появления может занять очень длительное время. К тому же вам придётся прерывать текущую работу для исправления таких ошибок с данными. Как правило, когда я трачу силы на скрипты верификации данных, я экономлю гораздо больше времени по сравнению с решением проблем по мере их появления. Кроме того, набравшись опыта в реализации таких скриптов, вы сможете лучшее и быстрее создавать новые. Если у вас есть скрипты, вы сможете быстро перепроверять все ассеты на наличие новых анимаций с проблемами. Это довольно полезно при разработке, когда новые анимации постоянно добавляются в игру.
Поделитесь со мной своим мнением
Надеюсь, мои советы помогут вам сэкономить несколько часов труда в процессе разработки. По крайней мере, некоторые из этих советов могут помочь создать среду, в которой будет проще масштабировать количество анимаций и переходы анимаций в вашем проекте. Если вам есть что сказать по поводу этих советов, или у вас есть собственные полезные рекомендации по работе с игровой анимацией, то напишите мне на jcdelannoy@gmail.com. Спасибо!