Я пишу клон майнкрафта на юнити. И я хочу добавить генерацию пещер. Проблема в том что я не знаю как это мне сделать. Ну идея такова: после генерации ландшафта мы с помощью 3д шума перлина вырезаем пещеры. Только я не знаю как это превратить в код.
Скрипт TerrainGenerator:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = System.Random;
[CreateAssetMenu(menuName = "Terrain Generator")]
public class TerrainGenerator : ScriptableObject
{
public float BaseHeight = 8;
public int g = 1;
public float b = 0.5f;
public int e = 1;
public NoiseOctaveSettings[] Octaves;
public NoiseOctaveSettings Domainwarp;
public BlockType grass = BlockType.Grass;
public BlockType bedrok = BlockType.Bedrok;
public BlockType stone = BlockType.Stone;
public Structures Structures;
[Serializable]
public class NoiseOctaveSettings
{
public FastNoiseLite.NoiseType NoiseType;
public float Frequency = 0.2f;
public float Amplitude = 1;
}
private FastNoiseLite[] octaveNoises;
private FastNoiseLite warpNoise;
public void Init()
{
octaveNoises = new FastNoiseLite[Octaves.Length];
for (int i = 0; i < Octaves.Length; i++)
{
octaveNoises[i] = new FastNoiseLite();
octaveNoises[i].SetNoiseType(Octaves[i].NoiseType);
octaveNoises[i].SetFrequency(Octaves[i].Frequency);
}
warpNoise = new FastNoiseLite();
warpNoise.SetNoiseType(Domainwarp.NoiseType);
warpNoise.SetFrequency(Domainwarp.Frequency);
warpNoise.SetDomainWarpAmp(Domainwarp.Amplitude);
}
public BlockType[,,] GenerateTerrain(float xOffset, float zOffset)
{
var result = new BlockType[ChunkRendering.ChunkWidht, ChunkRendering.ChunkHeight, ChunkRendering.ChunkWidht];
for (int x = 0; x < ChunkRendering.ChunkWidht; x++)
{
for (int z = 0; z < ChunkRendering.ChunkWidht; z++)
{
//float height = Mathf.PerlinNoise((x/4+xOffset) * .2f, (z/4+zOffset) * .2f) * 25 + 10;
float worldX = x * ChunkRendering.BlockScale+xOffset;
float worlY = z * ChunkRendering.BlockScale+zOffset;
float height = GetHeight(worldX, worlY);
float GrassLayerHeight = g + octaveNoises[0].GetNoise(worldX,worlY)*.2f;
float EarthLayerHeight = e + octaveNoises[0].GetNoise(worldX,worlY)*.2f;
float BedrokLayerHeight = b + octaveNoises[0].GetNoise(worldX,worlY)*.2f;
for (int y = 0; y < height / ChunkRendering.BlockScale; y++)
{
if (height - y * ChunkRendering.BlockScale < GrassLayerHeight)
{
result[x, y, z] = grass;
}
else if (y * ChunkRendering.BlockScale < BedrokLayerHeight)
{
result[x, y, z] = bedrok;
}
else if (y * ChunkRendering.BlockScale < EarthLayerHeight)
{
result[x, y, z] = BlockType.Earth;
}
else
{
result[x, y, z] = stone;
}
}
}
}
return result;
}
private float GetHeight(float x, float y)
{
warpNoise.DomainWarp(ref x, ref y);
float result = BaseHeight;
for (int i = 0; i < Octaves.Length; i++)
{
float noise = octaveNoises[i].GetNoise(x, y);
result += noise * Octaves[i].Amplitude / 2;
}
return result;
}
}
Скрипт GameWorld:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameWorld : MonoBehaviour
{
public Dictionary<Vector2Int, ChunkData> ChunkDatas = new Dictionary<Vector2Int, ChunkData>();
public ChunkRendering ChunkPrefab;
public int ViewRadius = 3;
private Camera mainCamera;
public TerrainGenerator Generator;
private Vector2Int currentPlayerChunk;
public Text blockText;
// Start is called before the first frame update
private void Start()
{
mainCamera = Camera.main;
Generator.Init();
StartCoroutine(Generate(false));
StartCoroutine(Delete(true));
}
private IEnumerator Generate(bool wait)
{
for (int x = currentPlayerChunk.x - ViewRadius; x < currentPlayerChunk.x + ViewRadius; x++)
{
for (int y = currentPlayerChunk.y - ViewRadius; y < currentPlayerChunk.y + ViewRadius; y++)
{
Vector2Int chunkPosition = new Vector2Int(x, y);
if(ChunkDatas.ContainsKey(chunkPosition)) continue;
LoadChunkAt(chunkPosition);
if (wait) yield return new WaitForSecondsRealtime(0.2f);
}
}
}
private IEnumerator Delete(bool wait)
{
for (int x = currentPlayerChunk.x + ViewRadius; x < currentPlayerChunk.x - ViewRadius; x++)
{
for (int y = currentPlayerChunk.y + ViewRadius; y < currentPlayerChunk.y - ViewRadius; y++)
{
Vector2Int chunkPosition = new Vector2Int(x, y);
if (ChunkDatas.ContainsKey(chunkPosition))
{
foreach (var chunkData in ChunkDatas)
{
Destroy(chunkData.Value.Rendering.gameObject);
}
}
if (wait) yield return new WaitForSecondsRealtime(0.2f);
}
}
}
[ContextMenu("Regerate world")]
public void Regenerate()
{
Generator.Init();
foreach (var chunkData in ChunkDatas)
{
Destroy(chunkData.Value.Rendering.gameObject);
}
ChunkDatas.Clear();
StartCoroutine(Generate(false));
StartCoroutine(Delete(true));
}
public void LoadChunkAt(Vector2Int chunkPosition)
{
int x;
int y;
ChunkData chunkData = new ChunkData();
chunkData.ChunkPosition = chunkPosition;
float xPos = chunkPosition.x * ChunkRendering.ChunkWidht * ChunkRendering.BlockScale;
float zPos = chunkPosition.y * ChunkRendering.ChunkWidht * ChunkRendering.BlockScale;
float yPos = chunkPosition.y * ChunkRendering.ChunkWidht * ChunkRendering.BlockScale;
chunkData.Blocks = Generator.GenerateTerrain(xPos, zPos);
ChunkDatas.Add(chunkPosition, chunkData);
var chunk = Instantiate(ChunkPrefab, new Vector3(xPos, 0, zPos), Quaternion.identity, transform);
chunk.ChunkDatas = chunkData;
chunk.ParentWorld = this;
chunkData.Rendering = chunk;
}
private void Update()
{
Vector3Int playerWorldPos = Vector3Int.FloorToInt(mainCamera.transform.position / ChunkRendering.BlockScale);
Vector2Int playerChunk = GetChunkContainingBlock(playerWorldPos);
if (playerChunk != currentPlayerChunk)
{
currentPlayerChunk = playerChunk;
StartCoroutine(Generate(true));
StartCoroutine(Delete(true));
}
GetInput();
}
private void GetInput()
{
if (Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1))
{
bool isDestroying = Input.GetMouseButtonDown(1);
Ray ray = mainCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f));
if (Physics.Raycast(ray, out var hitInfo))
{
Vector3 blockCenter;
if (isDestroying)
{
blockCenter = hitInfo.point - hitInfo.normal * ChunkRendering.BlockScale / 2;
}
else
{
blockCenter = hitInfo.point + hitInfo.normal * ChunkRendering.BlockScale / 2;
}
Vector3Int blockWorldPos = Vector3Int.FloorToInt(blockCenter / ChunkRendering.BlockScale);
Vector2Int chunkPos = GetChunkContainingBlock(blockWorldPos);
if (ChunkDatas.TryGetValue(chunkPos, out ChunkData chunkData))
{
Vector3Int chunkOrigin = new Vector3Int(chunkPos.x, 0, chunkPos.y) * ChunkRendering.ChunkWidht;
if (isDestroying)
{
chunkData.Rendering.DelBlock(blockWorldPos - chunkOrigin);
}
else
{
chunkData.Rendering.SpawnBlock(blockWorldPos - chunkOrigin);
}
}
}
}
}
public Vector2Int GetChunkContainingBlock(Vector3Int blockWorldPos)
{
Vector2Int chunkPosition = new Vector2Int(blockWorldPos.x / ChunkRendering.ChunkWidht, blockWorldPos.z / ChunkRendering.ChunkWidht);
if (blockWorldPos.x < 0) chunkPosition.x--;
if (blockWorldPos.y < 0) chunkPosition.y--;
return chunkPosition;
}
}
PS.
Может Кто-нибудь знает как добавить динамическую выгрузку старых чанков?