Создание карты - одна из самых интересных и занимательных систем, которая может существовать для того, чтобы разнообразить игровой процесс внутри игры. Ведь всё новые и новые локации для пользователя вызовут его интерес и заинтересованность.
Игровую карту можно генерировать достаточно разными способами и условиями. Благодаря которым могут создаваться различные препятствия и прочее.
![Генерация карты из шестиугольников.](https://habrastorage.org/getpro/habr/post_images/716/b6b/8a9/716b6b8a9d024dc87b2d645b5d4bdbb6.jpg)
Механика генерации карты в unity
В данном случае я расскажу как генерировать карту для игры, подобной героям третьим. Где существуют разные территории, и каждая территория имеет свой цвет. Каждый шестиугольник я буду называть тайлом, который имеет свою собственную текстуру и настройку.
И так, для начала нам необходимо разбить всё игровое пространство на несколько локаций. В моём случае это будет 4 различные локации:
- Людей (Green)
- Нежить (Black)
- Пустыня (Green)
- Зима (White)
Сразу скажу, что локации называются цветами только потому что, изначально вместо текстур я использовал различные цвета, для того чтобы упростить процесс создания.
![Разбитая карта на родительские тайлы](https://you-hands.ru/wp-content/uploads/2021/05/image-1024x437.png)
И так, после того как вы создали отдельные объекты для ваших тайлов, необходимо определить, какой из объектов будет содержать тип тайлов, в последствии чего создавать именно в нём определённый тип тайлов. (Звучит не очень, но на деле именно так)
Данные
Создаём специальный класс, который отвечает за хранение данных о родителях тайлов.
[System.Serializable]
public class GameTiles
{
public Transform gameTile; // родительский объект тайлов
public typeGameTiles type = typeGameTiles.none; // тип родительского объекта
public enum typeGameTiles // типы игровых тайлов
{
greend, // земля
white, // зима
black, // демоны
greey, // песок
none // неопределён
}
}
Данный код хранит в себе такие данные как:
- сам родительский объект тайлов
- тип родительского объекта
Назначение типов
Следующим шагом нам необходимо для каждого родительского объекта тайла назначить его тип. Сделать это нужно так, чтобы не возникало повторений, добавив к этому всему ещё рандом.
int step = 0; // переменная отвечающая за раздачу типов
// для начала рандомим тип локации
for (int i = 0; i < 4; i++)
{
// перебираем 4 локации
int id_random = UnityEngine.Random.Range(0, 4); // выбираем случайную локацию
if (gameTiles[id_random].type == GameTiles.typeGameTiles.none)
{
// если случайный родительский объект тайла не определён
if (step == 0) gameTiles[id_random].type = GameTiles.typeGameTiles.greend; // если шаг = 0 то земля
if (step == 1) gameTiles[id_random].type = GameTiles.typeGameTiles.white; // если шаг = 1, то снег
if (step == 2) gameTiles[id_random].type = GameTiles.typeGameTiles.black; // и т.д.
if (step == 3) gameTiles[id_random].type = GameTiles.typeGameTiles.greey; //
step++; // увеличиваем шаг
}
else i--; // если тип уже определён, возвращаемся назад
}
Данный код:
- Перебирает четыре локации
- Выбирает случайную из них
- Проверяет не присвоен ли данной локации значение
- Назначает ей новое значение
Если же значение уже присвоено, то возвращается на шаг назад. Знаю, алгоритм достаточно странный. И честно говоря, можно было бы сделать проще, но это мой код.
Тайлы
И так, когда типы у нас назначены, нам нужно откуда-то брать сами объекты, которые мы будем создавать. Для этого нам на помощь приходят ещё два класса. В себе они будут хранить данные об объектах которые мы создаём. Почему два? Потому что нам нужно сделать список списков, то есть двумерный массив. Но чтобы это работало просто.
Первый класс будет называться Tile и содержать в себе сам объект и тип этого объекта:
[System.Serializable]
// префабы для тайлов
public class Tile
{
public GameObject tile; // префаб создаваемого объекта
public GameTiles.typeGameTiles type; // тип создаваемого объекта
}
Второй класс будет называться Tiles. Он будет содержать в себе наименование группы тайлов, переменную для случайного создания и список префабов тайлов:
[System.Serializable]
public class Tiles
{ // список префабов тайлов
public string name = ""; // наименование группы тайлов
public int random_create = 0; // значение для рандома
public List<Tile> tiles = new List<Tile>(); // список префабов тайлов
}
Соответственно когда всё готово мы можем продолжать наш чудесный код для создания нашей локации. Теперь алгоритм достаточно прост. Мы перебираем четыре наших родительских объекта и создаём в них случайные тайлы в зависимости от типа. Далее центрируем их относительно нашего родительского объекта.
for (int i = 0; i < 4; i++) // перебираем родительские объекты
{
for (int a = 0; a < countTile; a++) // запускаем цикл от 0, до нужного кол-во создания тайлов (в моём случае 25)
{
for (int b = 0; b < countTile; b++) // запускаем второй цикл от 0, до нужного кол-во создания тайлов (в моём случае 25)
{
bool scos = false; // создаём переменную отвечающую за создания тайлов (поскольку у нас шестиугольники, необходимо сдвигать каждую чётную строку на половину)
if (b % 2 == 0) scos = true; // если наша строка чётная, запоминаем что нужно будет сдвинуть строку
int id_tile = UnityEngine.Random.Range(0, prefabTiles.Count); // выбираем ID случайного тайла, который хотим создать
int id_random = UnityEngine.Random.Range(0, 100); // выбираем случайное значение
if (prefabTiles[id_tile].random_create < id_random) // проверяем можем ли мы с нашей вероятностью создать тайл, если нет, выбираем другой
{
GameObject createTile = Instantiate(prefabTiles[id_tile].tiles.Find(x => x.type == gameTiles[i].type).tile, gameTiles[i].gameTile); // создаём тайл
if (!scos) // если у нас не чётная строка то не сдвигаем наш элемент =
createTile.GetComponent<RectTransform>().anchoredPosition = new Vector2(-countTile*84/2 + a * 84, -countTile * 76 / 2 + b * 76);
else // если же строка чётная, то сдвигаем наш элемент
createTile.GetComponent<RectTransform>().anchoredPosition = new Vector2(-countTile * 84 / 2 + 42 + a * 84, -countTile * 76 / 2 + b * 76);
createTile.transform.parent = createTile.transform.parent.parent; // меняем родителя (это необходимо для внутриигровой механики)
tiles.Add(createTile); // добавляем тайл в список всех тайлов (это необходимо для игровой механики)
}
else b--; // если мы не попали с вероятностью, возвращаемся на шаг назад
}
}
}
Ну и соответственно данный код создаст нам четыре различные площади определённого типа. Единственное что останется правильно заполнить в диспетчере задач два этих массива, скриншот которых прикреплён чуть ниже.
![Генерация карты в Unity](https://you-hands.ru/wp-content/uploads/2021/05/image-1.png)
Полный код
Ну и соответственно полный код который генерирует карту также представлен ниже:
[System.Serializable]
public class GameTiles
{
public Transform gameTile; // родительский объект тайлов
public typeGameTiles type = typeGameTiles.none; // тип родительского объекта
public enum typeGameTiles // типы игровых тайлов
{
greend, // земля
white, // зима
black, // демоны
greey, // песок
none // неопределён
}
}
[System.Serializable]
// префабы для тайлов
public class Tile
{
public GameObject tile; // префаб создаваемого объекта
public GameTiles.typeGameTiles type; // тип создаваемого объекта
}
[System.Serializable]
public class Tiles
{ // список префабов тайлов
public string name = ""; // наименование группы тайлов
public int random_create = 0; // значение для рандома
public List<Tile> tiles = new List<Tile>(); // список префабов тайлов
}
public List<Tiles> prefabTiles = new List<Tiles>();
[Header("Большие родительские тайлы")]
public List<GameTiles> gameTiles;
public int countTile = 25;
/// <summary>
/// Функция создания локации
/// </summary>
private void CreateMapTiles()
{
int step = 0; // переменная отвечающая за раздачу типов
// для начала рандомим тип локации
for (int i = 0; i < 4; i++)
{
// перебираем 4 локации
int id_random = UnityEngine.Random.Range(0, 4); // выбираем случайную локацию
if (gameTiles[id_random].type == GameTiles.typeGameTiles.none)
{
// если случайный родительский объект тайла не определён
if (step == 0) gameTiles[id_random].type = GameTiles.typeGameTiles.greend; // если шаг = 0 то земля
if (step == 1) gameTiles[id_random].type = GameTiles.typeGameTiles.white; // если шаг = 1, то снег
if (step == 2) gameTiles[id_random].type = GameTiles.typeGameTiles.black; // и т.д.
if (step == 3) gameTiles[id_random].type = GameTiles.typeGameTiles.greey; //
step++; // увеличиваем шаг
}
else i--; // если тип уже определён, возвращаемся назад
}
for (int i = 0; i < 4; i++) // перебираем родительские объекты
{
for (int a = 0; a < countTile; a++) // запускаем цикл от 0, до нужного кол-во создания тайлов (в моём случае 25)
{
for (int b = 0; b < countTile; b++) // запускаем второй цикл от 0, до нужного кол-во создания тайлов (в моём случае 25)
{
bool scos = false; // создаём переменную отвечающую за создания тайлов (поскольку у нас шестиугольники, необходимо сдвигать каждую чётную строку на половину)
if (b % 2 == 0) scos = true; // если наша строка чётная, запоминаем что нужно будет сдвинуть строку
int id_tile = UnityEngine.Random.Range(0, prefabTiles.Count); // выбираем ID случайного тайла, который хотим создать
int id_random = UnityEngine.Random.Range(0, 100); // выбираем случайное значение
if (prefabTiles[id_tile].random_create < id_random) // проверяем можем ли мы с нашей вероятностью создать тайл, если нет, выбираем другой
{
GameObject createTile = Instantiate(prefabTiles[id_tile].tiles.Find(x => x.type == gameTiles[i].type).tile, gameTiles[i].gameTile); // создаём тайл
if (!scos) // если у нас не чётная строка то не сдвигаем наш элемент =
createTile.GetComponent<RectTransform>().anchoredPosition = new Vector2(-countTile*84/2 + a * 84, -countTile * 76 / 2 + b * 76);
else // если же строка чётная, то сдвигаем наш элемент
createTile.GetComponent<RectTransform>().anchoredPosition = new Vector2(-countTile * 84 / 2 + 42 + a * 84, -countTile * 76 / 2 + b * 76);
createTile.transform.parent = createTile.transform.parent.parent; // меняем родителя (это необходимо для внутриигровой механики)
tiles.Add(createTile); // добавляем тайл в список всех тайлов (это необходимо для игровой механики)
}
else b--; // если мы не попали с вероятностью, возвращаемся на шаг назад
}
}
}
}