영웅 목록 = 해상도 대응 4열 · 유닛 −20 % · 진영 원 배지 · 병과 심볼(#1104 · PD 2026-11-09 영웅 목록 수정 5건)

1. 카드 축소 = 한 줄 4명(기준 1080 폭 · 칸 246 · 간격 16)
2. 해상도 대응 = UiAutoGrid(열 수 = round((폭 + 간격) ÷ (목표 칸 + 간격)) · 최소 3 · 칸 = 폭 ÷ 열 · 카드 = 원래 320×431 을 배율로) · 본문 = 화면 폭 전체 · 실측 1080×1920 = 4열 · 1536×2048 = 5열 · 1080×2400 = 4열 · 1080×2520 = 3열
3. 카드 유닛 20 % 작게(UnitPx 450 → 360)
4. 진영 = 원 배지 위·촉·오·군(UiKit.FactionBadge · 색 = 테마 fbadge_* · 촉 녹 · 위 청 · 오 적 · 군 짙은 회색) — 구 세력 깃발 대체(목록 · 편성 카드 공용)
5. 병과 = 단색 심볼(말 · 검 · 활 · 창 · 깃털부채) = Assets/UI/Symbols/cls_sym_*.png · 만든 법 = Tools/class_symbols.py(확정 납품 메달의 원본 투명 사물 그림 = 아트 R&D seed 21 의 형태 → 베이지 채움 + 안쪽 선 + 테두리 · 새로 그리지 않음) · UiKit.TypeSymbolId · 임포터 Symbols 폴더 Sprite
- 캡처 도구: 영웅 목록 1536×2048 · 1080×2400 · 1080×2520 + 열 수 로그 · 진영 확인용 위·오·군 영웅 1명씩(메모리 세이브만)
- 테스트 118/118(다른 세션 미커밋 전투 변경 포함 상태) · console error 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
깃 관리자 2026-11-09 19:30:58 +09:00
parent 747b3968a8
commit c64be33646
20 changed files with 1121 additions and 11 deletions

View File

@ -498,3 +498,16 @@ skin,btn_green,Button/Button01_s_Green.png,,1.22,,,,Secondary 녹 h100(광고
color,stage_pattern,,,,,,#FFFFFF0D,상자 탭 무대 문양(흰 5 % · 검수2 결함 4) color,stage_pattern,,,,,,#FFFFFF0D,상자 탭 무대 문양(흰 5 % · 검수2 결함 4)
color,chest_select,,,,,,#FFFFFF,상자 행 선택 테두리(흰 · 금 틀과 구분 · 검수2 결함 5) color,chest_select,,,,,,#FFFFFF,상자 행 선택 테두리(흰 · 금 틀과 구분 · 검수2 결함 5)
color,chest_dim,,,,,,#00000040,상자 행 비선택 어둡게(25 %) color,chest_dim,,,,,,#00000040,상자 행 비선택 어둡게(25 %)
icon,cls_sym_infantry,Assets/UI/Symbols/cls_sym_infantry.png,,,,,,병과 단색 심볼 보병 검(PD 2026-11-09 · 납품 그림 형태 → Tools/class_symbols.py)
icon,cls_sym_spear,Assets/UI/Symbols/cls_sym_spear.png,,,,,,병과 단색 심볼 창병 창(PD 2026-11-09 · 납품 그림 형태 → Tools/class_symbols.py)
icon,cls_sym_cavalry,Assets/UI/Symbols/cls_sym_cavalry.png,,,,,,병과 단색 심볼 기병 말(PD 2026-11-09 · 납품 그림 형태 → Tools/class_symbols.py)
icon,cls_sym_archer,Assets/UI/Symbols/cls_sym_archer.png,,,,,,병과 단색 심볼 궁병 활(PD 2026-11-09 · 납품 그림 형태 → Tools/class_symbols.py)
icon,cls_sym_strategist,Assets/UI/Symbols/cls_sym_strategist.png,,,,,,병과 단색 심볼 책사 깃털부채(PD 2026-11-09 · 납품 그림 형태 → Tools/class_symbols.py)
color,fbadge_shu,,,,,,#5DBB4E,진영 원 배지 촉 바깥(PD 2026-11-09 · 촉 녹 · 위 청 · 오 적 · 군 짙은 회색)
color,fbadge_shu_in,,,,,,#2F8A3A,진영 원 배지 촉 안쪽
color,fbadge_wei,,,,,,#4C8EF0,진영 원 배지 위 바깥(PD 2026-11-09 · 촉 녹 · 위 청 · 오 적 · 군 짙은 회색)
color,fbadge_wei_in,,,,,,#2459B8,진영 원 배지 위 안쪽
color,fbadge_wu,,,,,,#EE5A48,진영 원 배지 오 바깥(PD 2026-11-09 · 촉 녹 · 위 청 · 오 적 · 군 짙은 회색)
color,fbadge_wu_in,,,,,,#B23324,진영 원 배지 오 안쪽
color,fbadge_other,,,,,,#6E7280,진영 원 배지 군 바깥(PD 2026-11-09 · 촉 녹 · 위 청 · 오 적 · 군 짙은 회색)
color,fbadge_other_in,,,,,,#3E414B,진영 원 배지 군 안쪽

1 kind id path overlay ppu inset alpha color note
498 color stage_pattern #FFFFFF0D 상자 탭 무대 문양(흰 5 % · 검수2 결함 4)
499 color chest_select #FFFFFF 상자 행 선택 테두리(흰 · 금 틀과 구분 · 검수2 결함 5)
500 color chest_dim #00000040 상자 행 비선택 어둡게(25 %)
501 icon cls_sym_infantry Assets/UI/Symbols/cls_sym_infantry.png 병과 단색 심볼 보병 검(PD 2026-11-09 · 납품 그림 형태 → Tools/class_symbols.py)
502 icon cls_sym_spear Assets/UI/Symbols/cls_sym_spear.png 병과 단색 심볼 창병 창(PD 2026-11-09 · 납품 그림 형태 → Tools/class_symbols.py)
503 icon cls_sym_cavalry Assets/UI/Symbols/cls_sym_cavalry.png 병과 단색 심볼 기병 말(PD 2026-11-09 · 납품 그림 형태 → Tools/class_symbols.py)
504 icon cls_sym_archer Assets/UI/Symbols/cls_sym_archer.png 병과 단색 심볼 궁병 활(PD 2026-11-09 · 납품 그림 형태 → Tools/class_symbols.py)
505 icon cls_sym_strategist Assets/UI/Symbols/cls_sym_strategist.png 병과 단색 심볼 책사 깃털부채(PD 2026-11-09 · 납품 그림 형태 → Tools/class_symbols.py)
506 color fbadge_shu #5DBB4E 진영 원 배지 촉 바깥(PD 2026-11-09 · 촉 녹 · 위 청 · 오 적 · 군 짙은 회색)
507 color fbadge_shu_in #2F8A3A 진영 원 배지 촉 안쪽
508 color fbadge_wei #4C8EF0 진영 원 배지 위 바깥(PD 2026-11-09 · 촉 녹 · 위 청 · 오 적 · 군 짙은 회색)
509 color fbadge_wei_in #2459B8 진영 원 배지 위 안쪽
510 color fbadge_wu #EE5A48 진영 원 배지 오 바깥(PD 2026-11-09 · 촉 녹 · 위 청 · 오 적 · 군 짙은 회색)
511 color fbadge_wu_in #B23324 진영 원 배지 오 안쪽
512 color fbadge_other #6E7280 진영 원 배지 군 바깥(PD 2026-11-09 · 촉 녹 · 위 청 · 오 적 · 군 짙은 회색)
513 color fbadge_other_in #3E414B 진영 원 배지 군 안쪽

View File

@ -577,6 +577,22 @@ MonoBehaviour:
color: {r: 1, g: 1, b: 1, a: 1} color: {r: 1, g: 1, b: 1, a: 1}
- id: chest_dim - id: chest_dim
color: {r: 0, g: 0, b: 0, a: 0.2509804} color: {r: 0, g: 0, b: 0, a: 0.2509804}
- id: fbadge_shu
color: {r: 0.3647059, g: 0.73333335, b: 0.30588236, a: 1}
- id: fbadge_shu_in
color: {r: 0.18431373, g: 0.5411765, b: 0.22745098, a: 1}
- id: fbadge_wei
color: {r: 0.29803923, g: 0.5568628, b: 0.9411765, a: 1}
- id: fbadge_wei_in
color: {r: 0.14117648, g: 0.34901962, b: 0.72156864, a: 1}
- id: fbadge_wu
color: {r: 0.93333334, g: 0.3529412, b: 0.28235295, a: 1}
- id: fbadge_wu_in
color: {r: 0.69803923, g: 0.2, b: 0.14117648, a: 1}
- id: fbadge_other
color: {r: 0.43137255, g: 0.44705883, b: 0.5019608, a: 1}
- id: fbadge_other_in
color: {r: 0.24313726, g: 0.25490198, b: 0.29411766, a: 1}
skins: skins:
- id: card - id: card
pack: {fileID: 21300000, guid: 3070a6f23e6ca4273897acf1b7e8c5ce, type: 3} pack: {fileID: 21300000, guid: 3070a6f23e6ca4273897acf1b7e8c5ce, type: 3}
@ -1776,6 +1792,26 @@ MonoBehaviour:
pack: {fileID: 21300000, guid: 09135c198f8744fcd89493b169d11bd8, type: 3} pack: {fileID: 21300000, guid: 09135c198f8744fcd89493b169d11bd8, type: 3}
samguk: {fileID: 0} samguk: {fileID: 0}
tint: {r: 1, g: 1, b: 1, a: 1} tint: {r: 1, g: 1, b: 1, a: 1}
- id: cls_sym_infantry
pack: {fileID: 21300000, guid: 45255881d251469429f5a85c7d135441, type: 3}
samguk: {fileID: 0}
tint: {r: 1, g: 1, b: 1, a: 1}
- id: cls_sym_spear
pack: {fileID: 21300000, guid: 63a264aa26e829749a8114cd7e10a22f, type: 3}
samguk: {fileID: 0}
tint: {r: 1, g: 1, b: 1, a: 1}
- id: cls_sym_cavalry
pack: {fileID: 21300000, guid: 3ca6b69da9089eb4aabb4aa5fae10ecf, type: 3}
samguk: {fileID: 0}
tint: {r: 1, g: 1, b: 1, a: 1}
- id: cls_sym_archer
pack: {fileID: 21300000, guid: 9238470c5d76e8c499fd2ebf8a526054, type: 3}
samguk: {fileID: 0}
tint: {r: 1, g: 1, b: 1, a: 1}
- id: cls_sym_strategist
pack: {fileID: 21300000, guid: 63fd1be34f67f92488e14e322a34df12, type: 3}
samguk: {fileID: 0}
tint: {r: 1, g: 1, b: 1, a: 1}
- id: tile_bridge - id: tile_bridge
pack: {fileID: 0} pack: {fileID: 0}
samguk: {fileID: 21300000, guid: 8e5bf7eb1e300474785ff2781eeb5093, type: 3} samguk: {fileID: 21300000, guid: 8e5bf7eb1e300474785ff2781eeb5093, type: 3}

View File

@ -202,7 +202,7 @@ namespace TKS.EditorTools
static bool Ours(string path) static bool Ours(string path)
{ {
return path.StartsWith(UIThemeImporter.SamgukRoot + "/") || path.StartsWith("Assets/UI/Lobby/"); return path.StartsWith(UIThemeImporter.SamgukRoot + "/") || path.StartsWith("Assets/UI/Lobby/") || path.StartsWith("Assets/UI/Symbols/"); // Symbols = 병과 단색 심볼(Tools/class_symbols.py · PD 2026-11-09)
} }
void OnPreprocessTexture() void OnPreprocessTexture()

View File

@ -85,7 +85,7 @@ namespace TKS.OutGame
public static readonly Vector2 CellSize = new Vector2(320f, 431f); public static readonly Vector2 CellSize = new Vector2(320f, 431f);
public static readonly Vector2 Spacing = new Vector2(22f, 20f); public static readonly Vector2 Spacing = new Vector2(22f, 20f);
/// <summary>크기 표 size_px(머리 크기 기준 표준 키)가 카드에서 차지할 px — 배율 = UnitPx ÷ size_px(모든 유닛 머리 크기 같음) · 머리 끝 위치(카드 위에서 px).</summary> /// <summary>크기 표 size_px(머리 크기 기준 표준 키)가 카드에서 차지할 px — 배율 = UnitPx ÷ size_px(모든 유닛 머리 크기 같음) · 머리 끝 위치(카드 위에서 px).</summary>
const float UnitPx = 450f; const float UnitPx = 360f; // 450 → 360 = 유닛 20 % 작게(PD 2026-11-09 「현재 비율보다 20 % 정도 작게」)
const float HeadTop = 50f; const float HeadTop = 50f;
public static Image Card(Transform content, OutGameSession s, HeroDb.Hero h, bool deployed, System.Action onTap) public static Image Card(Transform content, OutGameSession s, HeroDb.Hero h, bool deployed, System.Action onTap)
@ -122,7 +122,7 @@ namespace TKS.OutGame
} }
else else
{ {
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<Image>(); art = UiKit.Rect("Character", b, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, -56f), new Vector2(376f, 368f)).gameObject.AddComponent<Image>();
art.preserveAspect = true; art.preserveAspect = true;
} }
art.sprite = sp; art.sprite = sp;
@ -154,8 +154,8 @@ namespace TKS.OutGame
bt.horizontalOverflow = HorizontalWrapMode.Overflow; bt.horizontalOverflow = HorizontalWrapMode.Overflow;
} }
// PD 커스터마이징(2026-11-09): 왼쪽 위 = 진영 깃발 · 그 오른쪽 = 이름(45 · 긴 이름 자동 축소) · 깃발 아래(왼쪽 가장자리) = 출전 · 오른쪽 위 = 병종 원(#35374E 58×60) // PD 커스터마이징(2026-11-09): 왼쪽 위 = 진영 깃발 · 그 오른쪽 = 이름(45 · 긴 이름 자동 축소) · 깃발 아래(왼쪽 가장자리) = 출전 · 오른쪽 위 = 병종 원(#35374E 58×60)
var flag = UiKit.Icon(t, UISkin.Icon("flag_" + h.faction) != null ? "flag_" + h.faction : "flag_other", new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), new Vector2(44f, -42f), 66f); // 진영 = 원 배지(위·촉·오·군 · PD 2026-11-09 · 구 세력 깃발 대체)
UiKit.DropShadow(flag, 3f); UiKit.DropShadow(UiKit.FactionBadge(t, h.faction, new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), new Vector2(44f, -42f), 62f), 3f);
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(80f, -40.5f), new Vector2(172f, 66f)), true); 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(80f, -40.5f), new Vector2(172f, 66f)), true);
name.resizeTextForBestFit = true; name.resizeTextForBestFit = true;
name.resizeTextMinSize = 26; name.resizeTextMinSize = 26;
@ -167,7 +167,7 @@ namespace TKS.OutGame
} }
var role = UiKit.Skin(UiKit.Rect("Role", t, new Vector2(1f, 1f), new Vector2(0.5f, 0.5f), new Vector2(-42f, -42f), new Vector2(58f, 60f)), "dot", UiKit.Col("circle_inner", UiKit.Dark)); var role = UiKit.Skin(UiKit.Rect("Role", t, new Vector2(1f, 1f), new Vector2(0.5f, 0.5f), new Vector2(-42f, -42f), new Vector2(58f, 60f)), "dot", UiKit.Col("circle_inner", UiKit.Dark));
role.preserveAspect = true; 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) if (UiKit.Icon(role.transform, UiKit.TypeSymbolId(h.type), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 46f) == null) // 병과 심볼(PD 2026-11-09)
UiKit.Title(UiKit.FillLabel(role.transform, UnitTypeInfo.Mark(h.type), 28, UiKit.White), true); 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(0f, 0f), new Vector2(0.5f, 0.5f), new Vector2(64f, 70f), 30f); // 성장 가능 = 레벨 배지 오른쪽 위 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); // 성장 가능 = 레벨 배지 오른쪽 위
return cell; return cell;
@ -226,6 +226,7 @@ namespace TKS.OutGame
Text filterText; Text filterText;
Text countText; Text countText;
int ownedCards; int ownedCards;
UiAutoGrid autoGrid;
public bool ShowingSoldiers public bool ShowingSoldiers
{ {
@ -255,9 +256,25 @@ namespace TKS.OutGame
var arrow = UiKit.Icon(pick, "arrow_down", new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-6f, 0f), 38f); var arrow = UiKit.Icon(pick, "arrow_down", new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-6f, 0f), 38f);
UiKit.DropShadow(arrow, 3f); UiKit.DropShadow(arrow, 3f);
var body = Body(); // 본문 = 화면 폭 전체(해상도 대응 · PD 2026-11-09) · 열 수·카드 배율 = UiAutoGrid(기준 1080 = 4열 · 좁으면 3 · 넓으면 5 이상)
float side = (OutGameRoot.Reference.x - CollectionCards.CellSize.x * 3f - CollectionCards.Spacing.x * 2f) / 2f; var body = UiKit.Fill("ListBody", Rt, 0f, 0f, OutGameRoot.TopBarH + OutGameRoot.HeaderH, OutGameRoot.TabBarH);
content = UiKit.ScrollGrid("List", body, side, side, 20f, 0f, CollectionCards.CellSize, CollectionCards.Spacing, 3, out scroll); content = UiKit.ScrollGrid("List", body, 24f, 24f, 20f, 0f, CollectionCards.CellSize, CollectionCards.Spacing, 4, out scroll);
autoGrid = content.gameObject.AddComponent<UiAutoGrid>();
autoGrid.design = CollectionCards.CellSize;
}
/// <summary>열 수(해상도 대응 · 테스트·캡처).</summary>
public int Columns
{
get { return autoGrid != null ? autoGrid.Columns : 0; }
}
/// <summary>격자 칸(holder) — 원래 크기 카드를 UiAutoGrid 배율로 넣는다.</summary>
Transform Slot()
{
var go = new GameObject("Slot", typeof(RectTransform));
go.transform.SetParent(content, false);
return go.transform;
} }
public void SetFilter(bool soldier) public void SetFilter(bool soldier)
@ -284,14 +301,15 @@ namespace TKS.OutGame
foreach (var h in owned) foreach (var h in owned)
{ {
var id = h.id; var id = h.id;
CollectionCards.Card(content, S, h, S.InActivePreset(id), () => Root.ShowHero(id)); CollectionCards.CardIn(Slot(), S, h, S.InActivePreset(id), () => Root.ShowHero(id), autoGrid.Scale);
ownedCards++; ownedCards++;
} }
int total = 0; int total = 0;
foreach (var h in S.Db.Heroes.heroes) foreach (var h in S.Db.Heroes.heroes)
if (h.isSoldier == soldiers) total++; if (h.isSoldier == soldiers) total++;
for (int i = owned.Count; i < total; i++) CollectionCards.Empty(content); for (int i = owned.Count; i < total; i++) CollectionCards.EmptyIn(Slot(), autoGrid.Scale);
countText.text = owned.Count + "/" + total; countText.text = owned.Count + "/" + total;
autoGrid.Apply();
} }
/// <summary>보유 카드 수(빈 카드 제외).</summary> /// <summary>보유 카드 수(빈 카드 제외).</summary>

