진영 배지 · 병과 단색 심볼 전 화면 적용 + 영웅 목록 유닛 위치·기병 크기(#1104 · PD 2026-11-09 「같이 바꿔」 · 「심볼은 배경 프레임 없이 단색」 · 「발 안 보이게 · 기병 말 머리가 보이게」)

- 병과 심볼 = 단색(채움 한 색 · 안쪽 선 = 뚫린 선 · 테두리 없음 · Tools/class_symbols.py 기본값 · --outline = 구 방식) · 모든 자리에서 뒤 원 틀 제거(목록 카드 · 도감 카드 · 영웅 상세 이름 띠 · 스킬 행 · 스테이지 팝업 적 구성 · 전투 HUD 유닛 판) · 그림자만
- 진영 원 배지(위·촉·오·군) = 영웅 상세 이름 띠 · 도감 카드에도(구 세력 깃발)
- 영웅 목록 카드: 걷는 유닛 머리 끝 +36(발이 카드 아래로) · 말 탄 유닛(크기 표 mounted) ×0.72 + 전신 높이 상한 390(여포 = 크기 표 size_px 449 로 혼자 크게 나오던 것 맞춤)
- 테스트 118/118 · console error 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
깃 관리자 2026-11-09 19:59:56 +09:00
parent 668c4df0fd
commit 89a831cd46
10 changed files with 50 additions and 36 deletions

View File

@ -47,10 +47,11 @@ namespace TKS.OutGame
name.horizontalOverflow = HorizontalWrapMode.Overflow; name.horizontalOverflow = HorizontalWrapMode.Overflow;
// 위 줄: 병종 원 · Lv 배지 · 세력 깃발 · (출전 꼬리 칩) // 위 줄: 병종 원 · Lv 배지 · 세력 깃발 · (출전 꼬리 칩)
var ring = UiKit.Skin(UiKit.Rect("Type", t, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(142f, -12f), new Vector2(52f, 52f)), "dot", UiKit.White); // 병과 = 단색 심볼만(배경 틀 없음 · PD 2026-11-09)
ring.preserveAspect = true; var ring = UiKit.Rect("Type", t, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(142f, -12f), new Vector2(52f, 52f));
var cls = UiKit.Icon(ring.transform, UiKit.TypeIconId(h.type), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 36f); var cls = UiKit.Icon(ring, UiKit.TypeSymbolId(h.type), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 50f);
if (cls == null) UiKit.Title(UiKit.FillLabel(ring.transform, UnitTypeInfo.Mark(h.type), 26, UiKit.White), true); if (cls != null) UiKit.DropShadow(cls, 2f);
else UiKit.Title(UiKit.FillLabel(ring, UnitTypeInfo.Mark(h.type), 26, UiKit.White), true);
if (owned) if (owned)
{ {
var badge = UiKit.Icon(t, "lv_badge_s", new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(202f, -8f), 56f); var badge = UiKit.Icon(t, "lv_badge_s", new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(202f, -8f), 56f);
@ -58,8 +59,7 @@ namespace TKS.OutGame
badge != null ? new Vector2(0.5f, 0.5f) : new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), badge != null ? new Vector2(0f, 2f) : new Vector2(230f, -36f), new Vector2(56f, 40f)), true); badge != null ? new Vector2(0.5f, 0.5f) : new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), badge != null ? new Vector2(0f, 2f) : new Vector2(230f, -36f), new Vector2(56f, 40f)), true);
lt.horizontalOverflow = HorizontalWrapMode.Overflow; lt.horizontalOverflow = HorizontalWrapMode.Overflow;
} }
var flag = UiKit.Icon(t, UISkin.Icon("flag_" + h.faction) != null ? "flag_" + h.faction : "flag_other", new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(264f, -14f), 44f); UiKit.DropShadow(UiKit.FactionBadge(t, h.faction, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(264f, -12f), 46f), 2f); // 진영 원 배지(PD 2026-11-09)
UiKit.DropShadow(flag, 2f);
if (deployed) if (deployed)
{ {
var tail = UiKit.Skin(UiKit.Rect("Deployed", t, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-10f, -10f), new Vector2(104f, 46f)), "tail", UiKit.Col("tail_red", UiKit.Red)); var tail = UiKit.Skin(UiKit.Rect("Deployed", t, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-10f, -10f), new Vector2(104f, 46f)), "tail", UiKit.Col("tail_red", UiKit.Red));
@ -87,6 +87,12 @@ namespace TKS.OutGame
/// <summary>크기 표 size_px(머리 크기 기준 표준 키)가 카드에서 차지할 px — 배율 = UnitPx ÷ size_px(모든 유닛 머리 크기 같음) · 머리 끝 위치(카드 위에서 px).</summary> /// <summary>크기 표 size_px(머리 크기 기준 표준 키)가 카드에서 차지할 px — 배율 = UnitPx ÷ size_px(모든 유닛 머리 크기 같음) · 머리 끝 위치(카드 위에서 px).</summary>
const float UnitPx = 360f; // 450 → 360 = 유닛 20 % 작게(PD 2026-11-09 「현재 비율보다 20 % 정도 작게」) const float UnitPx = 360f; // 450 → 360 = 유닛 20 % 작게(PD 2026-11-09 「현재 비율보다 20 % 정도 작게」)
const float HeadTop = 50f; const float HeadTop = 50f;
/// <summary>걷는 유닛(보병·창병·궁병·책사) = 머리 끝을 더 내려 발이 카드 아래로 숨게(PD 2026-11-09).</summary>
const float FootDrop = 36f;
/// <summary>말 탄 유닛(크기 표 mounted) = 더 작게 · 말 머리가 카드 안에 보이게(PD 2026-11-09).</summary>
const float MountedScale = 0.72f;
/// <summary>말 탄 유닛 전신(발 ~ 머리 끝) 높이 상한(카드 px) — 크기 표 실측이 튀는 그림(예 여포 size_px 449)도 다른 기병과 비슷하게.</summary>
const float MountedMaxBody = 390f;
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)
{ {
@ -118,7 +124,11 @@ namespace TKS.OutGame
{ {
float hgt = sp.rect.height; float hgt = sp.rect.height;
float headY = Mathf.Clamp01((m.pivot[1] * hgt + body) / hgt); // 머리 끝 = 프레임 아래에서의 비율 float headY = Mathf.Clamp01((m.pivot[1] * hgt + body) / hgt); // 머리 끝 = 프레임 아래에서의 비율
art = UiKit.Rect("Character", b, new Vector2(0.5f, 1f), new Vector2(0.5f, headY), new Vector2(0f, -HeadTop), sp.rect.size * (UnitPx / size)).gameObject.AddComponent<Image>(); bool mounted = a.Mounted;
float k = UnitPx / size * (mounted ? MountedScale : 1f);
if (mounted) k = Mathf.Min(k, MountedMaxBody / body);
float top = HeadTop + (mounted ? 0f : FootDrop);
art = UiKit.Rect("Character", b, new Vector2(0.5f, 1f), new Vector2(0.5f, headY), new Vector2(0f, -top), sp.rect.size * k).gameObject.AddComponent<Image>();
} }
else else
{ {
@ -165,10 +175,11 @@ namespace TKS.OutGame
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)); 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); UiKit.Title(UiKit.FillLabel(tail.transform, "출전", 24, UiKit.White), true);
} }
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)); // 병과 = 단색 심볼만(배경 틀 없음 · PD 2026-11-09 「심볼은 배경 프레임 없이 단색」)
role.preserveAspect = true; var role = UiKit.Rect("Role", t, new Vector2(1f, 1f), new Vector2(0.5f, 0.5f), new Vector2(-42f, -42f), new Vector2(60f, 60f));
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) var sym = UiKit.Icon(role, UiKit.TypeSymbolId(h.type), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 60f);
UiKit.Title(UiKit.FillLabel(role.transform, UnitTypeInfo.Mark(h.type), 28, UiKit.White), true); if (sym != null) UiKit.DropShadow(sym, 3f);
else 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 (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;
} }
@ -360,7 +371,7 @@ namespace TKS.OutGame
Image promoteImg; Image promoteImg;
Text promoteSub; Text promoteSub;
Image bandImg; Image bandImg;
Image flagIcon; RectTransform factionRoot;
Image clsIcon; Image clsIcon;
Image paraImg; Image paraImg;
Text gradeText; Text gradeText;
@ -576,10 +587,8 @@ namespace TKS.OutGame
// ④ 이름 띠 = 어두운 띠 + 세력 깃발 · 병종 원 · 등급 칩 · 이름 · 병종 · ★ // ④ 이름 띠 = 어두운 띠 + 세력 깃발 · 병종 원 · 등급 칩 · 이름 · 병종 · ★
bandImg = Spr(body, "NameBand", "card", 24f, 776f, 1032f, 114f, "white"); bandImg = Spr(body, "NameBand", "card", 24f, 776f, 1032f, 114f, "white");
Spr(body, "BandLight", "band_light", 40f, 782f, 1000f, 12f, "band_light"); Spr(body, "BandLight", "band_light", 40f, 782f, 1000f, 12f, "band_light");
flagIcon = Ico(body, null, 44f, 789f, 88f, 88f, 3f); factionRoot = UiKit.At(body, "Faction", 44f, 789f, 88f, 88f); // 진영 원 배지 자리(PD 2026-11-09 · 구 세력 깃발)
Spr(body, "TypeRing", "circle_frame", 140f, 794f, 78f, 78f, "white"); clsIcon = Ico(body, null, 140f, 794f, 78f, 78f, 3f); // 병과 = 단색 심볼만(배경 틀 없음 · PD 2026-11-09)
Spr(body, "TypeIn", "circle_frame", 144f, 798f, 70f, 70f, "circle_inner");
clsIcon = Ico(body, null, 147f, 801f, 64f, 64f);
paraImg = Spr(body, "Grade", "para_green", 232f, 805f, 96f, 56f, "white"); paraImg = Spr(body, "Grade", "para_green", 232f, 805f, 96f, 56f, "white");
gradeText = Txt(body, "", 232f, 811f, 38, "white", TextAnchor.MiddleCenter, 96f); gradeText = Txt(body, "", 232f, 811f, 38, "white", TextAnchor.MiddleCenter, 96f);
nameText = Txt(body, "", 344f, 797f, 66, "white"); nameText = Txt(body, "", 344f, 797f, 66, "white");
@ -733,8 +742,9 @@ namespace TKS.OutGame
evolveDot.gameObject.SetActive(!h.isSoldier && hs != null && evolveDot.sprite != null && HeroRules.CanEvolve(db, h, hs) == null); evolveDot.gameObject.SetActive(!h.isSoldier && hs != null && evolveDot.sprite != null && HeroRules.CanEvolve(db, h, hs) == null);
// 이름 띠 // 이름 띠
SetIcon(flagIcon, UISkin.Icon("flag_" + h.faction) != null ? "flag_" + h.faction : "flag_other", 1f); UiKit.Clear(factionRoot);
SetIcon(clsIcon, UiKit.TypeIconId(h.type), 1f); UiKit.DropShadow(UiKit.FactionBadge(factionRoot, h.faction, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 84f), 3f);
SetIcon(clsIcon, UiKit.TypeSymbolId(h.type), 1f);
nameText.text = h.nameKo; nameText.text = h.nameKo;
typeText.text = UnitTypeInfo.Korean(h.type); typeText.text = UnitTypeInfo.Korean(h.type);
PlaceX(typeText, 344f + nameText.preferredWidth + 24f); PlaceX(typeText, 344f + nameText.preferredWidth + 24f);
@ -750,7 +760,7 @@ namespace TKS.OutGame
var sk = SkillRules.Skill(db, h, i); var sk = SkillRules.Skill(db, h, i);
bool has = sk != null && !h.isSoldier && hs != null; bool has = sk != null && !h.isSoldier && hs != null;
SetIcon(skFrame[i], tk != null ? "skill_" + tk + (i == 0 ? "_a" : "_p") : null, has ? 1f : 0.5f); SetIcon(skFrame[i], tk != null ? "skill_" + tk + (i == 0 ? "_a" : "_p") : null, has ? 1f : 0.5f);
SetIcon(skCls[i], UiKit.TypeIconId(h.type), has ? 1f : 0.5f); SetIcon(skCls[i], UiKit.TypeSymbolId(h.type), has ? 1f : 0.5f);
skLv[i].transform.parent.gameObject.SetActive(has); skLv[i].transform.parent.gameObject.SetActive(has);
skBtn[i].gameObject.SetActive(has); skBtn[i].gameObject.SetActive(has);
if (i == 0) coolChip.SetActive(has); if (i == 0) coolChip.SetActive(has);

