150 lines
6.0 KiB
C#
150 lines
6.0 KiB
C#
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);
|
||
}
|
||
}
|