2026-10-29 13:21:25 +00:00
using System ;
using System.Collections.Generic ;
using UnityEngine ;
2026-10-30 01:44:46 +00:00
// 전투 유닛 도트 그림 — Resources/Art/Units/px_<그림 id>.png(PD 원화 시트 80종 도트화 · 2026-10-28 · 드래곤 501 = Krea 2 생성 2026-10-30)
2026-10-29 13:21:25 +00:00
// 유닛 → 그림 = Data/unit_art.json(표에 없거나 그림이 없으면 null → 코드 그림 그대로)
2026-10-30 01:44:46 +00:00
// 애니 프레임(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 · 공격 타격 프레임)
2026-10-29 13:21:25 +00:00
namespace VS.View
{
public static class UnitArt
{
[Serializable] class Row { public int id ; public int art ; }
[Serializable] class Table { public Row [ ] units ; }
2026-10-30 01:44:46 +00:00
[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 ; }
2026-10-29 13:21:25 +00:00
static Dictionary < int , int > map ;
static readonly Dictionary < int , Sprite > cache = new Dictionary < int , Sprite > ( ) ;
2026-10-30 01:44:46 +00:00
static readonly Dictionary < string , Sprite [ ] > frames = new Dictionary < string , Sprite [ ] > ( ) ;
static AnimTable anim ;
2026-10-29 13:21:25 +00:00
2026-10-29 15:31:24 +00:00
static void Load ( )
2026-10-29 13:21:25 +00:00
{
2026-10-29 15:31:24 +00:00
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 ;
2026-10-30 01:44:46 +00:00
}
public static Sprite ForUnit ( int unitId )
{
2026-10-29 15:31:24 +00:00
Load ( ) ;
2026-10-29 13:21:25 +00:00
return map . TryGetValue ( unitId , out var art ) ? Get ( art ) : null ;
}
2026-10-29 15:31:24 +00:00
/// 유닛 → 그림 번호(없으면 0) — 유닛 목록에서 보유 유닛이 쓰는 그림을 미보유 칸에서 뺄 때
public static int ArtOf ( int unitId )
{
Load ( ) ;
return map . TryGetValue ( unitId , out var art ) ? art : 0 ;
2026-10-29 13:21:25 +00:00
}
public static Sprite Get ( int art )
{
if ( cache . TryGetValue ( art , out var s ) ) return s ;
2026-10-30 01:44:46 +00:00
s = Load ( "Art/Units/px_" + art ) ;
cache [ art ] = s ; return s ;
}
static Sprite Load ( string path )
{
var s = Resources . Load < Sprite > ( path ) ;
2026-10-29 13:21:25 +00:00
if ( s = = null )
{
// .meta 가 Sprite 로 안 들어간 경우 — 텍스처로 읽어 점 필터 스프라이트로
2026-10-30 01:44:46 +00:00
var tex = Resources . Load < Texture2D > ( path ) ;
2026-10-29 13:21:25 +00:00
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 ) ; }
}
2026-10-30 01:44:46 +00:00
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 ;
2026-10-29 13:21:25 +00:00
}
}
}