Uploaded by Al Maz

Курсовая работа

advertisement
ФЕДЕРАЛЬНОЕ АГЕНТСТВО ЖЕЛЕЗНОДОРОЖНОГО ТРАНСПОРТА
Федеральное государственное бюджетное образовательное учреждение высшего
образования
«ИРКУТСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ПУТЕЙ СООБЩЕНИЯ
(ФГБОУ ВО ИрГУПС)»
Факультет «Управление на транспорте и информационные технологии»
Кафедра «Информационные системы и защита информации»
Программный модуль процедурной генерации
Курсовая работа
КР.420000.09.03.02. -2024.ПЗ
Выполнил
студент гр. ИС 1-22-1
Батанов А.Д.
Проверил
Ст. преподаватель
Вергасов А.С.
Иркутск 2024
Содержание
Содержание .................................................................................................... 1
1.
Введение ................................................................................................ 3
2.
Основная часть ..................................................................................... 3
2.1.
Постановка задачи ............................................................................ 3
2.2.
Программная реализация ................................................................. 6
2.3.
Краткая характеристика инструментальной программной среды 6
2.4.
Описание программных модулей .................................................... 6
2.4.1.
Математический алгоритм процедурной генерации .................. 7
2.5.
Инструкция пользователю ............................................................. 11
2.5.1.
Взаимодействие с API программного модуля .......................... 12
2.6.
Контрольный пример ...................................................................... 19
3.
Заключение ......................................................................................... 21
1. Введение
Проект курсовой работы представляет собой модуль для проекта,
который
является
компьютерной
игрой.
Для
достижения
большей
реиграбельности и интереса игрока к прохождению игр, разработчики
используют разные инструменты. Потому как компьютерная игра, для
которой разрабатывается проект данной курсовой работы, является игрой
мир, которой полностью генерируется с нуля, а сложность состоит в том, что,
интерес изучения и в целом получаемое удовольствие от игры зависит от
используемых в проектке алгоритмов процедурной генерации таких миров.
Способности
таких
генераторов
создать
по-настоящему
интересный
виртуадьный игровой мир.
2. Основная часть
2.1. Постановка задачи
Для понимания сути и необходимости данного программного модуля
необходимо описать что представляет из себя игра, частью которой является
данный модуль. Игра разработана на движке Unity, и представляет собой 2D
платформер с видом сбоку, мир данной игры состоит из блоков. Данный
модуль в свою очередь реализует генерацию мира в этой игре.
Терминология:
 Блок-элементарная часть мира.
 Чанк-совокупность блоков, шириной 16 и высотой 256 блоков.
 Unity-среда разработки в которой разрабатывается игра
Задачи модуля:
 Генерация поверхности земли, с неровностями и холмами. Также
размещать камень ниже поверхности земли, формируя таким
образом слой земли и подземелья
 Генерация пещер, таких, которые связаны между собой, и
которые дают игроку возможность передвигаться в нижней части
мира
 Генерация деревьев. Генератор должен уметь размещать деревья
разных видов и размеров на поверхности земли, в зависимости от
текущего
биома
(совокупность
параметров,
таких
как
в
толще
имеют
свои
температура и влажность)
 Генерация
подземных
ископаемых.
Размещение
подземелий
полезных
ископаемых,
которые
собственные параметры генерации, такие как высота, с которой
начинается генерация руды, кучность, количество руды в одном
кучу.
 Генерация должна быть одинаковой при вводе одного ключа,
который должен определять характер генерации, то есть один раз
воспользовавшись одним ключом, следующий раз, получим
точно такую же генерацию
 Модуль процедурной генерации должен быть модульным, то
есть, при дальнейшей разработке, модуль мог быть расширен
новыми возможностями
 Модуль должен иметь возможность интеграции с другими
модулями проекта
 Время работы алгоритма процедурной генерации не должно
превышать 100мс на один чанк
 Генерация должна быть однородной и бесшовной, то есть должна
прослеживаться общая тенденция и закономерность в генерации
мира на любом чанке
Аспекты использования модуля:
Все параметры процедурной генерации должны быть легко доступны
из редактора Unity, позволяя при этом легко и эффективно подбирать
наиболее оптимальные значения для генерации.
Геймплейные аспекты модуля:
Сгенерированная поверхность не должна создавать препятствия при
передвижении по ней, также должны быть плоские пространства для
размещения на них построек. Также желательно наличие открытых входов в
подземелье.
Сеть пещерных каналов должна быть связана, и образовывать единую
структуру, по которой можно перемещаться.
API программного модуля:
 ChunkGenerator – основной класс генератора, в котором
происходит основная часть процедурной генерации
 GenerateChunk(BlocksCounter
blocksCounter,
ChunkComponent
chunk, GenerationData genData, Vector2Int sizeChunk = default, int
seed = 0) – Метод для непосредственно генерации,
o blocksCounter – модуль для подсчета установленных блоков
o chunk – компонент чанка в пределах которого происходит
генерация
o genData – класс, содержащий параметры для генерации
o sizeChunk – размеры чанка
o seed – ключ генерации
 GenerateBackBack
