영웅 목록 = 미보유 영웅도 그림 카드(빈 카드 대신 · 일러 조금 어둡게 · 「미보유」 칩 · 탭 = 획득 안내)(#1108 · PD 2026-11-10 「왜 유닛 목록에 장수들의 모습이 보이지 않는거지? 이번에 추가된 리소스가 반영되도록」)

- HeroScreens: CollectionCards.Card owned 인자 · 미보유 = 레벨 배지·성장 점 없음 · ★ 높은 순 → 표 순서 · 상세 화면은 보유 전제라 미보유 탭 = 토스트(도감과 같은 안내 InfoText)
- 테마 표 ui_theme.csv: card_locked_art · tail_locked(SOT) → TKS/Import UI Theme
- 테스트(고친 부분만): Lobby_Tabs_Back_ComingSoon 에 미보유 카드 수·일러 확인 추가 1/1 · UILayerTests 1/1 · error 0
- 캡처 hero_list 1장 · 배치 134(미보유 64장 그림이 각자 다른 텍스처 → 축소 초상 아틀라스 대상)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
깃 관리자 2026-11-10 10:49:27 +09:00
parent cd6e84ec1e
commit 9718b99a00
4 changed files with 63 additions and 19 deletions

View File

@ -304,6 +304,8 @@ color,card_empty,,,,,,#303146,빈·미보유 카드(팩 CardFrame01_Empty Bg 원
color,card_empty_pattern,,,,,,#2D2F42,빈·미보유 카드 문양
color,card_empty_border,,,,,,#1A1B27,빈·미보유 카드 테두리
color,card_dim,,,,,,#000000A0,미보유 초상 어둡게
color,card_locked_art,,,,,,#A9AEC2,영웅 목록 미보유 카드 일러 틴트(모습은 보이게 · 보유보다 조금 어둡게 · PD 2026-11-10)
color,tail_locked,,,,,,#4A4C63,영웅 목록 미보유 꼬리 칩
color,tail_red,,,,,,#D8412F,출전 꼬리 칩
color,grade_white_card_bg,,,,,,#626E8B,영웅 카드 white card_bg
color,grade_white_card_grad,,,,,,#90A3BA,영웅 카드 white card_grad

1 kind id path overlay ppu inset alpha color note
304 color card_empty_pattern #2D2F42 빈·미보유 카드 문양
305 color card_empty_border #1A1B27 빈·미보유 카드 테두리
306 color card_dim #000000A0 미보유 초상 어둡게
307 color card_locked_art #A9AEC2 영웅 목록 미보유 카드 일러 틴트(모습은 보이게 · 보유보다 조금 어둡게 · PD 2026-11-10)
308 color tail_locked #4A4C63 영웅 목록 미보유 꼬리 칩
309 color tail_red #D8412F 출전 꼬리 칩
310 color grade_white_card_bg #626E8B 영웅 카드 white card_bg
311 color grade_white_card_grad #90A3BA 영웅 카드 white card_grad

View File

@ -335,6 +335,10 @@ MonoBehaviour:
color: {r: 0.101960786, g: 0.105882354, b: 0.15294118, a: 1}
- id: card_dim
color: {r: 0, g: 0, b: 0, a: 0.627451}
- id: card_locked_art
color: {r: 0.6627451, g: 0.68235296, b: 0.7607843, a: 1}
- id: tail_locked
color: {r: 0.2901961, g: 0.29803923, b: 0.3882353, a: 1}
- id: tail_red
color: {r: 0.84705883, g: 0.25490198, b: 0.18431373, a: 1}
- id: grade_white_card_bg

View File

@ -95,7 +95,8 @@ namespace TKS.OutGame
/// <summary>말 탄 유닛 전신(발 ~ 머리 끝) 높이 상한(카드 px) — 크기 표 실측이 튀는 그림(예 여포 size_px 449)도 다른 기병과 비슷하게.</summary>
const float MountedMaxBody = 460f;
public static Image Card(Transform content, OutGameSession s, HeroDb.Hero h, bool deployed, System.Action onTap)
/// <param name="owned">false = 미보유(PD 2026-11-10 「유닛 목록에 장수들의 모습이 보이도록」) — 일러를 테마 card_locked_art 로 조금 어둡게 · 「미보유」 꼬리 칩 · 레벨 배지·성장 점 없음</param>
public static Image Card(Transform content, OutGameSession s, HeroDb.Hero h, bool deployed, System.Action onTap, bool owned = true)
{
var cell = UiKit.Cell(content, "Card_" + h.id, Color.clear, onTap);
cell.sprite = null; // 바닥 = 클릭 판(투명) · 그림은 층으로
@ -138,6 +139,7 @@ namespace TKS.OutGame
}
art.sprite = sp;
art.raycastTarget = false;
if (!owned) art.color = UiKit.Col("card_locked_art", UiKit.InkSoft);
}
else
{
@ -155,10 +157,10 @@ namespace TKS.OutGame
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);
var badge = owned ? 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) : null;
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);
@ -171,23 +173,31 @@ namespace TKS.OutGame
name.resizeTextForBestFit = true;
name.resizeTextMinSize = 26;
name.resizeTextMaxSize = 45;
if (deployed)
if (deployed || !owned)
{
var tail = UiKit.Skin(UiKit.Rect("Deployed", t, new Vector2(0f, 1f), new Vector2(0f, 0.5f), new Vector2(10f, -96f), new Vector2(84f, 38f)), "tail", UiKit.Col("tail_red", UiKit.Red));
UiKit.Title(UiKit.FillLabel(tail.transform, "출전", 24, UiKit.White), true);
var tail = UiKit.Skin(UiKit.Rect(owned ? "Deployed" : "Locked", t, new Vector2(0f, 1f), new Vector2(0f, 0.5f), new Vector2(10f, -96f), new Vector2(owned ? 84f : 104f, 38f)), "tail",
owned ? UiKit.Col("tail_red", UiKit.Red) : UiKit.Col("tail_locked", UiKit.Dark));
UiKit.Title(UiKit.FillLabel(tail.transform, owned ? "출전" : "미보유", 24, UiKit.White), true);
}
// 병과 = 단색 심볼만(배경 틀 없음 · PD 2026-11-09 「심볼은 배경 프레임 없이 단색」)
var role = UiKit.Rect("Role", t, new Vector2(1f, 1f), new Vector2(0.5f, 0.5f), new Vector2(-42f, -42f), new Vector2(60f, 60f));
var sym = UiKit.Icon(role, UiKit.TypeSymbolId(h.type), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 60f);
if (sym == null) UiKit.Title(UiKit.FillLabel(role, UnitTypeInfo.Mark(h.type), 28, UiKit.White), true);
if (s.CanGrowAny(h.id)) UiKit.Icon(t, "new_dot", new Vector2(0f, 0f), new Vector2(0.5f, 0.5f), new Vector2(64f, 70f), 30f); // 성장 가능 = 레벨 배지 오른쪽 위
if (owned && s.CanGrowAny(h.id)) UiKit.Icon(t, "new_dot", new Vector2(0f, 0f), new Vector2(0.5f, 0.5f), new Vector2(64f, 70f), 30f); // 성장 가능 = 레벨 배지 오른쪽 위
return cell;
}
/// <summary>원래 크기(320×431) 카드를 holder 가운데에 배율 k 로 넣는다(편성 자리 · 편성 보유 목록).</summary>
public static Image CardIn(Transform holder, OutGameSession s, HeroDb.Hero h, bool deployed, System.Action onTap, float k)
/// <summary>원래 크기(320×431) 카드를 holder 가운데에 배율 k 로 넣는다(편성 자리 · 편성 보유 목록 · 영웅 목록 미보유 = owned false).</summary>
public static Image CardIn(Transform holder, OutGameSession s, HeroDb.Hero h, bool deployed, System.Action onTap, float k, bool owned = true)
{
return Fit(Card(holder, s, h, deployed, onTap), k);
return Fit(Card(holder, s, h, deployed, onTap, owned), k);
}
/// <summary>미보유·도감 안내 한 줄(이름(한자) · 병종 · ★ · 보유/미보유).</summary>
public static string InfoText(HeroDb.Hero h, bool own)
{
return h.nameKo + (string.IsNullOrEmpty(h.hanja) ? "" : "(" + h.hanja + ")") + " · " + UnitTypeInfo.Korean(h.type) + " · ★" + h.rarity +
(h.isSoldier ? " · 병사 유닛" : "") + (own ? " · 보유" : " · 미보유 — 거래 > 뽑기");
}
public static Image EmptyIn(Transform holder, float k)
@ -227,7 +237,7 @@ namespace TKS.OutGame
/// <summary>
/// 영웅 탭 = 영웅 목록(PD 2026-11-09 레퍼런스 = 팩 Collection_List) — 헤더(제목 · 장수/병사 고르기 ▼ · 사선 칸 「보유 / 전체」) ·
/// 3열 세로 카드(보유 = 레벨 높은 순) + 미보유 자리 = 빈 카드 → 탭 = 영웅 상세.
/// 세로 카드(보유 = 레벨 높은 순 → 탭 = 영웅 상세) + 미보유 = 그 영웅 카드(조금 어둡게 · 「미보유」 · 탭 = 획득 안내 · PD 2026-11-10).
/// </summary>
public class HeroListScreen : OutGameScreen
{
@ -237,6 +247,7 @@ namespace TKS.OutGame
Text filterText;
Text countText;
int ownedCards;
int lockedCards;
UiAutoGrid autoGrid;
public bool ShowingSoldiers
@ -315,19 +326,36 @@ namespace TKS.OutGame
CollectionCards.CardIn(Slot(), S, h, S.InActivePreset(id), () => Root.ShowHero(id), autoGrid.Scale);
ownedCards++;
}
int total = 0;
foreach (var h in S.Db.Heroes.heroes)
if (h.isSoldier == soldiers) total++;
for (int i = owned.Count; i < total; i++) CollectionCards.EmptyIn(Slot(), autoGrid.Scale);
countText.text = owned.Count + "/" + total;
// 미보유 = 빈 카드 대신 그 영웅 카드(일러 조금 어둡게 · 「미보유」 · 탭 = 획득 안내) · ★ 높은 순 → 표 순서
// PD 2026-11-10 「왜 유닛 목록에 장수들의 모습이 보이지 않는거지? 이번에 추가된 리소스가 반영되도록」
// (상세 화면은 레벨·세이브가 있는 보유 영웅 전제라 미보유는 열지 않는다)
var locked = new List<HeroDb.Hero>();
var all = S.Db.Heroes.heroes;
foreach (var h in all)
if (h.isSoldier == soldiers && !S.Save.Owns(h.id)) locked.Add(h);
locked.Sort((a, b) => a.rarity != b.rarity ? b.rarity.CompareTo(a.rarity) : all.IndexOf(a).CompareTo(all.IndexOf(b)));
lockedCards = 0;
foreach (var h in locked)
{
var hero = h;
CollectionCards.CardIn(Slot(), S, h, false, () => Root.Toast(CollectionCards.InfoText(hero, false)), autoGrid.Scale, false);
lockedCards++;
}
countText.text = owned.Count + "/" + (owned.Count + locked.Count);
autoGrid.Apply();
}
/// <summary>보유 카드 수(빈 카드 제외).</summary>
/// <summary>보유 카드 수(미보유 제외).</summary>
public int CardCount
{
get { return ownedCards; }
}
/// <summary>미보유 영웅 카드 수(그림 카드 · PD 2026-11-10).</summary>
public int LockedCount
{
get { return lockedCards; }
}
}
/// <summary>
@ -968,8 +996,7 @@ namespace TKS.OutGame
{
bool own = s.Save.Owns(h.id);
var hero = h;
HeroCards.Card(grid, s, h, false, own, () => root.Toast(hero.nameKo + (string.IsNullOrEmpty(hero.hanja) ? "" : "(" + hero.hanja + ")") + " · " +
UnitTypeInfo.Korean(hero.type) + " · ★" + hero.rarity + (hero.isSoldier ? " · 병사 유닛" : "") + (own ? " · 보유" : " · 미보유 — 거래 > 뽑기")));
HeroCards.Card(grid, s, h, false, own, () => root.Toast(CollectionCards.InfoText(hero, own)));
}
}
}