View File

@ -791,13 +791,9 @@ namespace TKS.OutGame
{ {
var ut = (UnitType)kinds[k]; var ut = (UnitType)kinds[k];
float cx = ex + k * Ew + 44f; float cx = ex + k * Ew + 44f;
var circ = UiKit.Skin(UiKit.Rect("Type" + k, field, new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), new Vector2(cx, -y - 94f), new Vector2(80f, 80f)), "circle_frame", UiKit.TypeColor(ut)); var circ = UiKit.Rect("Type" + k, field, new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), new Vector2(cx, -y - 94f), new Vector2(80f, 80f));
var ti = UiKit.Icon(circ.transform, UiKit.TypeIconId(ut), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 2f), 52f); var ti = UiKit.Icon(circ, UiKit.TypeSymbolId(ut), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 76f); // 병과 = 단색 심볼만(배경 틀 없음 · PD 2026-11-09)
if (ti != null) if (ti != null) UiKit.DropShadow(ti, 2f);
{
ti.color = UiKit.White;
UiKit.DropShadow(ti, 2f);
}
var n = UiKit.Title(UiKit.Label(field, "x" + counts[kinds[k]], 36, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 0.5f), new Vector2(cx + 50f, -y - 94f), new Vector2(100f, 50f)), true); var n = UiKit.Title(UiKit.Label(field, "x" + counts[kinds[k]], 36, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 0.5f), new Vector2(cx + 50f, -y - 94f), new Vector2(100f, 50f)), true);
n.horizontalOverflow = HorizontalWrapMode.Overflow; n.horizontalOverflow = HorizontalWrapMode.Overflow;
} }

