Ремейк Overwatch в Unity

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

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

Чтобы повторить эту динамику нужно было точно подобрать параметры скорости и ускорения капсулы героя чтобы не полагаться на глаз и не упустить деталей. Для этого я воспользовался конструктором режимов overwatch workshop.

В workshop я написал скрипт, который отслеживает положение персонажей, выводит в интерфейс скорость за прошедший сетевой кадр (0.05с) и ускорение.

HUD показывает буфер из 6 значений скорости, ускорение, количество фреймов и время ускорения/торможения, код темплейта в описании видео

Оказалось, что разгон у всех персонажей почти мгновенный (0.1с, два сетевых кадра), а вот замедление и остановка происходят по-разному в зависимости от героя. Так, Soldier-76 останавливается за 0.2с, а Lucio за 0.6с (* В OW2 почти все персонажи кроме Lucio и Echo стали замедляться за 0.1с).

Скорость движения без бустов почти у всех одинаковая: 5.5м/с - 6м/с, и 90% от неё при движении назад.

Затем я обнаружил, что гравитация капулы при прыжках нестандартная, и ускорение свободного падения составляет 14мс^2 вместо земных 9.8мс^2, что создает ощущение игрушечности, но добавляет динамики прыжкам.

Чтобы убедиться в корректности динамики мне захотелось построить знакомый уровень. Поначалу я стал воссоздавать его на глаз, но потом нашёл инструмент распаковки оригинальных ресурсов игры OWLib (https://github.com/overtools/OWLib) и это вовлекло меня в многосторонний процесс переноса оригинальных ресурсов в unity.

Я выбрал тестовый уровень и заметил, что экспортированная модель очень неоптимальна и занимает несколько сотен МБ. Оказалось, это происходит потому, что модули из которых собрана сцена сохранены как отдельные объекты, а не инстансы. Чтобы это исправить, я написал скрипт Link Similar Meshes для blender который перебирает все объекты, сравнивает число вершин, материалов, и других параметров, и позволяет быстро отыскать идентичные элементы, а затем обобщить их, превратив в инстансы. Это позволило сократить объем модели уровня с 200мб до 15мб, а также задействовать инстансинг геометрии во время рендеринга и повысить фреймрейт который очень важен при оценке ощущения от управления.

Ремейк Overwatch в Unity
import bpy active_object = bpy.context.view_layer.objects.active active_verts = len(active_object.data.vertices) for all_objects in bpy.context.view_layer.objects: if all_objects.type == 'MESH': all_objects_verts = len(all_objects.data.vertices) if all_objects_verts == active_verts: if all_objects.data.uv_layers.active.data[0].uv == active_object.data.uv_layers.active.data[0].uv: if active_object.data.vertices[0].co == all_objects.data.vertices[0].co: all_objects.select_set(state = True)
import bpy unique_objects = [] similar_count = 0 for current_object in bpy.context.view_layer.objects: if current_object.type == 'MESH': current_object_verts = len(current_object.data.vertices) found_similar_data = False for unique_object in unique_objects: if len(current_object.data.vertices) == len(unique_object.data.vertices): if current_object.data != unique_object.data and current_object.data.uv_layers.active.data[0].uv == unique_object.data.uv_layers.active.data[0].uv: if ( len(current_object.data.materials) == 0 and len(unique_object.data.materials) == 0 ) or (len(unique_object.data.materials) > 0 and len(current_object.data.materials)>0 and current_object.data.materials[0] == unique_object.data.materials[0]): if current_object.data.vertices[0].co == unique_object.data.vertices[0].co: current_object.data = unique_object.data found_similar_data = True similar_count = similar_count + 1 if not found_similar_data: unique_objects.append(current_object) print("unique_objects: ",len(unique_objects)) print("linked_objects: ",similar_count)

UPD: обильная критика моих одноразовых скриптов для линковки подтолкнула меня доработать полноценный плагин, пользуйтесь:

Edit->Preferences->Addons->Install from Disk. Появятся меню Select/Similar Objects и панель в разделе Tools (N)
Edit->Preferences->Addons->Install from Disk. Появятся меню Select/Similar Objects и панель в разделе Tools (N)

Кроме оригинальных уровеней мне удалось извлечь и персонажей с анимациями. Это подтолкнуло сделать и следующий шаг: повторить контроллер анимаций. Я выбрал Солдата-76 как самого стандартного героя с привычным управлением и интересной динамикой движения в спринте.

Сначала я просто прикрепил его автомат к капсуле и попробовал подобрать положение относительно камеры. Тут же я обнаружил, что угол обзора (FOV) у камеры для первого лица отличается от FOV камеры для окружения. Более того, он изменяется во время быстрого движения героя, создавая иллюзию более стремительного бега. Путём сравнения скриншотов я подобрал нужные значения. FOV камеры для первого лица оказался равен ~58.7, а для окружения - настраивается (todo: выяснить в каком диапазоне).

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

Самое важное - это процедурная анимация наведения оружия на прицел. Когда игрок вращает камерой, ствол оружия не прикреплен к ней намертво, а использует имитацию упругой связки чтобы передать ощущение веса оружия.

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

Немного преувеличенное шатание оружия

Почти сотню (83) анимационных клипов для персонажа от 1 лица пришлось сначала тщательно разобрать и переименовать, а затем собрать в анимационное дерево на нескольких слоях.

Ремейк Overwatch в Unity

1 слой - базовая анимация. Там выделены состояния покоя (IDLE), движения (LOCOMOTION), перезарядки и приземления. При движении и приземлении смешиваются две анимации по параметру скорости.

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

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

Ну и последнее что я сделал - добавил модель с анимациями для отображения персонажа от 3 лица. В анимации от 3 лица удалось создать довольно простое дерево смешивания по направлению вектора движения и смешать 8 анимаций бега. Сложнее было настроить кинематику прицеливания. Для этого пришлось воспользоваться Animation Rigging пекеджем, настроить риг, и вращать торс и голову персонажа по направлению прицела от первого лица вокруг специально подобранной точки.

Текущий результат

Что можно еще можно было бы сделать:

1) Добавить покачивание камеры и изменение FOV. Многие анимации сопровождаются очень легким наклоном камеры, неплохо бы разобраться как это устроено у разных героев. Может быть есть боббинг камеры или шейк при каких-то событиях игры, надо изучать.