(EcsPackedEntityWithWorld
chunkEntity,
GenerationData genData) – генерация задних блоков, для
формирования
наиболее
сложного
мира,
он
состоит
из
нескольких слоев (передний, фоновый, задний), данный метод
создает фон камня в подземелье
2.2. Программная реализация
2.3. Краткая характеристика инструментальной программной среды
Программный модуль разработан в программной среде JetBrains Rider.
В свою очередь игра, для которой данный модуль был разработан,
разрабатывается в программной среде Unity.
Unity - кроссплатформенная среда разработки компьютерных игр,
разработанная американской компанией Unity Technologies. Unity позволяет
создавать приложения, работающие на более чем 25 различных платформах,
включающих персональные компьютеры, игровые консоли, мобильные
устройства, веб-приложения и другие. Релиз Unity состоялся в 2005 году.
JetBrains
разработки
Rider
—
кроссплатформенная
интегрированная
обеспечения
платформы
.NET,
Поддерживаются
языки
программного
разрабатываемая
компанией
JetBrains.
для
среда
программирования C#, VB.NET и F#. Проект анонсирован в январе 2015
года. В его основе лежит другой продукт JetBrains — ReSharper. Среда
поддерживает платформы .NET Framework, .NET и Mono. Работает на
операционных системах Windows, macOS, Linux.
2.4. Описание программных модулей
ChunkGenerator
основной
программный
модуль
процедурной
генерации, объединяет все элементы процедурной генерации и производит
её.
GenerationData – класс, содержащий в себе параметры генерации, такие
как параметры шума, высоту начала генерации поверхности и конца
генерации поверхности.
MainNoiseData – класс, содержащий в себе параметры шума, такие как
тип шума (OpenSimplex2, OpenSimplex2S, Cellular, Perlin, ValueCubic, Value),
частота, минимальное значение, параметры фрактала. При помощи данного
класса, можно тонко регулировать поведение и внешний вид получаемой
генерации.
Генерация мира происходит поэтапно: сперва генерация поверхности
(также нижнего слоя), генерация пещер, генерация деревьев, генерация руд,
генерация ограничителей.
Для каждого этапа генерации необходимы параметры шума, за который
отвечает класс MainNoiseData, в котором и есть данные параметры. То есть
для каждого этапа генерации создается соответствующий класс данных
шума.
GenTerrain – модуль генерации поверхности и нижнего слоя.
GenOres – модуль генерации полезных ископаемых в нижнем слое.
2.4.1. Математический алгоритм процедурной генерации
При
разработке
программного
модуля
процедурной
генерации
использовался математический алгоритм генерации процедерной текстуры
псевдослучайным методом, называемый шумом Перлина. Шум Перлина
создан Кеном Перлином в 1983 году и впоследствии был назван в честь
своего создателя. Удобство в использовании данного алгоритма состоит в
том, что он генерирует бесшовную и однородную по структуре текстуру,
переходы по которой сглаженные и близки к естественным, благодаря
которому можно сгенерировать красивый ландшафт. При помощи данного
алгоритма создаются такие визуальные эффекты, как дым, облака, туман,
огонь и т. д. Он также очень часто используется как простая текстура,
покрывающая трехмерную модель.
Рисунок 1 Текстура, сгенерированная при помощи алгоритма Шума Перлина
Потому как разработать свою программу реализующая математический
алгоритм процедурной генерации является весьма сложной задачей,
используется готовая библиотека для работы с Шумом Перлина. Fast Noise
Lite – библиотека для работы с шумом Перлина, с открытым исходным
кодом, также помимо шума Перлина в этой библиотеке реализованы и другие
методы процедурной генерации, которые можно использовать в разных
целях. Например, Cellular можно использовать для точечной генерации.
Для понимания работы алгоритма, рассмотрим принцип его работы.
Для создания шума необходимы две вещи, это функция шума и функция
интерполяции.
Функция шума это просто напросто генератор случайных чисел. На
один аргумент, выдается всегда одно и тоже число. На данном графике
показан пример функции шума, где случайное значение от 0 до 1
присваивается каждой точке на оси X.
Рисунок 2 Функция шума
Используя
функцию
интерполяции,
мы
можем
преобразовать
исходную функцию шума в непрерывную, сглаженную функцию.
Рисунок 3 Интерполяция функции шума
Прежде чем начать объяснение алгоритма, необходимо внести
определения объясняющие элементы работы алгоритма. Это такие понятия
как частота и амплитуда. Частоту находим как обратное от длины волны:
1
длина волны
.
Рисунок 4 График функции sin(x)
Теперь, если взять много таких гладких функций, с различной частотой
и амплитудой, и объединить в одну, можно получить шум. Это и есть
функция шума Перлина.
Рисунок 5 Множество функий с разным частотами и амплитудами
Рисунок 6 Совмещение всех функций (Шум Перлина)
Количество этих функций называется октававми, шум, составленный
из большего числа октав будет иметь более неоднородную структуру.
Изменяя следующие параметры Шума Перлина, можно достичь
уникальных результатов в генерации ландшафта:
 Лакунарность (lacunarity) - контролирует изменение частоты.
 Постоянство (persistence) - контролирует изменение амплитуды.
 Октавы (octaves) – число функций шума