View File

@ -828,9 +828,9 @@ namespace TKS.OutGame
/// <summary>병종 배지(병종 색 칩 + 글자 · 신규 제작 cls_* 아이콘이 오면 아이콘).</summary> /// <summary>병종 배지(병종 색 칩 + 글자 · 신규 제작 cls_* 아이콘이 오면 아이콘).</summary>
public static Image TypeBadge(Transform parent, UnitType t, Vector2 anchor, Vector2 pivot, Vector2 pos, float h) public static Image TypeBadge(Transform parent, UnitType t, Vector2 anchor, Vector2 pivot, Vector2 pos, float h)
{ {
var b = Box("Type", parent, anchor, pivot, pos, new Vector2(h * 1.3f, h), UISkin.On ? TypeColor(t) : Dark); var b = Box("Type", parent, anchor, pivot, pos, new Vector2(h * 1.3f, h), UISkin.On ? Color.clear : Dark); // 테마 = 틀 없이 단색 심볼(PD 2026-11-09)
string icon = TypeIconId(t); string icon = TypeSymbolId(t);
if (HasFinalIcon(icon)) Icon(b.transform, icon, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 1f), h * 0.8f); if (UISkin.Icon(icon) != null) Icon(b.transform, icon, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 1f), h * 0.8f);
else Title(FillLabel(b.transform, UnitTypeInfo.Mark(t), Mathf.RoundToInt(h * 0.62f), White), true); else Title(FillLabel(b.transform, UnitTypeInfo.Mark(t), Mathf.RoundToInt(h * 0.62f), White), true);
return b; return b;
} }

