VSClone/Assets/Scripts/View/UnitArt.cs

98 lines
4.1 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
// 전투 유닛 도트 그림 — Resources/Art/Units/px_<그림 id>.png(PD 원화 시트 80종 도트화 · 2026-10-28 · 드래곤 501 = Krea 2 생성 2026-10-30)
// 유닛 → 그림 = Data/unit_art.json(표에 없거나 그림이 없으면 null → 코드 그림 그대로)
// 애니 프레임(2026-10-30 · Wan 2.2 I2V + 원화 고정 합성 · scripts/pixel/vs_anim.py) = Art/Units/anim/px_<art>_<walk|attack>_<k>.png · 표 = Data/unit_anim.json(fps · 공격 타격 프레임)
namespace VS.View
{
public static class UnitArt
{
[Serializable] class Row { public int id; public int art; }
[Serializable] class Table { public Row[] units; }
[Serializable] public class Clip { public int art; public string act; public int frames; public int hit = -1; public string seed; }
[Serializable] class AnimTable { public float fps = 10f; public Clip[] clips; }
static Dictionary<int, int> map;
static readonly Dictionary<int, Sprite> cache = new Dictionary<int, Sprite>();
static readonly Dictionary<string, Sprite[]> frames = new Dictionary<string, Sprite[]>();
static AnimTable anim;
static void Load()
{
if (map != null) return;
map = new Dictionary<int, int>();
var txt = GameApp.ReadData("unit_art");
if (txt != null) foreach (var r in JsonUtility.FromJson<Table>(txt).units) map[r.id] = r.art;
}
public static Sprite ForUnit(int unitId)
{
Load();
return map.TryGetValue(unitId, out var art) ? Get(art) : null;
}
/// 유닛 → 그림 번호(없으면 0) — 유닛 목록에서 보유 유닛이 쓰는 그림을 미보유 칸에서 뺄 때
public static int ArtOf(int unitId)
{
Load();
return map.TryGetValue(unitId, out var art) ? art : 0;
}
public static Sprite Get(int art)
{
if (cache.TryGetValue(art, out var s)) return s;
s = Load("Art/Units/px_" + art);
cache[art] = s; return s;
}
static Sprite Load(string path)
{
var s = Resources.Load<Sprite>(path);
if (s == null)
{
// .meta 가 Sprite 로 안 들어간 경우 — 텍스처로 읽어 점 필터 스프라이트로
var tex = Resources.Load<Texture2D>(path);
if (tex != null) { tex.filterMode = FilterMode.Point; s = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0f), 32); }
}
return s;
}
public static float Fps { get { LoadAnim(); return anim.fps; } }
static void LoadAnim()
{
if (anim != null) return;
var txt = GameApp.ReadData("unit_anim");
anim = txt != null ? JsonUtility.FromJson<AnimTable>(txt) : new AnimTable { clips = new Clip[0] };
if (anim.clips == null) anim.clips = new Clip[0];
if (anim.fps <= 0f) anim.fps = 10f;
}
/// 그 그림의 동작 프레임(없으면 null) — act = walk · attack
public static Sprite[] Frames(int art, string act)
{
string key = art + "_" + act;
if (frames.TryGetValue(key, out var f)) return f;
var list = new List<Sprite>();
for (int k = 0; k < 64; k++)
{
var s = Load($"Art/Units/anim/px_{art}_{act}_{k}");
if (s == null) break;
list.Add(s);
}
f = list.Count > 0 ? list.ToArray() : null;
frames[key] = f; return f;
}
/// 공격 타격 프레임 번호(unit_anim.json hit · 없으면 가운데) — 이 프레임이 시뮬의 타격 순간에 오도록 앞 프레임을 예비 동작으로 재생
public static int HitFrame(int art, int count)
{
LoadAnim();
foreach (var c in anim.clips) if (c.art == art && c.act == "attack" && c.hit >= 0) return Mathf.Clamp(c.hit, 0, count - 1);
return count / 2;
}
}
}