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___.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 map; static readonly Dictionary cache = new Dictionary(); static readonly Dictionary frames = new Dictionary(); static AnimTable anim; static void Load() { if (map != null) return; map = new Dictionary(); var txt = GameApp.ReadData("unit_art"); if (txt != null) foreach (var r in JsonUtility.FromJson(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(path); if (s == null) { // .meta 가 Sprite 로 안 들어간 경우 — 텍스처로 읽어 점 필터 스프라이트로 var tex = Resources.Load(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(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(); 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; } } }