View File

@ -0,0 +1,58 @@
using UnityEngine;
using UnityEngine.UI;
namespace TKS.OutGame
{
/// <summary>
/// 해상도 대응 카드 격자(PD 2026-11-09 「한 줄 최대 4명 · 가로가 길면 4명 이상 · 좁으면 3명 · 자동 리사이징」) —
/// 폭에 맞춰 열 수 = round((폭 + 간격) ÷ (목표 칸 폭 + 간격)) · 최소 minColumns · 칸 폭 = 폭을 열 수로 나눔(세로 = 원래 비율) ·
/// 각 칸(holder)의 첫 자식 = 원래 크기(design) 카드 → 배율 Scale 로 맞춤. 기준 1080 폭(좌우 24) = 4열.
/// </summary>
[RequireComponent(typeof(GridLayoutGroup))]
public class UiAutoGrid : MonoBehaviour
{
public Vector2 design = new Vector2(320f, 431f);
public float targetWidth = 246f;
public float gap = 16f;
public int minColumns = 3;
GridLayoutGroup grid;
bool busy;
public float Scale { get; private set; }
public int Columns { get; private set; }
void Awake()
{
grid = GetComponent<GridLayoutGroup>();
if (Scale <= 0f) Scale = targetWidth / design.x;
}
void OnRectTransformDimensionsChange()
{
Apply();
}
/// <summary>지금 폭으로 열 수·칸 크기·카드 배율을 다시 계산(칸을 새로 넣은 뒤에도 부른다).</summary>
public void Apply()
{
if (busy) return;
if (grid == null) Awake();
var rt = (RectTransform)transform;
float w = rt.rect.width - grid.padding.left - grid.padding.right;
if (w <= 1f) return;
busy = true;
int cols = Mathf.Max(minColumns, Mathf.RoundToInt((w + gap) / (targetWidth + gap)));
float cw = (w - (cols - 1) * gap) / cols;
Scale = cw / design.x;
Columns = cols;
var cell = new Vector2(cw, design.y * Scale);
if (grid.cellSize != cell) grid.cellSize = cell;
if (grid.spacing != new Vector2(gap, gap)) grid.spacing = new Vector2(gap, gap);
if (grid.constraintCount != cols) grid.constraintCount = cols;
foreach (Transform c in transform)
if (c.childCount > 0) c.GetChild(0).localScale = new Vector3(Scale, Scale, 1f);
busy = false;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 489991b2f3bb99e4ba6e6bc028da049f

View File

@ -411,6 +411,41 @@ namespace TKS.OutGame
return t; return t;
} }
/// <summary>병과 단색 심볼 id(PD 2026-11-09 · 말 · 검 · 활 · 창 · 깃털부채) — 없으면 기존 병과 메달 아이콘.</summary>
public static string TypeSymbolId(UnitType t)
{
string id = "cls_sym_" + TypeKey(t);
return UISkin.Icon(id) != null ? id : TypeIconId(t);
}
/// <summary>진영 글자(위 · 촉 · 오 · 군) · 키 = heroes.csv faction(shu · wei · wu · other).</summary>
public static string FactionKo(string f)
{
switch (f)
{
case "shu": return "촉";
case "wei": return "위";
case "wu": return "오";
}
return "군";
}
/// <summary>진영 원 배지(PD 2026-11-09 「원형 프레임에 위·촉·오·군 · 촉 녹 · 위 청 · 오 적 · 군 짙은 회색」) = 원 틀(fbadge_*) + 안쪽 원(fbadge_*_in) + 흰 글자.</summary>
public static Image FactionBadge(Transform p, string faction, Vector2 anchor, Vector2 pivot, Vector2 pos, float d)
{
string f = faction == "shu" || faction == "wei" || faction == "wu" ? faction : "other";
var ring = Skin(Rect("Faction", p, anchor, pivot, pos, new Vector2(d, d)), "circle_frame", Col("fbadge_" + f, Dark));
ring.preserveAspect = true;
var inner = Rect("In", ring.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(d * 0.72f, d * 0.72f)).gameObject.AddComponent<Image>();
inner.sprite = Circle;
inner.color = Col("fbadge_" + f + "_in", Dark);
inner.raycastTarget = false;
var t = Title(FillLabel(ring.transform, FactionKo(f), Mathf.RoundToInt(d * 0.48f), White), true);
t.rectTransform.offsetMin = new Vector2(0f, d * 0.03f);
t.horizontalOverflow = HorizontalWrapMode.Overflow;
return ring;
}
/// <summary>아이콘(테마 id · 없으면 null).</summary> /// <summary>아이콘(테마 id · 없으면 null).</summary>
public static Image Icon(Transform parent, string id, Vector2 anchor, Vector2 pivot, Vector2 pos, float size) public static Image Icon(Transform parent, string id, Vector2 anchor, Vector2 pivot, Vector2 pos, float size)
{ {

View File

@ -78,8 +78,24 @@ namespace TKS.OutGame
if (sid == id || sh == null || shs == null || sh.gear.Count == 0) continue; if (sid == id || sh == null || shs == null || sh.gear.Count == 0) continue;
shs.grade = sh.gear[Mathf.Min(gi++, sh.gear.Count - 1)].grade; shs.grade = sh.gear[Mathf.Min(gi++, sh.gear.Count - 1)].grade;
} }
// 진영 원 배지 확인용(메모리 세이브만 · PD 2026-11-09): 위 · 오 · 군 영웅 1명씩 보유 추가
foreach (var fac in new[] { "wei", "wu", "other" })
foreach (var fh in db.Heroes.heroes)
if (!fh.isSoldier && fh.faction == fac && !d.Owns(fh.id))
{
HeroRules.Grant(db, d, fh, HeroRules.GearSlotCount(db));
break;
}
root.Show(root.HeroList); root.Show(root.HeroList);
yield return Shot(cam, "hero_list", W, H); yield return Shot(cam, "hero_list", W, H);
Log("hero_list cols=" + root.HeroList.Columns);
// 해상도 대응(PD 2026-11-09): 넓은 화면(3:4) · 좁은 화면(9:20)
yield return Shot(cam, "hero_list_1536x2048", W2, H2);
Log("hero_list_1536x2048 cols=" + root.HeroList.Columns);
yield return Shot(cam, "hero_list_1080x2400", 1080, 2400);
Log("hero_list_1080x2400 cols=" + root.HeroList.Columns);
yield return Shot(cam, "hero_list_1080x2520", 1080, 2520);
Log("hero_list_1080x2520 cols=" + root.HeroList.Columns);
root.ShowHero(id); root.ShowHero(id);
yield return Shot(cam, "hero_detail", W, H); yield return Shot(cam, "hero_detail", W, H);
// 영웅 상세 등급 변형(UI 상업용 재설계 ① · 목업 hero_detail_5등급 대조 · 메모리 세이브만) = 그 영웅 장비 세트의 등급 순서대로 // 영웅 상세 등급 변형(UI 상업용 재설계 ① · 목업 hero_detail_5등급 대조 · 메모리 세이브만) = 그 영웅 장비 세트의 등급 순서대로

8
Assets/UI/Symbols.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: abcec5e654e973d49a8e4be2b67eb6fe
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,168 @@
fileFormatVersion: 2
guid: 9238470c5d76e8c499fd2ebf8a526054
TextureImporter:
internalIDToNameTable:
- first:
213: 3906269742430731014
second: cls_sym_archer_0
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 1024
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: cls_sym_archer_0
rect:
serializedVersion: 2
x: 20
y: 13
width: 215
height: 230
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 60bb07a62abd53630800000000000000
internalID: 3906269742430731014
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,168 @@
fileFormatVersion: 2
guid: 3ca6b69da9089eb4aabb4aa5fae10ecf
TextureImporter:
internalIDToNameTable:
- first:
213: 9171839899375206819
second: cls_sym_cavalry_0
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 1024
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: cls_sym_cavalry_0
rect:
serializedVersion: 2
x: 13
y: 13
width: 230
height: 230
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 3a183034bcbe84f70800000000000000
internalID: 9171839899375206819
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,168 @@
fileFormatVersion: 2
guid: 45255881d251469429f5a85c7d135441
TextureImporter:
internalIDToNameTable:
- first:
213: 7957124930109909397
second: cls_sym_infantry_0
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 1024
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: cls_sym_infantry_0
rect:
serializedVersion: 2
x: 28
y: 13
width: 199
height: 230
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 5992b9c17f26d6e60800000000000000
internalID: 7957124930109909397
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,168 @@
fileFormatVersion: 2
guid: 63a264aa26e829749a8114cd7e10a22f
TextureImporter:
internalIDToNameTable:
- first:
213: -4254521251010258662
second: cls_sym_spear_0
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 1024
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: cls_sym_spear_0
rect:
serializedVersion: 2
x: 32
y: 13
width: 192
height: 230
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: a19de20b677e4f4c0800000000000000
internalID: -4254521251010258662
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,168 @@
fileFormatVersion: 2
guid: 63fd1be34f67f92488e14e322a34df12
TextureImporter:
internalIDToNameTable:
- first:
213: 3818167502476526420
second: cls_sym_strategist_0
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 1024
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: cls_sym_strategist_0
rect:
serializedVersion: 2
x: 13
y: 13
width: 230
height: 230
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 453cd0e2d1bdcf430800000000000000
internalID: 3818167502476526420
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

84
Tools/class_symbols.py Normal file
View File

@ -0,0 +1,84 @@
"""병과 단색 심볼 만들기(PD 2026-11-09 「병과 표시 = 기병 말 · 보병 검 · 궁병 활 · 창병 창 · 책사 깃털부채 심볼 · 예시 = 어두운 원 위 베이지 말머리」).
새로 그리지 않는다 — 확정 납품 메달 아이콘(Assets/UI/Samguk/class/cls_<병과>.png)을 만들 때 쓴 투명 바탕 사물 그림
(아트 R&D `C:/ArtRnD/samguk_ui/_work/class_symbol/rgba/cls_<병과>_s21A_rgba.png` · picked_auto.json = seed 21)의 투명도 = 형태.
형태를 채움색으로 칠하고, 원본의 진한 안쪽 선(눈 · 칼날 경계 등)만 선 색으로 남긴 뒤 바깥 테두리를 두른다.
실행: <ComfyUI venv python> -I -B Tools/class_symbols.py [--src <rgba 폴더>] [--fill #RRGGBB] [--line #RRGGBB] [--out Assets/UI/Symbols] [--preview <png>]
출력 = <out>/cls_sym_<병과>.png(256 정사각 · 투명) · --preview = 어두운 원 위 5종 확인 그림.
"""
import argparse
import os
import numpy as np
from PIL import Image, ImageDraw, ImageFilter
TYPES = ["infantry", "spear", "cavalry", "archer", "strategist"]
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SRC = "C:/ArtRnD/samguk_ui/_work/class_symbol/rgba"
def hexrgb(s):
s = s.lstrip("#")
return tuple(int(s[i:i + 2], 16) for i in (0, 2, 4))
def symbol(path, fill, line, size=256, pad=14, stroke=8, inner_lum=45, erode_passes=6):
src = Image.open(path).convert("RGBA")
bb = src.getchannel("A").point(lambda v: 255 if v > 40 else 0).getbbox()
src = src.crop(bb)
# 안쪽 선 = 원본 해상도에서 형태 안쪽(바깥 외곽선 두께 ≈ 30 px 보다 깊이)의 진한 픽셀 — 눈 · 갈기 · 칼날 경계 등
core = src.getchannel("A").point(lambda v: 255 if v > 128 else 0)
for _ in range(erode_passes):
core = core.filter(ImageFilter.MinFilter(11))
rgb = np.asarray(src.convert("RGB")).astype(np.float32)
lum = rgb[:, :, 0] * 0.299 + rgb[:, :, 1] * 0.587 + rgb[:, :, 2] * 0.114
inner = Image.fromarray((((np.asarray(core) > 127) & (lum < inner_lum)) * 255).astype(np.uint8))
s = (size - 2 * pad - 2 * stroke) / max(src.size)
dim = (max(1, round(src.width * s)), max(1, round(src.height * s)))
alpha = src.getchannel("A").resize(dim, Image.LANCZOS)
inner = inner.resize(dim, Image.LANCZOS)
canvas = Image.new("L", (size, size), 0)
off = ((size - dim[0]) // 2, (size - dim[1]) // 2)
canvas.paste(alpha, off)
lines = Image.new("L", (size, size), 0)
lines.paste(inner, off)
out = Image.new("RGBA", (size, size), line + (0,))
out.putalpha(canvas.filter(ImageFilter.MaxFilter(stroke * 2 + 1))) # 바깥 테두리
body = Image.new("RGBA", (size, size), fill + (255,))
body.putalpha(canvas)
out.alpha_composite(body)
ink = Image.new("RGBA", (size, size), line + (255,))
ink.putalpha(lines)
out.alpha_composite(ink)
return out
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--src", default=SRC)
ap.add_argument("--seed", default="s21")
ap.add_argument("--fill", default="#F2D49B")
ap.add_argument("--line", default="#2A2230")
ap.add_argument("--out", default="Assets/UI/Symbols")
ap.add_argument("--preview", default="")
a = ap.parse_args()
out = os.path.join(ROOT, a.out)
os.makedirs(out, exist_ok=True)
tiles = []
for t in TYPES:
g = symbol(os.path.join(a.src, "cls_%s_%sA_rgba.png" % (t, a.seed)), hexrgb(a.fill), hexrgb(a.line))
g.save(os.path.join(out, "cls_sym_%s.png" % t))
tile = Image.new("RGBA", (300, 300), (0, 0, 0, 0))
ImageDraw.Draw(tile).ellipse((10, 10, 290, 290), fill=(53, 55, 78, 255), outline=(20, 20, 30, 255), width=8)
tile.alpha_composite(g.resize((200, 200), Image.LANCZOS), (50, 50))
tiles.append(tile)
if a.preview:
prev = Image.new("RGBA", (300 * len(tiles), 300), (40, 42, 60, 255))
for i, t in enumerate(tiles):
prev.alpha_composite(t, (i * 300, 0))
prev.save(a.preview)
print("ok ->", out)
if __name__ == "__main__":
main()