ThreeKingdomsSlingshot/Assets/Scripts/OutGame/UI/LoadingScreen.cs

694 lines
28 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.Collections.Generic;
using System.Globalization;
using UnityEngine;
using UnityEngine.UI;
namespace TKS.OutGame
{
/// <summary>
/// 로딩 화면 종류(PD 2026-11-13 「원격 다운로드가 필요한 시점 · 로딩이 필요한 시점 2 경우에 사용」) — 표 s_set 이 모드마다 다른 화면을 고른다.
/// Download = 원격 리소스 받기(배경 + 위 타이틀 로고 + 조운·병졸 + 용량) · Loading = 그 밖의 로딩(검은 화면 + 유비·관우·장비 말 타고 질주 · 심플).
/// </summary>
public enum LoadingMode
{
Download,
Loading
}
/// <summary>
/// 로딩·리소스 다운로드 화면(PD 2026-11-13) — 진행 바 위에서 무리가 제자리로 달리고(프레임 반복) 말 뒤로 먼지가 일어 흩어진다.
/// 다운로드 = 풀 덮인 땅 바 위를 조운(아두를 품음)이 백마로 질주 · 뒤를 병졸이 따름 · 위에 타이틀 로고 · 진행률·용량.
/// 로딩 = 배경 없이 검은 화면 · 단순 바 위를 유비·관우·장비가 각자 말을 타고 함께 질주 · 「Loading...」.
/// 값(자리·크기·속도·문구 · 세트별) = 표 Resources/UI/loading_screen.csv(LoadingTable · s_set = download | loading | 빈칸 = 공통 · C45) · 그림 = Resources/UI/Loading/.
/// 캔버스(P41 · 갱신 빈도별): 루트 Static(배경 · 로고 · 바 틀) → Live = Frame(바 채움 · 먼지 · 달리는 무리 · 점 3개) → Info = Event(상태 문구 · 진행률 · 용량).
/// 연결점: Show(cam, mode) → SetProgress(0~1) 또는 SetDownload(받은 바이트, 전체 바이트) → Hide(). 모드를 바꾸면 그 세트로 다시 만든다(같은 객체).
/// 게임에는 아직 리소스 다운로드·씬 비동기 로딩 흐름이 없다(Addressables 미도입 · 2026-11-13 실측) — 부르는 자리는 그 흐름을 만들 때 연결.
/// </summary>
public class LoadingScreen : MonoBehaviour
{
public static LoadingScreen Current { get; private set; }
/// <summary>
/// 이 화면의 시계(초) — 기본 = Time.unscaledTime(로딩 중 timeScale 과 무관). 확인 캡처만 Time.time + Time.captureDeltaTime 으로 바꿔
/// 원본 크기 화면 읽기가 느려도 캡처 한 장 = 게임 1/16 초(영상 간격 = 실제 재생 · UICaptureDriver.LoadingShots).
/// </summary>
public static System.Func<float> Clock;
public static float Now
{
get { return Clock != null ? Clock() : Time.unscaledTime; }
}
/// <summary>모드 → 표 세트 이름.</summary>
public static string SetOf(LoadingMode m)
{
return m == LoadingMode.Download ? "download" : "loading";
}
float lastNow = -1f;
Camera cam;
public LoadingMode Mode { get; private set; }
/// <summary>지금 화면을 만든 표 세트(download | loading).</summary>
public string Set { get; private set; }
/// <summary>목표 진행률(0~1 · 마지막으로 받은 값).</summary>
public float Target { get; private set; }
/// <summary>화면에 보이는 진행률(목표를 향해 부드럽게 따라감).</summary>
public float Shown { get; private set; }
public long DoneBytes { get; private set; }
public long TotalBytes { get; private set; }
public Canvas RootCanvas { get; private set; }
public RectTransform Live { get; private set; }
public RectTransform Info { get; private set; }
public Image Fill { get; private set; }
public Image Logo { get; private set; }
public Text StatusText { get; private set; }
public Text PercentText { get; private set; }
public Text SizeText { get; private set; }
public Text DotsText { get; private set; }
public readonly List<UiSpriteLoop> Runners = new List<UiSpriteLoop>();
public readonly List<LoadingDust> Dusts = new List<LoadingDust>();
LoadingTable.Row rowStatus;
LoadingTable.Row rowPercent;
LoadingTable.Row rowSize;
LoadingTable.Row rowDots;
float smooth = 1.5f;
float dotStep = 0.4f;
int dotMax = 3;
int shownPct = -1;
int dots = -1;
/// <summary>화면을 띄운다(이미 있으면 그것을 씀 · 모드가 다른 세트면 다시 만듦) · cam = 아웃게임과 같은 카메라(Screen Space - Camera · 캡처에도 찍힘).</summary>
public static LoadingScreen Show(Camera cam, LoadingMode mode)
{
if (Current == null)
{
var go = new GameObject("LoadingScreen");
Current = go.AddComponent<LoadingScreen>();
Current.cam = cam;
}
Current.SetMode(mode);
Current.gameObject.SetActive(true);
return Current;
}
public static void HideCurrent()
{
if (Current != null) Current.Hide();
}
public void Hide()
{
if (Current == this) Current = null;
Destroy(gameObject);
}
void OnDestroy()
{
if (Current == this) Current = null;
}
/// <summary>진행률 0~1 · instant = 부드럽게 따라가지 않고 바로(첫 표시 · 테스트).</summary>
public void SetProgress(float p01, bool instant = false)
{
Target = Mathf.Clamp01(p01);
if (instant) Shown = Target;
Apply();
}
/// <summary>받은 바이트 / 전체 바이트 → 용량 글자(그 세트에 size 줄이 있을 때) + 진행률(전체 0 이면 진행률은 그대로).</summary>
public void SetDownload(long doneBytes, long totalBytes, bool instant = false)
{
DoneBytes = System.Math.Max(0L, doneBytes);
TotalBytes = System.Math.Max(0L, totalBytes);
ApplySize();
if (TotalBytes > 0) SetProgress((float)((double)DoneBytes / TotalBytes), instant);
}
/// <summary>모드 바꾸기 — 표 세트가 다르면 같은 객체 안에서 화면을 다시 만든다(진행률·용량 값은 유지).</summary>
public void SetMode(LoadingMode m)
{
Mode = m;
string set = SetOf(m);
if (Set != set) Rebuild(set);
}
/// <summary>1 MB = 1024² 바이트 · 소수 한 자리.</summary>
public static string Bytes(long b)
{
double mb = b / (1024.0 * 1024.0);
if (mb >= 1024.0) return (mb / 1024.0).ToString("0.0", CultureInfo.InvariantCulture) + " GB";
if (mb >= 0.1) return mb.ToString("0.0", CultureInfo.InvariantCulture) + " MB";
return (b / 1024.0).ToString("0", CultureInfo.InvariantCulture) + " KB";
}
void Rebuild(string set)
{
// 이전 세트의 자식은 지움(Destroy = 이번 프레임 그리기 전에 사라짐) · 루트 캔버스 컴포넌트는 그대로 다시 씀
for (int i = transform.childCount - 1; i >= 0; i--)
{
var c = transform.GetChild(i).gameObject;
c.SetActive(false);
Destroy(c);
}
Runners.Clear();
Dusts.Clear();
Fill = null;
Logo = null;
StatusText = PercentText = SizeText = DotsText = null;
rowStatus = rowPercent = rowSize = rowDots = null;
shownPct = -1;
dots = -1;
Set = set;
Build(set);
ApplySize();
}
void Build(string set)
{
var opt = LoadingTable.First("option", "canvas", set);
int order = opt != null ? (int)opt.a : 900;
float plane = opt != null && opt.b > 0f ? opt.b : 0.3f;
RootCanvas = UiLayer.Root(gameObject, cam, order, plane, UiLayer.Reference, UiRate.Static);
var o = LoadingTable.First("option", "smooth", set);
if (o != null && o.a > 0f) smooth = o.a;
o = LoadingTable.First("option", "dots", set);
if (o != null)
{
if (o.a > 0f) dotStep = o.a;
if (o.size > 0) dotMax = o.size;
}
var root = (RectTransform)transform;
// 배경(화면을 덮음 · 아래 게임 화면 누름을 막음) · 그림이 없으면 색만(로딩 = 검정)
foreach (var r in LoadingTable.All("bg", set))
{
var img = UiKit.Fill("Bg", root).gameObject.AddComponent<Image>();
img.sprite = LoadingTable.Sprite(r.sprite);
img.color = r.color;
img.raycastTarget = true;
if (img.sprite != null)
{
var fit = img.gameObject.AddComponent<AspectRatioFitter>();
fit.aspectMode = AspectRatioFitter.AspectMode.EnvelopeParent;
fit.aspectRatio = img.sprite.rect.width / img.sprite.rect.height;
}
}
// 타이틀 로고(다운로드 · 비율 유지 · f_w×f_h 상자 안)
foreach (var r in LoadingTable.All("logo", set))
{
Logo = Pic("Logo", root, r.sprite);
Place(Logo, r);
Logo.preserveAspect = true;
}
foreach (var r in LoadingTable.All("bar", set))
Place(Pic("Bar", root, r.sprite), r);
// 매 프레임: 바 채움 · 먼지 · 달리는 무리 · 점
Live = UiLayer.Group("Live", root, UiRate.Frame);
foreach (var r in LoadingTable.All("fill", set))
{
Fill = Pic("Fill", Live, r.sprite);
Place(Fill, r);
Fill.type = Image.Type.Filled;
Fill.fillMethod = Image.FillMethod.Horizontal;
Fill.fillOrigin = (int)Image.OriginHorizontal.Left;
Fill.fillAmount = 0f;
}
var dustRoot = UiKit.Fill("Dust", Live);
var runnerRoot = UiKit.Fill("Runners", Live);
var runnerByID = new Dictionary<string, UiSpriteLoop>();
foreach (var r in LoadingTable.All("runner", set))
{
var frames = LoadingTable.Frames(r.sprite);
if (frames.Length == 0) continue;
var img = Pic("Runner_" + r.id, runnerRoot, null);
img.sprite = frames[0];
var rt = (RectTransform)img.transform;
rt.pivot = new Vector2(0.5f, 0f); // 발(그림 아래 가운데) = 표 좌표
float aspect = frames[0].rect.width / frames[0].rect.height;
rt.sizeDelta = new Vector2(r.h * aspect * Mathf.Abs(r.scale), r.h * Mathf.Abs(r.scale));
rt.anchoredPosition = Pos(r.x, r.y);
if (r.scale < 0f) rt.localScale = new Vector3(-1f, 1f, 1f);
var loop = UiSpriteLoop.Attach(img, frames, r.fps, r.phase);
loop.name = "Runner_" + r.id;
Runners.Add(loop);
runnerByID[r.id] = loop;
}
foreach (var r in LoadingTable.All("dust", set))
{
UiSpriteLoop host;
runnerByID.TryGetValue(r.reference, out host);
// 먼지 = 달리는 무리 전부의 뒤(앞 말 먼지가 뒤따르는 말 다리를 덮지 않게 · QA r3 M1 · r2 M2 · PD 2026-11-13 「경미한 수정사항 모두 반영」)
var d = LoadingDust.Create(dustRoot, r, host, Pos);
if (d == null) continue;
Dusts.Add(d);
}
// 진행률·상태 글자(값이 바뀔 때만)
Info = UiLayer.Group("Info", root, UiRate.Event);
rowStatus = LoadingTable.First("text", "status", set);
rowPercent = LoadingTable.First("text", "percent", set);
rowSize = LoadingTable.First("text", "size", set);
rowDots = LoadingTable.First("text", "dots", set);
if (rowStatus != null) StatusText = Txt("Status", Info, rowStatus);
if (rowPercent != null) PercentText = Txt("Percent", Info, rowPercent);
if (rowSize != null) SizeText = Txt("Size", Info, rowSize);
if (rowDots != null)
{
DotsText = Txt("Dots", Live, rowDots);
DotsText.alignment = TextAnchor.MiddleLeft;
var drt = (RectTransform)DotsText.transform;
drt.pivot = new Vector2(0f, 0.5f);
DotsText.text = "";
// 점 = 상태 문구 오른쪽 끝 바로 뒤(문구는 가운데 정렬)
if (StatusText != null)
{
var srt = (RectTransform)StatusText.transform;
drt.anchoredPosition = new Vector2(srt.anchoredPosition.x + StatusText.preferredWidth * 0.5f + rowDots.x, srt.anchoredPosition.y + rowDots.y);
}
}
Apply();
}
void Update()
{
if (RootCanvas == null) return; // Play 중 재컴파일 = 직렬화 안 된 값이 비면 멈춤
float now = Now;
float dt = lastNow < 0f ? 0f : Mathf.Max(0f, now - lastNow);
lastNow = now;
if (!Mathf.Approximately(Shown, Target))
{
Shown = Mathf.MoveTowards(Shown, Target, smooth * dt);
Apply();
}
if (DotsText != null && rowDots != null)
{
int n = (int)(now / dotStep) % (dotMax + 1);
if (n != dots)
{
dots = n;
DotsText.text = new string(rowDots.text.Length > 0 ? rowDots.text[0] : '.', n);
}
}
}
void Apply()
{
if (Fill != null && !Mathf.Approximately(Fill.fillAmount, Shown)) Fill.fillAmount = Shown;
int pct = Mathf.FloorToInt(Shown * 100f + 0.0001f);
if (PercentText != null && rowPercent != null && pct != shownPct)
{
shownPct = pct;
PercentText.text = string.Format(CultureInfo.InvariantCulture, rowPercent.text, pct);
}
}
void ApplySize()
{
if (SizeText != null && rowSize != null)
SizeText.text = string.Format(CultureInfo.InvariantCulture, rowSize.text, Bytes(DoneBytes), Bytes(TotalBytes));
}
// ---------- 만들기 도움 ----------
/// <summary>표 좌표(기준 화면 1080×1920 px · 왼쪽 위 0,0 · 아래로 +) → 가운데 기준 anchoredPosition.</summary>
static Vector2 Pos(float x, float y)
{
return new Vector2(x - UiLayer.Reference.x * 0.5f, UiLayer.Reference.y * 0.5f - y);
}
static Image Pic(string name, Transform parent, string sprite)
{
var rt = UiKit.Rect(name, parent, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(100f, 100f));
var img = rt.gameObject.AddComponent<Image>();
if (!string.IsNullOrEmpty(sprite)) img.sprite = LoadingTable.Sprite(sprite);
img.raycastTarget = false;
return img;
}
static void Place(Image img, LoadingTable.Row r)
{
var rt = (RectTransform)img.transform;
rt.anchoredPosition = Pos(r.x, r.y);
rt.sizeDelta = new Vector2(r.w, r.h);
img.color = r.color;
}
static Text Txt(string name, Transform parent, LoadingTable.Row r)
{
var t = UiKit.Label(parent, r.text, r.size, r.color, TextAnchor.MiddleCenter, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Pos(r.x, r.y), new Vector2(r.w, r.h));
t.name = name;
t.horizontalOverflow = HorizontalWrapMode.Overflow;
UiKit.Title(t);
if (r.b > 0f)
{
Color sc;
if (!ColorUtility.TryParseHtmlString(r.reference, out sc)) sc = UiKit.Stroke;
UISkin.Stroke(t, sc, r.b);
}
return t;
}
}
/// <summary>UI 이미지 프레임 반복(로딩 화면 달리기 · 고른 간격 fps · 시작 위상 초) — 매 프레임 바뀌므로 Frame 캔버스 안에서만(UiLayer.Isolate).</summary>
public class UiSpriteLoop : MonoBehaviour
{
public Image Img { get; private set; }
public Sprite[] Frames { get; private set; }
public float Fps { get; private set; }
public int Index { get; private set; }
float t0;
public static UiSpriteLoop Attach(Image img, Sprite[] frames, float fps, float phase)
{
if (img == null || frames == null || frames.Length == 0) return null;
var a = img.gameObject.AddComponent<UiSpriteLoop>();
a.Img = img;
a.Frames = frames;
a.Fps = fps > 0f ? fps : 12f;
a.t0 = LoadingScreen.Now - phase;
UiLayer.Isolate(img.gameObject);
return a;
}
/// <summary>지금 시각의 프레임 번호(0 ~ n-1).</summary>
public int FrameAt(float now)
{
int n = Frames.Length;
int k = Mathf.FloorToInt((now - t0) * Fps) % n;
return k < 0 ? k + n : k;
}
void Update()
{
if (Img == null || Frames == null || Frames.Length == 0) return;
Index = FrameAt(LoadingScreen.Now);
var sp = Frames[Index];
if (Img.sprite != sp) Img.sprite = sp;
}
}
/// <summary>
/// 말 뒤 먼지(표 dust 줄) — 달리는 그림의 정해진 프레임(발굽이 땅을 찰 때 · s_text 「on:3,10」)마다 또는 초당 f_fps 개로 먼지 덩이를 내고,
/// 덩이는 뒤(왼쪽)로 밀리며 떠올라 커지고 옅어진다. 덩이 수 = i_size 개 재사용(만들고 지우지 않음).
/// </summary>
public class LoadingDust : MonoBehaviour
{
class Puff
{
public RectTransform rt;
public Image img;
public float t = -1f;
public float x;
public float y;
public float s;
public float rot;
public float vx;
}
readonly List<Puff> pool = new List<Puff>();
LoadingTable.Row row;
UiSpriteLoop host;
Vector2 origin;
Sprite[] sprites;
int[] onFrames;
int lastFrame = -1;
float acc;
int next;
float lastNow = -1f;
System.Random rng;
public int Alive
{
get
{
int n = 0;
foreach (var p in pool)
if (p.t >= 0f)
n++;
return n;
}
}
public static LoadingDust Create(Transform parent, LoadingTable.Row r, UiSpriteLoop host, System.Func<float, float, Vector2> pos)
{
var sprites = LoadingTable.SpriteList(r.sprite);
if (sprites.Length == 0) return null;
var rt = UiKit.Fill("Dust_" + r.id, parent);
var d = rt.gameObject.AddComponent<LoadingDust>();
d.row = r;
d.host = host;
d.sprites = sprites;
d.rng = new System.Random(r.id.GetHashCode());
// 먼지 기준점 = 표 f_x,f_y(기준 화면 px) · 달리는 그림을 따라 붙이지 않고 고정(무리도 제자리)
d.origin = pos(r.x, r.y);
if (r.text.StartsWith("on:"))
{
var parts = r.text.Substring(3).Split(new[] { ',', ';' }, System.StringSplitOptions.RemoveEmptyEntries);
var list = new List<int>();
foreach (var s in parts)
{
int v;
if (int.TryParse(s.Trim(), out v)) list.Add(v);
}
d.onFrames = list.ToArray();
}
int n = Mathf.Max(1, r.size);
for (int i = 0; i < n; i++)
{
var prt = UiKit.Rect("Puff", rt, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(r.w, r.w));
var img = prt.gameObject.AddComponent<Image>();
img.raycastTarget = false;
img.sprite = sprites[i % sprites.Length];
img.preserveAspect = true;
img.color = new Color(r.color.r, r.color.g, r.color.b, 0f);
prt.gameObject.SetActive(false);
d.pool.Add(new Puff { rt = prt, img = img });
}
UiLayer.Isolate(rt.gameObject);
return d;
}
void Emit()
{
var p = pool[next];
next = (next + 1) % pool.Count;
float j = row.phase; // 흩어짐 px
p.t = 0f;
p.x = origin.x + (float)(rng.NextDouble() * 2 - 1) * j;
p.y = origin.y + (float)(rng.NextDouble() * 2 - 1) * j * 0.4f;
p.s = 0.75f + (float)rng.NextDouble() * 0.5f;
p.rot = (float)(rng.NextDouble() * 2 - 1) * 25f;
p.vx = row.a * (0.8f + (float)rng.NextDouble() * 0.4f);
p.img.sprite = sprites[rng.Next(sprites.Length)];
p.rt.gameObject.SetActive(true);
}
void Update()
{
if (row == null || pool.Count == 0) return;
float now = LoadingScreen.Now;
float dt = lastNow < 0f ? 0f : Mathf.Max(0f, now - lastNow);
lastNow = now;
if (onFrames != null && onFrames.Length > 0 && host != null)
{
int f = host.Index;
if (f != lastFrame)
{
foreach (var k in onFrames)
if (k == f)
Emit();
lastFrame = f;
}
}
else if (row.fps > 0f)
{
acc += dt * row.fps;
while (acc >= 1f)
{
acc -= 1f;
Emit();
}
}
float life = row.b > 0f ? row.b : 0.6f;
foreach (var p in pool)
{
if (p.t < 0f) continue;
p.t += dt;
float u = p.t / life;
if (u >= 1f)
{
p.t = -1f;
p.rt.gameObject.SetActive(false);
continue;
}
float ease = 1f - (1f - u) * (1f - u);
p.rt.anchoredPosition = new Vector2(p.x + p.vx * p.t, p.y + row.c * ease);
float size = Mathf.Lerp(row.w, row.h, ease) * p.s;
p.rt.sizeDelta = new Vector2(size, size);
p.rt.localEulerAngles = new Vector3(0f, 0f, p.rot);
// 처음 15 % 는 나타나고 그 뒤로 옅어짐
float a = u < 0.15f ? u / 0.15f : 1f - (u - 0.15f) / 0.85f;
p.img.color = new Color(row.color.r, row.color.g, row.color.b, row.color.a * a);
}
}
}
/// <summary>
/// 로딩 화면 표(Resources/UI/loading_screen.csv · utf-8-sig) — 좌표 = 기준 화면 1080×1920 px · 왼쪽 위 0,0 · 아래로 +.
/// s_kind: bg · logo · bar · fill · runner · dust · text · option · s_set = download | loading | 빈칸(공통). 칸 설명 = 표 머리 주석.
/// </summary>
public static class LoadingTable
{
public const string TablePath = "UI/loading_screen";
public class Row
{
public string kind = "";
/// <summary>표 세트(download | loading · 빈칸 = 모든 세트 공통).</summary>
public string set = "";
public string id = "";
public string sprite = "";
public string reference = "";
public string text = "";
public string note = "";
public Color color = Color.white;
public float x;
public float y;
public float w;
public float h;
public float fps;
public float phase;
public float scale = 1f;
public float a;
public float b;
public float c;
public int size;
}
static List<Row> rows;
public static void Reload()
{
rows = null;
}
public static List<Row> Rows
{
get
{
if (rows == null) rows = Load(TablePath);
return rows;
}
}
static bool InSet(Row r, string set)
{
return set == null || r.set.Length == 0 || r.set == set;
}
/// <summary>kind(·id) 의 첫 줄 · set = 그 세트 줄 + 공통 줄만(null = 모든 줄).</summary>
public static Row First(string kind, string id = null, string set = null)
{
foreach (var r in Rows)
if (r.kind == kind && (id == null || r.id == id) && InSet(r, set))
return r;
return null;
}
public static List<Row> All(string kind, string set = null)
{
var list = new List<Row>();
foreach (var r in Rows)
if (r.kind == kind && InSet(r, set))
list.Add(r);
return list;
}
public static Sprite Sprite(string path)
{
return string.IsNullOrEmpty(path) ? null : Resources.Load<Sprite>(path);
}
/// <summary>| 로 나눈 스프라이트 경로들.</summary>
public static Sprite[] SpriteList(string paths)
{
var list = new List<Sprite>();
foreach (var p in paths.Split(new[] { '|' }, System.StringSplitOptions.RemoveEmptyEntries))
{
var s = Sprite(p.Trim());
if (s != null) list.Add(s);
}
return list.ToArray();
}
/// <summary>폴더 안 스프라이트 전부(이름 순 = 프레임 순).</summary>
public static Sprite[] Frames(string folder)
{
var all = Resources.LoadAll<Sprite>(folder);
System.Array.Sort(all, (p, q) => string.CompareOrdinal(p.name, q.name));
return all;
}
public static List<Row> Load(string path)
{
var list = new List<Row>();
var ta = Resources.Load<TextAsset>(path);
if (ta == null) return list;
string[] head = null;
foreach (var raw in ta.text.Replace("\r", "").Split('\n'))
{
var line = raw.TrimStart('');
if (line.Trim().Length == 0 || line.TrimStart().StartsWith("#")) continue;
var cells = SkillVfx.SplitCsv(line);
if (head == null)
{
head = cells;
continue;
}
var r = new Row();
for (int i = 0; i < head.Length && i < cells.Length; i++)
{
string k = head[i].Trim(), v = cells[i].Trim();
if (v.Length == 0) continue;
switch (k)
{
case "s_kind": r.kind = v.ToLowerInvariant(); break;
case "s_set": r.set = v.ToLowerInvariant(); break;
case "s_id": r.id = v; break;
case "s_sprite": r.sprite = v; break;
case "s_ref": r.reference = v; break;
case "s_text": r.text = v; break;
case "s_note": r.note = v; break;
case "s_color":
Color c;
if (ColorUtility.TryParseHtmlString(v, out c)) r.color = c;
break;
case "f_x": r.x = F(v); break;
case "f_y": r.y = F(v); break;
case "f_w": r.w = F(v); break;
case "f_h": r.h = F(v); break;
case "f_fps": r.fps = F(v); break;
case "f_phase": r.phase = F(v); break;
case "f_scale": r.scale = F(v); break;
case "f_a": r.a = F(v); break;
case "f_b": r.b = F(v); break;
case "f_c": r.c = F(v); break;
case "i_size": r.size = (int)F(v); break;
}
}
if (r.kind.Length > 0) list.Add(r);
}
return list;
}
static float F(string v)
{
float f;
return float.TryParse(v, NumberStyles.Float, CultureInfo.InvariantCulture, out f) ? f : 0f;
}
}
}