Процедурная генерация мира
Хочу сделать генерацию мира как в Minecraft, но с более объемными блоками.
Есть ли актуальный и +- новый видеоролик или статья, где описывается как создается генерация мира?
Если не сложно, буду благодарен если отправите ссылку на видео или статью.
Спасибо каждому за ответ!
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:
Процедурная генерация 3д лабиринта
Всем привет. Несколько дней копаюсь в ютубе и гугле в поисках решения как генерировать лабиринт.
Процедурная генерация подземелья по готовому алгоритму
Всем привет, нашел такой алгоритм по генерации подземелья и он мне очень понравился. Решил.
Генерация мира
Привет! Извините за тупой вопрос. Я планирую создать пиксельную игру(не как minecraft), и мне.
Генерация мира
Реально ли сделать так, чтобы объекты, созданные скриптом сохранялись в иерархии?
253 / 200 / 64
Регистрация: 07.05.2017
Сообщений: 1,597
Сообщение от NoVate
Если не сложно, буду благодарен если отправите ссылку на видео или статью.
есть вариант воспользоваться интернетом и попробовать научиться пользоваться поисковиком. а если какие-то детали будут не понятны по ходу дела, спрашивать на форуме.
прикинь я спрошу: «как мне сделать GTA? накидайте инфы. только сами ищите. мне влом»
Регистрация: 22.09.2020
Сообщений: 74
Я и задал вопрос. В интернете я нашел, но они или не актуальны, или не подробные. Я попросил просто скинуть или статью или видеоурок. Все! Ничего более.
Добавлено через 1 минуту
На форуме люди не быстро отвечают и спрашивать «детали» как Вы говорите — будет очень долго. Тем более я с процедурной генерацией работаю первый раз, у меня их будет очень много
253 / 200 / 64
Регистрация: 07.05.2017
Сообщений: 1,597
Сообщение от NoVate
На форуме люди не быстро отвечают и спрашивать «детали» как Вы говорите
с такими абстрактными вопросами конечно будут долго отвечать. вы начните делать по тому, что нашли и более конкретные вопросы найдут свою аудиторию довольно быстро. я бы не сказал, что тут долго отвечают.
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
Помогаю со студенческими работами здесь
Процедурная генерация
Доброго времени суток, форумчане. Как сделать переходы между комнатами? Читал о применении.
Процедурная генерация
Делаю процедурную генерацию лабиринта на основе псевдослучайных чисел. Не понимаю почему на.
Процедурная генерация леса
Всем привет. Я любитель программист. Дано: 2д карта, сетка квадратная. Карта представлена.
Процедурная генерация комнатного лабиринта
Скрипт создает на сцене некоторое количество комнат со случайным положением и случайным.
Что такое процедурная генерация?)
Приветствую уважаемые форумчане! Задумался я под вечер грядущий о написании интересной программки.
Генерация мира
using UnityEngine; public class RoomManager : MonoBehaviour < public int curRoom; .
Процедурно генерируемые карты мира на Unity C#, часть 1
В этих обучающих статьях мы создадим процедурно генерируемые карты, похожие на такие:
- тепловая карта (левая верхняя)
- карта высот (правая верхняя)
- карта влажности (правая нижняя)
- карта биомов (левая нижняя)
Генерирование шума
В Интернете есть множество различных генераторов шума, большинство из них имеют открытые исходники, поэтому здесь не нужно изобретать велосипед. Я позаимствовал портированную версию библиотеки 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; >> > >
После запуска кода мы получим следующую текстуру:
Выглядит пока не очень интересно, но начало положено. У нас есть массив данных, содержащий значения от 0 до 1, с очень любопытным рисунком.
Теперь нам нужно придать значимости нашим данным. Например, пусть все, что меньше 0,4, будет считаться водой. Мы можем изменить следующее в нашем TextureGenerator, назначив все значения ниже 0,4 синими, а выше — белыми:
if (value < 0.4f) pixels[x + y * width] = Color.blue; else pixels[x + y * width] = Color.white;
После этого мы получил следующее конечное изображение:
У нас уже что-то получается. Появляются фигуры, соответствующие этому простому правилу. Давайте сделаем следующий шаг.
Добавим других настраиваемых переменных в наш класс 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);
Добавив таким образом новые правила, мы получим следующие результаты:
У нас получилась интересная карта вершин с представляющей ее текстурой.
Исходники кода первой части вы можете скачать отсюда: World Generator Part1.
- процедурная генерация
- процедурные текстуры
Реализация и оптимизация генератора уровней в Unity
В мае этого года мы обсуждали алгоритм, который используем для генерации внешнего мира в игре Fireside. Сегодня мы возвращаемся к этой теме. В прошлый раз нам удалось сгенерировать набор точек на текстуре с помощью фильтрации шума Перлина. Хотя это решение удобно, оно имеет две важные проблемы:
- Оно не особо быстрое
- На самом деле мы не создавали ассетов в Unity
- Создадим в Unity фреймворк, который позволит нам использовать алгоритм генерации текстур
- При помоги сгенерированных текстур создадим ассеты в игровом мире
- Распараллелим генерацию текстур с помощью C# System.Threading.Tasks, чтобы ускорить процесс
Интеграция генерации карт в движок Unity
Мы будем писать Scriptable Objects движка Unity для создания модульного окружения в целях генерации карт. Таким образом, мы дадим гейм-дизайнерам свободу настройки входных данных алгоритма без необходимости работы с кодом. Если вы ещё не слышали о ScriptableObjects, то рекомендую для начала изучить документацию Unity.
Сначала нам потребуется набор различных контейнеров данных. Наш конвейер довольно сложен, и если поместить все необходимые параметры в один объект, он окажется слишком объёмным. Поэтому мы будем использовать по одному пакету данных на каждый уровень алгоритма.
Итак, карта составляется из одного или нескольких сегментов (slice), состоящих из одного или нескольких фрагментов (chunk), созданных из одной или нескольких текстур. Примечание: в большинстве алгоритмов этап сегментов пропускается, но я включил этот этап для дизайна конкретной игры и генерации путей; о причинах я расскажу в этой статье. Можно без проблем игнорировать сегменты и всё равно реализовать описанное здесь решение. При помощи очень удобного ExtendedScriptableObjectDrawer Тома Кэйла мы можем расширить настройки для простоты редактирования.
Здесь вы видите, какой тип данных и на каком уровне мы упаковываем. По сути, каждая генерируемая нами текстура будет распределять один ассет карты. Поэтому чтобы получить разнообразное распределение ассетов, нам нужно наложить друг на друга множество текстур. Разбиение карты на фрагменты и сегменты позволяет нам изменять генерируемые ассеты в соответствии с расстоянием от точки начала координат.
- Конвейер, используемый для генерации текстур
- Масштаб, сопоставляющий пространство текстуры с мировым пространством
- Параметры для дорог
- Seed
- Какой сегмент (slice) будет использоваться на каком расстоянии от точки начала координат
- Какой фрагмент (chunk) используется в зависимости от расстояния до центра сегмента
- Сопоставление между текстурами и ассетом, который должен располагаться на точках, сгенерированных из текстуры
- Текстурные параметры для каждой текстуры
- Текстурные параметры для пути
- Все параметры, описанные в первой части нашего девлога по процедурной генерации карт.
Каждый уровень данных имеет связанный с ним класс C#, использующий паттерн «фабрика», который мы применяем для выполнения логики каждого этапа. Если бы мы хотели только распределять ассеты, то этапы генерации были бы очень простыми. Однако нам также нужно создать пути, по которым будет двигаться игрок. Это немного усложняет архитектуру, потому что после генерации точек нам нужно соединить фрагменты и сегменты.
Если не учитывать пока генерацию путей, то единственная логика, которая нам сейчас нужна — это преобразование сгенерированных на текстуре точек в мировое пространство. Это реализуется благодаря использованию параметра масштабирования из параметров карты, что обеспечивает нам удобный контроль над плотностью размещения ассетов.
internal static Vector3 TexturePointToWorldPoint( Vector2Int texturePoint, float scale, Plane plane, Vector3 origin, ProceduralTextureGenerationSettings settings) < float tx = (float)texturePoint.x / (float)settings.width; float ty = (float)texturePoint.y / (float)settings.height; Vector3 right = GetRight(plane) * scale * tx; Vector3 up = GetUp(plane) * scale * ty; return origin + right + up; >
Поскольку мы сохранили кажду точку в мировом пространстве со связанным с ней префабом, для расположения ассетов достаточно просто вызвать Instantiate для префаба, ссылка на который указана в соответствующем слое параметров фрагмента. Единственное, что нужно учитывать — наш алгоритм не гарантирует, что ассеты не наложатся друг на друга. Пока мы применим такое решение: дадим каждому префабу коллайдер и будем уничтожать все ассеты, с которыми пересекаемся при создании экземпляра префаба. Как сказано в нашем предыдущем девлоге, нужно вызвать Physics2D.SyncTransforms() и yield return new WaitForFixedUpdate(), чтобы проверки коллизий работали правильно.
public IEnumerator PlaceAssets(Chunk chunk) < GameObject chunkObject = new GameObject("Chunk::" + chunk.settings.name); chunkObject.transform.SetParent(worldRoot); ContactFilter2D cf2d = new ContactFilter2D(); foreach (int layerIndex in chunk.generatedLayerAnchors.Keys) < GameObject layerParent = new GameObject(); layerParent.name = chunkObject.name + "::" + "Layer::"+chunk.generatedLayerAnchors[layerIndex].Item1.asset.name; layerParent.transform.SetParent(chunkObject.transform); foreach (Vector3 point in chunk.generatedLayerAnchors[layerIndex].Item2) < PlaceableAsset inst = Instantiate(chunk.generatedLayerAnchors[layerIndex].Item1.asset, layerParent.transform); inst.transform.position = point; Collider2D[] cols = new Collider2D[16]; Physics2D.SyncTransforms(); int numOverlaps = Physics2D.OverlapCollider(inst.mapgenerationCollider, cf2d, cols); for (int i = 0; i < numOverlaps; i++) < if (cols[i].transform.parent != null && cols[i].transform.parent.TryGetComponent(out PlaceableAsset toDestroy)) Destroy(cols[i].transform.parent.gameObject); > > yield return new WaitForFixedUpdate(); > >
Вот и всё! Нам удалось преобразовать наш эксперимент на Processing в работающую систему на движке Unity! Но, увы…
Ускоряем работу
Мы улучшили наш алгоритм, распараллелив его. Так как мы генерируем набор независимых друг от друга текстур (но зависящих от лежащего в их основе шума Перлина), то можно распараллелить генерацию текстур в каждом фрагменте и даже распараллелить генерацию фрагментов.
В официальной документации C# написано, что async / await являются базовой функциональностью C#. Хотя я хорошо знаком с другими возможностями. перечисленными на этом сайте, до начала проекта я не использовал ни async, ни Tasks. Основная причина заключается в том, что в Unity есть похожая функциональность. И это… (барабанная дробь) корутины. На самом деле, в руководствах по программированию на C# в качестве примера используется стандартный способ применения корутин (выполнение запроса к серверу). Это объясняет, почему я (и многие другие Unity-разработчики, которых я знаю) пока не использовал пока асинхронное программирование на C#. Однако это очень полезная возможность и мы используем её, чтобы распараллелить генерацию карт.
//Foo prints the same result as Bar void Start() < Foo(); >async Task Foo() < Debug.Log(“Hello”); await Task.Delay(1000); Debug.Log(“There”); >void Start() < StartCoroutine(Bar()); >IEnumerator Bar()
Вот краткое введение в асинхронное программирование. Как и в случае с корутинами, при реализации асинхронного метода нам нужно возвращать особый тип (Task). Кроме того, нужно пометить метод ключевым словом async. Затем можно использовать ключевое слово await таким же образом, каким бы мы использовали оператор yield в корутине.
Однако существует также очень удобный метод Task.WhenAll, который создаёт Task, блокирующий исполнение, пока не будет завершён набор задач. Это позволяет нам реализовать следующее:
//Generates textures for all layers in parallel. foreach (ChunkLayerSettings setting in settings.chunkLayerSettings) < //generate texture for this chunk textureTasks.Add(textureGenerator.GenerateTextureRoutine( setting.textureSettings, seed, chunkCoords, new TextureGenerationData(seed, chunkCoords, setting.textureSettings))); >result = await Task.WhenAll(textureTasks);
В отличие от корутин, эти задачи выполняются параллельно и не тратят время выполнения в основном потоке. Теперь мы просто можем использовать такой подход при генерации как фрагментов, так и текстур. Это значительно увеличивает производительность: с примерно 10 секунд на сегмент до 3 на сегмент.
При этом мы получаем алгоритм, способный генерировать достаточно сложные и обширные карты примерно за 10 секунд (3 сегмента). Возможны дальнейшие оптимизации, а производительностью можно управлять с помощью размера используемых текстур.
- процедурная генерация
- процедурная генерация карт
- шум перлина
- корутины
- unity3d
[Unity] Процедурная генерация
Какое-то время по фану делаю процедурную генерацию мира, вдохновлялся офк майнкрафтом и другими играми.
Что есть на данный момент:
1. В основе лежит несколько слоёв Perlin Noise.
2. Для эффекта треугольников использовал Traingle.NET, если создавать вручную простым способом, то они будут сглаженными. Вершины у треугольников общие. 3. Чанк 50х50 создаётся примерно 10 миллисекунд.
4. Вокруг персонажа (пока что просто камера) 9 чанков. При передвижении создаются новые, а старые скрываются.
5. Всё разбито на потоки.
Спойлер: "Настройки в редакторе"
Спойлер: "Гифки"
Дефолтный пресет:
Раскраска происходит исходя из высоты точки, цвет берётся из градиента на скрине выше.
Попытка сделать биомы:
Метод "zoom out": массив цветов 3х3 превращается в 5х5 (2^n - 1) путём выбора случайного цвета из соседних. И так N раз, последниее N/2 итерации смешивают цвет соседей вместо выбора случайного.
Пресет "Ад":
Это всё те же треугольники, но высота каждой точки округлена к ближайшему целому, от чего создаётся эффект алгоритма Marching Cubes.
Лава та же вода, но с другими цветами и HDR (благодаря чему светится).
Спойлер: "Что самое сложное было"
Первая сложность, с которой я столкнулся это правильное определение цвета по высоте. Для каждого чанка я вычислял максимальную и минимальную высоту, чтобы потом можно было взять цвет из градиента. Особенность в том, что у соседних чанков эти значения могут отличаться, отчего будет виден небольшой шов по цвету.
Решением стало вычисление высот соседних чанков в момент создания текущего чанка и запись их в коллекцию (чтобы потом не вычислять снова), благодаря этому максимальный и минимальный цвет берётся включая соседей и швов больше нет.
Вторая сложность это производительность. На моём компьютере (7700hq, 1050ti) версия без потоков проседала с ~500 фпс до ~300 в момент создания чанка, то есть при стабильном движении. Но проверив на более слабом компьютере, я понял, что так не годится. Собственно, сложностью было перенос большинства thread unsafe действий из потока наружу. А так же адаптация этого всего к логике, отвечающей за создание чанков впереди и сокрытие позади.
Что дальше в планах? Доделать биомы.
Добавить флору, начну с процедурных low poly деревьев.
Всё, кроме воды, делал сам. Жду критики и идей.
Спойлер: "Корованы?"
Да, можно будет грабить