Ведущий технический художник Banzai.Games Роман Терский рассказывает о технических решениях, позволивших улучшить и оптимизировать физику мобильного многопользовательского файтинга Shadow Fight Arena. Главным нововведением игры является синхронный PvP, появления которого ждали 400 миллионов игроков по всему миру в течение 9 лет. И для команды было важно не только сохранить реалистичность анимаций, но и согласовать движения двух персонажей на двух разных устройствах.

Визуально Shadow Fight Arena (SFA) выгодно выделяется среди других игр серии благодаря переходу на Physically Based Rendering (PBR). Мы значительно улучшили графику, повысив качество текстур, изменив освещение и добавив постэффекты. Чтобы соответствовать установленной планке, мы так же переработали физику в игре, улучшив ее как визуально, так и в плане оптимизации.



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



Плоская иерархия


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

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



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

Как же переход на плоскую иерархию сказался на физике?

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

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

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

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



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

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



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

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



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

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



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

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



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



XPBD детерминированная физика, построенная на твердых телах.


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

В Shadow Fight Arena мы пошли дальше и, основываясь на предыдущем опыте, создали новый ragdoll для персонажей, основанный на подходе Extended position-based dynamics (XPBD). Новая физика также использует целочисленные вычисления и полностью детерминированна, однако имеет ряд преимуществ перед предыдущем решением, построенным на узлах.



С технической точки зрения традиционная Unity-физика имеет constraint solver, а XPBD-физика — iterative solver. То есть вместо того, чтобы решать каждый тик систему уравнений для выяснения того, какое положение тел удовлетворит всем ограничениям, мы итерационно выводим систему из состояния с нарушенными ограничениями в состояние, соответствующее им.

Рассмотрим на примере обнаружения проникновения двух тел друг в друга при столкновении. Если используется традиционный метод, сначала рассчитывается сила столкновения, вызванная проникновением объекта, а затем на основе силы вычисляется информация о скорости и местоположении. Прежде чем окончательно обновить положение объекта, этот метод должен рассчитать силу, скорость и текущее положение, что требует значительных вычислений.

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

Наш ragdoll состоит из box-коллайдеров, соединённых между собой джоинтами двух типов: Spherical и Hinge. Первый тип ограничивает поворот коллайдеров по заданным осям, а во втором указывается конус вращения по определенной оси. Для этих box-коллайдеров рассчитываются столкновения только с бесконечным горизонтальным полом, соответственно они не имеют коллизий между собой и с внешними динамическими коллайдерами. Таким образом, на результат симуляции влияет только стартовая позиция.

Избежать вероятности проникновения тел друг в друга нам удалось за счёт точечной настройки каждого джоинта. Также для каждого тела настраивается масса, а для соединений — степень затухания (damping) и другие стандартные для подобной системы параметры.



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



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



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

Нам хотелось добиться эффекта сгруппированности, как если бы персонаж был просто сбит с ног, но сохранял сознание. Для этого мы добавили ещё одно ограничение для box-коллайдеров — стремление к определённой позе. В результате мы получили сгруппированное, но очень жёсткое тело, поведение персонажа стало слишком «деревянным».

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

Это нужно для того, чтобы мягко выводить персонажа из стартовой позы к целевой, так как эти позы могут быть далеки друг от друга, и без Compliance этот переход может вызвать нежелательные импульсы в конечности. Затем мы обнуляем Compliance, оставляя ragdoll жёстким и сгруппированным вплоть до момента приземления, когда мы снова смягчаем тело, чтобы оно выглядело более расслабленным.


Original — Pose — Pose + Compliance

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

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

Помимо этого мы всегда придаем дополнительный импульс в размере 20% от основного по оси Z, слегка подкидывая ragdoll. А в случае если во время симуляции персонаж получит новый удар, мы сохраняем изначальный вектор движения, добавляя к нему только 30% от нового импульса. Все эти манипуляции позволили добиться достаточно реалистичного и кинематографичного результата.

image

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

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

Подробнее ознакомиться с методом Position based dynamics можно по ссылке.
А скачать демо и сорсы по этой ссылке.

Комментарии (4)


  1. picul
    20.10.2021 15:24

    А Вам точно нужен general-purpose трехмерный солвер столкновений в данной ситуации? С виду в 3D достаточно определять только столкновения с землей (горизонтальная плоскость, должно сильно упростить алгоритм по идее), а остальное можно определять и обрабатывать в 2D на проекции тел на плоскость экрана.


    1. bak
      20.10.2021 21:36

      Солвер нужен для того чтобы обсчитывать все ограничения а не только столкновения. В частности joint-ы между отдельными объектами (в данном случае частями тела персонажа). У персонажа все его части (руки / ноги / голова / etc.) полноценно движутся в 3d (боковые удары, развороты и куча всего), никакой проекции в 2d тут не получится.


  1. andrew911
    20.10.2021 20:28
    +1

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


  1. major-general_Kusanagi
    21.10.2021 11:58

    Очень круто! Мой любимый файтинг!
    Спасибо за него!