diff --git a/Assets/Data/UI/ui_theme.csv b/Assets/Data/UI/ui_theme.csv
index f8bf22da..3bef4ecf 100644
--- a/Assets/Data/UI/ui_theme.csv
+++ b/Assets/Data/UI/ui_theme.csv
@@ -300,7 +300,7 @@ icon,lv_badge_s,IconMisc/Icon_Badge03_Blue.png,,,,,,작은 레벨 배지(카드)
color,band_top,,,,,,#5461FF,목록형 상단 띠(재화 바 뒤 · 설계서 §2-2 B)
color,card_bottom,,,,,,#1B1A1FE6,영웅 카드 이름 띠
color,card_border,,,,,,#000000,영웅 카드 테두리
-color,card_empty,,,,,,#303146,빈·미보유 카드
+color,card_empty,,,,,,#2E2F43,빈·미보유 카드(레퍼런스 실측)
color,card_empty_pattern,,,,,,#2D2F42,빈·미보유 카드 문양
color,card_empty_border,,,,,,#1A1B27,빈·미보유 카드 테두리
color,card_dim,,,,,,#000000A0,미보유 초상 어둡게
@@ -370,4 +370,11 @@ color,arena_2,,,,,,#F1424B,연무장 카드 투기장
color,arena_3,,,,,,#FF9712,연무장 카드 적장 토벌
color,arena_glow,,,,,,#FFFFFF59,연무장 카드 빛(흰 35 %)
color,arena_inner,,,,,,#14182E8C,연무장 카드 글자 판(어둡게 55 %)
+skin,oblique,Frame/PanelFrame03_Oblique.png,,1,,,,헤더 오른쪽 사선 칸(틴트)
+icon,arrow_down,IconMisc/Icon_Arrow_Down.png,,,,,,드롭다운 화살표
+icon,lv_badge_max,IconMisc/Icon_Badge03_Yellow.png,,,,,,최고 레벨 배지(카드)
+color,list_bg,,,,,,#34374F,목록 화면 바탕(영웅 목록 · 레퍼런스 = 팩 Collection 실측)
+color,oblique,,,,,,#D8E8FF,헤더 사선 칸
+color,count_text,,,,,,#1E1E2A,사선 칸 수량 글자
+color,card_empty_face,,,,,,#262739,빈 카드 얼굴 실루엣
font,title,Assets/UI/GUIPro/ResourcesData/Fonts/Sen-ExtraBold.ttf,,,,,,제목·숫자(라틴 Sen · 한글 = 폴백 Malgun Gothic · PD 결정 전)
diff --git a/Assets/Resources/UI/UITheme.asset b/Assets/Resources/UI/UITheme.asset
index 183e1c60..de6c1d52 100644
--- a/Assets/Resources/UI/UITheme.asset
+++ b/Assets/Resources/UI/UITheme.asset
@@ -328,7 +328,7 @@ MonoBehaviour:
- id: card_border
color: {r: 0, g: 0, b: 0, a: 1}
- id: card_empty
- color: {r: 0.1882353, g: 0.19215687, b: 0.27450982, a: 1}
+ color: {r: 0.18039216, g: 0.18431373, b: 0.2627451, a: 1}
- id: card_empty_pattern
color: {r: 0.1764706, g: 0.18431373, b: 0.25882354, a: 1}
- id: card_empty_border
@@ -451,6 +451,14 @@ MonoBehaviour:
color: {r: 1, g: 1, b: 1, a: 0.34901962}
- id: arena_inner
color: {r: 0.078431375, g: 0.09411765, b: 0.18039216, a: 0.54901963}
+ - id: list_bg
+ color: {r: 0.20392157, g: 0.21568628, b: 0.30980393, a: 1}
+ - id: oblique
+ color: {r: 0.84705883, g: 0.9098039, b: 1, a: 1}
+ - id: count_text
+ color: {r: 0.11764706, g: 0.11764706, b: 0.16470589, a: 1}
+ - id: card_empty_face
+ color: {r: 0.14901961, g: 0.15294118, b: 0.22352941, a: 1}
skins:
- id: card
pack: {fileID: 21300000, guid: 3070a6f23e6ca4273897acf1b7e8c5ce, type: 3}
@@ -1005,6 +1013,13 @@ MonoBehaviour:
ppu: 1.7
inset: {x: 0, y: 0, z: 0, w: 0}
overlayAlpha: 1
+ - id: oblique
+ pack: {fileID: 21300000, guid: ab3a1764a033d4c09a5892a64ddc8c78, type: 3}
+ samguk: {fileID: 0}
+ overlay: {fileID: 0}
+ ppu: 1
+ inset: {x: 0, y: 0, z: 0, w: 0}
+ overlayAlpha: 1
icons:
- id: cur_gold
pack: {fileID: 21300000, guid: 6d5687fb0dd3a428d9cd26a6f36c6d78, type: 3}
@@ -1298,6 +1313,14 @@ MonoBehaviour:
pack: {fileID: 21300000, guid: 64a42696c813049fd9c00923ae5842df, type: 3}
samguk: {fileID: 0}
tint: {r: 1, g: 1, b: 1, a: 1}
+ - id: arrow_down
+ pack: {fileID: 21300000, guid: 9987152c36a3b4365ba46f7be2a44777, type: 3}
+ samguk: {fileID: 0}
+ tint: {r: 1, g: 1, b: 1, a: 1}
+ - id: lv_badge_max
+ pack: {fileID: 21300000, guid: 30dce27e256a74aa797da5e70d1c9a08, type: 3}
+ samguk: {fileID: 0}
+ tint: {r: 1, g: 1, b: 1, a: 1}
- id: tile_bridge
pack: {fileID: 0}
samguk: {fileID: 21300000, guid: 8e5bf7eb1e300474785ff2781eeb5093, type: 3}
diff --git a/Assets/Scripts/OutGame/UI/HeroScreens.cs b/Assets/Scripts/OutGame/UI/HeroScreens.cs
index f3da2ccb..b1957532 100644
--- a/Assets/Scripts/OutGame/UI/HeroScreens.cs
+++ b/Assets/Scripts/OutGame/UI/HeroScreens.cs
@@ -76,14 +76,104 @@ namespace TKS.OutGame
}
}
- /// 영웅 탭 = 보유 영웅·병사 목록(장수|병사 · 2줄 카드) → 탭 = 영웅 상세 · 도감.
+ ///
+ /// 영웅 목록 세로 카드(PD 2026-11-09 레퍼런스 = 팩 데모 Collection_List · 칸 320×431) — 등급색 바탕(마스크) · 그라데이션 · 일러 뒤 빛 · 일러 · 아래 어두운 띠 · 테두리 ·
+ /// 이름(왼쪽 위) · 병종 원 · 레벨 배지(왼쪽 아래 · 최고 = 노랑) · ★(오른쪽 아래) · 미보유 = 빈 카드(문양 + 얼굴 실루엣). 수치 = 팩 프리팹 RectTransform 그대로.
+ ///
+ public static class CollectionCards
+ {
+ public static readonly Vector2 CellSize = new Vector2(320f, 431f);
+ public static readonly Vector2 Spacing = new Vector2(22f, 20f);
+
+ public static Image Card(Transform content, OutGameSession s, HeroDb.Hero h, System.Action onTap)
+ {
+ var cell = UiKit.Cell(content, "Card_" + h.id, Color.clear, onTap);
+ cell.sprite = null; // 바닥 = 클릭 판(투명) · 그림은 층으로
+ var t = cell.transform;
+ var lv = s.Save.Level(h.id);
+ var hs = s.Save.Hero(h.id);
+ string g = h.isSoldier || hs == null || string.IsNullOrEmpty(hs.grade) ? "white" : hs.grade;
+ string G = "grade_" + g + "_";
+
+ // 바탕(마스크) 안 = 그라데이션 · 빛 · 일러 · 아래 띠(카드 둥근 모양으로 잘림)
+ var bg = UiKit.Layer(t, "Bg", "card_bg", UiKit.Col(G + "card_bg", UiKit.Paper), 2f, 2f, 2f, 2f);
+ bg.gameObject.AddComponent().showMaskGraphic = true;
+ var b = bg.transform;
+ UiKit.Layer(b, "Gradient", "card_grad", UiKit.Col(G + "card_grad", UiKit.White), 0f, 0f, 0f, 44.6f);
+ UiKit.Skin(UiKit.Rect("Glow", b, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(226f, 226f)), "card_glow", UiKit.Col(G + "card_glow", UiKit.White)).enabled = UISkin.Has("card_glow");
+ var sp = UiKit.ArtSprite(h.art);
+ if (sp != null)
+ {
+ // 우리 일러 = 전신 SD → 상반신이 카드를 채우게 키우고 아래로(다리는 아래 띠 뒤 · 레퍼런스 흉상 느낌)
+ var art = UiKit.Rect("Character", b, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, -56f), new Vector2(470f, 460f)).gameObject.AddComponent();
+ art.sprite = sp;
+ art.preserveAspect = true;
+ art.raycastTarget = false;
+ }
+ else
+ {
+ var dot = UiKit.Dot("Shape", b, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, -10f), 200f, UiKit.TypeColor(h.type));
+ UiKit.Title(UiKit.FillLabel(dot.transform, UnitTypeInfo.Mark(h.type), 100, UiKit.White), true);
+ }
+ var bottom = UiKit.Rect("Bottom", b, new Vector2(0.5f, 0f), new Vector2(0.5f, 0.5f), new Vector2(0f, 21.7f), new Vector2(0f, 91.5f));
+ bottom.anchorMin = new Vector2(0f, 0f);
+ bottom.anchorMax = new Vector2(1f, 0f);
+ bottom.sizeDelta = new Vector2(52.6f, 91.5f);
+ UiKit.Skin(bottom, "card_bottom", UiKit.Col("card_bottom", UiKit.Dark)).enabled = UISkin.Has("card_bottom");
+ UiKit.Layer(t, "Border", "card_border", UiKit.Col("card_border", UiKit.Line));
+
+ // ★(오른쪽 아래 · 42×45 · 28 간격 겹침)
+ int star = hs != null ? hs.star : h.rarity;
+ for (int i = star - 1; i >= 0; i--)
+ UiKit.Icon(t, "star_on", new Vector2(1f, 0f), new Vector2(1f, 1f), new Vector2(-15.5f - i * 28f, 64.8f), 44f);
+ // 레벨 배지(왼쪽 아래 · 최고 레벨 = 노랑)
+ int lvn = lv != null ? lv.lv : 1;
+ bool maxLv = s.Db.Levels != null && lvn >= s.Db.Levels.MaxLevel;
+ var badge = UiKit.Icon(t, maxLv ? "lv_badge_max" : "lv_badge_s", new Vector2(0f, 0f), new Vector2(0.5f, 0.5f), new Vector2(33.9f, 36.7f), 79f);
+ if (badge != null)
+ {
+ var bt = UiKit.Title(UiKit.Label(badge.transform, lvn.ToString(), 30, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(1f, -3f), new Vector2(74f, 50f)), true);
+ bt.horizontalOverflow = HorizontalWrapMode.Overflow;
+ }
+ // 이름(왼쪽 위 · 45) · 병종 원(#35374E 58×60)
+ var name = UiKit.Title(UiKit.Label(t, h.nameKo, 45, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 0.5f), new Vector2(24f, -40.5f), new Vector2(280f, 66f)), true);
+ name.horizontalOverflow = HorizontalWrapMode.Overflow;
+ var role = UiKit.Skin(UiKit.Rect("Role", t, new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), new Vector2(46f, -100.9f), new Vector2(58f, 60f)), "dot", UiKit.Col("circle_inner", UiKit.Dark));
+ role.preserveAspect = true;
+ if (UiKit.Icon(role.transform, UiKit.TypeIconId(h.type), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 40f) == null)
+ UiKit.Title(UiKit.FillLabel(role.transform, UnitTypeInfo.Mark(h.type), 28, UiKit.White), true);
+ if (s.CanGrowAny(h.id)) UiKit.Icon(t, "new_dot", new Vector2(1f, 1f), new Vector2(0.5f, 0.5f), new Vector2(-14f, -14f), 36f);
+ return cell;
+ }
+
+ /// 빈 카드(미보유 자리) = 어두운 바탕 + 문양 + 테두리 + 오른쪽 아래 얼굴 실루엣(팩 CardFrame01_Empty).
+ public static Image Empty(Transform content)
+ {
+ var cell = UiKit.Cell(content, "Empty", Color.clear, null);
+ cell.sprite = null;
+ var t = cell.transform;
+ UiKit.Layer(t, "Bg", "card_bg", UiKit.Col("card_empty", UiKit.Slot), 2f, 2f, 2f, 2f);
+ var pat = UiKit.Layer(t, "Pattern", "card_empty_pattern", UiKit.Col("card_empty_pattern", UiKit.Slot), 8f, 8f, 8f, 8f);
+ pat.type = Image.Type.Tiled;
+ UiKit.Layer(t, "Border", "card_border", UiKit.Col("card_empty_border", UiKit.Line));
+ var face = UiKit.Icon(t, "face", new Vector2(1f, 0f), new Vector2(0.5f, 0.5f), new Vector2(-87f, 90.8f), 127f);
+ if (face != null) face.color = UiKit.Col("card_empty_face", UiKit.Dark);
+ return cell;
+ }
+ }
+
+ ///
+ /// 영웅 탭 = 영웅 목록(PD 2026-11-09 레퍼런스 = 팩 Collection_List) — 헤더(제목 · 장수/병사 고르기 ▼ · 사선 칸 「보유 / 전체」) ·
+ /// 3열 세로 카드(보유 = 레벨 높은 순) + 미보유 자리 = 빈 카드 → 탭 = 영웅 상세.
+ ///
public class HeroListScreen : OutGameScreen
{
bool soldiers;
RectTransform content;
ScrollRect scroll;
- Button tabHero;
- Button tabSoldier;
+ Text filterText;
+ Text countText;
+ int ownedCards;
public bool ShowingSoldiers
{
@@ -93,12 +183,29 @@ namespace TKS.OutGame
public override void Build()
{
Tab = 0;
- Header("영웅", "", true); // 부제 삭제(설계서 §2-5)
+ // 바탕 = 진남(레퍼런스 실측) + 재화 바 뒤 띠
+ UiKit.Paint(UiKit.Fill("ListBg", Rt), UiKit.Col("list_bg", UiKit.Bg), false);
+ UiKit.Paint(UiKit.TopBand("ListTop", Rt, 0f, OutGameRoot.TopBarH), UiKit.Col("band_top", UiKit.Bg), false);
+ var head = Header("영웅", "", true);
+
+ // 오른쪽 사선 칸(보유 / 전체) — 팩 PanelFrame03_Oblique #D8E8FF 239 폭
+ var ob = UiKit.Rect("Oblique", head, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), Vector2.zero, new Vector2(239f, OutGameRoot.HeaderH - 7f));
+ UiKit.Skin(ob, "oblique", UiKit.Col("oblique", UiKit.White));
+ countText = UiKit.Title(UiKit.Label(ob, "", 52, UiKit.Col("count_text", UiKit.Dark), TextAnchor.MiddleCenter, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(12f, 0f), new Vector2(220f, 80f)));
+ countText.horizontalOverflow = HorizontalWrapMode.Overflow;
+
+ // 장수 / 병사 고르기(팩 「Property ▼」 자리 · 탭 = 전환)
+ var pick = UiKit.Rect("Filter", head, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-252f, 0f), new Vector2(240f, 90f));
+ var hit = pick.gameObject.AddComponent();
+ hit.color = Color.clear;
+ pick.gameObject.AddComponent