Физика в Unity — опыт разработчиков Shadow Fight 3

Оптимизация под слабые устройства и синхронный PvP.

Технический 3D-художник Banzai Games Роман Терский рассказал, как его команда интегрировала физику в игровой процесс мобильного файтинга Shadow Fight 3, какие приемы использовала для оптимизации и как переписала «с нуля» физику для персонажей для достижения её полной детерминированности в синхронном PvP.

Физика твёрдых тел

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

Детач костей

Самым простым решением стал детач костей. После инициализации всех элементов снаряжения с помощью скрипта мы вынимаем кости физически активных элементов из иерархии скелета персонажа и, используя компонент Character Joint, создаем связь с родительской костью.

Физика в Unity — опыт разработчиков Shadow Fight 3

Однако мы столкнулись с небольшой погрешностью, возникающей при просадке FPS: в этом случае кость, подверженная физической симуляции, с небольшой задержкой «догоняет» кость, с которой связана с помощью Joint. Как правило, эта погрешность настолько незначительна, что ей можно пренебречь. Для остальных случаев было применено альтернативное решение.

Фейковый импульс

Рассмотрим это решение на примере шлема мародёра, спартанский гребень которого подвержен физической симуляции. Мы разбили гребень на 5 частей, каждая из которых была прискинена к разным костям. В настройках Joint этих костей выставили лимиты на поворот по нужной оси и задали параметр Twist Limit Spring, отвечающий за эффект пружины.

Физика в Unity — опыт разработчиков Shadow Fight 3

Для физически реалистичной симуляции кости гребня вынесли из иерархии персонажа, однако в случае просадки FPS, например, на слабом устройстве меш некрасиво вытягивался из-за «догоняющих» костей.

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

Можно было бы считывать количество кадров в текущей анимации, потом вычитать из этого значения 15‑20 кадров и прикладывать импульс по истечении полученной разницы. Однако лишней арифметики нам удалось избежать, привязав момент срабатывания импульса к окончанию интервала анимации uninterrupted.

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

Таким образом, импульс срабатывает за несколько кадров до конца каждой анимации, как нам и было нужно. В момент инициализации импульса мы считываем координаты, в которых кость находилась в предыдущем и текущем кадре, получая вектор её движения. По этой оси и прикладывается наш импульс.

Элементы снаряжения

В целях оптимизации мы стараемся как можно реже использовать коллайдеры при симуляции физики для различных элементов снаряжения персонажей. В большинстве случаев нам удаётся это сделать, манипулируя лишь ограничениями по осям в настройках Joint костей, для которых проводится симуляция.

В ряде случаев (например, с металлическими пластинами) использование коллайдеров неизбежно. Однако основную нагрузку несёт не само наличие коллайдеров, а расчёт их столкновений. Минимизировать эту нагрузку помогает тонкая настройка матрицы столкновения слоёв (Layer Collision Matrix) в Project Settings. Для подобных элементов мы используем два отдельных слоя, которые коллизятся только между собой, таким образом избегая просчёта столкновения с коллайдерами других слоев (оружия, пола, стен и так далее).

Физика в Unity — опыт разработчиков Shadow Fight 3

Физический клон

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

Манипулируя параметром Is Kinematic в компоненте Rigidbody костей, в зависимости от ситуации мы включаем и выключаем физику для них.

Однако при использовании кусаригамы и ножа на цепи мы столкнулись с повышенной нагрузкой на слабых устройствах и получили просадку FPS. Проблема возникала именно тогда, когда кости возвращались в иерархию персонажа и физическая симуляция для них отключалась. Связано это с тем, что изменение трансформов родительской кости в иерархии скелета даёт нагрузку на физический движок для каждой дочерней кости, на которой есть компонент Rigidbody, даже если активен параметр Is Kinematic. И чем длиннее иерархия, тем больше нагрузка.

Решением стало создание физического клона. Рассмотрим это на примере ножа на цепи.

Во время загрузки боя для него инициализируется два скелета: основной, который находится внутри иерархии персонажа, и его физический клон. В костях основного скелета отсутствует компонент Rigidbody, на их трансформацию влияют только анимационные треки. Кости второго имеют настроенные связи (Joint) и компонент Rigidbody с активным параметром Is Kinematic.

В то время как на трансформацию костей основного скелета влияет анимационный трек, например, во время удара, параметр Is Kinematic в компоненте Rigidbody костей физического клона остаётся активным. Кости не трансформируются и не подвергаются физической симуляции. Во время последнего кадра анимации происходит синхронизация трансформов костей двух скелетов.

