From 8aa804168f489d9e601e37a5e5e8dddf0a31edad Mon Sep 17 00:00:00 2001 From: swrring Date: Tue, 10 Nov 2026 01:05:47 +0900 Subject: [PATCH] =?UTF-8?q?UI=20=EC=BA=94=EB=B2=84=EC=8A=A4=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0=20=EB=93=B1=EA=B8=89=20=EB=B6=84=ED=95=A0=20+=20UI=20?= =?UTF-8?q?=EC=8A=A4=ED=94=84=EB=9D=BC=EC=9D=B4=ED=8A=B8=20=EC=95=84?= =?UTF-8?q?=ED=8B=80=EB=9D=BC=EC=8A=A4(#1107=20=C2=B7=20PD=202026-11-10=20?= =?UTF-8?q?=E3=80=8C=EB=A6=AC=EC=86=8C=EC=8A=A4=C2=B7=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EA=B0=80=20=EC=96=BC=EB=A7=88=EB=82=98=20=EC=9E=90=EC=A3=BC=20?= =?UTF-8?q?=EB=B0=94=EB=80=8C=EB=8A=94=EC=A7=80=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=9D=BC=20=EC=BA=94=EB=B2=84=EC=8A=A4=EB=A5=BC=20=EB=82=98?= =?UTF-8?q?=EB=88=84=EC=96=B4=20=EC=B5=9C=EC=A0=81=ED=99=94=20=C2=B7=20?= =?UTF-8?q?=EB=93=9C=EB=A1=9C=EC=9A=B0=EC=BD=9C=20=EC=B5=9C=EC=86=8C?= =?UTF-8?q?=ED=99=94=20=EA=B5=AC=EC=A1=B0=E3=80=8D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 갱신 등급 3단(Static/Event/Frame · Runtime/UI/UiLayer): 아웃게임 화면마다 중첩 캔버스 · 스크롤 내용 · 재화 바 · 팝업 · 토스트 · 뽑기 결과 · 끌기 유령 분리 · UiPulse/UiPop 자동 Frame 분리 - 전투 HUD = 정적 판 루트 → Info → MinimapLive(Frame) · SkillGlow → Result · 월드맵 = 루트 → Content(스크롤) → 커서 · 팝업 - 매 프레임 갱신 코드 = 값이 같으면 대입 안 함 · UiPop 끝나면 정지 - 아틀라스 SOT Assets/Data/UI/ui_atlas.csv → Editor/UIAtlasBuilder(UI 테마 임포트 때 자동) → UI_Common 4096x2048 · UI_Content 2048x2048 · WorldMap_UI 1024x1024 · Sprite Atlas V2 - UISkin.WorldSprite = 아틀라스 자리(textureRect)로 자름 - 검증: UiLayerProbe(캡처 로그 layers 줄 + ui_layers.csv) · UILayerTests · 테스트 119/119 · 13화면 배치 합계 1124 → 439 Co-Authored-By: Claude Opus 5.5 --- Assets/Data/UI/ui_atlas.csv | 6 + Assets/Data/UI/ui_atlas.csv.meta | 7 + Assets/Scripts/Editor/UIAtlasBuilder.cs | 215 +++++++++++++++++ Assets/Scripts/Editor/UIAtlasBuilder.cs.meta | 2 + Assets/Scripts/Editor/UIThemeImporter.cs | 2 + .../Scripts/OutGame/UI/BattleResultPanel.cs | 1 + Assets/Scripts/OutGame/UI/FormationScreen.cs | 1 + Assets/Scripts/OutGame/UI/OutGameRoot.cs | 27 +-- Assets/Scripts/OutGame/UI/TradeScreen.cs | 1 + Assets/Scripts/OutGame/UI/UiKit.cs | 2 + Assets/Scripts/OutGame/UI/UiPop.cs | 20 +- Assets/Scripts/OutGame/UI/UiPulse.cs | 6 +- Assets/Scripts/OutGame/UICaptureDriver.cs | 2 + Assets/Scripts/Runtime/BattleHUD.cs | 100 +++++--- Assets/Scripts/Runtime/UI/UISkin.cs | 4 +- Assets/Scripts/Runtime/UI/UiLayer.cs | 138 +++++++++++ Assets/Scripts/Runtime/UI/UiLayer.cs.meta | 2 + Assets/Scripts/Runtime/UI/UiLayerProbe.cs | 101 ++++++++ .../Scripts/Runtime/UI/UiLayerProbe.cs.meta | 2 + Assets/Scripts/Runtime/WorldMapScreen.cs | 20 +- Assets/Tests/PlayMode/UILayerTests.cs | 174 ++++++++++++++ Assets/Tests/PlayMode/UILayerTests.cs.meta | 2 + Assets/UI/Atlases.meta | 8 + Assets/UI/Atlases/UI_Common.spriteatlasv2 | 216 ++++++++++++++++++ .../UI/Atlases/UI_Common.spriteatlasv2.meta | 43 ++++ Assets/UI/Atlases/UI_Content.spriteatlasv2 | 78 +++++++ .../UI/Atlases/UI_Content.spriteatlasv2.meta | 43 ++++ Assets/UI/Atlases/WorldMap_UI.spriteatlasv2 | 16 ++ .../UI/Atlases/WorldMap_UI.spriteatlasv2.meta | 43 ++++ CLAUDE.md | 15 +- ProjectSettings/EditorSettings.asset | 2 +- 31 files changed, 1231 insertions(+), 68 deletions(-) create mode 100644 Assets/Data/UI/ui_atlas.csv create mode 100644 Assets/Data/UI/ui_atlas.csv.meta create mode 100644 Assets/Scripts/Editor/UIAtlasBuilder.cs create mode 100644 Assets/Scripts/Editor/UIAtlasBuilder.cs.meta create mode 100644 Assets/Scripts/Runtime/UI/UiLayer.cs create mode 100644 Assets/Scripts/Runtime/UI/UiLayer.cs.meta create mode 100644 Assets/Scripts/Runtime/UI/UiLayerProbe.cs create mode 100644 Assets/Scripts/Runtime/UI/UiLayerProbe.cs.meta create mode 100644 Assets/Tests/PlayMode/UILayerTests.cs create mode 100644 Assets/Tests/PlayMode/UILayerTests.cs.meta create mode 100644 Assets/UI/Atlases.meta create mode 100644 Assets/UI/Atlases/UI_Common.spriteatlasv2 create mode 100644 Assets/UI/Atlases/UI_Common.spriteatlasv2.meta create mode 100644 Assets/UI/Atlases/UI_Content.spriteatlasv2 create mode 100644 Assets/UI/Atlases/UI_Content.spriteatlasv2.meta create mode 100644 Assets/UI/Atlases/WorldMap_UI.spriteatlasv2 create mode 100644 Assets/UI/Atlases/WorldMap_UI.spriteatlasv2.meta diff --git a/Assets/Data/UI/ui_atlas.csv b/Assets/Data/UI/ui_atlas.csv new file mode 100644 index 00000000..7a63f625 --- /dev/null +++ b/Assets/Data/UI/ui_atlas.csv @@ -0,0 +1,6 @@ +kind,name,match,value,note +theme,UI_Content,/gear/|/chest/|/logo_skill/|/gimmick/|ItemIcon|Icon_ShopItem,2048,화면별 콘텐츠 아이콘(장비·상자·스킬·기믹 타일·상점 상품) — 그 화면에서만 쓰임 +theme,UI_Common,*,4096,모든 화면 공통 크롬(틀·버튼·바·탭·재화·병과·작은 아이콘) — 위 줄에 안 걸린 테마 스프라이트 전부 +folder,WorldMap_UI,Assets/Resources/ChapterMap/UI,2048,월드맵 부품(깃발·띠·판·별·커서) — 폴더 전체 +option,single_area,,262144,이 넓이(px) 넘는 큰 그림(로비 바탕·키아트·로고·패턴)은 아틀라스에 넣지 않음 — 한 장 = 드로우콜 1 · 아틀라스 공간 낭비 방지 +option,padding,,4,스프라이트 간격(px) diff --git a/Assets/Data/UI/ui_atlas.csv.meta b/Assets/Data/UI/ui_atlas.csv.meta new file mode 100644 index 00000000..b7b5cd49 --- /dev/null +++ b/Assets/Data/UI/ui_atlas.csv.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: dbc9f67710bed05499c84db6137a1454 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/UIAtlasBuilder.cs b/Assets/Scripts/Editor/UIAtlasBuilder.cs new file mode 100644 index 00000000..e0991bc0 --- /dev/null +++ b/Assets/Scripts/Editor/UIAtlasBuilder.cs @@ -0,0 +1,215 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using UnityEditor; +using UnityEditor.U2D; +using UnityEngine; +using UnityEngine.U2D; + +namespace TKS.EditorTools +{ + /// + /// UI 스프라이트 아틀라스(PD 2026-11-10 「드로우콜 최소화 구조」) — 구성 SOT = Assets/Data/UI/ui_atlas.csv → Assets/UI/Atlases/<이름>.spriteatlasv2. + /// theme 줄 = UI 테마(UITheme.asset)가 쓰는 스프라이트를 match(경로 포함 · | = 또는 · * = 나머지 전부)로 나눔 · folder 줄 = 폴더 전체 · + /// option single_area = 그보다 넓은 그림은 묶지 않음 · padding. + /// UI 테마 임포트가 끝날 때마다 자동으로 다시 맞춘다(새 납품 스프라이트 = 코드 변경 없이 아틀라스에 들어감) · 구성이 같으면 파일을 건드리지 않는다. + /// 패커 = Sprite Atlas V2(에디터 Play 에서도 묶음 · 프로젝트 설정 EditorSettings). + /// + public static class UIAtlasBuilder + { + public const string CsvPath = "Assets/Data/UI/ui_atlas.csv"; + public const string OutDir = "Assets/UI/Atlases"; + + class Group + { + public string kind; + public string name; + public string[] match; + public int maxSize; + public readonly List paths = new List(); + } + + [MenuItem("TKS/Build UI Atlases (UI 아틀라스 갱신)")] + public static void Menu() + { + Build(AssetDatabase.LoadAssetAtPath(UIThemeImporter.AssetPath), true); + } + + /// 표대로 아틀라스를 맞춘다 · 반환 = 오류 없이 끝났는가(오류면 아틀라스 미변경). + public static bool Build(UITheme theme, bool log = false) + { + if (!File.Exists(CsvPath)) + { + Debug.LogError("[TKS] UI 아틀라스 표 없음: " + CsvPath); + return false; + } + var groups = new List(); + long singleArea = long.MaxValue; + int padding = 4; + var errors = new List(); + var lines = File.ReadAllText(CsvPath, Encoding.UTF8).TrimStart('').Replace("\r", "").Split('\n'); + for (int n = 1; n < lines.Length; n++) + { + if (string.IsNullOrWhiteSpace(lines[n])) continue; + var f = lines[n].Split(','); + if (f.Length < 4) + { + errors.Add((n + 1) + "행: 열 부족"); + continue; + } + string kind = f[0].Trim(), name = f[1].Trim(), match = f[2].Trim(), value = f[3].Trim(); + int v; + switch (kind) + { + case "theme": + case "folder": + if (!int.TryParse(value, out v) || v < 128) errors.Add((n + 1) + "행 " + name + ": 최대 크기 " + value); + groups.Add(new Group { kind = kind, name = name, match = match.Split('|'), maxSize = v }); + break; + case "option": + if (!int.TryParse(value, out v)) errors.Add((n + 1) + "행 " + name + ": 값 " + value); + else if (name == "single_area") singleArea = v; + else if (name == "padding") padding = v; + else errors.Add((n + 1) + "행: 모르는 옵션 " + name); + break; + default: + errors.Add((n + 1) + "행: 모르는 종류 " + kind); + break; + } + } + + // theme 스프라이트 → 첫 번째로 맞는 theme 줄(* = 나머지) + var singles = new List(); + foreach (var path in ThemeTextures(theme)) + { + var tex = AssetDatabase.LoadAssetAtPath(path); + if (tex == null) continue; + if ((long)tex.width * tex.height > singleArea) + { + singles.Add(path); + continue; + } + Group hit = null; + foreach (var g in groups) + if (g.kind == "theme" && g.match.Any(m => m != "*" && path.Contains(m))) + { + hit = g; + break; + } + if (hit == null) hit = groups.FirstOrDefault(g => g.kind == "theme" && g.match.Contains("*")); + if (hit != null) hit.paths.Add(path); + } + foreach (var g in groups) + if (g.kind == "folder") + { + if (!AssetDatabase.IsValidFolder(g.match[0])) errors.Add(g.name + ": 폴더 없음 " + g.match[0]); + else g.paths.Add(g.match[0]); + } + if (errors.Count > 0) + { + Debug.LogError("[TKS] UI 아틀라스 표 오류 " + errors.Count + "건(아틀라스 미변경):\n" + string.Join("\n", errors)); + return false; + } + + // 패커 = Sprite Atlas V2(에디터 Play 에서도 묶음) + if (EditorSettings.spritePackerMode != SpritePackerMode.SpriteAtlasV2) + { + EditorSettings.spritePackerMode = SpritePackerMode.SpriteAtlasV2; + AssetDatabase.SaveAssets(); + } + if (!AssetDatabase.IsValidFolder(OutDir)) AssetDatabase.CreateFolder(Path.GetDirectoryName(OutDir).Replace('\\', '/'), Path.GetFileName(OutDir)); + + int changed = 0; + var report = new StringBuilder(); + foreach (var g in groups) + { + g.paths.Sort(System.StringComparer.Ordinal); + if (Write(g, padding)) changed++; + report.Append(" · ").Append(g.name).Append(' ').Append(g.paths.Count); + } + if (changed > 0) SpriteAtlasUtility.PackAllAtlases(EditorUserBuildSettings.activeBuildTarget); + if (log || changed > 0) + Debug.Log("[TKS] UI 아틀라스" + report + " · 큰 그림(묶지 않음) " + singles.Count + " · 갱신 " + changed + "장"); + return true; + } + + /// 아틀라스 1장을 표 구성대로 · 구성(목록·크기·간격) 서명이 같으면 건드리지 않는다. + static bool Write(Group g, int padding) + { + string path = OutDir + "/" + g.name + ".spriteatlasv2"; + string sig = Hash128.Compute(g.maxSize + ";" + padding + ";" + string.Join("|", g.paths.Select(AssetDatabase.AssetPathToGUID))).ToString(); + var imp = AssetImporter.GetAtPath(path) as SpriteAtlasImporter; + if (imp != null && imp.userData == sig) return false; + + var asset = new SpriteAtlasAsset(); + var objs = new List(); + foreach (var p in g.paths) + { + var o = AssetDatabase.LoadMainAssetAtPath(p); + if (o != null) objs.Add(o); + } + asset.Add(objs.ToArray()); + SpriteAtlasAsset.Save(asset, path); + AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceSynchronousImport); + imp = (SpriteAtlasImporter)AssetImporter.GetAtPath(path); + imp.includeInBuild = true; + // UI = 사각 묶음(9-slice · Filled 가 그대로) · 회전 없음 · 밉맵 없음 + imp.packingSettings = new SpriteAtlasPackingSettings + { + blockOffset = 1, + enableRotation = false, + enableTightPacking = false, + enableAlphaDilation = true, + padding = padding + }; + imp.textureSettings = new SpriteAtlasTextureSettings + { + readable = false, + generateMipMaps = false, + sRGB = true, + filterMode = FilterMode.Bilinear, + anisoLevel = 0 + }; + var ps = imp.GetPlatformSettings("DefaultTexturePlatform"); + ps.maxTextureSize = g.maxSize; + ps.format = TextureImporterFormat.Automatic; + ps.textureCompression = TextureImporterCompression.Compressed; // 원본 UI 텍스처와 같은 압축(Normal) + imp.SetPlatformSettings(ps); + imp.userData = sig; + imp.SaveAndReimport(); + return true; + } + + /// 테마가 쓰는 스프라이트의 텍스처 경로(팩 · 신규 제작 · 덮개) — 중복 없음. + public static List ThemeTextures(UITheme theme) + { + var set = new HashSet(); + if (theme != null) + { + foreach (var s in theme.skins) + { + Add(set, s.pack); + Add(set, s.samguk); + Add(set, s.overlay); + } + foreach (var i in theme.icons) + { + Add(set, i.pack); + Add(set, i.samguk); + } + } + var list = set.ToList(); + list.Sort(System.StringComparer.Ordinal); + return list; + } + + static void Add(HashSet set, Sprite sp) + { + if (sp == null) return; + var p = AssetDatabase.GetAssetPath(sp); + // 팩 일부 = 확장자 대문자(.Png) → 확장자 대신 본 자산이 텍스처인지로 판정 + if (!string.IsNullOrEmpty(p) && AssetDatabase.GetMainAssetTypeAtPath(p) == typeof(Texture2D)) set.Add(p); + } + } +} diff --git a/Assets/Scripts/Editor/UIAtlasBuilder.cs.meta b/Assets/Scripts/Editor/UIAtlasBuilder.cs.meta new file mode 100644 index 00000000..36941201 --- /dev/null +++ b/Assets/Scripts/Editor/UIAtlasBuilder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 355884257a3577a4a9ea4e1bfe979ab7 \ No newline at end of file diff --git a/Assets/Scripts/Editor/UIThemeImporter.cs b/Assets/Scripts/Editor/UIThemeImporter.cs index 6250c9be..22485a2c 100644 --- a/Assets/Scripts/Editor/UIThemeImporter.cs +++ b/Assets/Scripts/Editor/UIThemeImporter.cs @@ -142,6 +142,8 @@ namespace TKS.EditorTools UITheme.Use(asset); Debug.Log("[TKS] UI 테마 임포트 · 색 " + asset.colors.Count + " · 스킨 " + asset.skins.Count + " · 아이콘 " + asset.icons.Count + " · 신규 제작 치환 " + replaced + " · 추가 " + added + " → " + AssetPath); + // 테마가 쓰는 스프라이트 = UI 아틀라스 구성(PD 2026-11-10 · 새 스프라이트도 자동으로 묶임 · 구성이 같으면 그대로) + UIAtlasBuilder.Build(asset); return true; } diff --git a/Assets/Scripts/OutGame/UI/BattleResultPanel.cs b/Assets/Scripts/OutGame/UI/BattleResultPanel.cs index 1ce88eb4..0b5c6959 100644 --- a/Assets/Scripts/OutGame/UI/BattleResultPanel.cs +++ b/Assets/Scripts/OutGame/UI/BattleResultPanel.cs @@ -76,6 +76,7 @@ namespace TKS.OutGame title.rectTransform.offsetMin = new Vector2(150f, 34f); // 별 3 = 받침 원(Circle78 chip_dark) + 별(가운데 크게 · 위로) · 0.15 s 간격 튀김 starRow = UiKit.At(pt, "StarRow", 0f, 120f, PW, 190f); + UiLayer.Nest(starRow.gameObject, UiRate.Frame, false); // 별 3 튀김 = 한 Frame 층(별마다 캔버스를 만들지 않음 · PD 2026-11-10) for (int i = 0; i < 3; i++) { bool mid = i == 1; diff --git a/Assets/Scripts/OutGame/UI/FormationScreen.cs b/Assets/Scripts/OutGame/UI/FormationScreen.cs index f2d25a3e..1239d590 100644 --- a/Assets/Scripts/OutGame/UI/FormationScreen.cs +++ b/Assets/Scripts/OutGame/UI/FormationScreen.cs @@ -328,6 +328,7 @@ namespace TKS.OutGame if (ghost != null) Object.Destroy(ghost.gameObject); ghost = UiKit.Portrait("Ghost", Root.TopRoot, S.Def(id), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(170f, 170f), true, 2.1f, UiKit.Red); ghost.gameObject.name = "Ghost_" + id; + UiLayer.Nest(ghost.gameObject, UiRate.Frame); // 끄는 동안 매 프레임 이동 = 자기 캔버스(PD 2026-11-10) MoveGhost(e); } diff --git a/Assets/Scripts/OutGame/UI/OutGameRoot.cs b/Assets/Scripts/OutGame/UI/OutGameRoot.cs index 3b6f2d1f..e22fa0f5 100644 --- a/Assets/Scripts/OutGame/UI/OutGameRoot.cs +++ b/Assets/Scripts/OutGame/UI/OutGameRoot.cs @@ -203,9 +203,12 @@ namespace TKS.OutGame wm.BackRequested = ShowLobby; } - mainCanvas = MakeCanvas("OutGameCanvas", 350, 0.45f); - resultCanvas = MakeCanvas("BattleResultCanvas", 200, 0.8f); - topCanvas = MakeCanvas("OutGameTopCanvas", 450, 0.4f); + // 캔버스 층(PD 2026-11-10 · 프로젝트 CLAUDE.md 「UI 캔버스·아틀라스 구조」): + // 본 화면 루트 = 바탕 · 하단 탭(Static) + 화면마다 중첩 캔버스(Event) · 스크롤 내용 · 호흡 등 = Frame 중첩 + // 위 덮개 루트 = 재화 바 · 팝업 · 토스트 · 끌기 유령 각각 중첩 · 전투 결과 루트 = 결과 패널(Event) + mainCanvas = MakeCanvas("OutGameCanvas", 350, 0.45f, UiRate.Static); + resultCanvas = MakeCanvas("BattleResultCanvas", 200, 0.8f, UiRate.Event); + topCanvas = MakeCanvas("OutGameTopCanvas", 450, 0.4f, UiRate.Event); TopRoot = (RectTransform)topCanvas.transform; var bg = UiKit.Fill("Bg", mainCanvas.transform); @@ -232,27 +235,18 @@ namespace TKS.OutGame { scr.Root = this; scr.Rt = UiKit.Fill(typeof(T).Name, mainCanvas.transform); + UiLayer.Nest(scr.Rt.gameObject, UiRate.Event, true); // 화면 1장 = 자기 캔버스(값 갱신이 바탕·탭·다른 화면을 다시 묶지 않게) scr.Build(); scr.Rt.gameObject.SetActive(false); screens.Add(scr); return scr; } - Canvas MakeCanvas(string name, int order, float plane) + Canvas MakeCanvas(string name, int order, float plane, UiRate rate) { var go = new GameObject(name); go.transform.SetParent(transform, false); - var c = go.AddComponent(); - c.renderMode = RenderMode.ScreenSpaceCamera; // 카메라 캡처(검증)에도 찍히게 - c.worldCamera = Cam; - c.planeDistance = plane; - c.sortingOrder = order; - var sc = go.AddComponent(); - sc.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; - sc.referenceResolution = Reference; - sc.matchWidthOrHeight = 0.5f; - go.AddComponent(); - return c; + return UiLayer.Root(go, Cam, order, plane, Reference, rate); // Screen Space - Camera = 카메라 캡처(검증)에도 찍힘 } // ---------- 화면 전환 ---------- @@ -375,6 +369,7 @@ namespace TKS.OutGame void BuildCurrencyBar() { barRoot = UiKit.TopBand("CurrencyBar", TopRoot, 0f, TopBarH); + UiLayer.Nest(barRoot.gameObject, UiRate.Event, true); // 군량 타이머 = 1초마다 · 팝업·토스트와 따로 // 테마 = 띠 없이 칩만(화면 바탕이 비침 · 목업 hero_detail) · 투명 판은 아래 클릭 막기용 UiKit.Paint(barRoot, UISkin.On ? Color.clear : new Color(0.15f, 0.11f, 0.09f), false, true); goldText = Pill(0, "cur_gold", "plus", "골", "골드", UiKit.Coin, () => Toast("골드 = 스테이지 보상 · 거래 > 상점 · (구매 상점 준비 중)")); @@ -547,6 +542,7 @@ namespace TKS.OutGame { CloseModal(); var dim = UiKit.Fill("Modal", TopRoot); + UiLayer.Nest(dim.gameObject, UiRate.Event, true); var dimImg = UiKit.Paint(dim, UISkin.Col("dim", new Color(0f, 0f, 0f, 0.6f)), false, true); dimImg.raycastTarget = true; modal = dim.gameObject; @@ -608,6 +604,7 @@ namespace TKS.OutGame ? UiKit.Skin(UiKit.Rect("Toast", TopRoot, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, TabBarH + 40f), new Vector2(940f, 110f)), "toast", UiKit.BarColor) : UiKit.Box("Toast", TopRoot, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, TabBarH + 40f), new Vector2(940f, 110f), new Color(0.1f, 0.08f, 0.07f, 0.92f)); toast = img.gameObject; + UiLayer.Nest(toast, UiRate.Event, false); toastText = UiKit.FillLabel(img.transform, "", 32, UiKit.White, TextAnchor.MiddleCenter, false, 16f); } toast.transform.SetAsLastSibling(); diff --git a/Assets/Scripts/OutGame/UI/TradeScreen.cs b/Assets/Scripts/OutGame/UI/TradeScreen.cs index 409d2438..aa0e1561 100644 --- a/Assets/Scripts/OutGame/UI/TradeScreen.cs +++ b/Assets/Scripts/OutGame/UI/TradeScreen.cs @@ -303,6 +303,7 @@ namespace TKS.OutGame { CloseResult(); var dim = UiKit.Fill("GachaResult", Root.TopRoot); + UiLayer.Nest(dim.gameObject, UiRate.Event, true); // 위 덮개 = 자기 캔버스(재화 바와 따로 · PD 2026-11-10) var dimg = UiKit.Paint(dim, new Color(0.08f, 0.05f, 0.04f, 0.9f), false, true); dimg.raycastTarget = true; var skip = dim.gameObject.AddComponent