Archived
Private
Public Access
1
0
This repository has been archived on 2026-02-04. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
ProjectBackup/Unity/Minecraft/Assets/Scripts/Terrain/World.cs
2022-11-12 13:10:03 +01:00

345 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Terrain {
public class World : MonoBehaviour {
public static Settings Settings;
[Header("World Generation Values")]
public BiomeAttributes[] biomes;
[Range(0.0f, 1.0f)] public float globalLightLevel;
public Color day;
public Color night;
public Transform player;
public Vector3 spawn;
public Clouds clouds;
public Material material;
public Material transparent;
public BlockType[] blockTypes;
[Header("UI")]
public GameObject creativeInventory;
public GameObject cursorSlot;
private readonly Chunk[,] _chunks = new Chunk[WorldData.WorldSizeInChunks, WorldData.WorldSizeInChunks];
private readonly List<Vector2Int> _activeChunks = new List<Vector2Int>();
private readonly List<Vector2Int> _chunksToCreate = new List<Vector2Int>();
public readonly List<Chunk> ChunksToUpdate = new List<Chunk>();
private readonly Queue<Queue<BlockMod>> _modificationQueues = new Queue<Queue<BlockMod>>();
private bool _applyingModifications;
private bool _menuOpen;
public readonly Queue<Chunk> ChunksToDraw = new Queue<Chunk>();
public Vector2Int lastPlayerChunk;
public readonly int[,] BlockBiomes = new int[WorldData.WorldSizeInBlocks, WorldData.WorldSizeInBlocks];
private Thread _chunkUpdateThread;
public readonly object ChunkUpdateThreadLock = new object();
private void Awake() {
Settings ??= Settings.Load("settings.json");
}
private void Start() {
Random.InitState(WorldData.Seed);
Shader.SetGlobalFloat("minGlobalLightLevel", WorldData.MinLightLevel);
Shader.SetGlobalFloat("maxGlobalLightLevel", WorldData.MaxLightLevel);
spawn = new Vector3(WorldData.WorldCenter, WorldData.ChunkSize.y - 50, WorldData.WorldCenter);
lastPlayerChunk = WorldData.GetChunkPositionFromWorldPosition(spawn);
if (Settings.enableThreading) {
_chunkUpdateThread = new Thread(ThreadedUpdate);
_chunkUpdateThread.Start();
}
GenerateWorld();
CheckViewDistance(true);
SetGlobalLightValue();
}
private void Update() {
CheckViewDistance();
if (_chunksToCreate.Count > 0)
CreateChunk();
if (ChunksToDraw.Count > 0) {
if (ChunksToDraw.Peek().Editable)
ChunksToDraw.Dequeue().CreateMesh();
}
if (!Settings.enableThreading) {
if (!_applyingModifications)
ApplyModifications();
if (ChunksToUpdate.Count > 0)
UpdateChunks();
}
}
public void SetGlobalLightValue() {
Shader.SetGlobalFloat("GlobalLightLevel", globalLightLevel);
Camera.main.backgroundColor = Color.Lerp(night, day, globalLightLevel);
}
public bool MenuOpen {
get => _menuOpen;
set {
_menuOpen = value;
if (_menuOpen) {
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
creativeInventory.SetActive(true);
cursorSlot.SetActive(true);
} else {
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
creativeInventory.SetActive(false);
cursorSlot.SetActive(false);
}
}
}
public bool CheckForBlock(in Vector3 block) {
Vector2Int chunkPos = WorldData.GetChunkPositionFromWorldPosition(block);
Chunk chunk = _chunks[chunkPos.x, chunkPos.y];
if (!IsBlockInWorld(block) || block.y < 0 || block.y > WorldData.ChunkSize.y)
return false;
if (chunk != null && chunk.Editable)
return blockTypes[chunk.GetBlockFromWorldPosition(block).Block].solid;
return blockTypes[GenerateBlock(block)].solid;
}
public BlockState GetBlock(in Vector3 block) {
Vector2Int chunkPos = WorldData.GetChunkPositionFromWorldPosition(block);
Chunk chunk = _chunks[chunkPos.x, chunkPos.y];
if (!IsBlockInWorld(block) || block.y < 0 || block.y > WorldData.ChunkSize.y)
return new BlockState(BlockType.Air);
if (chunk != null && chunk.Editable)
return chunk.GetBlockFromWorldPosition(block);
return new BlockState(GenerateBlock(block));
}
public Chunk GetChunkFromWorldPosition(in Vector3 pos) {
Vector2Int chunkPos = WorldData.GetChunkPositionFromWorldPosition(pos);
return _chunks[chunkPos.x, chunkPos.y];
}
private void GenerateWorld() {
for (int x = (WorldData.WorldSizeInChunks / 2) - Settings.viewDistance;
x < (WorldData.WorldSizeInChunks / 2) + Settings.viewDistance;
x++) {
for (int z = (WorldData.WorldSizeInChunks / 2) - Settings.viewDistance;
z < (WorldData.WorldSizeInChunks / 2) + Settings.viewDistance;
z++) {
Vector2Int currentChunk = new Vector2Int(x, z);
_chunks[x, z] = new Chunk(this, currentChunk);
_chunksToCreate.Add(currentChunk);
}
}
for (var i = 0; i < ChunksToUpdate.Count; i++) {
ChunksToUpdate[0].Update();
ChunksToUpdate.RemoveAt(0);
}
player.position = spawn;
}
public byte GenerateBlock(in Vector3 block) {
int yPos = Mathf.FloorToInt(block.y);
Vector2 noisePosition = new Vector2(block.x, block.z);
if (!IsBlockInWorld(block))
return BlockType.Air;
if (yPos == 0)
return BlockType.Bedrock;
#region BiomePass
int solidGroundHeight = 42;
float sumOfHeights = 0;
int count = 0;
float strongestWeight = 0;
int strongestBiomeIndex = 0;
for (int i = 0; i < biomes.Length; i++) {
float weight = Noise.Get2dPerlin(noisePosition, biomes[i].offset, biomes[i].scale);
if (weight > strongestWeight) {
strongestWeight = weight;
strongestBiomeIndex = i;
}
float height = biomes[i].terrainHeight * Noise.Get2dPerlin(noisePosition, 0, biomes[i].terrainScale) * weight;
if (height > 0) {
sumOfHeights += height;
count++;
}
}
BiomeAttributes biome = biomes[strongestBiomeIndex];
BlockBiomes[(int)block.x, (int)block.z] = strongestBiomeIndex;
sumOfHeights /= count;
int terrainHeight = Mathf.FloorToInt(sumOfHeights + solidGroundHeight);
#endregion
#region BasicTerrainPass
byte blockValue = 0;
if (yPos > terrainHeight)
return BlockType.Air;
if (yPos == terrainHeight)
blockValue = biome.surfaceBlock;
if (yPos < terrainHeight && yPos > terrainHeight - 4)
blockValue = biome.subSurfaceBlock;
if (yPos < terrainHeight)
blockValue = BlockType.Stone;
#endregion
#region SecondTerrainPass
if (blockValue == BlockType.Stone) {
foreach (var lode in biome.lodes) {
if (yPos < lode.minHeight || yPos > lode.maxHeight) continue;
if (Noise.Get3dPerlin(block, lode.noiseOffset, lode.scale, lode.threshold))
blockValue = lode.blockId;
}
}
#endregion
#region FloraPass
if (yPos == terrainHeight && biome.placeMajorFlora) {
if (Noise.Get2dPerlin(noisePosition, 0, biome.majorFloraZoneScale) > biome.majorFloraZoneThreshold) {
if (Noise.Get2dPerlin(noisePosition, 0, biome.majorFloraPlacementScale) > biome.majorFloraPlacementThreshold) {
_modificationQueues.Enqueue(Structure.GenerateMajorFlora(biome.majorFloraIndex, block, biome.minHeight, biome.maxHeight));
}
}
}
#endregion
return blockValue;
}
private void CreateChunk() {
Vector2Int chunk = _chunksToCreate[0];
_chunksToCreate.RemoveAt(0);
_chunks[chunk.x, chunk.y].Generate();
}
private void UpdateChunks() {
bool updated = false;
int index = 0;
lock (ChunkUpdateThreadLock) {
while (!updated && index < ChunksToUpdate.Count) {
if (ChunksToUpdate[index].Editable) {
ChunksToUpdate[index].Update();
if (!_activeChunks.Contains(ChunksToUpdate[index].Position))
_activeChunks.Add(ChunksToUpdate[index].Position);
ChunksToUpdate.RemoveAt(index);
updated = true;
}
else index++;
}
}
}
private void ThreadedUpdate() {
while (true) {
if (!_applyingModifications)
ApplyModifications();
if (ChunksToUpdate.Count > 0)
UpdateChunks();
}
}
private void OnDisable() {
if (Settings.enableThreading) {
_chunkUpdateThread.Abort();
}
}
private void ApplyModifications() {
_applyingModifications = true;
while (_modificationQueues.Count > 0) {
Queue<BlockMod> modifications = _modificationQueues.Dequeue();
while (modifications.Count > 0) {
BlockMod mod = modifications.Dequeue();
Vector2Int pos = WorldData.GetChunkPositionFromWorldPosition(mod.Position);
Chunk chunk = _chunks[pos.x, pos.y];
if (chunk == null) {
chunk = new Chunk(this, pos);
_chunks[pos.x, pos.y] = chunk;
_chunksToCreate.Add(pos);
continue;
}
chunk.Modifications.Enqueue(mod);
}
}
_applyingModifications = false;
}
private void CheckViewDistance(in bool forceUpdate = false) {
Vector2Int playerChunk = WorldData.GetChunkPositionFromWorldPosition(player.position);
if (playerChunk == lastPlayerChunk && !forceUpdate) return;
clouds.UpdateClouds();
lastPlayerChunk = playerChunk;
List<Vector2Int> prevActiveCunks = new List<Vector2Int>(_activeChunks);
_activeChunks.Clear();
for (int x = playerChunk.x - Settings.viewDistance; x < playerChunk.x + Settings.viewDistance; x++) {
for (int z = playerChunk.y - Settings.viewDistance; z < playerChunk.y + Settings.viewDistance; z++) {
Vector2Int currentPos = new Vector2Int(x, z);
prevActiveCunks.Remove(currentPos);
if (!IsChunkInWorld(currentPos)) continue;
Chunk currentChunk = _chunks[x, z];
if (currentChunk == null) {
_chunks[x, z] = new Chunk(this, currentPos);
_chunksToCreate.Add(currentPos);
}
else if (!currentChunk.Active) {
currentChunk.Active = true;
}
_activeChunks.Add(currentPos);
}
}
prevActiveCunks.ForEach(chunk => { _chunks[chunk.x, chunk.y].Active = false; });
}
private bool IsChunkInWorld(in Vector2Int chunk) {
return (chunk.x > 0 && chunk.x < WorldData.WorldSizeInChunks - 1 &&
chunk.y > 0 && chunk.y < WorldData.WorldSizeInChunks - 1);
}
private bool IsBlockInWorld(in Vector3 block) {
return (block.x > 0 && block.x <= WorldData.WorldSizeInBlocks &&
block.y > 0 && block.y < WorldData.ChunkSize.y &&
block.z > 0 && block.z <= WorldData.WorldSizeInBlocks);
}
}
}