158 lines
6.3 KiB
C#
158 lines
6.3 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
/// <summary>
|
||
/// Синглтон. Управляет всеми чанками.
|
||
/// Точка входа для постановки/разрушения блоков.
|
||
/// </summary>
|
||
public class WorldManager : MonoBehaviour
|
||
{
|
||
public static WorldManager Instance { get; private set; }
|
||
|
||
[Header("References")]
|
||
public BlockRegistry registry; // назначается автоматически если null
|
||
|
||
[Header("Chunk Prefab")]
|
||
[Tooltip("Можно оставить пустым — создаётся из кода")]
|
||
public GameObject chunkPrefab;
|
||
|
||
[Header("Chunk Layer")]
|
||
[Tooltip("Слой для всех чанков — должен совпадать с blockLayer в BlockPlacer")]
|
||
public LayerMask chunkLayer;
|
||
|
||
// chunkPos (в чанковых координатах) → Chunk
|
||
private readonly Dictionary<Vector3Int, Chunk> _chunks = new();
|
||
|
||
void Awake()
|
||
{
|
||
if (Instance != null) { Destroy(gameObject); return; }
|
||
Instance = this;
|
||
DontDestroyOnLoad(gameObject);
|
||
|
||
if (registry == null)
|
||
registry = FindObjectOfType<BlockRegistry>();
|
||
}
|
||
|
||
// ── Публичный API ─────────────────────────────────────────────────────────
|
||
|
||
/// <summary>Получить blockId по мировым координатам блока. null = пусто.</summary>
|
||
public string GetBlock(Vector3Int worldPos)
|
||
{
|
||
Vector3Int cp = ChunkData.WorldToChunkPos(worldPos);
|
||
if (!_chunks.TryGetValue(cp, out var chunk)) return null;
|
||
return chunk.GetBlock(ChunkData.WorldToLocal(worldPos));
|
||
}
|
||
|
||
/// <summary>Поставить блок. Создаёт чанк если нужно.</summary>
|
||
public void PlaceBlock(Vector3Int worldPos, string blockId)
|
||
{
|
||
var chunk = GetOrCreateChunk(ChunkData.WorldToChunkPos(worldPos));
|
||
chunk.SetBlock(ChunkData.WorldToLocal(worldPos), blockId);
|
||
|
||
// Соседние чанки на границе тоже надо пересобрать (face culling)
|
||
RebuildNeighboursIfBorder(worldPos);
|
||
}
|
||
|
||
/// <summary>Сломать блок. Возвращает blockId удалённого блока (или null).</summary>
|
||
public string BreakBlock(Vector3Int worldPos)
|
||
{
|
||
Vector3Int cp = ChunkData.WorldToChunkPos(worldPos);
|
||
if (!_chunks.TryGetValue(cp, out var chunk)) return null;
|
||
|
||
string blockId = chunk.GetBlock(ChunkData.WorldToLocal(worldPos));
|
||
if (blockId == null) return null;
|
||
|
||
chunk.RemoveBlock(ChunkData.WorldToLocal(worldPos));
|
||
RebuildNeighboursIfBorder(worldPos);
|
||
return blockId;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получить или создать чанк по чанковым координатам.
|
||
/// Используется генераторами мира и PlaceBlock.
|
||
/// </summary>
|
||
public Chunk GetOrCreateChunk(Vector3Int chunkPos)
|
||
{
|
||
if (_chunks.TryGetValue(chunkPos, out var existing))
|
||
return existing;
|
||
|
||
return CreateChunk(chunkPos, new ChunkData(chunkPos));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Создать чанк с готовыми данными (используется генераторами мира).
|
||
/// </summary>
|
||
public Chunk CreateChunk(Vector3Int chunkPos, ChunkData data)
|
||
{
|
||
if (_chunks.ContainsKey(chunkPos))
|
||
{
|
||
Debug.LogWarning($"[WorldManager] Chunk {chunkPos} already exists.");
|
||
return _chunks[chunkPos];
|
||
}
|
||
|
||
GameObject go;
|
||
if (chunkPrefab != null)
|
||
go = Instantiate(chunkPrefab, transform);
|
||
else
|
||
{
|
||
go = new GameObject($"Chunk_{chunkPos}");
|
||
go.transform.SetParent(transform);
|
||
}
|
||
|
||
go.transform.position = ChunkData.ChunkToWorld(chunkPos);
|
||
|
||
if (chunkLayer.value != 0)
|
||
go.layer = Mathf.RoundToInt(Mathf.Log(chunkLayer.value, 2));
|
||
|
||
var chunk = go.GetComponent<Chunk>() ?? go.AddComponent<Chunk>();
|
||
chunk.Initialise(data, registry.ChunkMaterial, buildMesh: false);
|
||
|
||
_chunks[chunkPos] = chunk;
|
||
return chunk;
|
||
}
|
||
|
||
// ── Вспомогательные ───────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// Если изменённый блок находится на границе чанка —
|
||
/// пересобираем соседний чанк, чтобы обновить face culling.
|
||
/// </summary>
|
||
void RebuildNeighboursIfBorder(Vector3Int worldPos)
|
||
{
|
||
Vector3Int local = ChunkData.WorldToLocal(worldPos);
|
||
|
||
if (local.x == 0) RebuildChunkAt(worldPos + Vector3Int.left);
|
||
if (local.x == ChunkData.SIZE - 1) RebuildChunkAt(worldPos + Vector3Int.right);
|
||
if (local.y == 0) RebuildChunkAt(worldPos + Vector3Int.down);
|
||
if (local.y == ChunkData.HEIGHT - 1) RebuildChunkAt(worldPos + Vector3Int.up);
|
||
if (local.z == 0) RebuildChunkAt(worldPos + new Vector3Int(0,0,-1));
|
||
if (local.z == ChunkData.SIZE - 1) RebuildChunkAt(worldPos + new Vector3Int(0,0,1));
|
||
}
|
||
|
||
void RebuildChunkAt(Vector3Int worldPos)
|
||
{
|
||
Vector3Int cp = ChunkData.WorldToChunkPos(worldPos);
|
||
if (_chunks.TryGetValue(cp, out var chunk))
|
||
chunk.RebuildMesh();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Перестраивает меши всех чанков.
|
||
/// Вызывать после того как все чанки созданы — исправляет граничные грани.
|
||
/// </summary>
|
||
public void RebuildAllChunks()
|
||
{
|
||
foreach (var chunk in _chunks.Values)
|
||
chunk.RebuildMesh();
|
||
}
|
||
|
||
/// <summary>Перевод мировой позиции игрока → мировые координаты блока.</summary>
|
||
public static Vector3Int WorldToBlock(Vector3 worldPosition)
|
||
{
|
||
return new Vector3Int(
|
||
Mathf.FloorToInt(worldPosition.x),
|
||
Mathf.FloorToInt(worldPosition.y),
|
||
Mathf.FloorToInt(worldPosition.z));
|
||
}
|
||
}
|