ThreeKingdomsSlingshot/Assets/Tests/PlayMode/TitleScreenTests.cs

180 lines
8.6 KiB
C#
Raw Normal View History

타이틀 화면 — 장수 모임 등장 · R22 대기 모션 · 꾸미기 연출 · Press Any Key(PD 2026-11-13 확정) - OutGame/UI/TitleScreen.cs: BootFlow.TitleProvider 등록(메모리 세이브 = 테스트·캡처는 건너뜀 → 기존처럼 바로 로비) 장수 6명이 화면 밖에서 뒷줄→앞줄로 모임(착지 흙먼지) → 로고 내려앉음·둥실·반짝이 → Press Any Key 깜박임 누름 = 등장 중이면 건너뜀 · 글자가 뜬 뒤면 검정 페이드 → 로비 · 타이틀 동안 아래 루트 캔버스 끔 층(P41) = 루트 Static(배경 · cover) → Ambient Frame(햇살·빛 번짐·흙먼지·불씨) → Stage Frame(장수·로고·글자 · contain) - 표 Resources/UI/title_screen.csv(자리·등장 박자·대기 유지 초·깜빡임·로고·글자·페이드) · title_ambient.csv(LobbyAmbient) - 그림 Resources/UI/Title/(배경 · 로고 A · 장수 6 × R22 자동 리그 4자세+깜빡임 · pack) · 아틀라스 UI_Title(ui_atlas.csv folder 1줄) - LobbyAmbient.Load: 테마에 없는 id 가 경로면 Resources 폴백(1줄) - 캡처 TitleCaptureDriver + Editor/TitleCapture(-executeMethod TKS.EditorTools.TitleCapture.Capture -titleDir) · 테스트 TitleScreenTests 8 - 검증: 에디터 TitleScreenTests 8/8 · BootFlowTests 4/4 · OutGameTests.Lobby_Tabs_Back_ComingSoon 1/1 · QA r1 B4(화면비) → r2 PASS(B 0) - 의존: BootFlow.cs·LoadingScreen.cs(로딩 화면 세션 · 아직 미커밋) — 그 커밋 뒤 push Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 03:50:04 +00:00
using System.Collections;
using NUnit.Framework;
using TKS.OutGame;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.UI;
namespace TKS.Tests
{
/// <summary>
/// 타이틀 화면(PD 2026-11-13 「장수가 모이는 연출로 시작 · 대기 움직임 · 꾸미기 가벼운 애니 · "Press Any Key" 깜박임 · 터치 → 다음」).
/// ① 표대로 장수 6 · 프레임(쉼 + 대기 자세) · 로고 · 글자 ② 등장 = 화면 밖에서 제자리로 · 끝나면 글자 깜박임 ③ 누름 = 등장 중이면 건너뜀 · 글자가 뜬 뒤면 페이드 → 시작 함수 · 타이틀 닫힘
/// ④ 대기 프레임이 바뀜 ⑤ 층(P41) = 루트 Static · 연출·글자 Frame · 글자 층에 레이캐스터 없음 ⑥ 시작 흐름 훅 = 메모리 세이브(테스트·캡처)면 건너뜀(기존 테스트 = 바로 로비).
/// </summary>
public class TitleScreenTests
{
Camera cam;
[SetUp]
public void SetUp()
{
cam = new GameObject("TestCam").AddComponent<Camera>();
cam.orthographic = true;
TitleScreen.ForceShow = false;
}
[TearDown]
public void TearDown()
{
TitleScreen.ForceShow = false;
if (TitleScreen.Current != null) Object.Destroy(TitleScreen.Current.gameObject);
foreach (var f in Object.FindObjectsByType<ScreenFader>(FindObjectsSortMode.None)) Object.Destroy(f.gameObject);
if (cam != null) Object.Destroy(cam.gameObject);
}
[UnityTest]
public IEnumerator Build_FollowsTable()
{
var rows = LoadingTable.Load(TitleScreen.TablePath);
int heroRows = rows.FindAll(r => r.kind == "hero").Count;
var ts = TitleScreen.Show(cam, null);
yield return null;
Assert.AreEqual(heroRows, ts.Heroes.Count, "표 hero 줄 수만큼 장수");
Assert.GreaterOrEqual(ts.Heroes.Count, 6);
foreach (var h in ts.Heroes) Assert.GreaterOrEqual(h.frames.Length, 4, h.id + " = 쉼 + 대기 자세 3 이상");
Assert.IsNotNull(ts.Logo != null ? ts.Logo.sprite : null, "로고 그림");
Assert.IsNotNull(ts.PressText, "Press Any Key 글자");
Assert.AreEqual(rows.Find(r => r.kind == "press").text, ts.PressText.text);
Assert.IsNotNull(ts.Ambient, "꾸미기 연출 층(LobbyAmbient)");
}
[UnityTest]
public IEnumerator Intro_GathersThenPressBlinks()
{
var ts = TitleScreen.Show(cam, null);
ts.SetTime(0f);
yield return null;
var first = ts.Heroes[0];
Assert.Less(first.img.color.a, 0.01f, "시작 전 장수는 안 보임");
ts.SetTime(first.row.phase + first.row.fps * 0.3f);
var target = TitleScreen.Target(first.row);
Assert.Greater(Vector2.Distance(first.rt.anchoredPosition, target), 1f, "날아오는 중 = 제자리 아님");
Assert.IsFalse(ts.PressReady, "등장 중에는 글자 안 뜸");
ts.SkipIntro();
foreach (var h in ts.Heroes)
{
Assert.Less(Vector2.Distance(h.rt.anchoredPosition, TitleScreen.Target(h.row)), 0.01f, h.id + " 제자리");
Assert.AreEqual(0f, Mathf.Repeat(h.rt.anchoredPosition.x - h.row.w * 0.5f, 1f), 0.001f, h.id + " 왼쪽 모서리 정수 픽셀(QA r1 M6)");
Assert.IsTrue(h.landed, h.id + " 착지");
}
Assert.IsTrue(ts.IntroDone);
float until = Time.unscaledTime + 2f;
while (!ts.PressReady && Time.unscaledTime < until) yield return null;
Assert.IsTrue(ts.PressReady, "등장 끝 뒤 글자가 뜸");
float minA = 1f, maxA = 0f;
for (float w = Time.unscaledTime + 1.6f; Time.unscaledTime < w;)
{
yield return null;
minA = Mathf.Min(minA, ts.PressText.color.a);
maxA = Mathf.Max(maxA, ts.PressText.color.a);
}
Assert.Greater(maxA - minA, 0.3f, "글자 깜박임(알파가 오르내림)");
}
[UnityTest]
public IEnumerator Press_SkipsIntroThenStarts()
{
bool started = false;
var ts = TitleScreen.Show(cam, () => started = true);
yield return null;
ts.Press();
Assert.IsTrue(ts.IntroDone, "등장 중 누름 = 끝 모습으로");
Assert.IsFalse(ts.Leaving, "첫 누름은 건너뛰기만");
float until = Time.unscaledTime + 2f;
while (!ts.PressReady && Time.unscaledTime < until) yield return null;
ts.Press();
Assert.IsTrue(ts.Leaving);
until = Time.unscaledTime + 3f;
while (!started && Time.unscaledTime < until) yield return null;
Assert.IsTrue(started, "페이드 뒤 시작 함수(로비)");
yield return null;
Assert.IsNull(TitleScreen.Current, "타이틀 닫힘");
}
[UnityTest]
public IEnumerator Idle_FramesChange()
{
var ts = TitleScreen.Show(cam, null);
ts.SkipIntro();
yield return null;
var seen = new System.Collections.Generic.Dictionary<string, System.Collections.Generic.HashSet<Sprite>>();
foreach (var h in ts.Heroes) seen[h.id] = new System.Collections.Generic.HashSet<Sprite>();
for (float w = Time.unscaledTime + 1.8f; Time.unscaledTime < w;)
{
yield return null;
foreach (var h in ts.Heroes) seen[h.id].Add(h.img.sprite);
}
foreach (var h in ts.Heroes) Assert.GreaterOrEqual(seen[h.id].Count, 3, h.id + " 대기 자세가 바뀜");
}
[UnityTest]
public IEnumerator Layers_P41()
{
var ts = TitleScreen.Show(cam, null);
yield return null;
Assert.AreEqual(UiRate.Static, ts.RootCanvas.GetComponent<UiLayerInfo>().rate, "루트 = 배경만(Static)");
Assert.AreEqual(UiRate.Frame, ts.Ui.GetComponent<UiLayerInfo>().rate, "로고·글자 = Frame");
Assert.IsNull(ts.Ui.GetComponent<GraphicRaycaster>(), "글자 층에 레이캐스터 없음(입력 = 폴링)");
var amb = ts.Ambient.GetComponent<UiLayerInfo>();
Assert.IsNotNull(amb);
Assert.AreEqual(UiRate.Frame, amb.rate, "연출·장수 = Frame");
}
[UnityTest]
public IEnumerator Show_HidesCanvasesBelowThenRestores()
{
var below = new GameObject("BelowCanvas");
var bc = UiLayer.Root(below, cam, 100, 1f, UiLayer.Reference, UiRate.Static, false);
var above = new GameObject("AboveCanvas");
var ac = UiLayer.Root(above, cam, 990, 0.2f, UiLayer.Reference, UiRate.Static, false);
var ts = TitleScreen.Show(cam, null);
yield return null;
Assert.IsFalse(bc.enabled, "타이틀 아래 루트 캔버스 = 끔(QA r1 M5)");
Assert.IsTrue(ac.enabled, "타이틀 위(페이드 등) = 그대로");
ts.Close();
yield return null;
Assert.IsTrue(bc.enabled, "닫으면 되살림");
Object.Destroy(below);
Object.Destroy(above);
}
[UnityTest]
public IEnumerator Stage_FitsInsideScreen()
{
var ts = TitleScreen.Show(cam, null);
yield return null;
var root = (RectTransform)ts.transform;
var sz = root.rect.size;
Assert.LessOrEqual(ts.Stage.localScale.x * TitleScreen.ArtW, sz.x + 0.5f, "무대 폭 ≤ 화면(contain · QA r1 B3·B4)");
Assert.LessOrEqual(ts.Stage.localScale.y * TitleScreen.ArtH, sz.y + 0.5f, "무대 높이 ≤ 화면(QA r1 B1·B2)");
Assert.GreaterOrEqual(ts.Art.localScale.x * TitleScreen.ArtW, sz.x - 0.5f, "배경 = 화면을 덮음(cover)");
Assert.GreaterOrEqual(ts.Art.localScale.y * TitleScreen.ArtH, sz.y - 0.5f);
Assert.AreEqual(ts.Stage, ts.Logo.rectTransform.parent, "로고 = 무대 위(장수와 같은 배율)");
}
[Test]
public void Provider_SkipsOnMemorySave()
{
SaveStore.UseMemory(true);
bool called = false;
Assert.IsFalse(TitleScreen.Provide(cam, () => called = true), "메모리 세이브(테스트·캡처) = 타이틀 건너뜀 → BootFlow 가 로비");
Assert.IsNull(TitleScreen.Current);
TitleScreen.ForceShow = true;
Assert.IsTrue(TitleScreen.Provide(cam, () => called = true), "ForceShow = 띄움");
Assert.IsNotNull(TitleScreen.Current);
Assert.IsFalse(called);
}
}
}