using System.Collections.Generic; using UnityEngine; /// /// Синглтон. Собирает все BlockDefinition, строит Texture2DArray, /// создаёт один shared Material для всех чанков. /// Вызвать Initialise() до создания любого чанка. /// public class BlockRegistry : MonoBehaviour { public static BlockRegistry Instance { get; private set; } [Header("All block definitions in the game")] public List definitions = new(); [Header("Shader")] public Shader voxelShader; // назначить Blocks/VoxelLit через Inspector // Доступно после Initialise() public Material ChunkMaterial { get; private set; } public Texture2DArray TexArray { get; private set; } private readonly Dictionary _byId = new(); private readonly Dictionary _byItemId = new(); void Awake() { if (Instance != null) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); Initialise(); } public void Initialise() { _byId.Clear(); _byItemId.Clear(); if (definitions == null || definitions.Count == 0) { Debug.LogWarning("[BlockRegistry] No BlockDefinitions assigned."); return; } // Собираем уникальные текстуры (top/bottom/side каждого блока) // Порядок: для блока i → top=i*3, bottom=i*3+1, side=i*3+2 int layerCount = definitions.Count * 3; // Авто-определяем разрешение по первой найденной текстуре (берём из rect спрайта) int res = 16; foreach (var d in definitions) { Sprite s = d.topTexture ?? d.sideTexture ?? d.bottomTexture; if (s != null) { res = (int)s.rect.width; break; } } TexArray = new Texture2DArray(res, res, layerCount, TextureFormat.RGBA32, true, false); TexArray.filterMode = FilterMode.Point; // пиксельный стиль TexArray.wrapMode = TextureWrapMode.Repeat; for (int i = 0; i < definitions.Count; i++) { var def = definitions[i]; def.topIndex = i * 3; def.bottomIndex = i * 3 + 1; def.sideIndex = i * 3 + 2; CopyTexture(def.topTexture, TexArray, def.topIndex, res); CopyTexture(def.bottomTexture, TexArray, def.bottomIndex, res); CopyTexture(def.sideTexture, TexArray, def.sideIndex, res); _byId[def.blockId] = def; if (def.linkedItem != null) _byItemId[def.linkedItem.id] = def; } TexArray.Apply(true, true); // загрузить на GPU, сделать non-readable // Создаём один материал для всех чанков if (voxelShader == null) voxelShader = Shader.Find("Blocks/VoxelLit"); ChunkMaterial = new Material(voxelShader); ChunkMaterial.SetTexture("_MainTexArray", TexArray); Debug.Log($"[BlockRegistry] Initialised {definitions.Count} blocks, {layerCount} texture layers."); } // ── Lookups ─────────────────────────────────────────────────────────────── public BlockDefinition GetById(string blockId) { _byId.TryGetValue(blockId, out var def); return def; } /// Находит BlockDefinition по id связанного ItemData (для постановки блока). public BlockDefinition GetByItemId(string itemDataId) { _byItemId.TryGetValue(itemDataId, out var def); return def; } // ── Утилиты ─────────────────────────────────────────────────────────────── /// /// Копирует один спрайт (или его часть из атласа) в слой Texture2DArray. /// Использует Graphics.Blit с UV offset/scale — Read/Write не нужен. /// Если src null — заполняет пурпурным (ошибка-текстура). /// static void CopyTexture(Sprite src, Texture2DArray dst, int layerIndex, int res) { if (src == null) { var pixels = new Color[res * res]; for (int p = 0; p < pixels.Length; p++) pixels[p] = (p / res + p) % 2 == 0 ? Color.magenta : Color.black; dst.SetPixels(pixels, layerIndex); return; } // Вычисляем UV offset/scale этого спрайта внутри атласа. // sprite.rect — пиксельные координаты (origin = bottom-left атласа). float atlasW = src.texture.width; float atlasH = src.texture.height; var scale = new Vector2(src.rect.width / atlasW, src.rect.height / atlasH); var offset = new Vector2(src.rect.x / atlasW, src.rect.y / atlasH); var rt = RenderTexture.GetTemporary(res, res, 0, RenderTextureFormat.ARGB32); var prevFilter = src.texture.filterMode; src.texture.filterMode = FilterMode.Point; Graphics.Blit(src.texture, rt, scale, offset); src.texture.filterMode = prevFilter; var prev = RenderTexture.active; RenderTexture.active = rt; var tmp = new Texture2D(res, res, TextureFormat.RGBA32, false); tmp.ReadPixels(new Rect(0, 0, res, res), 0, 0); tmp.Apply(); RenderTexture.active = prev; RenderTexture.ReleaseTemporary(rt); dst.SetPixels(tmp.GetPixels(), layerIndex); Object.Destroy(tmp); } }