Уроки Unity: создание файтинга, основанного на физике
Практические советы по созданию ключевых аспектов системы.
Разработчик Луис Бермудес, ранее работавший в EA, Disney и Roblox Corporation, опубликовал в своём блоге на Medium текст, в котором расписал ключевые аспекты создания физической системы в файтинге. Также автор описал практические способы реализации этого в Unity.
В реальности физика работает таким образом, что при ударе в лицо, голова продолжает движение в том направлении, в котором был совершён удар.
Кроме того, голова будет поворачиваться в том же направлении, в котором был совершён удар. В этом конкретном случае лицо будет вращаться вокруг вертикальной оси Y. Голова в конечном итоге достигнет предела вращения, как показано на рисунке ниже.
В конце концов, голова постарается вернуться в своё первоначальное положение, как показано на следующем рисунке.
В дополнение к вращению по вертикальной оси, есть также вращение по горизонтальной оси. Для повторения: сила удара будет перенесена на голову, и она будет вращаться в том же направлении, что и направление удара. В этом конкретном случае голова повернётся вокруг горизонтальной оси X (или Z). В конечном итоге голова достигнет предела вращения, как показано на рисунке ниже.
Также голова постарается вернуться в своё первоначальное положение, как показано на следующем рисунке.
После разъяснения можно объединить обе особенности. Сначала нужно сделать один поворот, а затем следующий для каждого временного шага — больше усложнять не нужно.
Вот простой код, который делает именно это:
void FixedUpdate() < Vector3 ySpringTorque = getTorsionalSpringTorque (Vector3(0,1,0), Vector3(0,1,0)); Vector3 xSpringTorque = getTorsionalSpringTorque (Vector3(1,0,0), Vector3(1,0,0)); rigidBody.applyTorque(ySpringTorque); rigidBody.applyTorque(xSpringTorque); >
Моделирование и реализация
Как уже упоминалось, после каждого вращения голова возвращается в состояние покоя. Это можно смоделировать с помощью крутящего момента. Уравнение крутящего момента выглядит следующим образом:
T = q * r * a1 + p * w * a2
T — крутящий момент, q — константа жёсткости, r — расстояние от требуемого вращения в градусах, p — константа затухания силы, а w — разница между текущей угловой скоростью и требуемой угловой скоростью. Также a1 — это ось вращения, a2 — это ось вращения для угловой скорости. Части уравнения a1 и a2 не обязательно являются одним и тем же вектором оси вращения. Кроме того, если затухание силы не нужно, тогда p равна нулю, и выражение упрощается до:
Следующая функция вычисляет требуемый крутящий момент, чтобы применить его к игровому объекту (например, к голове).
Vector3 getTorsionalSpringTorque (Vector3 axisOfRotation, Vector3 axisOfAngularVelocity) < float r_ij = desRot — currRot; float w_ij = desAngVel — currAngVel; Vector3 springTorque = Q * r_ij * axisOfRotation + P * w_ij * axisOfAngularVelocity; return springTorque; >
Важно понимать, что желаемого угла поворота можно достичь с помощью анимации — keyframes от аниматоров или данных с motion capture. Кроме того, константами q и p можно управлять, чтобы определить, насколько отзывчиво вращение. Это означает, что аниматор может контролировать, насколько быстро голова вернётся в нужное положение.
Ранее разговор шёл о том, как удар сказывается на голове (физика), но не о том, как боец может нацелить свой удар (кинематика). Это можно сделать с помощью инверсной кинематики. Если кулак находится достаточно близко к голове противника, можно переключиться с анимации удара на процедурную анимацию с помощью инверсной кинематики. Это можно воплотить, если сделать голову противника целью кулака.
Также стоит отметить, что желательно определить цель не как конкретное место, а создать вероятностное распределение между виском и подбородком. Например, это можно сделать через равномерную рандомизацию, при которой кулак с равной вероятностью может попасть в любое место на лице противника. Таким образом, можно получить широкий спектр адаптивных анимаций вместо того, чтобы каждый раз бить противника в одно и то же место.
Мера силы, которая передаётся противнику, зависит от площади поверхности во время контакта, относительной скорости удара и ускорения удара. Для вычисления меры силы достаточно просчитать или упростить эти переменные. В противном случае даже художник может самостоятельно определить силу для каждого типа удара или атаки.
Это основные аспекты файтинга, основанного на физике: наличие оппонента, который реагирует на удары; определение места ударов и их силы. Нужно понимать, что понадобится большой объём данных анимации для создания широкого спектра ударов и реакций на них. Использование этих методов физики и кинематики позволяет генерировать большое количество анимаций без необходимости большого количества данных анимации. Также все эти методы распространяются на удар ногой.
Управление персонажем в файтинге

