Ремейк 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) Звуки, которые дополняют анимации движения, бега, прыжков

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

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