ThreeKingdomsSlingshot/Assets/Scripts/Runtime/UnitArt.cs

289 lines
11 KiB
C#
Raw Permalink Normal View History

using System;
using System.Collections.Generic;
using UnityEngine;
namespace TKS
{
/// <summary>
/// Resources/Units/&lt;art&gt;/meta.json — 조직 스킬 skeletal-anim-pipeline 의 `rig_frames_export.py` 가 쓴다.
/// 프레임 PNG 는 Resources/Units/&lt;art&gt;/&lt;clip&gt;/f000.png … (임포트 설정 = Editor/UnitArtImporter).
/// </summary>
[Serializable]
public class UnitArtMeta
{
[Serializable]
public class Clip
{
public string name;
public int frames;
public bool loop;
}
public float fps = 15f;
public float ppu = 100f;
public int[] frame;
/// <summary>발 위치(리그 원점) — 왼쪽 아래 (0,0) 기준 비율.</summary>
public float[] pivot;
/// <summary>대기 첫 프레임의 캐릭터 불투명 세로 픽셀(무기 포함) — bodyHeightPx 가 없을 때의 세로 크기 기준.</summary>
public float charHeightPx;
/// <summary>몸(머리 끝~발) 세로 픽셀 · 무기 제외 — 리그 export 는 ppu(= 리그 1단위) · 정지 그림은 굵은 행 범위(2026-11-04 스켈레탈 세션 안내: charHeightPx 로 맞추면 긴 무기를 든 인물이 작아짐).</summary>
public float bodyHeightPx;
public Clip[] clips;
}
public class UnitArtData
{
public string Id;
public UnitArtMeta Meta;
public readonly Dictionary<string, Sprite[]> Clips = new Dictionary<string, Sprite[]>();
public readonly Dictionary<string, bool> Loop = new Dictionary<string, bool>();
/// <summary>
/// 크기 기준 px = Resources/Units/art_size.csv 의 size_px(눈 간격 ÷ 기준 비율 = 머리 크기가 표준인 유닛의 키로 환산 · Tools/unit_art_size.py) ·
/// 0 = 표에 없음 → BodyPx. PD 2026-11-09 「캐릭터 크기 동일 · 말 탄 유닛도 보병·창병과 비슷한 비율」.
/// </summary>
public float SizePx;
/// <summary>말 탄 유닛(크기 표 mounted = 1) — 게임 배치 때 머리 끝 상한(BalanceConfig.mountedArtMaxTop)을 받는다.</summary>
public bool Mounted;
영웅 목록 = 눈 줄 맞춤 + 너무 위에 뜨던 12종 크기 조금 키움 · 보는 방향 2차(원술·허저 반전 · 축융 되돌림) · 소교·초선 얼굴·소품 차별화(#1108 · PD 2026-11-10) PD: 「전위, 태사자 등과 같이 영웅 목록에서 위치가 너무 위쪽에 등장하는 영웅은 위치를 조금 더 내리고 크기를 약간 더 키워」 · 「여전히 방향이 반대로 되어 있는 유닛 체크」 · 「소교와 초선은 복장·헤어는 좋으나 생김새·소품(부채)이 너무 같다」 - 원인: 카드가 머리 끝(관·투구 꼭대기)을 맞춰서 관·투구가 낮은 유닛은 얼굴이 위로 뜸(카드 위 눈 높이 기존 중앙 216.5 · 허저 −62 · 전위 −48 · 태사자 −31 px) - 크기 표 eye_up_px(대기 그림 눈 높이 · 발 위 px · 자동 + 장비·하후돈·축융 수동) → UnitArt.EyeUpPx → HeroScreens 카드 = 눈 줄(걷는 216 · 기병 210 = 기존 20종 중앙값) 맞춤 · 없으면 머리 끝 맞춤 - 25 px 넘게 높던 12종 adjust × 0.95(5 % 크게) - 방향 판정 = 두 눈 가운데 ↔ 얼굴 피부 폭 가운데(머리 실루엣은 투구·끈에 속음) + 확대 확인 → 원술·허저 반전 · 축융 = 1차 반전이 오판이라 되돌림 · 출발·돌진 = 왼쪽 0 - 소교 = 동그란 얼굴·발그레 볼·밝은 웃음 + 대나무 피리 · 초선 = 가는 아몬드 눈·붉은 입술 + 춤 비단 리본(옷·머리 문장 그대로 · seed 51~54 중 · QA PASS B 0) - 테스트(고친 부분만): UnitArt_SizeTable · Lobby_Tabs_Back_ComingSoon · UILayerTests 각 1/1 · error 0 · 캡처 hero_list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:13:46 +00:00
/// <summary>대기 그림의 눈 높이(발 위 px · 크기 표 eye_up_px) · 0 = 없음 — 영웅 목록 카드가 눈 줄을 맞춘다(PD 2026-11-10 「전위·태사자처럼 너무 위쪽에 보이는 영웅은 내려」).</summary>
public float EyeUpPx;
/// <summary>그림 속 몸(머리 끝~발) 세로 px — bodyHeightPx(무기 제외) 우선, 없으면 charHeightPx.</summary>
public float BodyPx
{
get { return Meta.bodyHeightPx > 0.5f ? Meta.bodyHeightPx : Meta.charHeightPx; }
}
/// <summary>배율 기준 세로 크기(월드) — 임포터 PPU = meta.ppu 전제 · 크기 표 size_px 우선(모든 유닛의 머리 크기가 같아짐), 없으면 BodyPx.</summary>
public float CharHeightWorld
{
get
{
if (Meta.ppu <= 0.01f) return 1f;
return (SizePx > 0.5f ? SizePx : BodyPx) / Meta.ppu;
}
}
}
/// <summary>유닛 아트 로더(Resources · 캐시). 아트가 없으면 null → UnitView 가 도형으로 그린다.</summary>
public static class UnitArt
{
public const string Root = "Units/";
/// <summary>크기 표(TextAsset · utf-8-sig CSV · 열 art · size_px) = Resources/Units/art_size.csv.</summary>
public const string SizeTable = "art_size";
static readonly Dictionary<string, UnitArtData> cache = new Dictionary<string, UnitArtData>();
static Dictionary<string, float> sizes;
영웅 목록 = 눈 줄 맞춤 + 너무 위에 뜨던 12종 크기 조금 키움 · 보는 방향 2차(원술·허저 반전 · 축융 되돌림) · 소교·초선 얼굴·소품 차별화(#1108 · PD 2026-11-10) PD: 「전위, 태사자 등과 같이 영웅 목록에서 위치가 너무 위쪽에 등장하는 영웅은 위치를 조금 더 내리고 크기를 약간 더 키워」 · 「여전히 방향이 반대로 되어 있는 유닛 체크」 · 「소교와 초선은 복장·헤어는 좋으나 생김새·소품(부채)이 너무 같다」 - 원인: 카드가 머리 끝(관·투구 꼭대기)을 맞춰서 관·투구가 낮은 유닛은 얼굴이 위로 뜸(카드 위 눈 높이 기존 중앙 216.5 · 허저 −62 · 전위 −48 · 태사자 −31 px) - 크기 표 eye_up_px(대기 그림 눈 높이 · 발 위 px · 자동 + 장비·하후돈·축융 수동) → UnitArt.EyeUpPx → HeroScreens 카드 = 눈 줄(걷는 216 · 기병 210 = 기존 20종 중앙값) 맞춤 · 없으면 머리 끝 맞춤 - 25 px 넘게 높던 12종 adjust × 0.95(5 % 크게) - 방향 판정 = 두 눈 가운데 ↔ 얼굴 피부 폭 가운데(머리 실루엣은 투구·끈에 속음) + 확대 확인 → 원술·허저 반전 · 축융 = 1차 반전이 오판이라 되돌림 · 출발·돌진 = 왼쪽 0 - 소교 = 동그란 얼굴·발그레 볼·밝은 웃음 + 대나무 피리 · 초선 = 가는 아몬드 눈·붉은 입술 + 춤 비단 리본(옷·머리 문장 그대로 · seed 51~54 중 · QA PASS B 0) - 테스트(고친 부분만): UnitArt_SizeTable · Lobby_Tabs_Back_ComingSoon · UILayerTests 각 1/1 · error 0 · 캡처 hero_list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:13:46 +00:00
static Dictionary<string, float> eyeUps;
static HashSet<string> mounted;
/// <summary>크기 표의 size_px(없으면 0).</summary>
public static float SizeOf(string id)
{
EnsureSizes();
float v;
return id != null && sizes.TryGetValue(id, out v) ? v : 0f;
}
영웅 목록 = 눈 줄 맞춤 + 너무 위에 뜨던 12종 크기 조금 키움 · 보는 방향 2차(원술·허저 반전 · 축융 되돌림) · 소교·초선 얼굴·소품 차별화(#1108 · PD 2026-11-10) PD: 「전위, 태사자 등과 같이 영웅 목록에서 위치가 너무 위쪽에 등장하는 영웅은 위치를 조금 더 내리고 크기를 약간 더 키워」 · 「여전히 방향이 반대로 되어 있는 유닛 체크」 · 「소교와 초선은 복장·헤어는 좋으나 생김새·소품(부채)이 너무 같다」 - 원인: 카드가 머리 끝(관·투구 꼭대기)을 맞춰서 관·투구가 낮은 유닛은 얼굴이 위로 뜸(카드 위 눈 높이 기존 중앙 216.5 · 허저 −62 · 전위 −48 · 태사자 −31 px) - 크기 표 eye_up_px(대기 그림 눈 높이 · 발 위 px · 자동 + 장비·하후돈·축융 수동) → UnitArt.EyeUpPx → HeroScreens 카드 = 눈 줄(걷는 216 · 기병 210 = 기존 20종 중앙값) 맞춤 · 없으면 머리 끝 맞춤 - 25 px 넘게 높던 12종 adjust × 0.95(5 % 크게) - 방향 판정 = 두 눈 가운데 ↔ 얼굴 피부 폭 가운데(머리 실루엣은 투구·끈에 속음) + 확대 확인 → 원술·허저 반전 · 축융 = 1차 반전이 오판이라 되돌림 · 출발·돌진 = 왼쪽 0 - 소교 = 동그란 얼굴·발그레 볼·밝은 웃음 + 대나무 피리 · 초선 = 가는 아몬드 눈·붉은 입술 + 춤 비단 리본(옷·머리 문장 그대로 · seed 51~54 중 · QA PASS B 0) - 테스트(고친 부분만): UnitArt_SizeTable · Lobby_Tabs_Back_ComingSoon · UILayerTests 각 1/1 · error 0 · 캡처 hero_list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:13:46 +00:00
/// <summary>크기 표의 eye_up_px(대기 그림 눈 높이 · 발 위 px · 없으면 0).</summary>
public static float EyeUpOf(string id)
{
EnsureSizes();
float v;
return id != null && eyeUps.TryGetValue(id, out v) ? v : 0f;
}
/// <summary>크기 표 mounted = 1(말 탄 유닛).</summary>
public static bool IsMounted(string id)
{
EnsureSizes();
return id != null && mounted.Contains(id);
}
static void EnsureSizes()
{
if (sizes != null) return;
sizes = new Dictionary<string, float>();
영웅 목록 = 눈 줄 맞춤 + 너무 위에 뜨던 12종 크기 조금 키움 · 보는 방향 2차(원술·허저 반전 · 축융 되돌림) · 소교·초선 얼굴·소품 차별화(#1108 · PD 2026-11-10) PD: 「전위, 태사자 등과 같이 영웅 목록에서 위치가 너무 위쪽에 등장하는 영웅은 위치를 조금 더 내리고 크기를 약간 더 키워」 · 「여전히 방향이 반대로 되어 있는 유닛 체크」 · 「소교와 초선은 복장·헤어는 좋으나 생김새·소품(부채)이 너무 같다」 - 원인: 카드가 머리 끝(관·투구 꼭대기)을 맞춰서 관·투구가 낮은 유닛은 얼굴이 위로 뜸(카드 위 눈 높이 기존 중앙 216.5 · 허저 −62 · 전위 −48 · 태사자 −31 px) - 크기 표 eye_up_px(대기 그림 눈 높이 · 발 위 px · 자동 + 장비·하후돈·축융 수동) → UnitArt.EyeUpPx → HeroScreens 카드 = 눈 줄(걷는 216 · 기병 210 = 기존 20종 중앙값) 맞춤 · 없으면 머리 끝 맞춤 - 25 px 넘게 높던 12종 adjust × 0.95(5 % 크게) - 방향 판정 = 두 눈 가운데 ↔ 얼굴 피부 폭 가운데(머리 실루엣은 투구·끈에 속음) + 확대 확인 → 원술·허저 반전 · 축융 = 1차 반전이 오판이라 되돌림 · 출발·돌진 = 왼쪽 0 - 소교 = 동그란 얼굴·발그레 볼·밝은 웃음 + 대나무 피리 · 초선 = 가는 아몬드 눈·붉은 입술 + 춤 비단 리본(옷·머리 문장 그대로 · seed 51~54 중 · QA PASS B 0) - 테스트(고친 부분만): UnitArt_SizeTable · Lobby_Tabs_Back_ComingSoon · UILayerTests 각 1/1 · error 0 · 캡처 hero_list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:13:46 +00:00
eyeUps = new Dictionary<string, float>();
mounted = new HashSet<string>();
var ta = Resources.Load<TextAsset>(Root + SizeTable);
if (ta == null) return;
var lines = ta.text.TrimStart('').Split('\n');
var head = new List<string>(lines[0].Trim().Split(','));
영웅 목록 = 눈 줄 맞춤 + 너무 위에 뜨던 12종 크기 조금 키움 · 보는 방향 2차(원술·허저 반전 · 축융 되돌림) · 소교·초선 얼굴·소품 차별화(#1108 · PD 2026-11-10) PD: 「전위, 태사자 등과 같이 영웅 목록에서 위치가 너무 위쪽에 등장하는 영웅은 위치를 조금 더 내리고 크기를 약간 더 키워」 · 「여전히 방향이 반대로 되어 있는 유닛 체크」 · 「소교와 초선은 복장·헤어는 좋으나 생김새·소품(부채)이 너무 같다」 - 원인: 카드가 머리 끝(관·투구 꼭대기)을 맞춰서 관·투구가 낮은 유닛은 얼굴이 위로 뜸(카드 위 눈 높이 기존 중앙 216.5 · 허저 −62 · 전위 −48 · 태사자 −31 px) - 크기 표 eye_up_px(대기 그림 눈 높이 · 발 위 px · 자동 + 장비·하후돈·축융 수동) → UnitArt.EyeUpPx → HeroScreens 카드 = 눈 줄(걷는 216 · 기병 210 = 기존 20종 중앙값) 맞춤 · 없으면 머리 끝 맞춤 - 25 px 넘게 높던 12종 adjust × 0.95(5 % 크게) - 방향 판정 = 두 눈 가운데 ↔ 얼굴 피부 폭 가운데(머리 실루엣은 투구·끈에 속음) + 확대 확인 → 원술·허저 반전 · 축융 = 1차 반전이 오판이라 되돌림 · 출발·돌진 = 왼쪽 0 - 소교 = 동그란 얼굴·발그레 볼·밝은 웃음 + 대나무 피리 · 초선 = 가는 아몬드 눈·붉은 입술 + 춤 비단 리본(옷·머리 문장 그대로 · seed 51~54 중 · QA PASS B 0) - 테스트(고친 부분만): UnitArt_SizeTable · Lobby_Tabs_Back_ComingSoon · UILayerTests 각 1/1 · error 0 · 캡처 hero_list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:13:46 +00:00
int ia = head.IndexOf("art"), isz = head.IndexOf("size_px"), im = head.IndexOf("mounted"), ieu = head.IndexOf("eye_up_px");
if (ia < 0 || isz < 0 || im < 0)
{
Debug.LogWarning("[TKS] 유닛 크기 표 열(art · mounted · size_px) 없음");
return;
}
int need = Mathf.Max(ia, Mathf.Max(isz, im));
for (int i = 1; i < lines.Length; i++)
{
var c = lines[i].Trim().Split(',');
if (c.Length <= need) continue;
float v;
if (float.TryParse(c[isz], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out v) && v > 0f)
sizes[c[ia]] = v;
if (c[im] == "1") mounted.Add(c[ia]);
영웅 목록 = 눈 줄 맞춤 + 너무 위에 뜨던 12종 크기 조금 키움 · 보는 방향 2차(원술·허저 반전 · 축융 되돌림) · 소교·초선 얼굴·소품 차별화(#1108 · PD 2026-11-10) PD: 「전위, 태사자 등과 같이 영웅 목록에서 위치가 너무 위쪽에 등장하는 영웅은 위치를 조금 더 내리고 크기를 약간 더 키워」 · 「여전히 방향이 반대로 되어 있는 유닛 체크」 · 「소교와 초선은 복장·헤어는 좋으나 생김새·소품(부채)이 너무 같다」 - 원인: 카드가 머리 끝(관·투구 꼭대기)을 맞춰서 관·투구가 낮은 유닛은 얼굴이 위로 뜸(카드 위 눈 높이 기존 중앙 216.5 · 허저 −62 · 전위 −48 · 태사자 −31 px) - 크기 표 eye_up_px(대기 그림 눈 높이 · 발 위 px · 자동 + 장비·하후돈·축융 수동) → UnitArt.EyeUpPx → HeroScreens 카드 = 눈 줄(걷는 216 · 기병 210 = 기존 20종 중앙값) 맞춤 · 없으면 머리 끝 맞춤 - 25 px 넘게 높던 12종 adjust × 0.95(5 % 크게) - 방향 판정 = 두 눈 가운데 ↔ 얼굴 피부 폭 가운데(머리 실루엣은 투구·끈에 속음) + 확대 확인 → 원술·허저 반전 · 축융 = 1차 반전이 오판이라 되돌림 · 출발·돌진 = 왼쪽 0 - 소교 = 동그란 얼굴·발그레 볼·밝은 웃음 + 대나무 피리 · 초선 = 가는 아몬드 눈·붉은 입술 + 춤 비단 리본(옷·머리 문장 그대로 · seed 51~54 중 · QA PASS B 0) - 테스트(고친 부분만): UnitArt_SizeTable · Lobby_Tabs_Back_ComingSoon · UILayerTests 각 1/1 · error 0 · 캡처 hero_list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:13:46 +00:00
if (ieu >= 0 && ieu < c.Length && float.TryParse(c[ieu], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out v) && v > 0f)
eyeUps[c[ia]] = v;
}
}
public static UnitArtData Load(string id)
{
if (string.IsNullOrEmpty(id)) return null;
UnitArtData d;
if (cache.TryGetValue(id, out d)) return d;
d = LoadUncached(id);
cache[id] = d;
return d;
}
public static void ClearCache()
{
cache.Clear();
sizes = null;
영웅 목록 = 눈 줄 맞춤 + 너무 위에 뜨던 12종 크기 조금 키움 · 보는 방향 2차(원술·허저 반전 · 축융 되돌림) · 소교·초선 얼굴·소품 차별화(#1108 · PD 2026-11-10) PD: 「전위, 태사자 등과 같이 영웅 목록에서 위치가 너무 위쪽에 등장하는 영웅은 위치를 조금 더 내리고 크기를 약간 더 키워」 · 「여전히 방향이 반대로 되어 있는 유닛 체크」 · 「소교와 초선은 복장·헤어는 좋으나 생김새·소품(부채)이 너무 같다」 - 원인: 카드가 머리 끝(관·투구 꼭대기)을 맞춰서 관·투구가 낮은 유닛은 얼굴이 위로 뜸(카드 위 눈 높이 기존 중앙 216.5 · 허저 −62 · 전위 −48 · 태사자 −31 px) - 크기 표 eye_up_px(대기 그림 눈 높이 · 발 위 px · 자동 + 장비·하후돈·축융 수동) → UnitArt.EyeUpPx → HeroScreens 카드 = 눈 줄(걷는 216 · 기병 210 = 기존 20종 중앙값) 맞춤 · 없으면 머리 끝 맞춤 - 25 px 넘게 높던 12종 adjust × 0.95(5 % 크게) - 방향 판정 = 두 눈 가운데 ↔ 얼굴 피부 폭 가운데(머리 실루엣은 투구·끈에 속음) + 확대 확인 → 원술·허저 반전 · 축융 = 1차 반전이 오판이라 되돌림 · 출발·돌진 = 왼쪽 0 - 소교 = 동그란 얼굴·발그레 볼·밝은 웃음 + 대나무 피리 · 초선 = 가는 아몬드 눈·붉은 입술 + 춤 비단 리본(옷·머리 문장 그대로 · seed 51~54 중 · QA PASS B 0) - 테스트(고친 부분만): UnitArt_SizeTable · Lobby_Tabs_Back_ComingSoon · UILayerTests 각 1/1 · error 0 · 캡처 hero_list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:13:46 +00:00
eyeUps = null;
mounted = null;
}
static UnitArtData LoadUncached(string id)
{
var metaAsset = Resources.Load<TextAsset>(Root + id + "/meta");
if (metaAsset == null) return null;
UnitArtMeta meta;
try
{
meta = JsonUtility.FromJson<UnitArtMeta>(metaAsset.text);
}
catch (Exception e)
{
Debug.LogWarning("[TKS] 유닛 아트 meta 해석 실패 " + id + ": " + e.Message);
return null;
}
if (meta == null || meta.clips == null) return null;
영웅 목록 = 눈 줄 맞춤 + 너무 위에 뜨던 12종 크기 조금 키움 · 보는 방향 2차(원술·허저 반전 · 축융 되돌림) · 소교·초선 얼굴·소품 차별화(#1108 · PD 2026-11-10) PD: 「전위, 태사자 등과 같이 영웅 목록에서 위치가 너무 위쪽에 등장하는 영웅은 위치를 조금 더 내리고 크기를 약간 더 키워」 · 「여전히 방향이 반대로 되어 있는 유닛 체크」 · 「소교와 초선은 복장·헤어는 좋으나 생김새·소품(부채)이 너무 같다」 - 원인: 카드가 머리 끝(관·투구 꼭대기)을 맞춰서 관·투구가 낮은 유닛은 얼굴이 위로 뜸(카드 위 눈 높이 기존 중앙 216.5 · 허저 −62 · 전위 −48 · 태사자 −31 px) - 크기 표 eye_up_px(대기 그림 눈 높이 · 발 위 px · 자동 + 장비·하후돈·축융 수동) → UnitArt.EyeUpPx → HeroScreens 카드 = 눈 줄(걷는 216 · 기병 210 = 기존 20종 중앙값) 맞춤 · 없으면 머리 끝 맞춤 - 25 px 넘게 높던 12종 adjust × 0.95(5 % 크게) - 방향 판정 = 두 눈 가운데 ↔ 얼굴 피부 폭 가운데(머리 실루엣은 투구·끈에 속음) + 확대 확인 → 원술·허저 반전 · 축융 = 1차 반전이 오판이라 되돌림 · 출발·돌진 = 왼쪽 0 - 소교 = 동그란 얼굴·발그레 볼·밝은 웃음 + 대나무 피리 · 초선 = 가는 아몬드 눈·붉은 입술 + 춤 비단 리본(옷·머리 문장 그대로 · seed 51~54 중 · QA PASS B 0) - 테스트(고친 부분만): UnitArt_SizeTable · Lobby_Tabs_Back_ComingSoon · UILayerTests 각 1/1 · error 0 · 캡처 hero_list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:13:46 +00:00
var d = new UnitArtData { Id = id, Meta = meta, SizePx = SizeOf(id), Mounted = IsMounted(id), EyeUpPx = EyeUpOf(id) };
foreach (var c in meta.clips)
{
if (c == null || string.IsNullOrEmpty(c.name)) continue;
var sprites = Resources.LoadAll<Sprite>(Root + id + "/" + c.name);
if (sprites == null || sprites.Length == 0) continue;
Array.Sort(sprites, (a, b) => string.CompareOrdinal(a.name, b.name));
d.Clips[c.name] = sprites;
d.Loop[c.name] = c.loop;
}
return d.Clips.Count > 0 ? d : null;
}
}
/// <summary>프레임 애니 재생기. 비반복 클립은 끝난 뒤(유지 시간 포함) 대기 클립으로 돌아갈 수 있다.</summary>
public class FrameAnimator : MonoBehaviour
{
SpriteRenderer sr;
UnitArtData data;
Sprite[] frames;
string current;
string idle;
bool loop;
float t;
float hold;
bool backToIdle;
public SpriteRenderer Renderer
{
get { return sr; }
}
public string Current
{
get { return current; }
}
public string IdleClip
{
get { return idle; }
}
public UnitArtData Data
{
get { return data; }
}
/// <summary>그려진 머리 끝 높이(월드 · 발 기준) = BodyPx × 배율 — 크기 표로 커진 기마 유닛은 표준 키보다 높다.</summary>
public float TopHeight { get; private set; }
/// <param name="worldHeight">표준 키(월드) — 크기 표 size_px 가 이 높이가 되도록 배율(= 머리 크기 통일).</param>
/// <param name="mountedMaxTop">말 탄 유닛의 머리 끝 상한 = worldHeight × 이 값(0 = 상한 없음 · 넘으면 배율을 줄임 = 탄 사람은 보병보다 조금 작아짐).</param>
public void Setup(UnitArtData d, float worldHeight, int sortingOrder, float mountedMaxTop = 0f)
{
data = d;
sr = GetComponent<SpriteRenderer>();
if (sr == null) sr = gameObject.AddComponent<SpriteRenderer>();
sr.sortingOrder = sortingOrder;
float h = d.CharHeightWorld;
float s = h > 0.001f ? worldHeight / h : 1f;
float top = d.Meta.ppu > 0.01f ? d.BodyPx / d.Meta.ppu * s : worldHeight;
if (d.Mounted && mountedMaxTop > 0.01f && top > worldHeight * mountedMaxTop)
{
s *= worldHeight * mountedMaxTop / top;
top = worldHeight * mountedMaxTop;
}
transform.localScale = new Vector3(s, s, 1f);
TopHeight = top;
}
public bool Has(string clip)
{
return data != null && clip != null && data.Clips.ContainsKey(clip);
}
public void SetIdle(string clip)
{
idle = clip;
}
/// <param name="holdSeconds">비반복 클립을 최소 이만큼 보여준 뒤 대기로(1프레임 자세용).</param>
public bool Play(string clip, bool loopPlay, float holdSeconds = 0f, bool returnToIdle = false)
{
if (!Has(clip)) return false;
frames = data.Clips[clip];
current = clip;
loop = loopPlay;
hold = holdSeconds;
backToIdle = returnToIdle;
t = 0f;
sr.sprite = frames[0];
return true;
}
public void PlayIdle()
{
if (idle != null) Play(idle, true);
}
void Update()
{
if (frames == null || frames.Length == 0) return;
t += Time.deltaTime;
float fps = Mathf.Max(1f, data.Meta.fps);
int n = frames.Length;
int idx;
if (loop)
{
idx = Mathf.FloorToInt(t * fps) % n;
}
else
{
idx = Mathf.Min(n - 1, Mathf.FloorToInt(t * fps));
float dur = n / fps;
if (t >= dur && t >= hold && backToIdle && idle != null && current != idle)
{
PlayIdle();
return;
}
}
sr.sprite = frames[idx];
}
}
}