using System.Collections.Generic; using UnityEngine; /// /// MonoBehaviour-обёртка над ChunkData. /// Хранит данные блоков и собирает процедурный меш с face culling. /// [RequireComponent(typeof(MeshFilter))] [RequireComponent(typeof(MeshRenderer))] [RequireComponent(typeof(MeshCollider))] public class Chunk : MonoBehaviour { public ChunkData Data { get; private set; } MeshFilter _filter; MeshRenderer _renderer; MeshCollider _collider; Mesh _mesh; // ── 6 направлений граней ────────────────────────────────────────────────── // Порядок: Top, Bottom, Front(Z+), Back(Z-), Right(X+), Left(X-) static readonly Vector3Int[] Directions = { Vector3Int.up, Vector3Int.down, new(0,0,1), new(0,0,-1), new(1,0,0), new(-1,0,0) }; // Вершины каждой грани (относительно нижнего-левого угла блока) // Порядок по часовой стрелке, смотрим снаружи static readonly Vector3[][] FaceVerts = { // Top (Y+) new[] { new Vector3(0,1,0), new Vector3(0,1,1), new Vector3(1,1,1), new Vector3(1,1,0) }, // Bottom (Y-) new[] { new Vector3(0,0,0), new Vector3(1,0,0), new Vector3(1,0,1), new Vector3(0,0,1) }, // Front (Z+) new[] { new Vector3(1,0,1), new Vector3(0,0,1), new Vector3(0,1,1), new Vector3(1,1,1) }, // Back (Z-) new[] { new Vector3(0,0,0), new Vector3(1,0,0), new Vector3(1,1,0), new Vector3(0,1,0) }, // Right (X+) new[] { new Vector3(1,0,0), new Vector3(1,0,1), new Vector3(1,1,1), new Vector3(1,1,0) }, // Left (X-) new[] { new Vector3(0,0,1), new Vector3(0,0,0), new Vector3(0,1,0), new Vector3(0,1,1) } }; // UV для каждой из 4 вершин грани static readonly Vector2[] FaceUVs = { new(0,0), new(1,0), new(1,1), new(0,1) }; // ── Инициализация ───────────────────────────────────────────────────────── void Awake() { _filter = GetComponent(); _renderer = GetComponent(); _collider = GetComponent(); _mesh = new Mesh { name = "ChunkMesh" }; _mesh.MarkDynamic(); // оптимизация для часто изменяемых мешей _filter.sharedMesh = _mesh; } public void Initialise(ChunkData data, Material chunkMaterial) { Data = data; _renderer.sharedMaterial = chunkMaterial; RebuildMesh(); } // ── Публичный API ───────────────────────────────────────────────────────── public void SetBlock(Vector3Int localPos, string blockId) { Data.SetBlock(localPos, blockId); RebuildMesh(); } public void RemoveBlock(Vector3Int localPos) { Data.SetBlock(localPos, null); RebuildMesh(); } public string GetBlock(Vector3Int localPos) => Data.GetBlock(localPos); // ── Построение меша ─────────────────────────────────────────────────────── public void RebuildMesh() { if (Data == null) return; var verts = new List(); var tris = new List(); var uvs = new List(); // UV0: координаты на грани 0-1 var texCoord1 = new List(); // UV1: x = индекс в Texture2DArray for (int x = 0; x < ChunkData.SIZE; x++) for (int y = 0; y < ChunkData.HEIGHT; y++) for (int z = 0; z < ChunkData.SIZE; z++) { string blockId = Data.GetBlock(x, y, z); if (blockId == null) continue; var def = BlockRegistry.Instance?.GetById(blockId); if (def == null) continue; for (int f = 0; f < 6; f++) { Vector3Int neighbour = new Vector3Int(x, y, z) + Directions[f]; // Face culling: пропускаем грань если сосед — твёрдый блок if (HasSolidNeighbour(neighbour)) continue; // Индекс текстуры для этой грани int texIndex = f == 0 ? def.topIndex : f == 1 ? def.bottomIndex : def.sideIndex; int baseVert = verts.Count; var origin = new Vector3(x, y, z); for (int v = 0; v < 4; v++) { verts.Add(origin + FaceVerts[f][v]); uvs.Add(FaceUVs[v]); texCoord1.Add(new Vector2(texIndex, 0)); } // Два треугольника на грань tris.Add(baseVert + 0); tris.Add(baseVert + 1); tris.Add(baseVert + 2); tris.Add(baseVert + 0); tris.Add(baseVert + 2); tris.Add(baseVert + 3); } } _mesh.Clear(); _mesh.SetVertices(verts); _mesh.SetTriangles(tris, 0); _mesh.SetUVs(0, uvs); _mesh.SetUVs(1, texCoord1); _mesh.RecalculateNormals(); _mesh.RecalculateBounds(); // Обновляем коллайдер (нужен для рейкаста) _collider.sharedMesh = null; _collider.sharedMesh = _mesh; Data.ClearDirty(); } // ── Face culling ────────────────────────────────────────────────────────── bool HasSolidNeighbour(Vector3Int localPos) { // Сосед внутри этого чанка if (ChunkData.InBounds(localPos.x, localPos.y, localPos.z)) { string id = Data.GetBlock(localPos); if (id == null) return false; var def = BlockRegistry.Instance?.GetById(id); return def != null && def.isSolid; } // Сосед в соседнем чанке — спрашиваем WorldManager if (WorldManager.Instance == null) return false; Vector3Int worldOrigin = ChunkData.ChunkToWorld(Data.ChunkPos); Vector3Int worldNeighbour = worldOrigin + localPos; string neighbourId = WorldManager.Instance.GetBlock(worldNeighbour); if (neighbourId == null) return false; var neighbourDef = BlockRegistry.Instance?.GetById(neighbourId); return neighbourDef != null && neighbourDef.isSolid; } }