Процедурная генерация для рогаликов: Миллионы, миллионы, миллионы pixel shop!

Видеоверсия этой статьи

Вы когда-нибудь играли в Daggerfall? В классические рогалики? В Dwarf Fortress? Вам было интересно, как именно создаются в них здания, города или даже миры? Если да, то этот цикл (надеюсь) статей для вас!

Лапки вверх, с вами Мурмия!

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

Давайте начнем!

Процедурная генерация для рогаликов: Миллионы, миллионы, миллионы pixel shop!

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

Благодаря исключениям ТАКОЕ не сгенерируется никогда.
Благодаря исключениям ТАКОЕ не сгенерируется никогда.

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

Тип магазина может быть следующим:

  • С едой
  • С драгоценностями

  • С одеждой

  • С оружием

  • С броней

  • С зельями

  • С инструментами

  • С магическими предметами

Если тип стены, пола или магазина не указан, то он выбирается случайным образом.

Процедурная генерация для рогаликов: Миллионы, миллионы, миллионы pixel shop!

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

Как работает эта функция?

Создаем новую пустую комнату. Причем так как мы УЖЕ проверили весь магазин на корректность размеров и материалы, для этих комнат ничего проверять не надо. Расставляем на карте выход из магазина, масляную лампу, прилавок и кучку монет (эти объекты генерируются в каждом магазине). После этого определяем словарь, в котором мы выбираем для каждого типа магазина определенные типы предметов.

Что у нас будет на этот момент.
Что у нас будет на этот момент.

Далее немного магии:

Создаем список, который хранит в себе все координаты для предметов в определенной части магазина. Далее для каждого предмета мы запускаем бесконечный цикл. В нем мы случайно выбираем координату для него. Если этой координаты НЕТ в списке существующих, то мы:

  • Кладем предмет в эту клетку
  • Добавляем координату в список
  • Выходим из цикла
Картинка для тех, кто знает клингонский.<br />
Картинка для тех, кто знает клингонский.

Зачем вообще такие сложности? Допустим нам надо разместить на площади в пять на пять клеток двадцать предметов. Если мы каждый раз будем случайно выбирать клетку и класть в нее предмет, то у нас будет две проблемы:

Первая: будет много пустых клеток

Вторая: будет много клеток с двумя, а то и более предметами.

Процедурная генерация для рогаликов: Миллионы, миллионы, миллионы pixel shop!

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

Процедурная генерация для рогаликов: Миллионы, миллионы, миллионы pixel shop!

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

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

  • Создаем комнату
  • Делим ее пополам
  • Создаем стены: теперь у нас две комнаты
  • Создаем двери
  • Расставляем по спальне кровать, факел и сундук
  • В зависимости от типа магазина, случайно раскидываем по складу определенные товары
  • И возвращаем карту

Мы опять попадаем в главную функции генерации магазина. Вклеиваем в него СНАЧАЛА торговый зал, а потом склад со спальней. Почему важен порядок? Дверь из торгового зала в склад была создана на карте со складом, так что если вклеить сначала склад, а потом торговый зал, то дверь просто исчезнет. Дальше при желании мы можем создать экстерьер магазина и перевернуть здание. Это даст нам в пару раза больше разнообразия с минимальными усилиями.

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

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

А с вами была Мурмия, всем спасибо, всем пока!

Процедурная генерация для рогаликов: Миллионы, миллионы, миллионы pixel shop!
5555
26 комментариев

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

9
Ответить

Это можно сделать по-разному, например:

2
Ответить

Магазин-магазину рознь конечно, но валяющиеся по всей площади вещи - выглядит не очень. Да и лучше уж пресетом тогда делать их, имхо)
Я все же ждал алгоритмов "лабиринта" и связанных между собой уровней
( ಠ ͜ʖಠ)

4
Ответить

валяющиеся по всей площади вещи - выглядит не оченьКлассика (см. правую часть карты)

3
Ответить

Вы когда-нибудь играли в Daggerfall? Вам было интересно, как именно создаются в них здания, города или даже миры? Только в Даггерфоле были псевдорандомная генерация. Немного похожа на vaults в ADOM.
https://dtf.ru/games/210962-dyavolskaya-lapsha-posobie-po-vyzhivaniyu-v-daggerfolle

2
Ответить

А то, что на пути к столу лежит снаряжение это ок?

1
Ответить