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)); } }