Процедурно генерируемые карты мира на Unity C#, часть 1

от автора

image

В этом цикле статей мы научимся создавать процедурно генерируемые карты мира с помощью Unity и C#. Цикл будет состоять из четырех статей.

Содержание

Часть 1 (эта статья):

Введение
Генерирование шума
Начало работы
Генерирование карты высот

Часть 2:

Поворот карты по одной оси
Поворот карты по обеим осям
Поиск соседних элементов
Битовые маски
Заливка

Часть 3:

Генерирование тепловой карты
Генерирование карты воды
Генерирование рек

Часть 4:

Генерирование биомов
Генерирование сферических карт

Введение

В этих обучающих статьях мы создадим процедурно генерируемые карты, похожие на такие:

image

Здесь представлены следующие карты:

  • тепловая карта (левая верхняя)
  • карта высот (правая верхняя)
  • карта воды (правая нижняя)
  • карта биомов (левая нижняя)

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

Генерирование шума

В Интернете есть множество различных генераторов шума, большинство из них имеют открытые исходники, поэтому здесь не нужно изобретать велосипед. Я позаимствовал портированную версию библиотеки Accidental Noise.

Портирование на C# выполнено Nikolaj Mariager.

Для правильной работы в Unity в портированную версию внесены незначительные изменения.

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

Начало работы

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

Начнем с создания класса MapData. Переменные Min и Max нужны для отслеживания нижнего и верхнего пределов генерируемых значений.

public class MapData {      public float[,] Data;     public float Min { get; set; }     public float Max { get; set; }       public MapData(int width, int height)     {         Data = new float[width, height];         Min = float.MaxValue;         Max = float.MinValue;     } } 

Также мы создадим класс Tile, который будет позже использоваться для создания игровых объектов Unity из генерируемых данных.

public class Tile {     public float HeightValue { get; set; }     public int X, Y;               public Tile()     {     } } 

Чтобы посмотреть, что происходит, нам необходимо графическое представление данных. Для этого мы создадим новый класс TextureGenerator.

Пока этот класс будет создавать черно-белое отображение наших данных.