2) При приближении к препятствиям оружие сейчас проваливается в геометрию. Используя стекинг камер нужно рендерить оружие на отдельном слое. Но непонятно как быть с тенями которые попадают на оружие внутри стены. Эта проблема есть и в оригинале, но тени там как-то плавно исчезают, и проблема почти незаметна.

3) Стрельба, перезарядка, приседание, полёт, лазание по стенам. Можно воссоздать разные способы передвижения.

4) Применение абилок - рывки, телепортации, полёты, крюк-кошка и т.п.

5) Мультиплеер. В целом несложно синхронизировать стандартные движения: реплицировать вектора движения и взгляда, события. Сложнее будет сделать лагокомпенсацию и роллбэк при стрельбе проджектайлами для настоящей соревновательной игры.

6) Звуки, которые дополняют анимации движения, бега, прыжков

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

21
13 комментариев

Огнище. Молодец. ТруЪ реверс инжиниринг. Побольше бы такого на ДТФ.

2) ... Эта проблема есть и в оригинале, но тени там как-то плавно исчезают, и проблема почти незаметна.

Советую посмотреть в решение похожей истории из Геншин Импакта:
https://www.artstation.com/blogs/bjayers/9oOD/blender-npr-recreating-the-genshin-impact-shader
Там они стреляют рэйкастом от середины капсулы персонажа в направлении дирекшенал лайта сцены. И если рэйкаст сталкивается с коллизией, то они аккуратным лерпом материал полностью в тень переводят. У них, правда сам шейдер в этом плане попроще - там только дирекшенал лайт на него влияет, вроде.

В овере придется чуть больше помудрить и убирать только влияние дирекшенал лайта, оставляя работать остальные источники (чтобы оно продолжало бликовать от всяких лампочек и взрывов, когда орудие находится в тени). Благо, у Unity больше свободы по Light-функциям, чем в каком-нибудь УЕ. И это можно будет реализовать.

2

Вот это мега вложенные условия, чтобы точно движок понял что ты не шаришь ничего)

Вот для этого мат логику в универах и учат, чтобы такой хуйни не писать

1

буквально 90% кода в проде в любой компании, потому что всем плевать, главное побыстрее сделать

1

Да пофиг вообще, это не сказывается ни на функционале, ни на производительности. Один раз выполнил и дальше побежал. Но обильная критика подтолкнула меня написать полноценный плагин, так что вот пользуйтесь: https://gist.github.com/dnnkeeper/b6858b3dd04b3231c10e17f9e1d2247e

Кто тебя так код писать учил? If if if if if

Это результат итеративного поиска простого разового решения на коленке. Вообще не стояло задачи делать из этого утилиту, так что пофиг на стиль. Был бы чатгпт тогда, скормил бы ему. Можно так и поступить чтобы сделать прикольный плагин для оптимизации сцен с копиями объектов