using System.Collections; using System.Globalization; using NUnit.Framework; using TKS.OutGame; using UnityEngine; using UnityEngine.TestTools; using UnityEngine.UI; namespace TKS.Tests { /// /// 로딩·리소스 다운로드 화면(PD 2026-11-13 · 두 경우) — 다운로드 세트(배경 + 위 타이틀 로고 + 조운·병졸 + 진행률·용량) / 로딩 세트(검은 화면 + 유비·관우·장비 말 타고 질주 · 심플). /// ① 표 runner 줄 수만큼 달리는 그림 · 프레임 ≥ 2 · 시간이 지나면 프레임이 바뀜(제자리 반복) ② 캔버스 층(P41): 루트 Static · 바 채움·무리·먼지 = Frame · 글자 = Event /// ③ 진행률(0~1) → 바 채움·% 글자 · 용량(바이트) → 「12.4 MB / 48.0 MB」 · 부드럽게 따라감 ④ 먼지가 나옴 ⑤ 모드를 바꾸면 그 세트로 다시 만듦(같은 객체 · 값 유지) ⑥ Hide. /// 기대 문구·줄 수는 표 Resources/UI/loading_screen.csv 에서 세트별로 읽는다(C45). /// public class LoadingScreenTests { Camera cam; [SetUp] public void SetUp() { LoadingTable.Reload(); cam = new GameObject("TestCam").AddComponent(); cam.orthographic = true; } [TearDown] public void TearDown() { LoadingScreen.HideCurrent(); if (cam != null) Object.Destroy(cam.gameObject); } static UiRate RateOf(Transform t) { var c = t.GetComponentInParent(true); Assert.IsNotNull(c, t.name + ": 캔버스 없음"); var tag = c.GetComponent(); Assert.IsNotNull(tag, c.name + ": 등급 표시 없음"); return tag.rate; } /// a 가 b 보다 계층 순서상 먼저(= uGUI 에서 뒤에 그려짐)인지 · 공통 조상 아래 형제 순서로 판정. static bool DrawnBefore(Transform a, Transform b) { var pa = new System.Collections.Generic.List(); for (var t = a; t != null; t = t.parent) pa.Insert(0, t); var pb = new System.Collections.Generic.List(); for (var t = b; t != null; t = t.parent) pb.Insert(0, t); int i = 0; while (i < pa.Count && i < pb.Count && pa[i] == pb[i]) i++; if (i >= pa.Count || i >= pb.Count) return false; // 한쪽이 다른 쪽의 조상 return pa[i].GetSiblingIndex() < pb[i].GetSiblingIndex(); } /// 세트 하나의 공통 검사(달리는 무리 · 층 · 먼지 · 상태 문구). static IEnumerator CheckSet(LoadingScreen ls, string set) { Assert.AreEqual(set, ls.Set); var rows = LoadingTable.All("runner", set); Assert.Greater(rows.Count, 0, set + ": 표에 runner 줄 없음"); Assert.AreEqual(rows.Count, ls.Runners.Count, set + ": 표 runner 줄 수 = 화면 그림 수"); foreach (var r in ls.Runners) { Assert.GreaterOrEqual(r.Frames.Length, 2, r.name + ": 프레임 2 장 이상"); Assert.AreEqual(UiRate.Frame, RateOf(r.transform), r.name + ": 매 프레임 그림은 Frame 캔버스"); } Assert.AreEqual(UiRate.Static, ls.RootCanvas.GetComponent().rate, "루트 = Static"); Assert.IsNotNull(ls.Fill, set + ": 표 fill 줄"); Assert.AreEqual(Image.Type.Filled, ls.Fill.type); Assert.AreEqual(UiRate.Frame, RateOf(ls.Fill.transform), "바 채움 = Frame"); Assert.IsNotNull(ls.StatusText, set + ": 상태 문구"); Assert.AreEqual(UiRate.Event, RateOf(ls.StatusText.transform), "상태 글자 = Event"); Assert.AreEqual(LoadingTable.First("text", "status", set).text, ls.StatusText.text); foreach (var c in ls.GetComponentsInChildren(false)) if (c != ls.RootCanvas) Assert.IsFalse(c.overrideSorting, c.name + ": 중첩 캔버스 overrideSorting 끔"); Assert.AreEqual(LoadingTable.All("logo", set).Count > 0, ls.Logo != null, set + ": 로고 줄 ↔ 화면"); Assert.AreEqual(LoadingTable.All("dust", set).Count, ls.Dusts.Count, set + ": 표 dust 줄 수"); // 먼지 = 무리 전부의 뒤(앞 말 먼지가 뒤 말 다리를 덮지 않음 · QA r3 M1) foreach (var d in ls.Dusts) foreach (var r in ls.Runners) Assert.IsTrue(DrawnBefore(d.transform, r.transform), set + ": 먼지 " + d.name + " 가 " + r.name + " 보다 먼저(뒤에) 그려짐"); // 제자리 반복 — 시간이 지나면 프레임이 바뀜 var lead = ls.Runners[ls.Runners.Count - 1]; int f0 = lead.Index; var s0 = lead.Img.sprite; yield return new WaitForSecondsRealtime(0.25f); Assert.AreNotEqual(f0, lead.Index, set + ": 프레임이 넘어감"); Assert.AreNotSame(s0, lead.Img.sprite); if (ls.Dusts.Count > 0) { yield return new WaitForSecondsRealtime(1.0f); int alive = 0; foreach (var d in ls.Dusts) alive += d.Alive; Assert.Greater(alive, 0, set + ": 먼지가 나옴"); Assert.AreEqual(UiRate.Frame, RateOf(ls.Dusts[0].transform), "먼지 = Frame"); } } [UnityTest] public IEnumerator Download_LogoRunnersProgressSize() { var ls = LoadingScreen.Show(cam, LoadingMode.Download); yield return null; yield return CheckSet(ls, LoadingScreen.SetOf(LoadingMode.Download)); Assert.IsNotNull(ls.Logo, "다운로드 = 위 타이틀 로고(PD 2026-11-13)"); Assert.IsNotNull(ls.Logo.sprite, "로고 그림"); var pct = LoadingTable.First("text", "percent", ls.Set); ls.SetProgress(0.5f, true); Assert.AreEqual(0.5f, ls.Fill.fillAmount, 1e-4f); Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, pct.text, 50), ls.PercentText.text); ls.SetDownload(13002342L, 50331648L, true); // 12.4 MB / 48.0 MB var size = LoadingTable.First("text", "size", ls.Set); Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, size.text, "12.4 MB", "48.0 MB"), ls.SizeText.text); Assert.AreEqual(13002342.0 / 50331648.0, ls.Fill.fillAmount, 1e-4); Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, pct.text, 25), ls.PercentText.text); ls.SetProgress(1f); Assert.Less(ls.Shown, 1f, "목표로 바로 뛰지 않고 따라감"); float before = ls.Shown; yield return new WaitForSecondsRealtime(0.2f); Assert.Greater(ls.Shown, before, "보이는 진행률이 목표 쪽으로 움직임"); Assert.LessOrEqual(ls.Shown, 1f); ls.Hide(); yield return null; Assert.IsNull(LoadingScreen.Current); } [UnityTest] public IEnumerator Loading_SimpleBlack_ThreeRiders_ModeSwitch() { var ls = LoadingScreen.Show(cam, LoadingMode.Loading); yield return null; yield return CheckSet(ls, LoadingScreen.SetOf(LoadingMode.Loading)); Assert.AreEqual(3, LoadingTable.All("runner", ls.Set).Count, "로딩 = 유비·관우·장비 3기(PD 2026-11-13)"); var bg = ls.transform.Find("Bg"); Assert.IsNotNull(bg, "배경 판(누름 막기)"); Assert.IsNull(bg.GetComponent().sprite, "로딩 = 배경 그림 없음(검은 화면)"); Assert.Less(bg.GetComponent().color.grayscale, 0.05f, "검정"); Assert.IsNull(ls.Logo, "로딩 = 로고 없음"); Assert.IsNull(ls.SizeText, "로딩 = 용량 줄 없음"); // 모드 바꾸기 = 같은 객체에서 그 세트로 다시 만듦 · 진행률 유지 ls.SetProgress(0.4f, true); ls.SetMode(LoadingMode.Download); yield return null; Assert.AreSame(ls, LoadingScreen.Current); Assert.AreEqual(LoadingScreen.SetOf(LoadingMode.Download), ls.Set); Assert.AreEqual(LoadingTable.All("runner", ls.Set).Count, ls.Runners.Count); Assert.AreEqual(0.4f, ls.Fill.fillAmount, 1e-4f, "진행률 유지"); ls.SetMode(LoadingMode.Loading); yield return null; Assert.AreEqual(3, ls.Runners.Count); Assert.AreEqual(0, CountInactive(ls.transform), "이전 세트 자식은 다음 프레임에 지워짐"); Assert.IsNull(ls.transform.Find("Logo"), "다운로드 세트 로고가 남지 않음"); } static int CountInactive(Transform t) { int n = 0; for (int i = 0; i < t.childCount; i++) if (!t.GetChild(i).gameObject.activeSelf) n++; return n; } [Test] public void Bytes_Format() { Assert.AreEqual("12.4 MB", LoadingScreen.Bytes(13002342L)); Assert.AreEqual("48.0 MB", LoadingScreen.Bytes(50331648L)); Assert.AreEqual("1.5 GB", LoadingScreen.Bytes(1610612736L)); Assert.AreEqual("50 KB", LoadingScreen.Bytes(51200L)); } } }