using UnityEngine;   public static class TextureGenerator {               public static Texture2D GetTexture(int width, int height, Tile[,] tiles)     {         var texture = new Texture2D(width, height);         var pixels = new Color[width * height];           for (var x = 0; x < width; x++)         {             for (var y = 0; y < height; y++)             {                 float value = tiles[x, y].HeightValue;                   //Set color range, 0 = black, 1 = white                 pixels[x + y * width] = Color.Lerp (Color.black, Color.white, value);             }         }                   texture.SetPixels(pixels);         texture.wrapMode = TextureWrapMode.Clamp;         texture.Apply();         return texture;     }       } 

Скоро мы расширим этот класс.

Генерирование карты высот

Я решил, что карты будут фиксированного размера, поэтому нужно указать ширину (Width) и высоту (Height) карты. Также нам понадобятся настраиваемые параметры для генератора шума.

Мы сделаем эти данные отображаемыми в Unity Inspector, чтобы настройка карт была намного проще.

Класс Generator инициализирует модуль Noise, генерирует данные карты высот, создает массив тайлов, а затем генерирует текстурное представление этих данных.

Вот код с комментариями:

using UnityEngine; using AccidentalNoise;   public class Generator : MonoBehaviour {       // Настраиваемые переменные для Unity Inspector     [SerializeField]     int Width = 256;     [SerializeField]     int Height = 256;     [SerializeField]     int TerrainOctaves = 6;     [SerializeField]     double TerrainFrequency = 1.25;       // Модуль генератора шума     ImplicitFractal HeightMap;           // Данные карты высот     MapData HeightData;       // Конечные объекты     Tile[,] Tiles;           // Вывод нашей текстуры (компонент unity)     MeshRenderer HeightMapRenderer;       void Start()     {         // Получаем меш, в который будут рендериться выходные данные         HeightMapRenderer = transform.Find ("HeightTexture").GetComponent ();           // Инициализируем генератор         Initialize ();                   // Создаем карту высот         GetData (HeightMap, ref HeightData);                   // Создаем конечные объекты на основании наших данных         LoadTiles();           // Рендерим текстурное представление нашей карты         HeightMapRenderer.materials[0].mainTexture = TextureGenerator.GetTexture (Width, Height, Tiles);     }       private void Initialize()     {         // Инициализируем генератор карты высот         HeightMap = new ImplicitFractal (FractalType.MULTI,                                         BasisType.SIMPLEX,                                         InterpolationType.QUINTIC,                                         TerrainOctaves,                                         TerrainFrequency,                                         UnityEngine.Random.Range (0, int.MaxValue));     }           // Извлекаем данные из модуля шума     private void GetData(ImplicitModuleBase module, ref MapData mapData)     {         mapData = new MapData (Width, Height);           // циклично проходим по каждой точке x,y - получаем значение высоты         for (var x = 0; x < Width; x++)         {             for (var y = 0; y < Height; y++)             {                 //Сэмплируем шум с небольшими интервалами                 float x1 = x / (float)Width;                 float y1 = y / (float)Height;                   float value = (float)HeightMap.Get (x1, y1);                   //отслеживаем максимальные и минимальные найденные значения                 if (value > mapData.Max) mapData.Max = value;                 if (value < mapData.Min) mapData.Min = value;                   mapData.Data[x,y] = value;             }         }        }           // Создаем массив тайлов из наших данных     private void LoadTiles()     {         Tiles = new Tile[Width, Height];                   for (var x = 0; x < Width; x++)         {             for (var y = 0; y < Height; y++)             {                 Tile t = new Tile();                 t.X = x;                 t.Y = y;                                   float value = HeightData.Data[x, y];                                   //нормализуем наше значение от 0 до 1                 value = (value - HeightData.Min) / (HeightData.Max - HeightData.Min);                                   t.HeightValue = value;                   Tiles[x,y] = t;             }         }     }   } 

После запуска кода мы получим следующую текстуру:

image

Выглядит пока не очень интересно, но начало положено. У нас есть массив данных, содержащий значения от 0 до 1, с очень интересным рисунком.

Теперь нам нужно придать значимости нашим данным. Например, пусть все, что меньше 0,4, будет считаться водой. Мы можем изменить следующее в нашем TextureGenerator, назначив все значения ниже 0,4 синими, а выше — белыми:

if (value < 0.4f)     pixels[x + y * width] = Color.blue; else     pixels[x + y * width] = Color.white; 

После этого мы получил следующее конечное изображение:

image

У нас уже что-то получается. Появляются фигуры, соответствующие этому простому правилу. Давайте сделаем следующий шаг.

Добавим других настраиваемых переменных в наш класс Generator. Они будут указывать на параметры, с которыми связаны значения высот.

float DeepWater = 0.2f; float ShallowWater = 0.4f;   float Sand = 0.5f; float Grass = 0.7f; float Forest = 0.8f; float Rock = 0.9f; float Snow = 1; 

Также добавим новые цвета в генератор текстур:

private static Color DeepColor = new Color(0, 0, 0.5f, 1); private static Color ShallowColor = new Color(25/255f, 25/255f, 150/255f, 1); private static Color SandColor = new Color(240 / 255f, 240 / 255f, 64 / 255f, 1); private static Color GrassColor = new Color(50 / 255f, 220 / 255f, 20 / 255f, 1); private static Color ForestColor = new Color(16 / 255f, 160 / 255f, 0, 1); private static Color RockColor = new Color(0.5f, 0.5f, 0.5f, 1);             private static Color SnowColor = new Color(1, 1, 1, 1); 

Добавив таким образом новые правила, мы получим следующие результаты:

image

У нас получилась интересная карта вершин с представляющей ее текстурой.

Исходники кода первой части вы можете скачать отсюда: World Generator Part1.

ссылка на оригинал статьи https://habrahabr.ru/post/276251/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *