using System.Collections.Generic;
using UnityEngine;
///
/// Синглтон. Управляет всеми чанками.
/// Точка входа для постановки/разрушения блоков.
///
public class WorldManager : MonoBehaviour
{
public static WorldManager Instance { get; private set; }
[Header("References")]
public BlockRegistry registry; // назначается автоматически если null
[Header("Chunk Prefab")]
[Tooltip("Можно оставить пустым — создаётся из кода")]
public GameObject chunkPrefab;
// chunkPos (в чанковых координатах) → Chunk
private readonly Dictionary _chunks = new();
void Awake()
{
if (Instance != null) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject);
if (registry == null)
registry = FindObjectOfType();
}
// ── Публичный API ─────────────────────────────────────────────────────────
/// Получить blockId по мировым координатам блока. null = пусто.
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));
}
/// Поставить блок. Создаёт чанк если нужно.
public void PlaceBlock(Vector3Int worldPos, string blockId)
{
var chunk = GetOrCreateChunk(ChunkData.WorldToChunkPos(worldPos));
chunk.SetBlock(ChunkData.WorldToLocal(worldPos), blockId);
// Соседние чанки на границе тоже надо пересобрать (face culling)
RebuildNeighboursIfBorder(worldPos);
}
/// Сломать блок. Возвращает blockId удалённого блока (или null).
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;
}
///
/// Получить или создать чанк по чанковым координатам.
/// Используется генераторами мира и PlaceBlock.
///
public Chunk GetOrCreateChunk(Vector3Int chunkPos)
{
if (_chunks.TryGetValue(chunkPos, out var existing))
return existing;
return CreateChunk(chunkPos, new ChunkData(chunkPos));
}
///
/// Создать чанк с готовыми данными (используется генераторами мира).
///
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);
var chunk = go.GetComponent() ?? go.AddComponent();
chunk.Initialise(data, registry.ChunkMaterial);
_chunks[chunkPos] = chunk;
return chunk;
}
// ── Вспомогательные ───────────────────────────────────────────────────────
///
/// Если изменённый блок находится на границе чанка —
/// пересобираем соседний чанк, чтобы обновить face culling.
///
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();
}
/// Перевод мировой позиции игрока → мировые координаты блока.
public static Vector3Int WorldToBlock(Vector3 worldPosition)
{
return new Vector3Int(
Mathf.FloorToInt(worldPosition.x),
Mathf.FloorToInt(worldPosition.y),
Mathf.FloorToInt(worldPosition.z));
}
}