Физика в Unity-проекте на примере мобильного файтинга
Физика стала неотъемлемой частью любой современной игры. Будь то простая симуляция ткани или полноценная физика движения транспорта. Не являются исключением и мобильные игры. Однако, настраивая физику для них, нужно оглядываться на ограничения, связанные с относительно низкой производительностью поддерживаемых устройств старого поколения. Ведущий технический 3D-художник Banzai.Games Роман Терский рассказал, как его команда интегрировала физику в игровой процесс мобильного файтинга Shadow Fight 3, какие приемы использовала для оптимизации и как переписала «с нуля» физику для персонажей для достижения ее полной детерминированности в синхронном PVP.
Физика твердых тел
Снаряжение персонажей в Shadow Fight 3 имеет множество элементов, подверженных физической симуляции, которая добавляет динамики происходящему на экране. Одной из основных трудностей, с которой мы столкнулись при настройке физики для данных элементов, является тот факт, что кости, к которым они прискинены, находятся внутри иерархии скелета самого персонажа. При движении они повторяют трансформации родительских костей и не получают физически реалистичного импульса.
Детач костей
Самым простым решением стал детач костей. После инициализации всех элементов снаряжения с помощью скрипта мы вынимаем кости физически активных элементов из иерархии скелета персонажа и, используя компонент Character Joint, создаем связь с родительской костью.
Однако мы столкнулись с небольшой погрешностью, возникающей при просадке fps: в этом случае кость, подверженная физической симуляции, с небольшой задержкой «догоняет» кость, с которой связана Joint«ом. Как правило, эта погрешность настолько незначительна, что ей можно пренебречь. Для остальных случаев было применено альтернативное решение.
Фейковый импульс
Рассмотрим это решение на примере шлема мародера, спартанский гребень которого подвержен физической симуляции. Мы разбили гребень на 5 частей, каждая из которых была прискинена к разным костям. В настройках Joint«а этих костей выставили лимиты на поворот по нужной оси и задали параметр Twist Limit Spring, отвечающий за эффект пружины.
Для физически реалистичной симуляции кости гребня вынесли из иерархии персонажа, однако в случае просадки fps, например, на слабом устройстве меш некрасиво вытягивался из-за «догоняющих» костей.
Поэтому кости гребня мы решили оставить внутри иерархии персонажа, а для повышения динамичности придать им фейковый импульс. Для этого нам потребовалось в каждой анимации (кроме боевой стойки) определить момент, когда прикладывать импульс, а также его направление.
Можно было бы считывать количество кадров в текущей анимации, потом вычитать из этого значения 15–20 кадров и прикладывать импульс по истечении полученной разницы. Однако лишней арифметики нам удалось избежать, привязав момент срабатывания импульса к окончанию интервала анимации uninterrupted.
У каждой анимации (опять же кроме боевой стойки) есть преднастроенный период, в течение которого игрок не может ее прервать. По истечении этого срока или в момент получения удара интервал uninterrupted заканчивает свое действие, и в этот момент срабатывает наш импульс. Нужно было только настроить исключения для нескольких анимаций.
Таким образом, импульс срабатывает за несколько кадров до конца каждой анимации, как нам и было нужно. В момент инициализации импульса мы считываем координаты, в которых кость находилась в предыдущем и текущем кадре, получая вектор ее движения. По этой оси и прикладывается наш импульс.
Элементы снаряжения
В целях оптимизации мы стараемся как можно реже использовать коллайдеры при симуляции физики для различных элементов снаряжения персонажей. В большинстве случаев нам удается это сделать, манипулируя лишь ограничениями по осям в настройках Joint«а костей, для которых проводится симуляция.
В ряде случаев (например, с металлическими пластинами) использование коллайдеров неизбежно. Однако основную нагрузку несет не само наличие коллайдеров, а расчет их столкновений. Минимизировать эту нагрузку помогает тонкая настройка матрицы столкновения слоев (Layer Collision Matrix) в Project Settings. Для подобных элементов мы используем два отдельных слоя, которые коллизятся только между собой, таким образом избегая просчета столкновения с коллайдерами других слоев (оружия, пола, стен и т.д.)
Физический клон
В Shadow Fight 3 есть несколько типов оружия, для которых применяется физическая симуляция вне атакующих анимаций. На текущий момент это нож на цепи, кусаригама, нунчаки и цеп. По описанным выше причинам мы решили вынимать кости оружия из иерархии персонажа вне атакующих анимаций и возвращать обратно тогда, когда физическая симуляция не требуется. Манипулируя параметром Is Kinematic в компоненте Rigidbody костей, в зависимости от ситуации мы включаем и выключаем физику для них.
Однако при использовании кусаригамы и ножа на цепи мы столкнулись с повышенной нагрузкой на слабых устройствах и получили просадку fps. Проблема возникала именно тогда, когда кости возвращались в иерархию персонажа и физическая симуляция для них отключалась. Связано это с тем, что изменение трансформов родительской кости в иерархии скелета дает нагрузку на физический движок для каждой дочерней кости, на которой есть компонент Rigidbody, даже если активен параметр Is Kinematic. И чем длиннее иерархия, тем больше нагрузка.
Решением стало создание физического клона. Рассмотрим это на примере ножа на цепи.
Во время загрузки боя для него инициализируется 2 скелета: основной, который находится внутри иерархии персонажа, и его физический клон. В костях основного скелета отсутствует компонент Rigidbody, на их трансформацию влияют только анимационные треки. Кости второго имеют настроенные связи (Joint«ы) и компонент Rigidbody с активным параметром Is Kinematic.
В то время как на трансформацию костей основного скелета влияет анимационный трек, например, во время удара, параметр Is Kinematic в компоненте Rigidbody костей физического клона остается активным. Кости не трансформируются и не подвергаются физической симуляции. Во время последнего кадра анимации происходит синхронизация трансформов костей двух скелетов. Физический клон считывает положение и ротацию костей основного скелета и задает своим точно такие же параметры. Затем деактивируется Is Kinematic, и кости физического клона подвергаются симуляции. Далее, вплоть до начала следующей атакующей анимации, уже основной скелет каждый кадр считывает трансформы костей физического клона, которые в этот момент двигаются по физике, и задает эти параметры своим костям. Такой подход позволил существенно снизить нагрузку на физический движок и улучшить производительность на слабых устройствах.
Симуляция тканей
При настройке симуляции тканей в рамках производительности мобильных устройств основное ограничение — использование коллизии тканей с коллайдерами. Более дешевой альтернативой служит тонкая настройка Surface Penetration для констрейнтов ткани. Так как в нашей игре множество анимаций и различных поз персонажей, был составлен список самых «опасных» из них, на которых все ткани проверялись на предмет проникновения сквозь другие части тела.
Также симуляцию тканей мы использовали при создании FX-эффекта пламени на оружиях и на голове босса Теневой разум. В настройках Cloth для этих элементов мы отключили влияние гравитации и задали значения ускорения (Acceleration) по оси Y: постоянный, чтобы пламя стремилось вверх, и рандомный — для эффекта трепыхания. Чтобы при движении не было резкого искажения геометрии, мы выставили повышенное значение сопротивления (Damping). Таким образом мы получили достаточно реалистичный и дешевый в плане производительности эффект пламени.
Детерминированная физика для синхронного PvP
В момент смерти и в определенных ситуациях при получении удара для персонажей в Shadow Fight 3 активируется симуляция физики. Долгое время для этого использовалась стоковая физика твердых тел Unity. Однако при внедрении синхронного PVP в проект от нее пришлось отказаться в пользу собственной разработки.
Синхронное PVP подразумевает одинаковую симуляцию игры на двух клиентах. С анимацией проблем нет, поскольку все рассчитано заранее, в то время как с физикой возникают определенные проблемы.
Дело в том, что вычисления с плавающей запятой, которые используются внутри физики в Unity, работают по-разному на процессорах разных производителей. В связи с этим в процессе игры накапливаются ошибки положения персонажей — на одном клиенте персонаж расположен не так, как на другом. И если вне физики это расхождение можно легко исправить, периодически синхронизируя положение на основе показателей с одного из клиентов, то в момент инициализации физики из-за стартовой ошибки положения физическая симуляция развивается по-разному на двух клиентах.
В итоге персонаж оказывается существенно в разных местах и разных положениях. После такого расхождения рано или поздно возникнет ситуация, при которой на одном клиенте удары регистрироваться будут, а на другом — нет.
Самое простое, на первый взгляд, решение — во время физической симуляции брать положение персонажа на одном клиенте и передавать на другой, синхронизируя их. Но регдолл персонажа представляет из себя длинную иерархию костей с большим количеством отдельных независимых твердых тел (конечности, голова), для корректной синхронизации положения которых нужно передать большой объем данных за короткий промежуток времени. Такой вариант оказался слишком «дорогим», поэтому мы решили написать свою физику, которая была бы детерминированной. Чтобы мы могли быть уверены, что на любом клиенте физические состояния персонажей совпадают вне зависимости от того, на каком процессоре производятся вычисления.
Итак, что же представляет из себя наш регдолл? Тело состоит из узлов, которые являются материальными точками.У них нет ориентации, но есть положение и масса, и между ними реализованы связи регулируемой жесткости. К каждой кости внутри скелета персонажа привязана группа таких узлов. Данная архитектура подразумевает отсутствие внутренних коллизий и ограничений в суставах, а внешние коллизии и трение реализованы на уровне узлов. При движении узлов в пространстве учитываются гравитация, внешние силы и инерция.
Между узлами существует два вида связей: жесткие ребра (синие) и эластичные мышцы (красные). Ребра играют роль костей, заставляя узлы находиться на определенном расстоянии друг от друга и не давая им разлететься в разные стороны. Мышцы же из любого стартового положения формируют из узлов определенную позу, стягивая их, если расстояние между ними больше целевого значения, и расталкивая, если меньше.
Заглянем «под капот» и посмотрим, как это работает. Сначала мы позволяем узлам двигаться свободно, затем итеративно корректируем связи таким образом, чтобы они восстановились до своих целевых характеристик. На одну итерацию корректировки мышц приходится две итерации корректировки ребер. Делая ребра более жесткими, мы можем быть уверены, что реберные связи не сломаются после воздействия мышц на узлы.
Как следствие, чем сильнее узлы успевают сместиться на стадии свободного движения, тем больше вычислительных затрат необходимо вложить, чтобы восстановить ребра и мышцы. Чтобы минимизировать эти затраты и риск нарушения конструкции, мы решили разбить итеративный процесс на несколько шагов. То есть за один кадр несколько раз происходит свободное движение узлов и их корректировка. За один шаг узлы успевают сдвинуться существенно меньше, и корректировать их становится намного проще. Таким образом, мы серьезно экономим на количестве необходимых итераций корректировки ребер и мышц.
Совокупность длин мышц определяет целевую позу, к которой стремится персонаж из любого положения после перехода в физическую симуляцию. Чтобы избежать слишком резкого перехода и нарушения конструкции, мы добавили интерполяцию поз. В момент входа в физику мы берем текущую позу персонажа и делаем ее целевой, а затем в течение пятидесяти кадров интерполируем ее к преднастроенной целевой позе, получая плавный переход.
Основная проблема, с которой мы столкнулись при использовании нашей физики, — периодическое выкручивание конечностей, в основном рук. Связано это с тем, что в момент перехода в физику персонаж может находиться в позе, далекой от целевой, к которой его стягивают мышцы. Чтобы минимизировать, а в дальнейшем и полностью избежать подобных ситуаций, был применен ряд мер. В первую очередь, мы настроили несколько целевых поз, к которым мышцы могут стягивать узлы. В момент входа в физику мы берем текущую позу, смотрим, к какой из преднастроенных целевых поз она ближе всего, и стягиваем узлы именно к ней.
Изначально при переходе в физику мышцы жестко расталкивали узлы, приводя их в нужную позицию. Зачастую резкость этого расталкивания также приводила к тому, что конечности сильно выкручивались. Мы добавили плавное увеличение силы мышц, чем сильно улучшили ситуацию. В течение первых двух кадров после начала симуляции физики сила мышц сохраняется максимальной, чтобы стабилизировать узлы после применения к ним импульса. Затем мышцы расслабляются, их сила становится 55%, а далее в течение 120 кадров сила постепенно увеличивается вплоть до 100%.
Последним шагом стало добавление двух стабилизирующих узлов: спереди на уровне груди и сзади на уровне ног. Эти узлы имеют реберные связи с фиксированными узлами груди и таза соответственно, а мышцами стягивают нестабильные узлы. Стабилизирующие узлы имеют низкое значение массы и не имеют коллизии с полом, в отличие от остальных узлов.
На гифке ниже вы видите результат: мы получили полностью детерминированную физику, построенную на целочисленных вычислениях, стабильно работающую при 60 fps даже на самых слабых устройствах, которые мы поддерживаем.
Подпишитесь на страницы Banzai Games в соцсетях: Facebook, Vkontakte, Instagram, LinkedIn
В команду Banzai Games требуется опытный VFX-специалист. Подробнее о вакансии можно прочитать здесь.