지하 최적화
블록 프리팹 단위 -> 블록 청크 단위 스폰 기타 건설, 조준 관련 사이드이펙트 버그 수정
This commit is contained in:
389
Assets/Scripts/Underground/ChunkedUndergroundGenerator.cs
Normal file
389
Assets/Scripts/Underground/ChunkedUndergroundGenerator.cs
Normal file
@@ -0,0 +1,389 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Generates underground terrain using chunk-based system.
|
||||
/// Replaces individual MineableBlock NetworkObjects with MineableChunk NetworkObjects.
|
||||
/// </summary>
|
||||
public class ChunkedUndergroundGenerator : NetworkBehaviour
|
||||
{
|
||||
public static ChunkedUndergroundGenerator Instance { get; private set; }
|
||||
|
||||
[Header("Generation Range (in blocks)")]
|
||||
[SerializeField] private Vector3Int generationRange = new Vector3Int(20, 30, 10);
|
||||
[SerializeField] private float noiseScale = 0.12f;
|
||||
|
||||
[Header("Thresholds (0 to 1)")]
|
||||
[SerializeField, Range(0, 1)] private float hollowThreshold = 0.35f;
|
||||
[SerializeField, Range(0, 1)] private float baseResourceThreshold = 0.85f;
|
||||
|
||||
[Header("Resource Distribution")]
|
||||
[SerializeField] private float resourceNoiseScale = 0.25f; // Larger = more spread out
|
||||
[SerializeField, Range(0, 1)] private float resourceSpawnChance = 0.3f; // Additional random chance
|
||||
|
||||
[Header("Depth Settings")]
|
||||
[SerializeField] private bool increaseResourceWithDepth = true;
|
||||
[SerializeField] private float depthFactor = 0.003f;
|
||||
|
||||
[Header("Block Health")]
|
||||
[SerializeField] private byte normalBlockHealth = 100;
|
||||
[SerializeField] private byte resourceBlockHealth = 150;
|
||||
|
||||
[Header("Chunk Prefab")]
|
||||
[SerializeField] private GameObject chunkPrefab;
|
||||
|
||||
[Header("Organization")]
|
||||
[SerializeField] private string containerName = "UndergroundChunks";
|
||||
private Transform _chunkContainer;
|
||||
|
||||
// Chunk registry for neighbor queries
|
||||
private Dictionary<Vector3Int, MineableChunk> _chunkRegistry = new Dictionary<Vector3Int, MineableChunk>();
|
||||
|
||||
// Noise seeds
|
||||
private float _seedX, _seedY, _seedZ;
|
||||
private float _resourceSeedX, _resourceSeedY, _resourceSeedZ;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
_seedX = Random.Range(0f, 99999f);
|
||||
_seedY = Random.Range(0f, 99999f);
|
||||
_seedZ = Random.Range(0f, 99999f);
|
||||
|
||||
// Separate seeds for resource distribution
|
||||
_resourceSeedX = Random.Range(0f, 99999f);
|
||||
_resourceSeedY = Random.Range(0f, 99999f);
|
||||
_resourceSeedZ = Random.Range(0f, 99999f);
|
||||
|
||||
// Create container for hierarchy organization
|
||||
_chunkContainer = new GameObject(containerName).transform;
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
// Register with MineableChunk for neighbor queries
|
||||
MineableChunk.SetChunkManager(this);
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
GenerateChunks();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate all chunks for the underground area
|
||||
/// </summary>
|
||||
private void GenerateChunks()
|
||||
{
|
||||
Vector3Int originGrid = BuildManager.Instance.WorldToGrid3D(transform.position);
|
||||
|
||||
// Calculate chunk range
|
||||
int chunkSizeX = Mathf.CeilToInt((float)generationRange.x / ChunkCoord.CHUNK_SIZE);
|
||||
int chunkSizeY = Mathf.CeilToInt((float)generationRange.y / ChunkCoord.CHUNK_SIZE);
|
||||
int chunkSizeZ = Mathf.CeilToInt((float)generationRange.z / ChunkCoord.CHUNK_SIZE);
|
||||
|
||||
Debug.Log($"[ChunkedGenerator] Generating {chunkSizeX}x{chunkSizeY}x{chunkSizeZ} chunks = {chunkSizeX * chunkSizeY * chunkSizeZ} total");
|
||||
|
||||
int chunksGenerated = 0;
|
||||
int chunksSkipped = 0;
|
||||
|
||||
// Generate chunks
|
||||
for (int cx = 0; cx < chunkSizeX; cx++)
|
||||
{
|
||||
for (int cy = 0; cy < chunkSizeY; cy++)
|
||||
{
|
||||
for (int cz = 0; cz < chunkSizeZ; cz++)
|
||||
{
|
||||
// Calculate chunk grid origin (going downward for Y)
|
||||
Vector3Int chunkGridOrigin = originGrid + new Vector3Int(
|
||||
cx * ChunkCoord.CHUNK_SIZE,
|
||||
-cy * ChunkCoord.CHUNK_SIZE, // Negative Y (underground)
|
||||
cz * ChunkCoord.CHUNK_SIZE
|
||||
);
|
||||
|
||||
// Generate block data for this chunk
|
||||
ChunkState state = GenerateChunkState(chunkGridOrigin);
|
||||
|
||||
// Check if chunk has any blocks
|
||||
bool hasBlocks = false;
|
||||
for (int i = 0; i < ChunkState.BLOCKS_PER_CHUNK; i++)
|
||||
{
|
||||
if (!state.blocks[i].IsEmpty)
|
||||
{
|
||||
hasBlocks = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasBlocks)
|
||||
{
|
||||
chunksSkipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Spawn chunk
|
||||
SpawnChunk(chunkGridOrigin, state);
|
||||
chunksGenerated++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"[ChunkedGenerator] Generated {chunksGenerated} chunks, skipped {chunksSkipped} empty chunks");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate block state for a chunk
|
||||
/// </summary>
|
||||
private ChunkState GenerateChunkState(Vector3Int chunkGridOrigin)
|
||||
{
|
||||
ChunkState state = ChunkState.CreateEmpty();
|
||||
|
||||
for (int lx = 0; lx < ChunkCoord.CHUNK_SIZE; lx++)
|
||||
{
|
||||
for (int ly = 0; ly < ChunkCoord.CHUNK_SIZE; ly++)
|
||||
{
|
||||
for (int lz = 0; lz < ChunkCoord.CHUNK_SIZE; lz++)
|
||||
{
|
||||
Vector3Int gridPos = chunkGridOrigin + new Vector3Int(lx, ly, lz);
|
||||
int index = ChunkCoord.LocalToIndex(lx, ly, lz);
|
||||
|
||||
// Check if within generation range
|
||||
Vector3Int originGrid = BuildManager.Instance.WorldToGrid3D(transform.position);
|
||||
Vector3Int offset = gridPos - originGrid;
|
||||
|
||||
if (offset.x < 0 || offset.x >= generationRange.x ||
|
||||
offset.y > 0 || offset.y <= -generationRange.y ||
|
||||
offset.z < 0 || offset.z >= generationRange.z)
|
||||
{
|
||||
state.blocks[index] = BlockData.Empty;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sample noise
|
||||
float noise = Get3DNoise(gridPos.x, gridPos.y, gridPos.z);
|
||||
|
||||
// Check hollow threshold
|
||||
if (noise < hollowThreshold)
|
||||
{
|
||||
state.blocks[index] = BlockData.Empty;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine block type using separate resource noise for better distribution
|
||||
float resourceNoise = GetResourceNoise(gridPos.x, gridPos.y, gridPos.z);
|
||||
|
||||
float currentThreshold = baseResourceThreshold;
|
||||
if (increaseResourceWithDepth)
|
||||
{
|
||||
currentThreshold += gridPos.y * depthFactor; // Lower threshold = more resources at depth
|
||||
}
|
||||
|
||||
// Resource spawns only if: resource noise > threshold AND random chance passes
|
||||
bool isResource = resourceNoise > currentThreshold &&
|
||||
Random.value < resourceSpawnChance;
|
||||
|
||||
if (isResource)
|
||||
{
|
||||
state.blocks[index] = BlockData.Resource(resourceBlockHealth);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.blocks[index] = BlockData.Normal(normalBlockHealth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawn a chunk NetworkObject
|
||||
/// </summary>
|
||||
private void SpawnChunk(Vector3Int chunkGridOrigin, ChunkState state)
|
||||
{
|
||||
if (chunkPrefab == null)
|
||||
{
|
||||
Debug.LogError("[ChunkedGenerator] Chunk prefab not assigned!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate world position for chunk
|
||||
Vector3 worldPos = BuildManager.Instance.GridToWorld(chunkGridOrigin);
|
||||
|
||||
// Instantiate chunk
|
||||
GameObject chunkObj = Instantiate(chunkPrefab, worldPos, Quaternion.identity, _chunkContainer);
|
||||
|
||||
// Spawn on network
|
||||
NetworkObject netObj = chunkObj.GetComponent<NetworkObject>();
|
||||
netObj.Spawn();
|
||||
|
||||
// Initialize chunk with block data
|
||||
MineableChunk chunk = chunkObj.GetComponent<MineableChunk>();
|
||||
if (chunk != null)
|
||||
{
|
||||
chunk.InitializeBlocks(state);
|
||||
|
||||
// Register in dictionary
|
||||
ChunkCoord coord = ChunkCoord.FromGridPos(chunkGridOrigin);
|
||||
_chunkRegistry[coord.chunkPos] = chunk;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3D Perlin noise sampling for terrain shape
|
||||
/// </summary>
|
||||
private float Get3DNoise(int x, int y, int z)
|
||||
{
|
||||
float xCoord = (x + _seedX + 10000f) * noiseScale;
|
||||
float yCoord = (y + _seedY + 10000f) * noiseScale;
|
||||
float zCoord = (z + _seedZ + 10000f) * noiseScale;
|
||||
|
||||
float ab = Mathf.PerlinNoise(xCoord, yCoord);
|
||||
float bc = Mathf.PerlinNoise(yCoord, zCoord);
|
||||
float ac = Mathf.PerlinNoise(xCoord, zCoord);
|
||||
|
||||
return (ab + bc + ac) / 3f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Separate 3D noise for resource distribution (more spread out)
|
||||
/// </summary>
|
||||
private float GetResourceNoise(int x, int y, int z)
|
||||
{
|
||||
float xCoord = (x + _resourceSeedX + 10000f) * resourceNoiseScale;
|
||||
float yCoord = (y + _resourceSeedY + 10000f) * resourceNoiseScale;
|
||||
float zCoord = (z + _resourceSeedZ + 10000f) * resourceNoiseScale;
|
||||
|
||||
float ab = Mathf.PerlinNoise(xCoord, yCoord);
|
||||
float bc = Mathf.PerlinNoise(yCoord, zCoord);
|
||||
float ac = Mathf.PerlinNoise(xCoord, zCoord);
|
||||
|
||||
return (ab + bc + ac) / 3f;
|
||||
}
|
||||
|
||||
#region Public Query Methods
|
||||
|
||||
/// <summary>
|
||||
/// Get chunk at grid position
|
||||
/// </summary>
|
||||
public MineableChunk GetChunkAtGrid(Vector3Int gridPos)
|
||||
{
|
||||
ChunkCoord coord = ChunkCoord.FromGridPos(gridPos);
|
||||
_chunkRegistry.TryGetValue(coord.chunkPos, out MineableChunk chunk);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get chunk at world position
|
||||
/// </summary>
|
||||
public MineableChunk GetChunkAtWorld(Vector3 worldPos)
|
||||
{
|
||||
ChunkCoord coord = ChunkCoord.FromWorldPos(worldPos);
|
||||
_chunkRegistry.TryGetValue(coord.chunkPos, out MineableChunk chunk);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if block at grid position is solid (for cross-chunk face culling)
|
||||
/// </summary>
|
||||
public bool IsBlockSolid(Vector3Int gridPos)
|
||||
{
|
||||
MineableChunk chunk = GetChunkAtGrid(gridPos);
|
||||
if (chunk == null) return false;
|
||||
|
||||
Vector3Int localPos = ChunkCoord.GridToLocal(gridPos);
|
||||
int index = ChunkCoord.LocalToIndex(localPos);
|
||||
BlockData block = chunk.GetBlock(index);
|
||||
|
||||
return !block.IsEmpty && block.IsDiscovered;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reveal blocks in radius around a world position (for fog of war)
|
||||
/// </summary>
|
||||
public void RevealBlocksInRadius(Vector3 worldPos, float radius)
|
||||
{
|
||||
if (!IsServer) return;
|
||||
|
||||
// Find all chunks that could be affected
|
||||
foreach (var chunk in _chunkRegistry.Values)
|
||||
{
|
||||
if (chunk == null) continue;
|
||||
|
||||
// Quick bounds check - chunk center distance
|
||||
float chunkDist = Vector3.Distance(worldPos, chunk.transform.position);
|
||||
float maxChunkRadius = ChunkCoord.CHUNK_SIZE * 0.866f; // diagonal
|
||||
|
||||
if (chunkDist <= radius + maxChunkRadius)
|
||||
{
|
||||
chunk.RevealBlocksInRadius(worldPos, radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all active chunks
|
||||
/// </summary>
|
||||
public IEnumerable<MineableChunk> GetAllChunks()
|
||||
{
|
||||
return _chunkRegistry.Values;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Gizmos
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
BuildManager bm = BuildManager.Instance;
|
||||
if (bm == null) bm = FindFirstObjectByType<BuildManager>();
|
||||
if (bm == null) return;
|
||||
|
||||
Vector3Int originGrid = bm.WorldToGrid3D(transform.position);
|
||||
|
||||
// Draw chunk boundaries
|
||||
int chunkSizeX = Mathf.CeilToInt((float)generationRange.x / ChunkCoord.CHUNK_SIZE);
|
||||
int chunkSizeY = Mathf.CeilToInt((float)generationRange.y / ChunkCoord.CHUNK_SIZE);
|
||||
int chunkSizeZ = Mathf.CeilToInt((float)generationRange.z / ChunkCoord.CHUNK_SIZE);
|
||||
|
||||
Gizmos.color = new Color(0, 1, 0, 0.3f);
|
||||
|
||||
for (int cx = 0; cx < chunkSizeX; cx++)
|
||||
{
|
||||
for (int cy = 0; cy < chunkSizeY; cy++)
|
||||
{
|
||||
for (int cz = 0; cz < chunkSizeZ; cz++)
|
||||
{
|
||||
Vector3Int chunkGridOrigin = originGrid + new Vector3Int(
|
||||
cx * ChunkCoord.CHUNK_SIZE,
|
||||
-cy * ChunkCoord.CHUNK_SIZE,
|
||||
cz * ChunkCoord.CHUNK_SIZE
|
||||
);
|
||||
|
||||
Vector3 worldPos = bm.GridToWorld(chunkGridOrigin);
|
||||
// Offset to center of chunk
|
||||
worldPos += new Vector3(
|
||||
(ChunkCoord.CHUNK_SIZE - 1) * 0.5f,
|
||||
(ChunkCoord.CHUNK_SIZE - 1) * 0.5f,
|
||||
(ChunkCoord.CHUNK_SIZE - 1) * 0.5f
|
||||
);
|
||||
|
||||
Gizmos.DrawWireCube(worldPos, Vector3.one * ChunkCoord.CHUNK_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user