2.5. Инструкция пользователю
Для получения более реалистичной и более интересного ландшафта,
данный модуль имеет множество элементов графического взаимодействия,
таким образом пользователь может эффективно и наглядно редактировать
параметры процедурной генерации, получая результат в виде удобного
графического представления его элементов.
Для класса MainNoiseData реализовано следующая графическая
оболочка (рис. 1), при редактировании параметров шума, изображение внизу,
изменяется в зависимости данных параметров. В данном случае показан
MainNoiseData для генератора пещер.
Рисунок 7 Класс MainNoiseData для генератора пещер
Рисунок 8 MainNoiseData для генератора руд
2.5.1. Взаимодействие с API программного модуля
Прежде всего для того, чтобы воспользоваться возможностями
процедурной
генерации
ChunkGenerator,
и
необходимо
вызвать
метод
взаимодействовать
GenerateChunk,
с
классом
однако
можно
самостоятельно делать то, что, реализует метод GenerateChunk. Рассмотрим
пример использования процедурной генерации при загрузке чанков.
Метод LoadChunk, загружает соответствующий чанк, переданный в
параметры данного метода. При загрузке чанка, необходимо проверить
является ли данный игрок хостом (сервер-клиент), если да, то загружаем
данные из сохранения, если нет, используем данные, переданные в
аргументы метода. Если информация о чанке, пуста, это происходит, когда
чанк еще не загружен, либо данные сохранения были удалены, именно в этот
момент и происходит генерация чанка, в противном случае загружаем чанк.
Перед вызовом метода генерации чанка, программист, работающий с
данным программным модулем, должен приготовить необходимые для
работы данные (такие как параметры генерации, текущий чанк и то прочее),
и только потом происходит вызов метода генерации чанка.
Потому как
движок
Unity не
в
полной
мере
поддерживает
многопоточность, необходимо вызывать такие функции лишь в основном
потоке,
как
например
в
методе
LoadChunk
есть
строка:
“_chunkVisibleManager.Clear(chunkPool.Get(chunkEntity));”, она заносится в
список функций, которые вызываются позже всех в основном потоке. В
методе LoadChunk объявляется переменная listActionsOnMainThread, которая
в свою очередь и содержит те самые функции, вызываемые в основном
потоке, и также LoadChunk возвращает данный список.
В свою очередь класс ChunkGenerator, имеет функции, вызываемые
лишь в основном потоке Unity, поэтому он также заносится в данный список.
В данном проекте используется фреймворк ECS, компонентносущностная система, в которой сущностями являются чанки, а при работе с
данным фреймворком, есть особенности при передачи таких сущностей,
поэтому их запаковывают в EcsPackedEntityWithWorld.
private Action LoadChunk(EcsPackedEntityWithWorld chunkPacked,
LoadChunkEventComponent load, ChunkData chunkDataFromSave)
{
var listActionsOnMainThread = new Action(() => { });
if (!chunkPacked.Unpack(out var chunkWorld, out var id))
return listActionsOnMainThread;
var chunkPool = chunkWorld.GetPool<ChunkComponent>();
ref var chunk = ref chunkPool.Get(id);
if (chunk.ChunkGlobalPos == load.ChunkGlobalPos && load.Inclusion)
return listActionsOnMainThread;
listActionsOnMainThread += () =>
{
if (!chunkPacked.Unpack(out _, out var chunkEntity))
_chunkVisibleManager.Clear(chunkPool.Get(chunkEntity));
};
chunk.ClearBlocks();
chunk.ChunkGlobalPos = load.ChunkGlobalPos;
var chunkDataInfo
load.ChunkData;
=
!load.IsClient
?
chunkDataFromSave
:
var genData = _dataBaseManager.GetMainData();
_chunkGenerator.GenerateBackBack(chunkPacked,
listActionsOnMainThread);
genData,ref
if (chunkDataInfo == null)
{
Debug.Log("ChunkData is null, generating chunk");
listActionsOnMainThread += _chunkGenerator.GenerateChunk(
load.BlocksCounter,
chunk,
chunkPacked,
genData,
seed: _worldManager.WorldDataCurrent.Seed);
}
else
LoadChunkFromSave(chunk);
return listActionsOnMainThread;
void LoadChunkFromSave(ChunkComponent chunkLocal)
{
var blocks = chunkDataInfo.Blocks;
load.BlocksCounter.MaxCount = blocks.Count;
foreach (var chunkBlock in blocks)
{
var
blockData
_dataBaseManager.GetBlock(chunkBlock.blockName);
var block = chunkBlock;
=
listActionsOnMainThread += () =>
{
//Debug.Log("SetBlockEvent");
_sharedData.EventsBus.NewEvent<BlockSetEvent>() = new
BlockSetEvent(chunkPacked, blockData,
block.pos,
block.memories,
chunkLocal.ChunkPosToGlobalPosVector2Int(block.pos),
Vector2.one,
block.blockLayer,
false,
onSuccessBlockEvent: () => { load.BlocksCounter.Counter++;
},
onFailBlockEvent: () =>
{
load.BlocksCounter.MaxCount--;
Debug.Log("fail");
});
};
}
}
}
2.5.2. Использование компонентов модуля
В следущем классе не происходит обращение к основному модулю
ChunkGenerator, иначе, происходит использование отдельных классов
программного модуля, отвечающих за отдельные этапы генерации чанков.
GenerateTester используется для тестирования генерации, в нем можно не
входя в игру, тестировать параметры генерации мира, и видеть графическое
представление генерации мира, которые как раз таки представлены в
контрольном примере.
Суть генератора состоит в том, что он создает n-чанков, и генерирует
массив блоков, из которых состоят данные чанки, затем беря средний цвет
текстуры данных блоков строит текстуру, которая и отображается в редкторе
Unity.
GenerateTester затрагивает не все этапы генерации чанка, а лишь
основные:
var listGens = new List<GenBase>
{
new GenTerrain(genData.stoneNoise, genData.dirtNoise),
new GenOres(genData.oresNoise),
new GenCaves(genData.caveNoise),
new GenTrees(genData.treesNoise),
new GenLimiters()
};
Следующая часть кода строит текстуру по сгенерированному массиву
блоков.
for (var i = 0; i < chunkW; i++)
for (var j = 0; j < chunkH; j++)
{
if (chunkDataGen.ChunkFront[i, j] != null)
Set(ColorS.GetAverageColorForSprite(chunkDataGen.ChunkFront[i,
j].GetSprite()));
void Set(Color color)
{
texture.SetPixel(pos * chunkW + i, j, color);
}
}
[Bind]
public class GenerateTester : MonoBehaviour
{
[SerializeField] private short startPosX;
[SerializeField] [Range(1, 16)] private int chunksWidth;
[SerializeField] private RawImage mapWorld;
[SerializeField] private StandartGenerationData baseGenData;
public bool randomSeed;
public int seed;
public GenerationData genData;
public bool generateOnValidate;
private void OnValidate()
{
#if UNITY_EDITOR
if (generateOnValidate)
Generate();
#endif
}
[ButtonMethod]
public void Generate()
{
if (randomSeed)
seed = Random.Range(0, 100000000);
mapWorld.rectTransform.sizeDelta = new Vector2(chunksWidth *
16, ChunksConstants.ChunkHeight);
var
texture
=
new
Texture2D(chunksWidth
ChunksConstants.ChunkHeight)
{
filterMode = FilterMode.Point,
anisoLevel = 0
};
*
16,
var sizeChunk = ChunksConstants.ChunkSize;
var chunkW = sizeChunk.x;
var chunkH = sizeChunk.y;
for (var i = 0; i < chunksWidth * 16; i++)
for (var j = 0; j < ChunksConstants.ChunkHeight; j++)
texture.SetPixel(i, j, Color.black);
var sharedData = new SharedData()
{
EventsBus = new EventsBus()
};
for (short pos = 0; pos < chunksWidth; pos++)
{
var ecsWorld = new EcsWorld();
var chunkEntity = ecsWorld.NewEntity();
var verChunks = new int[16];
for (byte j = 0; j < 16; j++)
verChunks[j] = ecsWorld.NewEntity();
var chunkPool = ecsWorld.GetPool<ChunkComponent>();
var
verChunkPool
ecsWorld.GetPool<VerticalChunkComponent>();
ref var chunk = ref chunkPool.Add(chunkEntity);
chunk.Set(ecsWorld.PackEntityWithWorld(chunkEntity),
verChunks.Select(s=>ecsWorld.PackEntityWithWorld(s)).ToArray());
chunk.ChunkGlobalPos = (short)(startPosX + pos);
for (byte j = 0; j < 16; j++)
=
{
var entity = verChunks[j];
var min = new Vector2Int(0, j * 15);
var max = min + new Vector2Int(16, 16);
ref var verChunk = ref verChunkPool.Add(entity);
verChunk.Set(new Leopotam.Group.Math.Bounds2i(min, max),
j,
ecsWorld.PackEntityWithWorld(entity),
ecsWorld.PackEntityWithWorld(chunkEntity));
}
var chunkDataGen = new ChunkDataGeneration
{
ChunkFront = new BlockData[chunkW, chunkH],
ChunkBack = new BlockData[chunkW, chunkH]
};
var listGens = new List<GenBase>
{
new GenTerrain(genData.stoneNoise, genData.dirtNoise),
new GenOres(genData.oresNoise),
new GenCaves(genData.caveNoise),
new GenTrees(genData.treesNoise),
new GenLimiters()
};
var listActionOnMainThread = new List<Action>();
foreach (var listGen in listGens)
listGen.Init(sizeChunk,
genData,
new BlocksCounter(),
listActionOnMainThread,
ecsWorld.PackEntityWithWorld(chunkEntity),
ecsWorld,
chunkDataGen,
baseGenData,
seed,
pos, sharedData);
foreach (var gen in listGens)
gen.OnGeneration();
for (var i = 0; i < chunkW; i++)
for (var j = 0; j < chunkH; j++)
{
if (chunkDataGen.ChunkFront[i, j] != null)
Set(ColorS.GetAverageColorForSprite(chunkDataGen.ChunkFront[i,
j].GetSprite()));
void Set(Color color)
{
texture.SetPixel(pos * chunkW + i, j, color);
}
}
}
sharedData.EventsBus.Destroy();
texture.Apply();
mapWorld.texture = texture;
}
2.6. Контрольный пример
В данный момент в проекте используется параметры процедурной
генерации,
которые
генерируют
Рисунок 9 Пример генерации мира
мир
следующим
образом:
Рисунок 10 Пример генерации мира из окна игры
Рисунок 11 Скриншот из самой игры
Рисунок 12 Общая картина генерации мира
3. Заключение
При помощи алгоритмов процедурной генерации можно создать
интересный и реалистичный ландшафт, комбинируя различные методы
генерации шума.
4. Приложение
4.1. Исходный код программы
5. ChunkGenerator
using System;
using System.Collections.Generic;
using Game.Blocks.BlockData;
using Game.Chunks.ChunksECS.Components;
using Game.Chunks.ChunksECS.Components.Events;
using Game.Chunks.Generation.Gen;
using Game.Chunks.Other;
using Game.ECS;
using Leopotam.EcsLite;
using SurvDI.Core.Common;
using UnityEngine;
namespace Game.Chunks.Generation
{
public class ChunkDataGeneration
{
public BlockData[,] ChunkBack;
public BlockData[,] ChunkFront;
}
public class ChunkGenerator
{
[Inject] private StandartGenerationData _baseGenData;
[Inject] private SharedData _sharedData;
public Action GenerateChunk(BlocksCounter blocksCounter,
ChunkComponent chunk, EcsPackedEntityWithWorld chunkPacked,
GenerationData genData, Vector2Int sizeChunk = default, int seed =
0)
{
if (!chunkPacked.Unpack(out var world, out var chunkEntity))
{
Debug.LogWarning("ChunkPack fail");
return () => {};
}
//Debug.Log($"Seed {seed}");
var listActionOnMainThread = new List<Action>();
if (sizeChunk == default)
sizeChunk = ChunksConstants.ChunkSize;
var chunkW = sizeChunk.x;
var chunkH = sizeChunk.y;
var chunkDataGen = new ChunkDataGeneration
{
ChunkFront = new BlockData[chunkW, chunkH],
ChunkBack = new BlockData[chunkW, chunkH]
};
var listGens = new List<GenBase>
{
new GenTerrain(genData.stoneNoise, genData.dirtNoise),
new CopyFrontToBack(),
new GenCaves(genData.caveNoise),
new GenTrees(genData.treesNoise),
new GenOres(genData.oresNoise),
new GenLimiters()
};
foreach (var listGen in listGens)
listGen.Init(sizeChunk,
genData,
blocksCounter,
listActionOnMainThread,
chunkPacked,
world,
chunkDataGen,
_baseGenData,
seed,
chunk.ChunkGlobalPos,
_sharedData);
foreach (var gen in listGens)
gen.OnGeneration();
//Debug.Log($"GeneratingChunk:
{chunk.ChunkGlobalPos}");
#region BuildChunk
var setBlockPos = Vector2Int.zero;
for (var i = 0; i < chunkW; i++)
for (var j = 0; j < chunkH; j++)
{
setBlockPos.x = i;
setBlockPos.y = j;
var block = chunkDataGen.ChunkFront[i, j];
var blockBack = chunkDataGen.ChunkBack[i, j];
if (block != null)
BlockSet(block, setBlockPos);
if (blockBack != null)
BlockSet(blockBack, setBlockPos, BlockLayer.Back);
}
void BlockSet(BlockData blockData, Vector2Int pos,
BlockLayer blockLayer = BlockLayer.Front)
{
blocksCounter.MaxCount++;
listActionOnMainThread.Add(() =>
{
//Debug.Log("SetBlockInGen");
_sharedData.EventsBus.NewEvent<BlockSetEvent>() =
new BlockSetEvent(chunkPacked, blockData, pos, null,
chunk.ChunkPosToGlobalPosVector2Int(setBlockPos),
Vector2.one,
blockLayer,
onSuccessBlockEvent:
()
=>
{
blocksCounter.Counter++; },
onFailBlockEvent: () => { blocksCounter.MaxCount--;
});
});
}
#endregion
var action = new Action(() => { });
foreach (var action1 in listActionOnMainThread)
action += action1;
return action;
}
public void GenerateBackBack(EcsPackedEntityWithWorld
chunkEntity,
GenerationData
genData,
ref
Action
actionsOnMainThread)
{
if (!chunkEntity.Unpack(out var world, out var id))
{
Debug.LogWarning("Error, unpacking chunk");
return;
}
var chunkPool = world.GetPool<ChunkComponent>();
var size = ChunksConstants.ChunkSize;
var stone = _baseGenData.stone;
var chunk = chunkPool.Get(id);
//Debug.Log("GeneratingBackBack");
//Генерация фонового камня
var generationStoneBackBack = genData.minSurfaceHeight;
//Высота от вершины мира до уровня камней
for (var i = 0; i < size.x; i++)
for (var j = 0; j < generationStoneBackBack; j++)
{
var pos = new Vector2Int(i, j);
actionsOnMainThread += () =>
{
//Debug.Log("SetBackBack");
_sharedData.EventsBus.NewEvent<BlockSetEvent>() =
new BlockSetEvent(chunkEntity,
stone,
pos,
null,
chunk.ChunkPosToGlobalPosVector2Int(pos),
Vector2.one,
BlockLayer.BackBack);
};
}
}
private static void InitChunkSize(ref Vector2Int size)
{
if (size == default)
size = ChunksConstants.ChunkSize;
}
}
}
6. CopyFrontToBack
namespace Game.Chunks.Generation.Gen
{
public class CopyFrontToBack : GenBase
{
public override void OnGeneration()
{
for (var i = 0; i < ChunkW; i++)
for (var j = 0; j < ChunkH; j++)
ChunkDataGeneration.ChunkBack[i,
ChunkDataGeneration.ChunkFront[i, j];
}
}
}
j]
=
7. GenBase
namespace Game.Chunks.Generation.Gen
{
public abstract class GenBase
{
private Vector2Int SizeChunk { get; set; }
private BlocksCounter BlocksCounter { get; set; }
private List<Action> ListActionsOnMainThread { get; set; }
private EcsPackedEntityWithWorld ChunkEntity { get; set; }
private EcsWorld EcsWorld { get; set; }
protected ChunkDataGeneration ChunkDataGeneration { get;
set; }
protected int ChunkPos { get; set; }
protected Noise Noise { get; private set; }
protected int Seed { get; private set; }
protected ChunkComponent Chunk
{
get
{
if (ChunkEntity.Unpack(out var world, out var entity))
return
EcsWorld.GetPool<ChunkComponent>().Get(entity);
return default;
}
}
protected int ChunkW => SizeChunk.x;
protected int ChunkH => SizeChunk.y;
protected GenerationData GenData { get; private set; }
protected StandartGenerationData BaseGenData { get; private
set; }
protected SharedData SharedData { get; private set; }
public void Init(Vector2Int size,
GenerationData generationData,
BlocksCounter blocksCounter,
List<Action> actions,
EcsPackedEntityWithWorld chunkEntity,
EcsWorld ecsWorld,
ChunkDataGeneration chunkDataGeneration,
StandartGenerationData baseGenData,
int seed,
int chunkPos,
SharedData sharedData)
{
SizeChunk = size;
GenData = generationData;
BlocksCounter = blocksCounter;
ListActionsOnMainThread = actions;
ChunkEntity = chunkEntity;
EcsWorld = ecsWorld;
ChunkDataGeneration = chunkDataGeneration;
Seed = seed;
ChunkPos = chunkPos;
BaseGenData = baseGenData;
SharedData = sharedData;
if (Noise != null)
{
Noise.XOffset = ChunkPos * ChunkW;
Noise.SetSeed(seed);
}
OnInit();
}
public abstract void OnGeneration();
protected virtual void OnInit() { }
protected void SetFront(int x, int y, BlockData blockData)
{
ChunkDataGeneration.ChunkFront[x, y] = blockData;
}
protected void SetBlock(Vector2Int pos, BlockData blockData)
{
BlocksCounter.MaxCount++;
ListActionsOnMainThread.Add(() =>
{
SharedData.EventsBus.NewEvent<BlockSetEvent>() = new
BlockSetEvent(ChunkEntity,
blockData,
pos,
null,
Chunk.ChunkPosToGlobalPosVector2Int(pos),
Vector2.one,
BlockLayer.Front,
false,
checkCanSet: true,
onSuccessBlockEvent:
()
=>
{
BlocksCounter.Counter++; },
onFailBlockEvent: () => { BlocksCounter.MaxCount--;
});
});
}
protected void UpdateMapHeight(BlockLayer blockLayer =
BlockLayer.Front)
{
if (ChunkEntity.Unpack(out var www, out var i))
{
var chunkPool = EcsWorld.GetPool<ChunkComponent>();
ref var chunk = ref chunkPool.Get(i);
chunk.GenerateMapHeight(blockLayer
==
BlockLayer.Front
?
ChunkDataGeneration.ChunkFront
:
ChunkDataGeneration.ChunkBack);
}
}
protected void InitNoise(MainNoiseData noiseData)
{
Noise = new Noise();
NoiseInit(Noise, noiseData);
}
protected
noiseData)
{
Noise
NoiseInit(Noise
noise,
MainNoiseData
return noise.Init(Seed, noiseData, ChunkW * ChunkPos);
}
}
}
8. GenCaves
using Game.Chunks.Generation.NoiseInfo;
namespace Game.Chunks.Generation.Gen
{
public class GenCaves : GenBase
{
public GenCaves(MainNoiseData noiseData)
{
InitNoise(noiseData);
}
public override void OnGeneration()
{
for (var x = 0; x < ChunkW; x++)
for (var y = 0; y < GenData.minSurfaceHeight; y++)
if (Noise.GetNoiseBool(x,y))
SetFront(x, y, null);
}
}
}
9. GenLimiters
namespace Game.Chunks.Generation.Gen
{
public class GenLimiters : GenBase
{
public override void OnGeneration()
{
var limiter = BaseGenData.limiter;
for (var i = 0; i < ChunkW; i++)
{
SetFront(i, 0, limiter);
SetFront(i, ChunkH - 1, limiter);
}
}
}
}
10.GenOres
using Game.Blocks.BlockData;
using Game.Chunks.BlocksECS.Components.Blocks;
using Game.Chunks.Generation.NoiseInfo;
namespace Game.Chunks.Generation.Gen
{
public class GenOres : GenBase
{
public GenOres(MainNoiseData noiseData)
{
InitNoise(noiseData);
}
public override void OnGeneration()
{
//Генерация руд
foreach (var blockData in BaseGenData.ores)
foreach (var data in blockData.Components)
if (data is OreBlock oreData)
Generate(blockData, oreData.startGenerationOre);
void Generate(BlockData blockData, int endHeight)
{
Noise.GetNoise.mSeed += 1;
for (var x = 0; x < ChunkW; x++)
for (var y = 0; y < endHeight; y++)
if (Noise.GetNoiseBool(x, y))
SetFront(x, y, blockData);
}
}
}
}
11.GenTerrain
using Game.Chunks.Generation.NoiseInfo;
using UnityEngine;
namespace Game.Chunks.Generation.Gen
{
public class GenTerrain : GenBase
{
private readonly MainNoiseData _dirtNoiseData;
private readonly MainNoiseData _stoneNoiseData;
private Noise _noiseDirt;
private Noise _noiseStone;
public GenTerrain(MainNoiseData stoneNoise, MainNoiseData
dirtNoise)
{
_stoneNoiseData = stoneNoise;
_dirtNoiseData = dirtNoise;
}
protected override void OnInit()
{
_noiseDirt = NoiseInit(new Noise(), _dirtNoiseData);
_noiseStone = NoiseInit(new Noise(), _stoneNoiseData);
}
public override void OnGeneration()
{
var
height
=
GenData.maxSurfaceHeight
GenData.minSurfaceHeight;
for (var x = 0; x < ChunkW; x++)
{
var noiseDirt = _noiseDirt.GetNoiseRaw(x);
var noiseStone = _noiseStone.GetNoiseRaw(x);
-
if (noiseDirt < 0f)
noiseDirt = Mathf.Abs(noiseDirt);
if (noiseStone < 0f)
noiseStone = Mathf.Abs(noiseStone);
var min = GenData.minSurfaceHeight;
var max = min;
min -= (int)(noiseStone * height);
max += (int)(noiseDirt * height);
for (var y = 0; y < min; y++)
SetFront(x, Mathf.Clamp(y,
BaseGenData.stone);
for (var y = min; y < max; y++)
SetFront(x, Mathf.Clamp(y,
BaseGenData.mainDirt);
}
}
}
}
12.GenTrees
0,
ChunkH
-
1),
0,
ChunkH
-
1),
using System.Collections.Generic;
using Game.Blocks;
using Game.Chunks.Generation.NoiseInfo;
using UnityEngine;
namespace Game.Chunks.Generation.Gen
{
public class GenTrees : GenBase
{
private readonly Noise _trees;
private readonly Noise _treesHeights;
public GenTrees(MainNoiseData treesNoise)
{
_trees = NoiseInit(new Noise(), treesNoise);
_treesHeights = NoiseInit(new Noise(), treesNoise);
InitNoise(treesNoise);
}
public override void OnGeneration()
{
UpdateMapHeight();
var mapHeight = Chunk.MapHeight;//Карта высот
var listCanCreate = new List<(byte x, byte y)>();
for (var x = 0; x < mapHeight.Length; x++)
listCanCreate.Add(((byte) x, mapHeight[x]));
for (var i = 0; i < listCanCreate.Count; i ++)
{
var (x, y) = listCanCreate[i];
//SetBlock(new Vector2Int(x, y+1), BaseGenData.grass);
var value = _trees.GetNoiseBool(i);
if (value)
CreateTree(x, y, GenData.treesData);
else
if (y + 1 < ChunkH && x < ChunkW)
SetFront(x,y+1, BaseGenData.grass);
}
void CreateTree(int x, int y, TreeData tree)
{
var startPos = new Vector2Int(x, y);
var posLog = startPos;
var
treeHeidth
(int)_treesHeights.GetNoiseClamped(x,0,4,6);
for (var i = 0; i < treeHeidth; i++)
{
posLog.y++;
SetBlockLog(posLog, tree);
}
treeHeidth += y;
SetBlockLeaves(x - 1, treeHeidth, tree);
SetBlockLeaves(x + 1, treeHeidth, tree);
SetBlockLeaves(x, treeHeidth + 1, tree);
SetBlockLeaves(x - 1, treeHeidth + 1, tree);
SetBlockLeaves(x + 1, treeHeidth + 1, tree);
SetBlockLeaves(x - 2, treeHeidth + 1, tree);
SetBlockLeaves(x + 2, treeHeidth + 1, tree);
SetBlockLeaves(x, treeHeidth + 2, tree);
SetBlockLeaves(x - 1, treeHeidth + 2, tree);
SetBlockLeaves(x + 1, treeHeidth + 2, tree);
SetBlockLeaves(x - 2, treeHeidth + 2, tree);
SetBlockLeaves(x + 2, treeHeidth + 2, tree);
SetBlockLeaves(x, treeHeidth + 3, tree);
SetBlockLeaves(x - 1, treeHeidth + 3, tree);
SetBlockLeaves(x + 1, treeHeidth + 3, tree);
}
void SetBlockLog(Vector2Int pos, TreeData tree)
{
SetBlock(pos, tree.log);
}
void SetBlockLeaves(int x, int y, TreeData tree)
{
SetBlock(new Vector2Int(x, y), tree.leaves);
}
}
}
}
13.BiomBase
=
using System;
using Game.Blocks;
using Game.Blocks.BlockData;
namespace Game.Chunks.Generation.Biomes
{
[Serializable]
public abstract class BiomBase
{
public string biomeName;
public TreeData biomeTree;
public BlockData dirt;
}
}
14.GenerationData
using System;
using Game.Blocks;
using Game.Chunks.Generation.NoiseInfo;
using UnityEngine;
namespace Game.Chunks.Generation
{
[Serializable]
[CreateAssetMenu(fileName = "GenData",
"Data/GenData", order = 0)]
public class GenerationData : ScriptableObject
{
public TreeData treesData;
menuName
=
[Header("World Size Settings")] public int maxSurfaceHeight =
48;
public int minSurfaceHeight = 16;
public MainNoiseData caveNoise;
public MainNoiseData oresNoise;
public MainNoiseData stoneNoise;
public MainNoiseData dirtNoise;
public MainNoiseData treesNoise;
}
}
15.Noise
using Game.Chunks.Generation.NoiseInfo;
using Plugins.FastNoise;
using UnityEngine;
namespace Game.Chunks.Generation
{
public class Noise
{
private FastNoiseLite _noise;
private MainNoiseData _noiseData;
public int XOffset { get; set; }
public FastNoiseLite GetNoise => _noise;
public Noise Init(int seed, MainNoiseData noiseData, int
xOffset)
{
_noise = noiseData.GetNoise(seed);
_noiseData = noiseData;
XOffset = xOffset;
return this;
}
public void SetSeed(int seed)
{
_noise.SetSeed(seed);
}
public float GetNoiseRaw(int x, int y = 0)
{
return _noise.GetNoise(x + XOffset, y);
}
public float GetNoiseApprox(int x, int y = 0)
{
var v = GetNoiseRaw(x, y);
if (v > 0)
v += 1;
else if (v < 0)
v = Mathf.Abs(v);
v /= 2;
return v;
}
public float GetNoiseClamped(int x, int y, int startHeight, int
endHeight)
{
var range = endHeight - startHeight;
var value = GetNoiseRaw(x);
return Mathf.Clamp(startHeight + range * value, startHeight, y
> 0 ? y : endHeight);
}
public bool GetNoiseBool(int x, int y = 0)
{
if (_noiseData.invert)
return GetNoiseApprox(x, y) < _noiseData.thresold;
return GetNoiseApprox(x, y) > _noiseData.thresold;
}
}
}
16.StandartGenerationData
using Game.Blocks.BlockData;
using UnityEngine;
namespace Game.Chunks.Generation
{
[CreateAssetMenu(fileName = "GenData", menuName
"Data/StandartGenData", order = 0)]
public class StandartGenerationData : ScriptableObject
{
public BlockData mainDirt;
public BlockData stone;
public BlockData[] ores;
public BlockData grass;
public BlockData limiter;
}
}
17.CellularData
using System;
using Plugins.FastNoise;
using UnityEngine;
namespace Game.Chunks.Generation.NoiseInfo
{
[Serializable]
public class CellularData
{
[Header("Cellular"), Range(0f,1f), Delayed]
cellularJitter;
public
=
float
public
FastNoiseLite.CellularDistanceFunction
cellularDistanceFunction
=
FastNoiseLite.CellularDistanceFunction.EuclideanSq;
public FastNoiseLite.CellularReturnType cellularReturnType =
FastNoiseLite.CellularReturnType.Distance;
}
}
18.MainNoiseData
using InspectorUtils;
using Plugins.FastNoise;
using UnityEngine;
namespace Game.Chunks.Generation.NoiseInfo
{
[CreateAssetMenu(fileName = "MainNoiseData", menuName =
"Data/Noise/Noise", order = 0)]
public class MainNoiseData : ScriptableObject
{
public FastNoiseLite.NoiseType noiseType;
public FastNoiseLite.RotationType3D rotationType3D =
FastNoiseLite.RotationType3D.None;
[Delayed] public float frequency = 0.2f;
[Range(0f,1f), Delayed] public float thresold;
public bool invert;
public bool isHeight;
public int height;
public bool toApprox = true;
public float scalePreview = 1f;
public NoiseFractalData fractalData;
[ShowIf(nameof(IsCellular))] public CellularData cellularData;
public bool isBool;
private
bool
IsCellular
FastNoiseLite.NoiseType.Cellular;
=>
public FastNoiseLite GetNoise(int seed)
{
var noise = new FastNoiseLite();
noise.SetNoiseType(noiseType);
noise.SetFrequency(frequency);
noiseType
==
noise.SetRotationType3D(rotationType3D);
noise.SetFractalPingPongStrength(fractalData.pingPongStrength);
noise.SetFractalWeightedStrength(fractalData.weightedStrength);
noise.SetFractalType(fractalData.fractalType);
noise.SetFractalOctaves(fractalData.octaves);
noise.SetFractalLacunarity(fractalData.lacunarity);
noise.SetFractalGain(fractalData.gain);
if
(cellularData
!=
null
&&
noiseType
FastNoiseLite.NoiseType.Cellular)
{
noise.SetCellularJitter(cellularData.cellularJitter);
==
noise.SetCellularDistanceFunction(cellularData.cellularDistanceFunct
ion);
noise.SetCellularReturnType(cellularData.cellularReturnType);
}
noise.SetSeed(seed);
return noise;
}
}
}
19.NoiseFractalData
using System;
using Plugins.FastNoise;
using UnityEngine;
namespace Game.Chunks.Generation.NoiseInfo
{
[Serializable]
public class NoiseFractalData
{
public FastNoiseLite.FractalType fractalType;
[Range(1, 8), Delayed] public int octaves = 2;
[Range(0.5f, 8f), Delayed] public float lacunarity = 2f;
[Range(0.005f, 4f), Delayed] public float gain = 0.2f;
[Range(0f, 1f), Delayed] public float weightedStrength;
[Range(0f, 1f), Delayed] public float pingPongStrength;
}
}
Download