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

150 lines
6.0 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>
/// Синглтон. Собирает все BlockDefinition, строит Texture2DArray,
/// создаёт один shared Material для всех чанков.
/// Вызвать Initialise() до создания любого чанка.
/// </summary>
public class BlockRegistry : MonoBehaviour
{
public static BlockRegistry Instance { get; private set; }
[Header("All block definitions in the game")]
public List<BlockDefinition> 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<string, BlockDefinition> _byId = new();
private readonly Dictionary<string, BlockDefinition> _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;
}
/// <summary>Находит BlockDefinition по id связанного ItemData (для постановки блока).</summary>
public BlockDefinition GetByItemId(string itemDataId)
{
_byItemId.TryGetValue(itemDataId, out var def);
return def;
}
// ── Утилиты ───────────────────────────────────────────────────────────────
/// <summary>
/// Копирует один спрайт (или его часть из атласа) в слой Texture2DArray.
/// Использует Graphics.Blit с UV offset/scale — Read/Write не нужен.
/// Если src null — заполняет пурпурным (ошибка-текстура).
/// </summary>
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);
}
}