Files
my-project/Assets/Blocks/Scripts/WorldManager.cs
2026-04-17 02:14:56 +05:00

158 lines
6.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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));
}
}