Ребят, подскажите, пожалуйста, как сделать более сложное управление персонажем в Unity например: вниз,вперёд+удар рукой; рука,рука,нога для комбы; назад+нога (подсечка); вперёд,вниз, вперёд рука; вперёд вперёд для мука и просто вперёд для ходьбы?
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:
Управление персонажем
Не работает ни прыжок, ни просто движение. using System.Collections; using.

Управление персонажем
У меня не получается написать плавное передвижение на AWSD для куба Можете написать пример.
Управление персонажем
С управлением персонажа разобрался, бегает, поворачивается, но проблемка — не хочет перемещаться.
Управление персонажем в 3D на андроид
Доброго времени суток! Начал разрабатывать свою первую игру на Unity3D. Задача в том, чтобы.
74 / 53 / 24
Регистрация: 19.10.2012
Сообщений: 212
делал аналог, но не драки и не на Unity )
с помощью цифр.
При нажатии кнопки добавляется цифра, рука-1 нога-2 Если в течение полу секунды не прибавлена цифра то обнулять ее.
Если комбо =12 то удар один, если 121 то другой и т.д. при конечном комбо 1215 обнуляй ее, тк комбо закончено. Но я не программист Unity. Я просто давно увлекаюсь этим.
Есть ли какие-то встроенные функции Unity Тоже бы хотел узнать.
Регистрация: 14.12.2016
Сообщений: 32
Спасибо за ответ!
Вот тут таки нашел, как запилить инпут, осталось только попробовать
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
Помогаю со студенческими работами здесь
Unity3d управление персонажем
здравствуйте. подскажите пожалуйста, как сделать управление персонажем от первого лица в unity3d.
Управление персонажем Unity 2D 5.2
Доброго времени суток. Только начинаю изучать unity и немного уже изучаю C#. Проблема в.
Unity3d — Управление персонажем (C#)
Подскажите пожалуйста каким способом можно сделать управление выбранным персонажем (например.
Управление персонажем (шар)
Всем привет, форумчане. Начал делать игру наподобие balance 3d. Остановился на управлении шаром с.

Управление персонажем мышкой
Нужно что бы персонаж(шарик), при зажатой мышке на экране "приклеился" к ней по позиции x. Как.

2D rpg управление персонажем и анимация
Добрый день ! . Только начал изучать Unity 2D. Я создал 1 объект "man" . Создал 8 анимаций.
Или воспользуйтесь поиском по форуму:
Как мы это сделали: Fright Fight — 3D-файтинг от zGames

Главным триумфатором премии DevGAMM Awards, вручение которой состоялось в рамках одноименной минской конференции разработчиков и издателей игр, стала команда zGames — подразделение компании Softeq Development. Их онлайновый кроссплатформенный многопользовательский 3D-файтинг Fright Fight признан лучшей игрой-2014, оставив позади 25 претендентов.
Команда zGames в лице менеджера проекта Николая Дзнеладзе, гейм-дизайнера Павла Штангеева и главного разработчика Артёма Воробьёва рассказала о том, как появилась идея создания игры в жанре файтингов, о сложных моментах в разработке и радости долгожданного релиза.
Под катом: как всё начиналось и подробно о разработке
Как всё начиналось
В начале 2012 года мы закончили очередной проект и размышляли над следующим шагом. В портфолио команды уже был ряд не очень масштабных проектов, и в этот раз хотелось проверить свои силы и сделать что-то значительно более сложное.
Мы просмотрели тонны игр в App Store и заметили, что жанр файтингов представлен всего лишь несколькими портами с консолей, со всеми вытекающими недостатками в духе 12 виртуальных кнопок на экране. Именно этот момент и стал отправной точкой проекта, известного сегодня как Fright Fight — кроссплатформенный многопользовательский файтинг на Unity 3D в стилистике, напоминающей культовых Super Smash Brothers.

От идеи до прототипа
Файтинг — один из самых сложных жанров с точки зрения игрового дизайна. Дизайн файтинга для мобильных устройств сильно усложняется, так как не приходится рассчитывать на игровые контроллеры, а тачскрин банально неудобен для управления классической боевой системой файтингов. Начиная писать дизайн для Fright Fight, мы ориентировались, в первую очередь, на адаптацию управления под мобильные платформы. Это повлекло за собой ряд изменений в ключевых механиках, было создано множество прототипов управления, но результат стоил потраченных сил.
Первый вариант дизайна появился всего за пару недель и в корне отличался от того, что мы видим сейчас: камера находилась за спиной персонажа, игрок управлял только движением рук, нанося удары и блокируя атаки противника.
После обсуждения концепта внутри команды мы решили, что игре недостаёт динамики, и отправили документацию на свалку истории. Второй вариант был гораздо ближе к финальной версии игры. Появились комплексные уровни, парящие в воздухе платформы, двойные прыжки у персонажей — игра позаимствовала ряд механик из платформеров. Данный тип игр неплохо адаптируется под мобильные платформы и хорошо интегрируется со сложными боевыми системами. В итоге мы убрали нагрузку с боевой системы, заполнив пробелы необходимостью постоянно перемещаться по уровню в попытке занять более выгодное положение для атаки.
Разработка: дизайн
С точки зрения дизайна, в проекте было два проблемных момента: адаптация сложной боевой системы под крайне простое управление и построение прогресса без тонны контента (время и бюджет были ограничены, и мы не смогли реализовать даже запланированный изначально контент).
Проблему со сложной боевой системой и простым управлением мы решили в два этапа:
- Убрали второстепенные части боевки и добавили вспомогательные автоматические механики для балансировки. Например, мы убрали из игры броски, что сделало блок доминирующей стратегией. Для балансировки мы дали возможность части медленных атак пробивать блок и ввели показатель «ярости» — она требуется для поддержания блока.
- Повесили все атаки на единственный жест — тап в правой части экрана — и сделали их контекстными. По сути, игрок может либо тапнуть для проведения атаки (несколько быстрых тапов приведут к выполнению комбо), либо тапнуть и удерживать палец для «зарядки» более сильной атаки. Конкретная атака зависит от текущего положения персонажа (стоит, бежит, прыгает и так далее) и количества накопленной «ярости».

Для построения прогресса в игру были добавлены ролевые элементы в виде деревьев скиллов (индивидуальное дерево для каждого персонажа) и прокачиваемых параметров. Это решение было одним из самых спорных за всё время разработки проекта. С одной стороны, оно экономило много времени на разработке контента и добавляло вариативности в геймплей. С другой — существенно усложняло и без того крайне непростой баланс и накладывало ограничения на подбор соперников по сети.
В целом эксперимент с ролевой системой удался. Мы сумели сбалансировать её практически идеально. Это потребовало построения довольно сложной математической модели и проведения ряда продолжительных плейтестов, но результат удивил даже нас — после 1 000 000 сыгранных онлайн матчей, разница в победах между персонажами колебалась в районе 1%.
Вообще, плейтесты стали одним из ключевых факторов успеха. Они выявили ряд недоработок в дизайне на ранних этапах, когда их исправление было не так болезненно, позволили построить базовый баланс до выхода открытой бета версии, что вылилось в положительные отзывы комьюнити прямо со старта, и так далее. Дополнительно мы несколько раз выкладывали видео геймплея (альфа и бета версии) и читали отзывы — информации к размышлению было довольно много.
Вывод: начинайте тестировать игру, как только в ней появляется хоть какое-то взаимодействие с пользователем, показывайте её целевой аудитории для выявления проблем и девушкам / бабушкам для настройки UI и снижения порога вхождения.

Разработка: программирование
Самой сложной задачей для программистов было сделать сетевой мультиплеер. Когда начинался Fright Fight, мы не знали ничего о том, как играть в одном мире с разных девайсов. Мы понимали, что есть сотни способов сделать сеть плохо. Начали с изучения теории: в книге «Game Engine Architecture» (J. Gregory) было вкратце описано, как строится архитектура мультиплеерных игр, но уж слишком коротко. Из «Networking and online games» (G. Armitage) узнали об основных проблемах в сетевых играх и о том, как их можно решать. Затем стали смотреть сетевые движки, из которых нам больше всего понравился Photon — у него были хорошие отзывы и возможность попробовать бесплатно. Базовый API PhotonCloud оказался слишком базовым и использовать его напрямую было бы чересчур громоздко. Поэтому стали искать обёртки — рассмотрели три (в т. ч. PUN), нарисовали их архитектурные схемы, ужаснулись и… придумали свою.
Время на разработку было очень ограничено, поэтому мы решили не делать сервер, а обсчитывать всё на клиентах. План был простой: каждый клиент рассчитывает то, что касается него, и сообщает результат остальным. Добавить защиту от взлома, выделить клиента, который бы принимал общие решения, — и всё должно работать, так? Так, но лучше бы мы делали сервер.
Во-первых, защита от взлома отнимает время разработчиков, на сервере проверки делать проще.
Во-вторых, клиент, который принимает общие решения (мастер-клиент), может отключиться, и это нужно обрабатывать.
В-третьих, синхронизировать состояние с сервером было бы быстрее — в нашем случае данные сначала шли от клиента А в PhotonCloud, потом из PhotonCloud в клиент Б, и потом то же самое от Б к А; итого, если задержка между А и PhotonCloud составляла 0.1 секунды, то общая задержка синхронизации — 0.4 секунды. Для файтинга 0.4 секунды — это очень много.
В итоге решали проблему с помощью предсказания: игрок нажимает кнопку, игра считает результат, начинает синхронизировать его с другими игроками и проигрывает анимацию. Если произошли проблемы при синхронизации — откатываем состояние. В теории красиво, на практике — боль страшнейшая 🙂 Тонны кода по синхронизации и предсказанию во всех геймплей-системах — в движениях, в атаках, в бонусах, в движущихся платформах и т. д. В итоге разработка механики занимала времени меньше, чем её синхронизация по сети.
Можно обобщить наш опыт в следующих советах:
- использовать обсчёт на клиентах можно, но только при медленном геймплее, для быстрого геймплея нужен сервер;
- нужно обобщать синхронизацию данных, чтобы не пришлось синхронизировать каждую игровую механику по отдельности. Хорошую идею мы подсмотрели в Quake3, но это тема отдельной статьи 🙂
- для мобильных мультиплеерных игр делать геймплей по возможности более медленным, чтобы меньше зависеть от задержек сети; либо делать асинхронный мультиплеер, как в Clash of Clans;
- организовывать запросы к серверу в виде отдельных объектов (мы называем их джобами), которые можно создать, запустить на выполненеие, дождаться их окончания и получить из них результат — этого нам не хватало в существующих обёртках Photon.
Помимо сети, Fright Fight дал нам очень много опыта в других областях. Одной из лучших находок стала система программирования действий персонажа. Мы составляли движения из атомарных частей (экшенов) без единой строчки кода — прямо в редакторе, просто собирали из кусочков. Затем мы обобщили эту систему, сделали с её помощью скиллы персонажам. Затем обобщили ещё больше и сделали на ней туториал. Сейчас из экшенов можно сделать что угодно — от анимации нажатия кнопки до ботов.
Мы разработали модульную архитектуру игры, которая позволяет брать подходящие готовые модули и дописывать недостающие, и это без каких-либо ограничений на тип и структуру геймплея. Среди готовых модулей — обёртки для Facebook, Twitter, аналитики, рекламы, базы данных, игрового профиля, ачивок и многого другого. Такие модули подключаются к новым проектам за несколько минут и экономят уйму времени. По сути, мы стали решать проблему всего один раз для всех проектов. Это то, что мы на протяжении нескольких лет искали на конференциях и в книгах, о чём спорили днями напролет и набивали себе шишку за шишкой.
Долгожданный релиз и жизнь после него
В конце 2013 мы решились представить широкой публике альфа-версию своей новинки, начав активную кампанию на известном краудфандинговом портале Kickstarter.com. Несмотря на скептический настрой многих собратьев по цеху и отечественных медийных ресурсов, а также невзирая на то, что инициатива по сбору средств на развитие игры не достигла желаемого результата, нам удалось добиться резонанса в игровом сообществе и заручиться поддержкой поклонников жанра онлайн-файтинга со всего мира. Весной 2014 год игра появилась в App Store.
Теперь наши рабочие будни проходят вот так:
А в этом видеоролике команда проекта в качестве бонуса к интервью рассказала о том, приятны ли неожиданности, зачем ходить на конференции, что делают разработчики вечером после триумфа, чем полезен аутсорсинг и бывает ли он успешным в геймдеве, а также почему своя обёртка ближе к телу.
Fright Fight доступна для свободного скачивания на Android и iOS-устройствах, а также игровой консоли OUYA.
Физика в 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 даже на самых слабых устройствах, которые мы поддерживаем.
