ThreeKingdomsSlingshot/Assets/Scripts/OutGame/UI/LobbyScreen.cs

188 lines
12 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
using UnityEngine.UI;
namespace TKS.OutGame
{
/// <summary>
/// 본영 로비(Fable 제안서 §4-1 · PD 2026-11-09 「카피바라GO·탕탕특공대 참고 상업 수준」) — 키아트 + 위 비네트 · 진행 띠(지도 핀 · 장 이름 · 진행 바 n/m) ·
/// 좌우 세로 버튼 열(임무·우편·동맹 / 상점·상자·이벤트 · 미구현 = 잠금 자리) · 출전 편성 5인 · 아래 버튼 행(편성 원 · 출정 3층 · 연무장 원) + 뒤 어두운 띠 · 하단 탭 5.
/// 본문 좌표 = 절대 y − 224.
/// </summary>
public class LobbyScreen : OutGameScreen
{
Text progress;
Text progressNum;
RectTransform progressFill;
Text sortieSub;
Text chestBadge;
RectTransform field;
/// <summary>로비 출전 영웅 표준 키(px · 크기 표 size_px 가 이 높이) — 유비가 예전 칸 맞춤 때와 같은 크기(797×826 틀 → 196 칸 = ×0.246 · size 837 × 0.246 ≈ 206).</summary>
const float LobbyUnitPx = 206f;
static RectTransform At(Transform p, string name, float x, float y, float w, float h)
{
return UiKit.Rect(name, p, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(x, -y), new Vector2(w, h));
}
public override void Build()
{
Header("본영", "", false);
var outer = Body();
// 배경 = 테마 bg_lobby(키아트 · 어둡게) · 없으면 땅 색 판
var art = UISkin.Icon("bg_lobby");
if (art != null)
{
var clip = UiKit.Fill("LobbyBg", outer);
clip.gameObject.AddComponent<RectMask2D>();
// 위쪽 맞춤 × 1.22 — 키아트 아래쪽 아군 5인은 잘라 내고(편성 5인과 겹치지 않게) 전장·적진만 보이게
var art2 = UiKit.Rect("Art", clip, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), Vector2.zero, Vector2.zero);
art2.anchorMin = new Vector2(-0.11f, 1f);
art2.anchorMax = new Vector2(1.11f, 1f);
art2.offsetMin = art2.offsetMax = Vector2.zero;
var bimg = art2.gameObject.AddComponent<Image>();
bimg.sprite = art;
bimg.color = UISkin.Col("lobby_tint", Color.white);
bimg.raycastTarget = false;
var fit = bimg.gameObject.AddComponent<AspectRatioFitter>();
fit.aspectMode = AspectRatioFitter.AspectMode.WidthControlsHeight;
fit.aspectRatio = art.rect.width / Mathf.Max(1f, art.rect.height);
}
// 위 비네트(Glow_Panel01 = 위 불투명 → 아래 투명 · 실측) · 아래 버튼 뒤 어두운 띠
var vg = UiKit.Rect("Vignette", outer, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), Vector2.zero, new Vector2(0f, 180f));
vg.anchorMin = new Vector2(0f, 1f);
vg.anchorMax = new Vector2(1f, 1f);
vg.sizeDelta = new Vector2(0f, 180f);
UiKit.Skin(vg, "scroll_fade", UiKit.Col("lobby_vignette", Color.clear)).enabled = UISkin.Has("scroll_fade");
var body = UiKit.Rect("Mock", outer, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), Vector2.zero, new Vector2(OutGameRoot.Reference.x, OutGameRoot.BodyH));
var band = At(body, "BottomBand", -40f, 1236f, 1160f, 330f);
UiKit.Skin(band, "card", UiKit.Col("lobby_band", Color.clear));
// 진행 띠(절대 236~312) = 지도 핀 + 장 이름 + 진행 바 n/m
var pb = At(body, "Progress", 24f, 12f, 1032f, 76f);
UiKit.Skin(pb, "card", UiKit.Col("chip_dark", UiKit.Dark));
UiKit.DropShadow(UiKit.Icon(pb, "map_point", new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(18f, 2f), 50f), 3f);
progress = UiKit.Title(UiKit.Label(pb, "", 34, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(78f, 2f), new Vector2(560f, 60f)), true);
progress.horizontalOverflow = HorizontalWrapMode.Overflow;
var bar = UiKit.Rect("Bar", pb, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-104f, 0f), new Vector2(240f, 26f));
UiKit.Skin(bar, "lv_bar_bg", UiKit.White);
progressFill = UiKit.Rect("Fill", bar, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(4f, 0f), new Vector2(0f, 16f));
UiKit.Skin(progressFill, "lv_fill", UiKit.Col("lv_fill", UiKit.Coin));
progressNum = UiKit.Title(UiKit.Label(pb, "", 26, UiKit.White, TextAnchor.MiddleRight, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-18f, 2f), new Vector2(80f, 50f)), true);
// 좌우 세로 버튼 열(166×150 · 미구현 = 잠금 자리)
SideButton(body, 24f, 106f, "lobby_mission", "임무", true, () => Root.ComingSoon("임무"));
SideButton(body, 24f, 272f, "lobby_mail", "우편", true, () => Root.ComingSoon("우편"));
SideButton(body, 24f, 438f, "lobby_alliance", "동맹", true, () => Root.ComingSoon("동맹", true));
SideButton(body, 890f, 106f, "lobby_shop", "상점", false, () => Root.Trade.OpenShop());
var chest = SideButton(body, 890f, 272f, "lobby_chest", "상자", false, () =>
{
Root.Show(Root.Treasures);
Root.Treasures.SetPage(2);
});
var cb = UiKit.Skin(UiKit.Rect("Alert", chest, new Vector2(1f, 1f), new Vector2(0.5f, 0.5f), new Vector2(-12f, -12f), new Vector2(44f, 46f)), "badge_circle", UiKit.Col("alert_red", UiKit.Red));
cb.preserveAspect = true;
chestBadge = UiKit.Title(UiKit.FillLabel(cb.transform, "", 24, UiKit.White), true);
SideButton(body, 890f, 438f, "lobby_event", "이벤트", true, () => Root.ComingSoon("이벤트"));
// 전장 자리 · 출전 편성 5인
var ground = UiKit.Fill("Field", body, 24f, 24f, 140f, 520f);
if (art == null) UiKit.Paint(ground, new Color(0.47f, 0.52f, 0.36f));
var shadow = UiKit.Box("Shadow", ground, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 70f), new Vector2(940f, 150f), new Color(0.2f, 0.18f, 0.14f, 0.55f), false);
shadow.sprite = UiKit.Circle;
shadow.type = Image.Type.Simple;
field = UiKit.Fill("Lineup", ground, 20f, 20f, 40f, 40f);
// 아래 버튼 행(절대 1500~1740) = 편성 원 · 출정 3층 · 연무장 원
RoundButton(body, 160f, "btn_formation", "편성", () => Root.Show(Root.Formation));
RoundButton(body, 920f, "btn_arena", "연무장", () =>
{
Root.Arena.SetPage(0);
Root.Show(Root.Arena); // 연무장(PD #1093 C)
});
var so = At(body, "Sortie", 310f, 1290f, 460f, 160f);
var soImg = UiKit.Skin(so, "tapered1", UiKit.Red, true);
so.gameObject.AddComponent<Button>().onClick.AddListener(() => Root.OpenWorldMap());
var gl = UiKit.Rect("Gloss", so, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -10f), new Vector2(400f, 70f));
UiKit.Skin(gl, "tapered2", new Color(1f, 1f, 1f, 0.3f)).enabled = UISkin.Has("tapered2");
UiKit.Icon(so, "tapered3", new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), new Vector2(60f, -26f), 24f);
UiKit.Title(UiKit.Label(so, "출정", 64, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 18f), new Vector2(440f, 90f)), true);
var chip = UiKit.Skin(UiKit.Rect("Chip", so, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 14f), new Vector2(300f, 44f)), "chip", UiKit.Col("chip_dark", UiKit.Dark));
sortieSub = UiKit.Title(UiKit.FillLabel(chip.transform, "", 26, UiKit.White), true);
}
/// <summary>측면 버튼(팩 Button_Round03_Dark 166×150 · 아이콘 96 · 라벨 28 · 잠금 = 흐림 + 자물쇠).</summary>
Transform SideButton(RectTransform body, float x, float y, string icon, string label, bool locked, System.Action onClick)
{
var b = At(body, "Side_" + label, x, y, 166f, 150f);
UiKit.Skin(b, "btn_round03", UiKit.White, true);
b.gameObject.AddComponent<Button>().onClick.AddListener(() => onClick());
var ic = UiKit.Icon(b, icon, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -10f), 92f);
if (ic != null)
{
UiKit.DropShadow(ic, 3f);
if (locked) ic.color = UiKit.Col("tab_locked", UiKit.InkSoft);
}
UiKit.Title(UiKit.Label(b, label, 28, locked ? UiKit.InkSoft : UiKit.White, TextAnchor.MiddleCenter, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 4f), new Vector2(160f, 40f)), true);
if (locked) UiKit.Icon(b, "lock_s", new Vector2(1f, 1f), new Vector2(0.5f, 0.5f), new Vector2(-24f, -28f), 40f);
return b;
}
/// <summary>원형 버튼(팩 Button_Circle122 124 · 진남 틴트 · 아이콘 72 · 라벨 30 아래).</summary>
void RoundButton(RectTransform body, float cx, string icon, string label, System.Action onClick)
{
var b = At(body, "Round_" + label, cx - 62f, 1290f, 124f, 124f);
var img = UiKit.Skin(b, "circle122", UiKit.Col("menu_btn", UiKit.Dark), true);
img.preserveAspect = true;
b.gameObject.AddComponent<Button>().onClick.AddListener(() => onClick());
UiKit.DropShadow(UiKit.Icon(b, icon, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 4f), 76f), 3f);
UiKit.Title(UiKit.Label(body, label, 30, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0.5f, 1f), new Vector2(cx, -1420f), new Vector2(200f, 44f)), true);
}
public override void Refresh()
{
var map = Root.Map;
var cfg = Root.Battle.Config;
float pct = 0f;
if (map != null && map.HasData)
{
int ch = Root.CurrentChapter();
int nodes = map.NodesIn(ch);
int done = WorldProgress.Cleared(ch);
int node = Mathf.Min(done, Mathf.Max(0, nodes - 1));
int si = map.StageIndexOf(ch, node);
var def = cfg.chapters[ch];
progress.text = "제" + def.no + "장 " + def.title;
progressNum.text = Mathf.Min(done, nodes) + "/" + nodes;
pct = nodes > 0 ? Mathf.Clamp01(done / (float)nodes) : 0f;
sortieSub.text = si >= 0 ? "챕터 " + def.no + " · " + def.no + "-" + cfg.stages[si].stage : "챕터 " + def.no;
}
else
{
progress.text = "—";
progressNum.text = "";
sortieSub.text = "챕터 월드맵";
}
progressFill.sizeDelta = new Vector2(232f * pct, progressFill.sizeDelta.y);
progressFill.gameObject.SetActive(pct > 0.01f);
int chests = 0;
foreach (var c in S.Db.Chests.chests) chests += TreasureRules.ChestCount(S.Save, c.id);
chestBadge.text = chests.ToString();
chestBadge.transform.parent.gameObject.SetActive(chests > 0);
UiKit.Clear(field);
var lineup = S.BuildLineup();
int n = Mathf.Max(1, lineup.Count);
float w = Mathf.Min(200f, 980f / n);
for (int i = 0; i < lineup.Count; i++)
{
var h = S.Def(lineup[i].id);
float x = (i - (n - 1) / 2f) * w;
// 크기 표 기준으로 발 맞춰 세움(PD 2026-11-10 「로비 관우만 작게 보임」 = 칸에 그림 틀을 맞추면 틀이 넓은 관우만 작아짐) · 일러 없으면 초상
if (UiKit.UnitStand("Hero" + i, field, h, new Vector2(0.5f, 0f), new Vector2(x, 20f), LobbyUnitPx, cfg.mountedArtMaxTop) == null)
UiKit.Portrait("Hero" + i, field, h, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(x, 20f), new Vector2(w, 340f), false, 1f, Color.clear);
}
}
}
}