View File

@ -479,6 +479,17 @@ namespace TKS.Tests
Assert.AreEqual(og.HeroList, og.Current);
yield return null;
Assert.AreEqual(og.Session.Owned(false).Count, og.HeroList.CardCount, "보유 장수 카드");
// 미보유 장수 = 빈 카드가 아니라 그 영웅 그림 카드(PD 2026-11-10 「유닛 목록에 장수들의 모습이 보이도록」)
int heroTotal = 0;
foreach (var h in og.Session.Db.Heroes.heroes)
if (!h.isSoldier) heroTotal++;
Assert.AreEqual(heroTotal - og.Session.Owned(false).Count, og.HeroList.LockedCount, "미보유 장수 카드 = 그림 카드");
Assert.Greater(og.HeroList.LockedCount, 0, "신규 유저 = 미보유 있음");
var lockedTail = og.HeroList.Rt.GetComponentsInChildren<Transform>(true);
int lockedWithArt = 0;
foreach (var tr in lockedTail)
if (tr.name == "Locked" && tr.parent.Find("Bg/Character") != null) lockedWithArt++;
Assert.AreEqual(og.HeroList.LockedCount, lockedWithArt, "미보유 카드마다 일러(Character)");
og.HeroList.SetFilter(true);
yield return null;
Assert.AreEqual(og.Session.Owned(true).Count, og.HeroList.CardCount, "병사 필터");