175 lines
9.1 KiB
C#
175 lines
9.1 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using NUnit.Framework;
|
|
using TKS.OutGame;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.TestTools;
|
|
using UnityEngine.UI;
|
|
|
|
namespace TKS.Tests
|
|
{
|
|
/// <summary>
|
|
/// UI 캔버스 층 · 아틀라스 구조(PD 2026-11-10 「리소스·정보가 얼마나 자주 바뀌는지에 따라 캔버스를 나누어 최적화 · 드로우콜 최소화 구조 필수」) —
|
|
/// ① 중첩 캔버스 = overrideSorting 끔 + 등급 표시 ② 아웃게임 화면마다 자기 캔버스 · 스크롤 내용 = Frame ③ 매 프레임 요소(호흡·튀김·커서·미니맵)는 Frame 캔버스 안
|
|
/// ④ 중첩 캔버스 위 누름이 그대로 동작하고 팝업이 아래 화면을 가림 ⑤ 테마 스프라이트(큰 그림 제외)는 아틀라스로 묶임 · 월드 9-slice 사본이 아틀라스 자리에서 잘림.
|
|
/// 기준값(큰 그림 넓이)은 표 Assets/Data/UI/ui_atlas.csv 에서 읽는다(C45).
|
|
/// </summary>
|
|
public class UILayerTests
|
|
{
|
|
[TearDown]
|
|
public void TearDown()
|
|
{
|
|
SaveStore.UseMemory(false);
|
|
}
|
|
|
|
static void AssertNested(Canvas c, UiRate rate, string what)
|
|
{
|
|
Assert.IsNotNull(c, what + ": 캔버스 없음");
|
|
// 꺼진 캔버스는 isRootCanvas = true 로 보고되므로 부모 사슬로 판정
|
|
Assert.IsNotNull(c.transform.parent != null ? c.transform.parent.GetComponentInParent<Canvas>(true) : null, what + ": 중첩이어야 함");
|
|
Assert.IsFalse(c.overrideSorting, what + ": overrideSorting 은 끈다(순서·자르기 유지)");
|
|
var tag = c.GetComponent<UiLayerInfo>();
|
|
Assert.IsNotNull(tag, what + ": 등급 표시 없음");
|
|
Assert.AreEqual(rate, tag.rate, what + ": 등급");
|
|
}
|
|
|
|
/// <summary>t 가 그리는 캔버스(가장 가까운 것)가 Frame 등급인가.</summary>
|
|
static void AssertInFrame(Transform t, string what)
|
|
{
|
|
var c = t.GetComponentInParent<Canvas>(true);
|
|
Assert.IsNotNull(c, what);
|
|
var tag = c.GetComponent<UiLayerInfo>();
|
|
Assert.IsTrue(tag != null && tag.rate == UiRate.Frame, what + ": 매 프레임 요소가 Frame 캔버스 밖(" + c.name + ")");
|
|
}
|
|
|
|
static GameObject TopHit(Vector2 screen)
|
|
{
|
|
var hits = new List<RaycastResult>();
|
|
EventSystem.current.RaycastAll(new PointerEventData(EventSystem.current) { position = screen }, hits);
|
|
return hits.Count > 0 ? hits[0].gameObject : null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator Canvases_SplitByUpdateRate_InputAndAtlas()
|
|
{
|
|
WorldProgress.Reset();
|
|
SaveStore.UseMemory(true);
|
|
SceneManager.LoadScene("Battle");
|
|
yield return null;
|
|
yield return null;
|
|
var og = OutGameRoot.Instance;
|
|
Assert.IsNotNull(og);
|
|
|
|
// ② 화면마다 자기 캔버스(Event · 누름 있음)
|
|
foreach (var scr in new OutGameScreen[] { og.Lobby, og.HeroList, og.HeroDetail, og.Formation, og.Trade, og.Domestic, og.Treasures, og.Arena })
|
|
{
|
|
AssertNested(scr.Rt.GetComponent<Canvas>(), UiRate.Event, scr.GetType().Name);
|
|
Assert.IsNotNull(scr.Rt.GetComponent<GraphicRaycaster>(), scr.GetType().Name + ": 레이캐스터");
|
|
}
|
|
og.Show(og.HeroList);
|
|
yield return null;
|
|
yield return null;
|
|
var scrolls = og.HeroList.Rt.GetComponentsInChildren<ScrollRect>(true);
|
|
Assert.Greater(scrolls.Length, 0, "영웅 목록 스크롤");
|
|
foreach (var sr in scrolls) AssertNested(sr.content.GetComponent<Canvas>(), UiRate.Frame, "스크롤 내용 " + sr.name);
|
|
|
|
// ④ 중첩 캔버스(스크롤 내용) 위 카드 누름이 맞고, 팝업을 띄우면 같은 점이 팝업에 맞는다
|
|
var content = scrolls[0].content;
|
|
Selectable card = null;
|
|
foreach (var s in content.GetComponentsInChildren<Selectable>()) { card = s; break; }
|
|
Component handler = card;
|
|
if (handler == null) handler = content.GetComponentInChildren<TapDrag>();
|
|
Assert.IsNotNull(handler, "영웅 카드(누름) 없음");
|
|
var crt = (RectTransform)handler.transform;
|
|
var screen = RectTransformUtility.WorldToScreenPoint(og.Cam, crt.TransformPoint(crt.rect.center));
|
|
var hit = TopHit(screen);
|
|
Assert.IsNotNull(hit, "카드 위 레이캐스트 결과 없음");
|
|
Assert.IsTrue(hit.transform.IsChildOf(content), "카드 위 첫 결과가 스크롤 내용 밖: " + hit.name);
|
|
og.Modal("검사", "팝업", new OutGameRoot.ModalButton("확인", Color.gray, null));
|
|
yield return null;
|
|
hit = TopHit(screen);
|
|
Assert.IsNotNull(hit);
|
|
Assert.IsTrue(hit.transform.IsChildOf(og.TopRoot), "팝업이 아래 화면 누름을 가려야 함: " + hit.name);
|
|
og.CloseModal();
|
|
|
|
// ③ 아웃게임의 매 프레임 요소
|
|
og.Show(og.Treasures);
|
|
og.Treasures.SetPage(2);
|
|
yield return null;
|
|
foreach (var p in Object.FindObjectsByType<UiPulse>(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) AssertInFrame(p.transform, "호흡 " + p.name);
|
|
|
|
// 월드맵: 스크롤 내용 = Frame · 커서 = 자기 Frame 캔버스 · 팝업 = Event
|
|
var wm = og.Map;
|
|
og.OpenWorldMap();
|
|
yield return null;
|
|
yield return null;
|
|
var cursor = GameObject.Find("cursor");
|
|
if (cursor != null)
|
|
{
|
|
AssertNested(cursor.GetComponent<Canvas>(), UiRate.Frame, "월드맵 커서");
|
|
AssertNested(cursor.transform.parent.GetComponent<Canvas>(), UiRate.Frame, "월드맵 스크롤 내용");
|
|
}
|
|
wm.OpenPopup(0);
|
|
yield return null;
|
|
AssertNested(GameObject.Find("WorldMapCanvas/Popup").GetComponent<Canvas>(), UiRate.Event, "월드맵 팝업");
|
|
|
|
// 전투 HUD: 루트 = Static · Info = Event · 미니맵 실시간 = Frame · 결과 = Event
|
|
wm.ConfirmPopup();
|
|
var bm = BattleManager.Instance;
|
|
float t0 = Time.realtimeSinceStartup;
|
|
while (bm.Phase != BattlePhase.PlayerTurn && Time.realtimeSinceStartup - t0 < 10f) yield return null;
|
|
var hud = bm.Hud.transform;
|
|
Assert.AreEqual(UiRate.Static, hud.GetComponent<UiLayerInfo>().rate, "HUD 루트 = 정적 판");
|
|
AssertNested(hud.Find("Info").GetComponent<Canvas>(), UiRate.Event, "HUD Info");
|
|
AssertNested(hud.Find("Result").GetComponent<Canvas>(), UiRate.Event, "HUD 결과");
|
|
var live = hud.Find("Minimap/MinimapLive");
|
|
Assert.IsNotNull(live, "미니맵 실시간 층");
|
|
AssertNested(live.GetComponent<Canvas>(), UiRate.Frame, "미니맵 실시간");
|
|
AssertInFrame(live.Find("View"), "미니맵 시야");
|
|
foreach (var p in hud.GetComponentsInChildren<UiPulse>(true)) AssertInFrame(p.transform, "HUD 호흡 " + p.name);
|
|
Assert.AreEqual(hud.childCount - 1, hud.Find("Result").GetSiblingIndex(), "결과 층 = 맨 위");
|
|
Assert.AreEqual(hud.childCount - 2, hud.Find("Info").GetSiblingIndex(), "Info 층 = 정적 판 위");
|
|
|
|
// ⑤ 아틀라스 — 테마 스프라이트(큰 그림 제외)는 묶임 · 월드 9-slice 사본 = 같은 크기
|
|
long single = SingleArea();
|
|
int checkedN = 0;
|
|
var theme = UITheme.Current;
|
|
Assert.IsNotNull(theme);
|
|
var sprites = new List<Sprite>();
|
|
foreach (var s in theme.skins) sprites.Add(s.Sprite);
|
|
foreach (var i in theme.icons) sprites.Add(i.Sprite);
|
|
foreach (var sp in sprites)
|
|
{
|
|
if (sp == null || (long)(sp.rect.width * sp.rect.height) > single) continue;
|
|
Assert.IsTrue(sp.packed, "아틀라스에 안 묶임: " + sp.name + " — 메뉴 TKS/Build UI Atlases");
|
|
checkedN++;
|
|
}
|
|
Assert.Greater(checkedN, 0);
|
|
foreach (var id in new[] { "hp_bg", "hp_fill", "pip" })
|
|
{
|
|
var src = UISkin.Theme.Skin(id);
|
|
if (src == null) continue;
|
|
var ws = UISkin.WorldSprite(id, 100f);
|
|
Assert.AreEqual(src.Sprite.rect.size, ws.rect.size, "월드 9-slice 사본 크기(" + id + ")");
|
|
}
|
|
}
|
|
|
|
static long SingleArea()
|
|
{
|
|
var path = Path.Combine(Application.dataPath, "Data/UI/ui_atlas.csv");
|
|
Assert.IsTrue(File.Exists(path), path);
|
|
foreach (var line in File.ReadAllLines(path))
|
|
{
|
|
var f = line.Split(',');
|
|
long v;
|
|
if (f.Length >= 4 && f[0].Trim() == "option" && f[1].Trim() == "single_area" && long.TryParse(f[3].Trim(), out v)) return v;
|
|
}
|
|
Assert.Fail("ui_atlas.csv 에 single_area 없음");
|
|
return 0;
|
|
}
|
|
}
|
|
}
|