Доброго времени суток! Если вы вдруг захотели сделать игру со случайной генерацией мира (В моем случае это приключенческий выживач-боссобойка) но у вас как и у меня нет либо навыков, либо желания делать сложную процедурную генерацию полноценных структур, то, возможно, вам подойдет мой вариант, который я постараюсь расписать ниже.
Играли в Морской Бой? В нем на двухмерной матрице 10x10 на нужно расставить опр. количество маленьких, средних и больших кораблей.
Вот у нас в ядре своем, мир тоже будет двухмерной матрицей (только нужного вам размера), в котором случайные ячейки мы помечаем как будущие: малые острова (синие), средние острова (зеленые), и крупные острова (красные)
Потом, используя данные этой карты, мы в координатах игрового мира будем размещать случайные ландшафты островов из заранее созданного пресета (об это далее). И схематично, на выходе мы получим что-то вот такое:
Само собой это можно будет конфигурировать под ваши нужны, например если бы вам хотелось чтобы крупных островов было больше, а маленькие, к примеру, могли бы прилегать к островам побольше. Да в принципе и понятие "большой" можно наверное превратить и в целые материки, не 2x2 ячейки, а скажем 6x6.
Штош, хватит картинок и буковок, займемся кодом! (Ага, теперь буковки даже без картинок пойдут...)
2. Пишем код для генерации 2d матрицы с данными о расположении островов
Для начала определимся что карта островов будет int двумерным массивом, где:
0 - пустое пространство
1 - маленький остров
2 - средний остров
3 - большой остров
И так, создаём скрипт "IslandMapGenerator", и первым делом задаём перечисление (enum) с нашими островами (заодно видны настройки класса, он не статический, и не Monobehavior)
Далее, я хочу подвязать генерацию карты с seed, вы видели это наверное во всех играх с процедурной генерацией мира. Если нет, то это необходимо чтобы создать эдакий "ключ генерации", при котором мир будет генерироваться каждый раз одинаково.
Конструктором мы будем определять экземпляр класса, задавая ему при инициализации seed
Метод GenerateIslandsMap()
Далее пишем основной метод, который и будет возвращать 2d массив с островами, она же наша карта островов
Метод принимает следующие параметры:
worldSize - размер мира, то есть размер нашего 2d массива
b|m|s_islandCount - количество островов каждого типа, которые необходимо генерировать.
Далее метод использует другой метод PlaceIslands() (который вы еще не написали, да) для каждого вида островов, начинаем с больших, и заканчиваем маленькими.
Возвращает метод int[,] islandsMap, то бишь необходимый нам результат в виде 2д массива с будущими островами, пока что это просто цифорки)
Теперь, собственно, перейдем к методу PlaceIslands() :
Метод принимает 2d массив карты (1), который мы определили методом ранее, размер мира (2), количество островов (3) и тип острова (4).
Он что делает... Он выполняет цикл while пока :
А Не будет создано необходимое кол-во островов.
Б количество попыток/итераций превысит лимит.
В каждой попытке он через наш _random определяет случайную координату для нашего массива, и... И вызывает еще один метод (Обожаю, сука, методы в методах!) TryPlaceIsland(), и если этот метод успешен (вернул true), то мы увеличиваем значение созданных островов на 1 (сама пометка ячейки под цифру острова происходит "глубже")
Метод TryPlaceIsland()
Принимает в параметрах этот метод следующее:
1. 2d массив карты
2. размер мира
3 и 4. координаты в которых будем генерироваться (пытатся) остров, ну и вид острова.
Он через Switch определяет вид острова, и в зависимости от вида, вызывает ЕЩЕ ОДИН ВЛОЖЕННЫЙ МЕТОД (Эти последние, правда! (нет)), для каждого вида острова свой, зощем? Да потому что у каждого вида острова своя логика размещения.
(3) Большие острова - это 4 ячейки вместе (2х2), и соотв. за раз метод красит 4 ячейки и проверяет отсутствие соседей вокруг этих 4 ячеек, а не вокруг одной.
(2) Средние острова могут быть горизонтальными или же вертикальными, и состоят из 2х соседних ячеек
(1) Ну а с мелкими всё и так понятно
И наконец последние 3 метода, которые и выполняют всю логику пометок ячеек массива соотв. числом.
Все следующие методы будут принимать один и тот же набор параметров:
1. 2d массив карты
2. размер мира
3. позицию генерации х (внутри массива) и позицию y
Метод для большого острова TryPlaceBigIsland()
1) Первым делом мы проверяем что наша координата массива (startX и startY) не находятся в правом крайнем, или нижнем ряду массива, ведь тогда при попытке создать 4 ячейки острова он просто выпадет в ошибку (выход за границы массива)
Далее мы циклом перебираем все 4 ячейки будущего большого острова (красной точкой я отметил нашу startX | startY ячейку), внутри которых выполняем метод (этот точно последний, простите) IsCellValidForIsland(), метод выполняет проверку всех соседей вокруг ячейки, и возвращает true если соседей нет. (Код этого метода напишу в конце)
3) Если все 4 ячейки успешно прошли проверку этим методом, тогда мы переходим к третьему циклу, в котором мы точно так же проходимся по всем 4м нашим ячейкам, и передаем им значение 3 ((int)IslandSize.Big это 3)
Метод для среднего острова TryPlaceMediumIsland()
Так как средний остров может быть либо вертикальным, либо горизонтальным, тут 2 поведения, в зависимости от значения horizontal, которое мы определяем через простенькое выражение (1) _random.Next(0, 2), генерирует случайное число в заданном диапазоне, и если == 0, то горизонтальная ориентация, иначе вертикальная.
2) Делаем в принципе аналогичные действия что и для большого острова, только для 2х ячеек а не для 4х, и со смещением на одну вправо по x
3) Аналогично, но смещаемся на одну ячейку вниз по y
Метод для маленького острова TryPlaceSmallIsland()
Ну тут все максимально просто, проверили соседей, влепили ячейке 1
Ну сам метод проверки отсутствия соседей IsCellValidForIsland()
Как я уже написал ранее, этот метод принимает в параметрах координаты ячейки, проверяет все ячейки вокруг (от x -1 y -1 до x+1 y+1) и возвращает истину если ни одна из этих ячеек не занята, и не выходит за границы массива
На этом всё! Код который будет нам генерировать шаблон размещения островов по сиду в принципе готов. Надо только теперь его проверить как-то, хотя бы схематично и для отладки
Давайте создадим еще скрипт (В этот раз стандартный Monobehavior ), и назовём его WorldGenerator, и повесим его на новый объект в сцене, и назовем его World
И напишите в скрипте следующий код:
DebugPrintIslandsMap() выведет в консоль игры текстовый вариант карты, которую мы в параметре ему передаем
А в Start объявляется в переменную генератор наш IslandMapGenerator, с сидом 123, создается маленькая карта через генератор, 10 ячеек, 2 больших острова, 3 средних и четыре маленьких, и выполняется метод вызова в консоль.
У меня при заданном сиде 505 получился вот такой результат, немного повело из-за разницы в размере символ, но в целом читается.
На этом первую часть думаю стоит завершить, слишком много текста получилось. Пока что довольно непрезентабельно и примитивно, но я сам вызвался писать поэтапно) А это по сути просто массив-заготовка. Всё еще впереди)