VSClone/Assets/Scripts/View/UnitArt.cs

107 lines
4.8 KiB
C#
Raw Permalink 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;
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|idle>_<k>.png · 표 = Data/unit_anim.json(fps · 공격 타격 프레임)
// 409·105·416 = ③ 원화 스타일(96×96 · .meta 이중선형 필터 · idle 8 / walk 8 / attack 5 · scripts/pixel/vs_final3_export.py · 2026-10-30 PD 승인) — UI Image 는 sizeDelta 고정이라 화면 크기는 48칸 도트와 같다
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; public float fps; } // fps 0 = 표 전역 fps · 공격 15(PD 2026-10-31 「공격이 조금 더 빨라야」)
[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;
}
/// 클립 재생 fps(unit_anim.json 클립 fps · 없으면 전역 fps)
public static float ClipFps(int art, string act)
{
LoadAnim();
foreach (var c in anim.clips) if (c.art == art && c.act == act && c.fps > 0f) return c.fps;
return anim.fps;
}
/// 공격 타격 프레임 번호(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;
}
}
}