Физический клон считывает положение и ротацию костей основного скелета и задаёт своим точно такие же параметры. Затем деактивируется Is Kinematic, и кости физического клона подвергаются симуляции. Далее, вплоть до начала следующей атакующей анимации, уже основной скелет считывает каждый кадр трансформ костей физического клона, которые в этот момент двигаются по физике, и задаёт эти параметры своим костям. Такой подход позволил существенно снизить нагрузку на физический движок и улучшить производительность на слабых устройствах.

Симуляция тканей

При настройке симуляции тканей в рамках производительности мобильных устройств основное ограничение — использование коллизии тканей с коллайдерами. Более дешёвой альтернативой служит тонкая настройка Surface Penetration для констрейнтов ткани. Так как в нашей игре множество анимаций и различных поз персонажей, был составлен список самых «опасных» из них, на которых все ткани проверялись на предмет проникновения сквозь другие части тела.

Физика в Unity — опыт разработчиков Shadow Fight 3

Также симуляцию тканей мы использовали при создании FX-эффекта пламени на оружиях и на голове босса Теневой разум. В настройках Cloth для этих элементов мы отключили влияние гравитации и задали значения ускорения (Acceleration) по оси Y: постоянный, чтобы пламя стремилось вверх, и рандомный — для эффекта трепыхания. Чтобы при движении не было резкого искажения геометрии, мы выставили повышенное значение сопротивления (Damping). Таким образом мы получили достаточно реалистичный и дешёвый в плане производительности эффект пламени.

Физика в Unity — опыт разработчиков Shadow Fight 3

Детерминированная физика для синхронного PvP

В момент смерти и в определённых ситуациях при получении удара для персонажей в Shadow Fight 3 активируется симуляция физики. Долгое время для этого использовалась стоковая физика твёрдых тел Unity. Однако при внедрении синхронного PvP в проект от неё пришлось отказаться в пользу собственной разработки.

Синхронное PvP подразумевает одинаковую симуляцию игры на двух клиентах. С анимацией проблем нет, поскольку всё рассчитано заранее, в то время как с физикой возникают определённые проблемы.

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

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

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

Такой вариант оказался слишком «дорогим», поэтому мы решили написать свою физику, которая была бы детерминированной. Чтобы мы могли быть уверены, что на любом клиенте физические состояния персонажей совпадают вне зависимости от того, на каком процессоре производятся вычисления.

Физика в Unity — опыт разработчиков Shadow Fight 3

Итак, что же представляет из себя наш регдолл? Тело состоит из узлов, которые являются материальными точками. У них нет ориентации, но есть положение и масса, и между ними реализованы связи регулируемой жесткости. К каждой кости внутри скелета персонажа привязана группа таких узлов. Данная архитектура подразумевает отсутствие внутренних коллизий и ограничений в суставах, а внешние коллизии и трение реализованы на уровне узлов. При движении узлов в пространстве учитываются гравитация, внешние силы и инерция.

Между узлами существует два вида связей: жёсткие рёбра (синие) и эластичные мышцы (красные). Рёбра играют роль костей, заставляя узлы находиться на определенном расстоянии друг от друга и не давая им разлететься в разные стороны. Мышцы же из любого стартового положения формируют из узлов определённую позу, стягивая их, если расстояние между ними больше целевого значения, и расталкивая, если меньше.

Рёбра​
Мышцы​

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

Физика в Unity — опыт разработчиков Shadow Fight 3

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

Физика в Unity — опыт разработчиков Shadow Fight 3

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

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

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

Физика в Unity — опыт разработчиков Shadow Fight 3

Изначально при переходе в физику мышцы жёстко расталкивали узлы, приводя их в нужную позицию. Зачастую резкость этого расталкивания также приводила к тому, что конечности сильно выкручивались. Мы добавили плавное увеличение силы мышц, чем сильно улучшили ситуацию.

В течение первых двух кадров после начала симуляции физики сила мышц сохраняется максимальной, чтобы стабилизировать узлы после применения к ним импульса. Затем мышцы расслабляются, их сила становится 55% , а далее в течение 120 кадров сила постепенно увеличивается вплоть до 100%.

Физика в Unity — опыт разработчиков Shadow Fight 3

Последним шагом стало добавление двух стабилизирующих узлов: спереди на уровне груди и сзади на уровне ног. Эти узлы имеют рёберные связи с фиксированными узлами груди и таза соответственно, а мышцами стягивают нестабильные узлы. Стабилизирующие узлы имеют низкое значение массы и не имеют коллизии с полом, в отличие от остальных узлов.

Физика в Unity — опыт разработчиков Shadow Fight 3

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

33 показа
9.8K9.8K открытий