View File

@ -219,8 +219,11 @@ namespace TKS
mask.gameObject.AddComponent<Mask>().showMaskGraphic = false; mask.gameObject.AddComponent<Mask>().showMaskGraphic = false;
portraitArt = MakeImage("Art", mask.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, -40f), new Vector2(230f, 230f), Color.white); portraitArt = MakeImage("Art", mask.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, -40f), new Vector2(230f, 230f), Color.white);
portraitArt.preserveAspect = true; portraitArt.preserveAspect = true;
// 병과 = 단색 심볼만(배경 틀 없음 · PD 2026-11-09)
typeRing = OutGame.UiKit.Skin(OutGame.UiKit.At(unitBody, "TypeRing", 188f, 20f, 52f, 52f), "circle_frame", Color.white); typeRing = OutGame.UiKit.Skin(OutGame.UiKit.At(unitBody, "TypeRing", 188f, 20f, 52f, 52f), "circle_frame", Color.white);
typeIcon = OutGame.UiKit.Icon(typeRing.transform, "cls_infantry", new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 34f); typeRing.enabled = false;
typeIcon = OutGame.UiKit.Icon(typeRing.transform, OutGame.UiKit.TypeSymbolId(UnitType.Infantry), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 52f);
if (typeIcon != null) OutGame.UiKit.DropShadow(typeIcon, 2f);
nameText = OutGame.UiKit.Title(OutGame.UiKit.Label(unitBody, "", 36, Color.white, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(250f, -18f), new Vector2(300f, 56f)), true); nameText = OutGame.UiKit.Title(OutGame.UiKit.Label(unitBody, "", 36, Color.white, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(250f, -18f), new Vector2(300f, 56f)), true);
nameText.horizontalOverflow = HorizontalWrapMode.Overflow; nameText.horizontalOverflow = HorizontalWrapMode.Overflow;
stateText = OutGame.UiKit.Label(unitBody, "", 24, soft, TextAnchor.MiddleRight, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-20f, -22f), new Vector2(220f, 48f), false); stateText = OutGame.UiKit.Label(unitBody, "", 24, soft, TextAnchor.MiddleRight, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-20f, -22f), new Vector2(220f, 48f), false);
@ -288,8 +291,7 @@ namespace TKS
portraitArt.sprite = sp; portraitArt.sprite = sp;
portraitArt.enabled = sp != null; portraitArt.enabled = sp != null;
portraitRing.color = u.Faction == Faction.Player ? UISkin.Col("hud_turn_player", Color.blue) : UISkin.Col("hud_turn_enemy", Color.red); portraitRing.color = u.Faction == Faction.Player ? UISkin.Col("hud_turn_player", Color.blue) : UISkin.Col("hud_turn_enemy", Color.red);
typeRing.color = OutGame.UiKit.TypeColor(u.Type); var tsp = UISkin.Icon(OutGame.UiKit.TypeSymbolId(u.Type));
var tsp = UISkin.Icon(OutGame.UiKit.TypeIconId(u.Type));
if (typeIcon != null && tsp != null) typeIcon.sprite = tsp; if (typeIcon != null && tsp != null) typeIcon.sprite = tsp;
} }
nameText.text = u.DisplayName + (u.IsBoss ? " <size=24>장수</size>" : ""); nameText.text = u.DisplayName + (u.IsBoss ? " <size=24>장수</size>" : "");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -2,7 +2,7 @@
새로 그리지 않는다 — 확정 납품 메달 아이콘(Assets/UI/Samguk/class/cls_<병과>.png)을 만들 때 쓴 투명 바탕 사물 그림 새로 그리지 않는다 — 확정 납품 메달 아이콘(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)의 투명도 = 형태. (아트 R&D `C:/ArtRnD/samguk_ui/_work/class_symbol/rgba/cls_<병과>_s21A_rgba.png` · picked_auto.json = seed 21)의 투명도 = 형태.
형태를 채움색으로 칠하고, 원본의 진한 안쪽 선(눈 · 칼날 경계 등)만 선 색으로 남긴 뒤 바깥 테두리를 두른다. 기본 = 단색(PD 2026-11-09 「배경 프레임 없이 단색」 · 채움 한 색 · 원본의 진한 안쪽 선 = 뚫린 선) · --outline = 테두리 + 안쪽 선 색(구 방식).
실행: <ComfyUI venv python> -I -B Tools/class_symbols.py [--src <rgba 폴더>] [--fill #RRGGBB] [--line #RRGGBB] [--out Assets/UI/Symbols] [--preview <png>] 실행: <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종 확인 그림. 출력 = <out>/cls_sym_<병과>.png(256 정사각 · 투명) · --preview = 어두운 원 위 5종 확인 그림.
""" """
@ -22,7 +22,7 @@ def hexrgb(s):
return tuple(int(s[i:i + 2], 16) for i in (0, 2, 4)) 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): def symbol(path, fill, line, size=256, pad=14, stroke=8, inner_lum=45, erode_passes=6, mono=True):
src = Image.open(path).convert("RGBA") src = Image.open(path).convert("RGBA")
bb = src.getchannel("A").point(lambda v: 255 if v > 40 else 0).getbbox() bb = src.getchannel("A").point(lambda v: 255 if v > 40 else 0).getbbox()
src = src.crop(bb) src = src.crop(bb)
@ -42,6 +42,12 @@ def symbol(path, fill, line, size=256, pad=14, stroke=8, inner_lum=45, erode_pas
canvas.paste(alpha, off) canvas.paste(alpha, off)
lines = Image.new("L", (size, size), 0) lines = Image.new("L", (size, size), 0)
lines.paste(inner, off) lines.paste(inner, off)
if mono:
# 단색(PD 2026-11-09 「배경 프레임 없이 단색」) = 채움 한 색 · 테두리 없음 · 안쪽 선 = 뚫린 선(형태 구분)
out = Image.new("RGBA", (size, size), fill + (255,))
a = np.asarray(canvas).astype(np.float32) * (1.0 - np.asarray(lines).astype(np.float32) / 255.0)
out.putalpha(Image.fromarray(a.clip(0, 255).astype(np.uint8)))
return out
out = Image.new("RGBA", (size, size), line + (0,)) out = Image.new("RGBA", (size, size), line + (0,))
out.putalpha(canvas.filter(ImageFilter.MaxFilter(stroke * 2 + 1))) # 바깥 테두리 out.putalpha(canvas.filter(ImageFilter.MaxFilter(stroke * 2 + 1))) # 바깥 테두리
body = Image.new("RGBA", (size, size), fill + (255,)) body = Image.new("RGBA", (size, size), fill + (255,))
@ -61,15 +67,15 @@ def main():
ap.add_argument("--line", default="#2A2230") ap.add_argument("--line", default="#2A2230")
ap.add_argument("--out", default="Assets/UI/Symbols") ap.add_argument("--out", default="Assets/UI/Symbols")
ap.add_argument("--preview", default="") ap.add_argument("--preview", default="")
ap.add_argument("--outline", action="store_true", help="테두리 + 진한 안쪽 선(구 방식) · 기본 = 단색")
a = ap.parse_args() a = ap.parse_args()
out = os.path.join(ROOT, a.out) out = os.path.join(ROOT, a.out)
os.makedirs(out, exist_ok=True) os.makedirs(out, exist_ok=True)
tiles = [] tiles = []
for t in TYPES: 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 = symbol(os.path.join(a.src, "cls_%s_%sA_rgba.png" % (t, a.seed)), hexrgb(a.fill), hexrgb(a.line), mono=not a.outline)
g.save(os.path.join(out, "cls_sym_%s.png" % t)) g.save(os.path.join(out, "cls_sym_%s.png" % t))
tile = Image.new("RGBA", (300, 300), (0, 0, 0, 0)) 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)) tile.alpha_composite(g.resize((200, 200), Image.LANCZOS), (50, 50))
tiles.append(tile) tiles.append(tile)
if a.preview: if a.preview: