Платформер в песочнице: дружат ли геймдизайн и физическая симуляция

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

В этой статье рассказывается об опыте создания такой игры.

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

Опорный игровой жанр - артиллерия, вроде Scorched Earth. В этой игре изначально была заложена разрушаемость земли, но она была статичной: из холма вычитался выжигаемый взрывом кружок. Это было круто и свежо для эпохи доса, но пришло время желать большего.

Моя идея состояла в том, чтобы скрестить игру из жанра песочницы (вроде "Powder Toy" или "OE CAKE") со старой доброй артиллерийской стрелялочкой. То есть, реализовать аркадный геймплей в физической симуляции.

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

Решение меня потрясло, требовалось просто перейти с процессора на видеокарту. Производительность видеокарт оказалась в 50-100 раз выше, чем у процессоров, и для параллельной обработки десятков тысяч частиц они годились идеально. Я не ожидал такого гигантского бонуса, но получив его, понял, что игра будет сделана ровно такой, какой я её задумал, без компромиссов.

Без компромиссов, впрочем, не обошлось, но о них ниже.

Непосредственный кодинг шейдера для обсчёта физики оказался простым и приятным. Язык HLSL хорошо развит, и изначально относится к семейству СИ-подобных, так что начать программировать получилось сразу. Проблемы были только с тем, чтобы разобраться, как индексируются потоки, но хелп в msdn очень подробный, так что дело пошло.

Компилятор языка HLSL нашёлся в Юнити, там есть классы ComputeShader и ComputeBuffer, которые предоставили исчерпывающий интерфейс для вычислений на видеокарте.

Решив технические вопросы, я вплотную занялся непосредственно физикой.

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

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

Чтобы перейти от O(N^2) к O(N * log(N)), я использовал стандартный метод, двумерную решётку, каждая ячейка которой запоминала лежащие на её площади частицы, чтобы затем при обсчёте каждая частица обращалась не ко всем частицам, а только к соседям по узлу решётки. В итоге, каждая из десятков тысяч частиц взаимодействовала лишь с десятком соседей.

Рендер сделал так же в шейдерном коде, каждая частица рисовалась на текстуре в виде нескольких пикселей.

После реализации первой версии модели получилось вот так:

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

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

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

Но в результате получилось что-то похожее на твёрдое тело (хотя, скорее на манную кашу).

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

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

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

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

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

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

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

Ещё я постоянно изыскивал возможности для упрочнения материи, постепенно переходил от желе к твёдому телу.

Но тут была фундаментальная загвоздка. Единственная валюта, которая принималась к оплате в обмен на упрочнение - производительность.

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

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

В итоге, стало так:

Уже похоже на игру и прослеживаются элементы дизайна уровней, но нужны были визуальные эффекты от взрывов. Я не хотел делать свою систему эффектов на стороне видеокарты, будучи хорошо знакомым с Particle System в Юнити. Так что надо было просто передавать данные о взрывах из видеокарты обратно в память на стороне центрального процессора.

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

К счастью, на мой призыв о помощи откликнулся участник форума на unity3d.com и написал нативный плагин для асинхронного чтения данных из GPU. Стало возможным получать данные о взрывах без урона производительности. Тогда я быстренько создал эффекты для всех типов оружия, наклепал несколько уровней и залил игру на гринлайт.

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

Надо было придумать интересный геймплей, и именно этим я сейчас занят.

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

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

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

Пока вырисовывается что-то такое:

На всё это ушло 10 месяцев. Но остаётся сделать не так уж много, так что скорее всего получится выпустить игру уже в апреле.

Спасибо за внимание, с удовольствием отвечу на вопросы.

33
3 комментария

Комментарий недоступен

Ответить

Ага.

Ответить