@Kairill

Как мне сделать генерацию пещер как в майнкрафт?

Я пишу клон майнкрафта на юнити. И я хочу добавить генерацию пещер. Проблема в том что я не знаю как это мне сделать. Ну идея такова: после генерации ландшафта мы с помощью 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.
Может Кто-нибудь знает как добавить динамическую выгрузку старых чанков?
  • Вопрос задан
  • 135 просмотров
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы