using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.UI; namespace TKS.OutGame { /// /// 전투 결과 패널(기획서 §6 · 비주얼 = Fable 제안서 §4-2 #1104) — 별 3(받침 원 + 등장 튀김) · 조건 행 3 · 보상 타일(골드 · 경험 · 보옥 · 장비 · 상자) · /// 버튼 = 「다시」(Tertiary · 같은 스테이지 재도전) + 「확인」(Primary 노랑) · 승리면 리본 뒤 금빛. /// 전투 HUD 의 승리/패배 판(sortingOrder 100) 위에 덮는다 · 화면 아무 데나 탭 = 기존 그대로(SlingshotInput → 월드맵 복귀 · 「다시」 위만 제외). /// public class BattleResultPanel { // 패널 배치(UI px · 패널 왼쪽 위 기준) const float PW = 920f; const float PH = 1080f; const float Side = 60f; const float RowW = PW - Side * 2f; const float RowH = 64f; const float Tile = 132f; const float BtnY = PH - 40f - 110f; readonly OutGameRoot root; readonly GameObject go; readonly Text title; readonly Image ribbon; readonly Image glow; readonly RectTransform starRow; readonly Image[] starImg = new Image[3]; readonly RectTransform body; readonly Text plain; readonly Button retry; readonly Button ok; readonly Text retryCost; string rewardsText = ""; public bool Visible { get { return go.activeSelf; } } public string RewardsText { get { return rewardsText; } } public BattleResultPanel(OutGameRoot r, RectTransform canvas) { root = r; var dim = UiKit.Fill("Result", canvas); UiKit.Paint(dim, UISkin.Col("dim", new Color(0f, 0f, 0f, 0.55f)), false); go = dim.gameObject; Image panel = null; if (UISkin.Has("popup") && UISkin.Has("panel_frame")) { // 리본 뒤 금빛(승리만 · 팩 Glow_Circle01 · 호흡) → 팩 팝업 틀 → 제목 리본(승리 붉은 · 패배 회색) if (UISkin.Has("glow_circle")) { glow = UiKit.Skin(UiKit.Rect("Glow", dim, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 20f + PH * 0.5f + 90f), new Vector2(1240f, 1240f)), "glow_circle", UiKit.Col("ribbon_glow", Color.clear)); var pulse = glow.gameObject.AddComponent(); pulse.min = 0.65f; pulse.period = 1.6f; // 안쪽 한 겹 더(그림 가운데 α 0.5 라 한 장으로는 후광이 약함 · 검수2 보충) var core = UiKit.Skin(UiKit.Rect("Core", glow.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(720f, 720f)), "glow_circle", UiKit.Col("ribbon_glow", Color.clear)); core.raycastTarget = false; } // 패널 = 섹션 패널 층(그림자 · 밝은 프레임 #3F4262 · 필드 #262840 · 윗면 광택 · 검수 결함 10) var holder = UiKit.Rect("Panel", dim, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 20f), new Vector2(PW, PH)); RectTransform froot; UiKit.SectionPanel(holder, "Frame", 0f, 0f, PW, PH, false, false, out froot); var pt = (Transform)holder; var rib = UiKit.Rect("Ribbon", pt, new Vector2(0.5f, 1f), new Vector2(0.5f, 0.5f), new Vector2(0f, 10f), new Vector2(1000f, 200f)); ribbon = UiKit.Skin(rib, "ribbon", Color.white); title = UiKit.Title(UiKit.FillLabel(rib, "", 88, UiKit.White, TextAnchor.MiddleCenter, true, 150f), true); 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; float plate = mid ? 150f : 120f; var pos = new Vector2((i - 1) * 190f, mid ? 12f : -14f); UiKit.Skin(UiKit.Rect("Plate" + i, starRow, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), pos, new Vector2(plate, plate)), "circle_frame", UiKit.Col("chip_dark", UiKit.Dark)); starImg[i] = UiKit.Icon(starRow, "star_on_l", new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), pos + new Vector2(0f, 4f), mid ? 132f : 106f); if (starImg[i] == null) continue; UiKit.DropShadow(starImg[i], 5f); starImg[i].gameObject.AddComponent().delay = 0.15f * i; } body = UiKit.Fill("Body", pt); retry = UiKit.SkinBtn(pt, "Retry", "round08", UiKit.Col("menu_btn", UiKit.Dark), "다시", 34, UiKit.White, Side, BtnY, 260f, 110f, () => root.RetryStage()); retryCost = UiKit.BtnCost(retry, "cur_stamina", "", 36f, 28); // 재도전 = 군량 소모 표시(검수 결함 2) ok = UiKit.SkinBtn(pt, "Ok", UISkin.Has("btn_l_yellow") ? "btn_l_yellow" : "button", UISkin.Has("btn_l_yellow") ? UiKit.White : UiKit.Coin, "확인", 46, UiKit.Col("text_on_yellow", UiKit.Ink), PW - Side - 520f, BtnY, 520f, 110f, null); } else { panel = UiKit.Box("Panel", dim, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 40f), new Vector2(PW, PH), UiKit.Paper); UiKit.Border(panel, UiKit.Line, 4f); var head = UiKit.TopBand("Head", panel.transform, 0f, 170f); UiKit.Paint(head, UiKit.Dark); title = UiKit.FillLabel(head, "", 96, UiKit.White); plain = UiKit.Label(panel.transform, "", 36, UiKit.Ink, TextAnchor.UpperLeft, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -200f), new Vector2(760f, 860f), false); plain.lineSpacing = 1.15f; UiKit.Label(panel.transform, "화면을 탭하면 계속", 34, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 36f), new Vector2(800f, 60f), false); } go.SetActive(false); } public void SetVisible(bool on) { if (go.activeSelf != on) go.SetActive(on); } /// 화면 좌표가 「다시」 버튼 위인가 — 화면 탭(월드맵 복귀)을 이 버튼에서는 건너뛴다. public bool PointerOnRetry(Vector2 screen) { if (retry == null || !go.activeInHierarchy || !retry.gameObject.activeSelf) return false; var c = go.GetComponentInParent(); return RectTransformUtility.RectangleContainsScreenPoint((RectTransform)retry.transform, screen, c != null ? c.worldCamera : null); } public void Fill(StageReward r) { var s = root.Session; rewardsText = r.win ? RewardText(s, r) : "보상 없음"; title.text = (r.win ? "승리" : "패배") + " " + r.chapter + "-" + r.stage + (r.boss ? " 보스" : "") + ""; ShowStars(r.win ? r.stars : 0, r.win); SetButtons(true); if (retryCost != null && root.Battle != null && root.Battle.Config != null && root.Battle.StageIndex >= 0 && root.Battle.StageIndex < root.Battle.Config.stages.Length) { int cost = EconomyRules.StageCost(s.Db, s.Save, root.Battle.Config.stages[root.Battle.StageIndex]); retryCost.text = cost.ToString(); retry.GetComponent().color = s.Save.stamina >= cost ? UiKit.Col("menu_btn", UiKit.Dark) : UiKit.Col("disabled", UiKit.Disabled); } if (body == null) { plain.text = (r.win ? "별 " + UiKit.Stars(r.stars, 3) + "\n\n" : "아군이 전멸했습니다\n\n") + rewardsText; return; } UiKit.Clear(body); float y = 330f; if (r.win) { string[] cond = { "스테이지 클리어", "아군 전원 생존", r.starTurns + "턴 이내 클리어 (" + r.turns + "턴)" }; for (int i = 0; i < 3; i++) UiKit.CondRow(body, "Cond" + i, Side, y + i * (RowH + 8f), RowW, RowH, r.conds[i] ? "check" : "x_small", r.conds[i], cond[i], 30); } else { UiKit.CondRow(body, "Cond0", Side, y, RowW, RowH, "x_small", false, "아군이 전멸했습니다", 30); UiKit.CondRow(body, "Cond1", Side, y + RowH + 8f, RowW, RowH, "cur_stamina", false, "군량 " + r.stamina + " 소모 · 별 없음", 30, false); } y = 566f; UiKit.SubTitle(body, "보상", Side, y, RowW); y += 64f; if (!r.win) { var none = UiKit.Title(UiKit.Label(body, "보상 없음", 40, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(Side, -y - 10f), new Vector2(RowW, 70f))); none.horizontalOverflow = HorizontalWrapMode.Overflow; UiKit.Label(body, "영웅 레벨업 · 승급 · 편성을 바꿔 다시 도전", 30, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(Side, -y - 90f), new Vector2(RowW, 50f), false); return; } Tiles(s, r, y); Notes(s, r, y + Tile + 62f); } /// 보상 타일 행(가운데 정렬 · 칸 아래 이름) — 골드 · 경험 · 보옥(있을 때) · 장비(등급 칸 · 있을 때) · 상자(있을 때). void Tiles(OutGameSession s, StageReward r, float y) { var list = new List(); // 아이콘 · 수량 · 칸 색 접두 · 이름 list.Add(new[] { "cur_gold", r.gold.ToString("N0"), "gear_white_", r.first ? "골드" : "골드 ×" + s.Db.Economy.RepeatRewardMult }); list.Add(new[] { "cur_exp", r.exp.ToString("N0"), "gear_white_", "경험 · " + r.expTo.Count + "명" }); if (r.gem > 0) list.Add(new[] { "cur_gem", r.gem.ToString("N0"), "gear_blue_", "보옥" }); if (!string.IsNullOrEmpty(r.gearId)) { var g = s.Db.Gear.Get(r.gearId); string grade = g != null && !string.IsNullOrEmpty(g.grade) ? g.grade : "white"; list.Add(new[] { g != null ? "gear_" + g.kind : "cur_gear", "", "gear_" + grade + "_", g != null ? g.nameKo : r.gearId }); } if (!string.IsNullOrEmpty(r.chest) && s.Db.Chests != null && s.Db.Chests.Get(r.chest) != null) list.Add(new[] { "chest_" + r.chest, "", "gear_purple_", s.Db.Chests.Get(r.chest).nameKo }); float gap = list.Count >= 5 ? 22f : 30f; float x = (PW - list.Count * Tile - (list.Count - 1) * gap) * 0.5f; for (int i = 0; i < list.Count; i++) { var e = list[i]; float tx = x + i * (Tile + gap); var cell = UiKit.RewardTile(body, "Tile" + i, tx, y, Tile, e[2], e[0], e[1], e[2] != "gear_white_"); if (r.first && (e[0] == "cur_gold" || e[0] == "cur_gem")) FirstBadge(cell); // 첫 클리어 = 뱃지(스테이지 팝업과 같은 표현 · 검수 결함 8) var n = UiKit.Label(body, e[3], 26, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.UpperCenter, new Vector2(0f, 1f), new Vector2(0.5f, 1f), new Vector2(tx + Tile * 0.5f, -y - Tile - 12f), new Vector2(Tile + gap, 36f), false); n.horizontalOverflow = HorizontalWrapMode.Overflow; } } /// 「첫」 뱃지(Label_Badge01_Yellow · 칸 오른쪽 위). public static void FirstBadge(Transform cell) { var b = UiKit.Icon(cell, "badge_first", new Vector2(1f, 1f), new Vector2(0.5f, 0.5f), new Vector2(-8f, -6f), 58f); if (b != null) UiKit.Title(UiKit.FillLabel(b.transform, "첫", 24, UiKit.White), true); } /// 타일 아래 = 레벨업 가능 칩(위 화살표 + 노란 글자 · 검수 결함 8) · 없으면 비움. void Notes(OutGameSession s, StageReward r, float y) { var ready = ReadyNames(s, r); if (ready.Length == 0) return; string text = "레벨업 가능 " + ready; var t = UiKit.InfoChip(body, "LevelUp", Side, y, RowW, 56f, "up_green", text, 28); t.color = UiKit.Col("select_line", UiKit.Coin); float w = Mathf.Min(RowW, Mathf.Ceil(t.preferredWidth) + 110f); var c = (RectTransform)t.transform.parent; c.anchoredPosition = new Vector2((PW - w) * 0.5f, -y); c.sizeDelta = new Vector2(w, 56f); t.rectTransform.sizeDelta = new Vector2(w - 80f, 56f); } /// 연무장 결과(탑 · 던전 · 투기장 · 적장 토벌 · PD #1093 C) — 별 없음 · 결과 행 · 탭 = 연무장. public void FillMode(ModeResult r) { SetButtons(false); rewardsText = r != null ? string.Join("\n", r.lines.ToArray()) : ""; if (r == null) { title.text = "연무장"; ShowStars(-1, true); if (body != null) UiKit.Clear(body); else plain.text = ""; return; } bool win = r.win || r.kind == ArenaRules.Raid; title.text = r.kind == ArenaRules.Raid ? "토벌 종료" : r.win ? "승리" : "패배"; ShowStars(-1, win); if (body == null) { plain.text = "" + r.title + "\n\n" + rewardsText; return; } UiKit.Clear(body); var head = UiKit.Title(UiKit.Label(body, r.title, 44, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(Side, -150f), new Vector2(RowW, 110f)), true); head.horizontalOverflow = HorizontalWrapMode.Overflow; UiKit.SubTitle(body, "결과", Side, 290f, RowW); float y = 354f; for (int i = 0; i < r.lines.Count && y + RowH <= BtnY - 20f; i++, y += RowH + 8f) UiKit.CondRow(body, "Line" + i, Side, y, RowW, RowH, null, true, r.lines[i], 30); } /// 스테이지 = 「다시」 + 「확인」 · 연무장 = 「확인」 가운데. void SetButtons(bool stage) { if (retry == null) return; retry.gameObject.SetActive(stage); ((RectTransform)ok.transform).anchoredPosition = new Vector2(stage ? PW - Side - 520f : (PW - 520f) * 0.5f, -BtnY); } /// 별 아이콘(n < 0 = 숨김) · 리본 색(승리 붉은 · 패배 회색) · 금빛 = 승리만. void ShowStars(int n, bool win) { if (glow != null) glow.gameObject.SetActive(win); if (starRow == null) return; starRow.gameObject.SetActive(n >= 0); for (int i = 0; i < 3; i++) if (starImg[i] != null) starImg[i].sprite = UISkin.Icon(i < n ? "star_on_l" : "star_off") ?? starImg[i].sprite; if (ribbon != null && !UISkin.Apply(ribbon, win ? "ribbon" : "ribbon_gray")) ribbon.color = win ? Color.white : UiKit.Disabled; } static string ReadyNames(OutGameSession s, StageReward r) { var ready = new StringBuilder(); foreach (var id in r.expTo) { var lv = s.Save.Level(id); if (HeroRules.CanLevelUp(s.Db, s.Save, lv) != null) continue; if (ready.Length > 0) ready.Append(" · "); ready.Append(s.Def(id).nameKo); } return ready.ToString(); } /// 보상 줄(소탕 팝업 · 테마 없을 때 결과 화면 공용). public static string RewardText(OutGameSession s, StageReward r) { var e = s.Db.Economy; var sb = new StringBuilder(); sb.Append("골드 +").Append(r.gold.ToString("N0")).Append(r.first ? " (첫 클리어)" : " (반복 ×" + e.RepeatRewardMult + ")"); sb.Append("\n경험 +").Append(r.exp).Append(" 출전 ").Append(r.expTo.Count).Append("명"); if (r.gem > 0) sb.Append("\n보옥 +").Append(r.gem).Append(r.boss ? " (첫 클리어 + 보스)" : " (첫 클리어)"); if (!string.IsNullOrEmpty(r.gearId)) { var g = s.Db.Gear.Get(r.gearId); sb.Append("\n장비 ").Append(g != null ? g.nameKo + " (" + UiKit.GradeKo(g.grade) + ")" : r.gearId).Append(" 획득"); } else sb.Append("\n장비 드랍 없음"); if (!string.IsNullOrEmpty(r.chest) && s.Db.Chests != null && s.Db.Chests.Get(r.chest) != null) sb.Append("\n").Append(s.Db.Chests.Get(r.chest).nameKo).Append(" 획득 (보물 > 상자)"); if (r.sweep) sb.Append("\n군량 ").Append(r.stamina).Append(" 소모 · 소탕(전투 없음)"); var ready = ReadyNames(s, r); if (ready.Length > 0) sb.Append("\n레벨업 가능: ").Append(ready).Append(""); return sb.ToString(); } } }