[Перевод] Генерацируем тайловые уровни и прячем квадраты от игрока

y7gxlziwifqpjitd5m4rqtp-x8a.gif


Мы очень гордимся генератором уровней игры Unexplored 2, это программа, отвечающая всем современным требованиям. В посте я расскажу о том, как создаются уровни игры.

Нам не пришлось заново изобретать велосипед. В Unexplored 1 мы уже создали техники, которые сильно повлияли на успех первой игры. Unexplored 2 просто продолжила начатое. Фундамент нашей технологии состоит из двух частей: мы применяем многоэтапную генерацию, которая почти имитирует процесс, очень похожий на работу живого дизайнера уровней. Поверх него мы используем технику под названием «циклическая генерация подземелий», которая гораздо лучше справляется с генерацией естественно выглядящих уровней, чем большинство стандартных приложений генеративного создания контента. В этом посте я расскажу о первом аспекте. Адаптация циклической генерации подземелий к Unexplored 2 будет темой будущего поста.

Имитация «человеческого» дизайна уровней


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

Первый этап уровня относительно прост. Мы используем битовую карту низкого разрешения для расположения самых основ уровня. В показанном ниже примере уровень изначально очень прост: он состоит всего лишь из входа (e) слева, входа справа и соединяющего их прямого пути. Большинство других тайлов или неопределено (u), или заблокировано (B), потому что они находятся по краям уровня.

a03aa52fae0cb45e82425c4c95e38214.png


Рисунок 1: Простой набросок уровня

На следующем этапе добавляются детали: появляется группа комнат, соединённых воротами, которые рассчитаны на прохождение в определённом направлении:

2b97cd1e4732bab984f5181a37ba71f7.png


Рисунок 2: Добавленная структура

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

2b3da05c5967229d8d14816df5bf80ce.png


Рисунок 3: Базовый граф

В графе некоторые узлы содержат подузлы, в нашем случае большинство ворот помечены как опасные (H), а одни помечены как открытые (O).

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

7843a56e0fb89b3a1350076657c212c5.png


Рисунок 4: К графу добавлены новые элементы.

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

465e6518e606b4eb70e4d04b2b3ca8c1.png


Рисунок 5: Тайловая карта высокого разрешения

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

7229b7140304bb1f8302586b2dfb6c16.png


Рисунок 6: Украшенная тайловая карта.

Карта как она есть генерируется исключительно для представления в геймплее. Белыми тайлами обозначены открытые пространства, а почти все остальные тайлы обозначают очень конкретные геймплейные элементы, например, тайный проход через кусты (зелёные круги), «ворота в зарослях» (зелёные квадраты и сиреневые полосы) или места спауна для привязки деревьев (красные круги с буквой s). Основная часть уровня по-прежнему неопределена, и на этом этапе генератор предполагает, что эти области должны быть заполнены так, чтобы блокировать перемещение игрока.

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

e376b6f11b4e4e526986238c4d0c50cc.png


Рисунок 7: Типы поверхности земли (трава, грязь и камни)

2d78989ac691225d306a3319c2f7e111.png


Рисунок 8: Вода

021ade7992212c857d6e4101e31273e0.png


Рисунок 9: Растительность и другие украшения

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

bc9b8975c9b0704266c5786af53834eb.png


Рисунок 10: Готовый уровень

Добавление геймплея


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

Возьмём для примера пещерную карту в более классическом стиле подземелья (по сравнению с лесным уровнем из примера выше). Базовый граф этого уровня имеет всего один вход и ещё пару новых типов ворот. Пара из них закрыта (L): одни ворота ловят игрока с одной стороны в ловушку (T), а тёмно-зелёные я называю «клапаном»: такой тип ворот позволяет игроку проходить только в одном направлении.

45d72c52a99a84d42541b602c84b7806.png


Рисунок 11: Базовый граф пещеры

Структура этого уровня позволяет генератору создать гораздо более сложную миссию. Например, единственный способ попадания на этот уровень — проход через «клапан», заставляющий игрока искать другой путь наружу. Ключ для открытия пути наружу (внизу слева) расположен за опасностью вверху слева, а цель расположена за воротами, которые ловят игрока в ловушку. Примером таких ворот может быть проход, который обрушивается за игроком. В целом это создаёт пещеру, которую интересно исследовать саму по себе, но Unexplored 2 также имеет возможность добавления существ и событий, примешивающихся во время выполнения.

45d72c52a99a84d42541b602c84b7806.png


Рисунок 12: В пещеру добавлены замки и ключи

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

fca65cc79406fa4ba471d763f8ab6539.png


Рисунок 13: Пещерный уровень в полной детализации

Подведём итог


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

a158953a829bff67da8a242686b2675b.png


Генератор контента Unexplored 2 генерирует тайловые карты. Типичный результат выглядит так:

0e2f20fff2077a7259c639e8aa5bdda8.png


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

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

76051a40f79ffbbb15a3054536893003.png


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

Магия Вороного


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

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

5fd15a3382b48d74706d5f5458c4f7cb.png


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

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

0dda26a2fa9d01ea045c1219d0a3c999.png


8eb45115b429cbb8b612f46ce3fad524.png


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

Результат выглядит примерно так:

4a333a043cf9caadc0a09ff606c046ca.png


ab6c250312ab2f4e4efec42788c129d5.png


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

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

6b1ccb69e7e6ca475e6c62c56f52b56c.png


b06787b2f08c28d84f009caffd76ff14.png


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

21a8221fb1d87fec480db9cc2c95425e.png


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

Игра использует эту информацию для смещения порождающих точек графа Вороного. Каждый загруглённый угол сдвигает местоположение порождающей точки (см. изображение ниже). Кроме того, он также сдвигает порождающие точки четырёх его ортогональных соседей. Этот процесс кумулятивен; порождающие точки могут сдвигаться несколько раз, если они находятся рядом с несколькими углами. Однако после обработки всех смещений порождающие точки немного рандомизируются (примерно на 10% от ширины тайла в каждом из направлений), и окончательное смещение ограничивается максимумом в 40% от ширины тайла.

a949b93f5e2e724a95658f75af6a50c4.png


Результат уже получается довольно качественным:

ccc830f24c201bd4e519ca0407f77c18.png


b8bd27edb8a753ee92e9d2d5551e6a05.png


Но мы ещё не закончили…

Украшаем с умом


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

d274effc6224874373a8c8c355dcb209.png


Результат выглядит так:

e97591baad40e954f3c4b164f2b91739.png


Далее мы использовали 3D-ассеты, чтобы добавить обрывам текстур:

71170a6b0ea154c6974e8787be8c0680.png


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

76051a40f79ffbbb15a3054536893003.png


Локальная вариативность


Углы — не единственный тип смещения, который мы используем. В частности, мы хотим, чтобы рядом с искусственными структурами (например, показанными ниже разрушенными стенами) края были прямее:

8399f5127ae11017b243716b075c3324.png


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

b845b1372a6623d931eb30ed29fe5bcc.png


09e2c4f64fad5ee474fcbfa21e607c73.png


Если посмотреть на землю, то можно чётко увидеть, что отдельные области сделаны прямыми, в то время как другие изгибаются более естественно:

c605e363554553121d8c70c61ebaa5b2.png


Разве это не красиво?

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

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

© Habrahabr.ru