Рассказываем на примере Minecraft и No Man’s Sky о том, почему псевдослучайные миры получаются такими насыщенными.
Процедурная генерация — это, пожалуй, один из самых простых способов создать убедительный игровой мир. Всего несколько сотен килобайт алгоритма могут сгенерировать целую вселенную, размеры которой ограничены лишь вместимостью вашего носителя — или серверов разработчиков.
Из-за простоты применения процедурную генерацию используют везде — от ААА-блокбастеров вроде третьего «Ведьмака» до инди-песочниц и «рогаликов». Она значительно упрощает производство игры — ведь написать алгоритм создания мира куда дешевле, чем привлечь к работе штат художников.
Но что если построить всю игру вокруг одной идеи — исследования бесконечно разнообразных миров, и ни один из них не будет похож на прежний? Minecraft, No Man’s Sky и прочие «выживалки» — именно про это. И добились они такого эффекта благодаря простой, но в то же время гениальной задумке генерации миров.
Все когда-либо созданные миры в Minecraft или Terraria кардинально отличаются друг от друга. Да, они состоят из одних и тех же элементов, так называемых биомов — гор, равнин, пустынь и океанов, — но эффекта дежавю у вас никогда не возникнет.
Почему? Всё благодаря принципу работы генератора уровня. И тут стоит немного углубиться в теорию.
Настоящей случайности в играх не бывает. Её очень сложно добиться — для этого нужна дорогостоящая аппаратура, которая создаёт последовательность чисел на основе хаотических физических процессов из реального мира. Так, например, тепловой шум или источники радиоактивного распада могут выдать набор случайных значений — ведь заранее в жизни их предсказать невозможно.
Броуновское движение — частицы перемещаются хаотически и беспорядочно
Но в играх такие переусложнения никому не нужны — поэтому вместо истинной случайности разработчики используют псевдослучайность. Она достигается благодаря важному элементу процедурных генераторов миров — ГПСЧ, или генератору псевдослучайных чисел, который работает на основе сложных математических формул.
Говоря простыми словами, «случайные» числа в компьютерах не берутся из воздуха — они создаются специальными алгоритмами. А функция «random» в любом языке программирования — и есть результат работы одного из таких алгоритмов.
Но если процедурный генератор работает на основе одних и тех же алгоритмов и правил, а истинной случайности в играх нет, почему каждый раз создаются совершенно разные миры? Для этого необходимы «сиды» (seeds) — «зёрна» или, если корректнее, «порождающие элементы».
Сид — это, по сути, значение, которое закладывается в алгоритмы генератора при создании мира. Чаще всего оно представлено в виде псевдослучайного ряда цифр. В качестве «зерна» можно использовать точное время, взятое из системы: например, если вы создадите мир 19 ноября 2020 года в 19:13, 26 секунд и 874 миллисекунд, сид может выглядеть так — 19112020191326874. Но обычно, конечно, за основу берутся значения намного сложнее.
Линейный конгруэнтный метод — один из самых простых ГПСЧ
Главное свойство сидов — это воспроизводимость. Если раз за разом использовать одно и то же зерно в генераторе мира, результат будет одним и тем же. Это помогает разработчикам экономить ресурсы — чтобы загрузить сохранение, игре достаточно помнить зерно уровня и все изменения, которые игрок совершил в созданном мире. Иначе говоря, загружаемый уровень каждый раз как бы создаётся заново.
В играх с псевдослучайными мирами — будь то «рогалики» или «выживалки» — каждый разработчик самостоятельно решает, какие принципы заложить в основу алгоритма создания уровня. Попытаемся выделить два разных подхода.
В первом случае процедурный генератор работает как своего рода конструктор — собирает уровень из уже созданных вручную элементов. Например, классический «рогалик» Binding of Isaac, где данжи хоть и случайные, но по сути состоят из заранее заготовленных комнат. Единственное, что меняется — проходы к ним, а также запрятанный лут и расстановка противников.
Генератор в Binding of Isaac работает так. Создаётся комната, в которой может быть как минимум одна дверь и как максимум — четыре. Там, где проход всё же есть, создаётся ещё одна комната по тому же принципу. В какой-то момент комнат в подземелье становится достаточно много — и на краях «коридоров» генерируются тупиковые помещения. Как правило, в них находятся боссы или хороший лут.
В Binding of Isaac тоже есть сиды, значения которых можно ввести при генерации мира. Если два игрока введут один и тот же сид, они получат абсолютно идентичные подземелья.
То есть, в играх в духе Binding of Isaac мир в самом деле создаётся из заранее готовых элементов. Но как в таком случае устроен процедурный генератор, например, в Minecraft? Здесь мы переходим ко второму подходу создания уровней.
Создатель игры Маркус Перссон взял за основу шум Перлина — математический алгоритм, который генерирует текстуру псевдослучайным методом. То, что получается в итоге, выглядит как-то так.
На первый взгляд может показаться, что это текстура от облаков или, например, дыма. Действительно, шум Перлина часто применяется в компьютерной графике при генерации спецэффектов для повышения реалистичности.
В Minecraft же шум Перлина — это, по сути, карта высот. Чем светлее пиксель, тем выше находится отдельно взятый блок. Иначе говоря, белые пятна — это горы, серые — равнины, а чёрные — низины.
Трёхмерное представление шума Перлина в Roblox
Шум Перлина может применяться и в 2D-играх. Для этого достаточно взять двухмерный срез карты.
Чтобы разнообразить ландшафт, разработчики создают октавы — то есть, накладывают разные вариации карт шумов друг на друга, чтобы их амплитуда как бы усреднилась между собой.
Так, например, из шести разных карт шумов создаётся одна — наиболее детализированная. Причём каждая последующая октава всё меньше и меньше влияет на конечный результат — обратите внимание, что по форме получившийся шум Перлина больше всего напоминает именно первую октаву. Остальные нужны, чтобы придать ландшафту более реалистичный вид — с обрывистой местностью.
В No Man’s Sky мы генерируем столько слоёв шума… Один лежит на другом, каждый следующий исходит из предыдущего, и так далее и тому подобное.
создатель No Man’s Sky
Шум Перлина уникален тем, что ни одна из карта шумов, созданная алгоритмов, не похожа на предыдущую.
Изначально шум Перлина создали для компьютерных спецэффектов в фильме «Трон» 1982 года
Карты шумов отвечают не только за ландшафт карты. При создании сетей извилистых пещер используется Ridged multifractal noise — или ребристый мультифрактальный шум. В двухмерных «выживалках» он выглядит примерно так.
Светлые полосы — это пещеры, а чёрные пятна — слои грунта. При этом необязательно пещера должна проходить лишь там, где пиксели строго белые — так, например, разработчики могут настроить генерацию пустого пространства в пределах определённого тона серого цвета. А всё что темнее — камень и земля.
В трёхмерных играх вроде Minecraft пещеры создаются точно таким же образом, но для этого используется не одна карта шумов, а сразу несколько — в разных плоскостях. Там, где линии накладываются друг на друга, образуются трёхмерные пустые пространства.
После того, как основа для уровня сгенерирована, набор специфических алгоритмов и шаблонов случайным образом раскидывает по миру объекты — деревья, крепости, данжи, места спауна мобов. К примеру, игра определяет, что уровень моря не может подняться выше 63 блока по вертикали. А деревья могут заспауниться только на поверхности, но никак не в пещере или под водой.
Набор правил определяется в том числе и биомом, в котором размещаются объекты — будь то леса или пустыни, тундра или болота. К примеру, берёзы никогда не появятся в саванне, а в пустыне не пойдёт дождь. Октавы в каждом из биомов тоже разные — поэтому в горах амплитуда перепада высот намного выше, чем в равнине.
В No Man’s Sky процедурный генератор устроен ещё сложнее — местные миры состоят не из блоков, а вокселей. Поначалу шум Перлина создавал недостаточно реалистичные планеты — для того, чтобы они казались натуральными, не хватало эффектов вроде эрозии ландшафта. И чтобы добиться его, разработчикам пришлось прямо-таки извернуться.
Авторы игры не могли предсказать заранее, где окажется конкретный воксель, на равнине или вершине горы — ведь планета ещё не создана. Да, можно было взять конкретную точку пространства и сгенерировать её, но чтобы получить контекст, пришлось бы создать и все воксели вокруг неё. А для них в свою очередь — ещё воксели вокруг, и так далее, и так далее. Это огромная и попросту ненужная нагрузка на систему.
В итоге в Hello Games взяли производную с шума Перлина, которая позволила увидеть изменения в ландшафте ещё до непосредственной генерации планеты. Теперь разработчики знали, где находится воксель, будь то склон или равнина, — и благодаря этому они смогли детально настроить отдельные типы поверхностей.
На изображениях ниже хорошо видна разница — «оригинальный» шум Перлина и тот, к которому применили производную. Это не карта из No Man’s Sky, а просто наглядный пример.
Также разработчики добавили дополнительные функции, которые деформируют шум Перлина — благодаря им планеты с высоты выглядят так, будто это настоящий снимок из космоса. Появились русла рек, береговые линии, плато, террасы и так далее.
Вот так выглядит стандартный шум Перлина.
Вот так — после первой деформации.
И вот так — после второй.
Подход инди-разработчиков к созданию миров с нуля настолько хорошо себя зарекомендовал, что его задействовали студии калибра побольше.
Так, например, XCOM 2 — это пошаговая стратегия, но уровни в нём создаются вполне себе по принципу «рогаликов». О том, как в нём работает процедурный генератор, разработчики рассказывали на конференции GDC в 2018 году.
Там в основе генерации лежит так называемый plot — «сюжет» или «чертёж». Это, по сути, заранее заготовленный шаблон, который определяет сеттинг карты — городской центр, природа, пригороды, посёлки отшельников или же инопланетные сооружения.
«Чертёж» городского центра
Внутри «чертежей» расположены «участки земли» (parcels) — области, в которых генерируются различные объекты и здания. Здесь авторы XCOM совместили и процедурную генерацию, и ручную работу: здания могут как создаваться автоматически на основе библиотеки ассетов, так и подгружаться уже в готовом виде из файлов игры.
При этом планировка улиц ещё и подчиняется некоторым правилам, заложенным в генератор карт. Так, например, в «чертеже» пригорода жилые дома расположены по краям уровня, в то время как магазины и кафе — в центре. А главный вход у торговых центров находится со стороны улицы, а не переулка.
Все эти правила сильно снизили вариативность генерации карт — казалось, будто игра раз за разом подгружала одни и те же уровни. И для того, чтобы разнообразить уровни ещё сильнее, в Firaxis применили несколько других приёмов.
К примеру, генератор карт постоянно менял расположение домов — вращал их по оси и перекидывал с одной улицы на другую. Более того, разработчики предусмотрели так называемые «чертёжные участки» (plot covered parcels, или PCP) — районы карты, которые не были зданиями и отдельно прорабатывались художниками.
К примеру, для «чертежа» пригородов разработчики отдельно проработали четыре вида дворов, два вида поворотов, восемь видов прямых дорог и десять видов перекрёстков — с автомобильными дорогами, бульварами и железнодорожными путями. Всё это — и есть PCP.
В отличие от участков, PCP мы могли использовать по несколько раз на одной и той же карте. Они были небольшими и достаточно простыми по своей сути — поэтому, пока вы не встретите рядом два абсолютно идентичных ассета, вы и не поймёте, что мы вновь и вновь используем один и тот же контент.
ведущий левелдизайнер Firaxis
Также на картах меняются биомы — в природном сеттинге действие может происходить как в тёплом лесу, так и посреди пустыни или заснеженной тайги. И это не говоря о том, что время суток на картах в XCOM всегда разнится.
В результате получившиеся уровни выглядели как реалистичные, живые локации, а не нагромождение ассетов из библиотеки. Да, порой игрок мог наткнуться на похожие места, но в целом каждая новая партия отличалась от другой.
И это здорово решило проблему первой XCOM, в которой все карты разработали вручную. Firaxis одним выстрелом убила двух зайцев — сократила издержки на создание уровней и повысила реиграбельность.