진영 배지 · 병과 단색 심볼 전 화면 적용 + 영웅 목록 유닛 위치·기병 크기(#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>
|
|
@ -47,10 +47,11 @@ namespace TKS.OutGame
|
|||
name.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
|
||||
// 위 줄: 병종 원 · 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);
|
||||
ring.preserveAspect = true;
|
||||
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);
|
||||
if (cls == null) UiKit.Title(UiKit.FillLabel(ring.transform, UnitTypeInfo.Mark(h.type), 26, UiKit.White), true);
|
||||
// 병과 = 단색 심볼만(배경 틀 없음 · PD 2026-11-09)
|
||||
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, UiKit.TypeSymbolId(h.type), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 50f);
|
||||
if (cls != null) UiKit.DropShadow(cls, 2f);
|
||||
else UiKit.Title(UiKit.FillLabel(ring, UnitTypeInfo.Mark(h.type), 26, UiKit.White), true);
|
||||
if (owned)
|
||||
{
|
||||
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);
|
||||
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(flag, 2f);
|
||||
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)
|
||||
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));
|
||||
|
|
@ -87,6 +87,12 @@ namespace TKS.OutGame
|
|||
/// <summary>크기 표 size_px(머리 크기 기준 표준 키)가 카드에서 차지할 px — 배율 = UnitPx ÷ size_px(모든 유닛 머리 크기 같음) · 머리 끝 위치(카드 위에서 px).</summary>
|
||||
const float UnitPx = 360f; // 450 → 360 = 유닛 20 % 작게(PD 2026-11-09 「현재 비율보다 20 % 정도 작게」)
|
||||
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)
|
||||
{
|
||||
|
|
@ -118,7 +124,11 @@ namespace TKS.OutGame
|
|||
{
|
||||
float hgt = sp.rect.height;
|
||||
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
|
||||
{
|
||||
|
|
@ -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));
|
||||
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));
|
||||
role.preserveAspect = true;
|
||||
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);
|
||||
// 병과 = 단색 심볼만(배경 틀 없음 · 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.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); // 성장 가능 = 레벨 배지 오른쪽 위
|
||||
return cell;
|
||||
}
|
||||
|
|
@ -360,7 +371,7 @@ namespace TKS.OutGame
|
|||
Image promoteImg;
|
||||
Text promoteSub;
|
||||
Image bandImg;
|
||||
Image flagIcon;
|
||||
RectTransform factionRoot;
|
||||
Image clsIcon;
|
||||
Image paraImg;
|
||||
Text gradeText;
|
||||
|
|
@ -576,10 +587,8 @@ namespace TKS.OutGame
|
|||
// ④ 이름 띠 = 어두운 띠 + 세력 깃발 · 병종 원 · 등급 칩 · 이름 · 병종 · ★
|
||||
bandImg = Spr(body, "NameBand", "card", 24f, 776f, 1032f, 114f, "white");
|
||||
Spr(body, "BandLight", "band_light", 40f, 782f, 1000f, 12f, "band_light");
|
||||
flagIcon = Ico(body, null, 44f, 789f, 88f, 88f, 3f);
|
||||
Spr(body, "TypeRing", "circle_frame", 140f, 794f, 78f, 78f, "white");
|
||||
Spr(body, "TypeIn", "circle_frame", 144f, 798f, 70f, 70f, "circle_inner");
|
||||
clsIcon = Ico(body, null, 147f, 801f, 64f, 64f);
|
||||
factionRoot = UiKit.At(body, "Faction", 44f, 789f, 88f, 88f); // 진영 원 배지 자리(PD 2026-11-09 · 구 세력 깃발)
|
||||
clsIcon = Ico(body, null, 140f, 794f, 78f, 78f, 3f); // 병과 = 단색 심볼만(배경 틀 없음 · PD 2026-11-09)
|
||||
paraImg = Spr(body, "Grade", "para_green", 232f, 805f, 96f, 56f, "white");
|
||||
gradeText = Txt(body, "", 232f, 811f, 38, "white", TextAnchor.MiddleCenter, 96f);
|
||||
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);
|
||||
|
||||
// 이름 띠
|
||||
SetIcon(flagIcon, UISkin.Icon("flag_" + h.faction) != null ? "flag_" + h.faction : "flag_other", 1f);
|
||||
SetIcon(clsIcon, UiKit.TypeIconId(h.type), 1f);
|
||||
UiKit.Clear(factionRoot);
|
||||
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;
|
||||
typeText.text = UnitTypeInfo.Korean(h.type);
|
||||
PlaceX(typeText, 344f + nameText.preferredWidth + 24f);
|
||||
|
|
@ -750,7 +760,7 @@ namespace TKS.OutGame
|
|||
var sk = SkillRules.Skill(db, h, i);
|
||||
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(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);
|
||||
skBtn[i].gameObject.SetActive(has);
|
||||
if (i == 0) coolChip.SetActive(has);
|
||||
|
|
|
|||
|
|
@ -791,13 +791,9 @@ namespace TKS.OutGame
|
|||
{
|
||||
var ut = (UnitType)kinds[k];
|
||||
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 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);
|
||||
if (ti != null)
|
||||
{
|
||||
ti.color = UiKit.White;
|
||||
UiKit.DropShadow(ti, 2f);
|
||||
}
|
||||
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, 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) 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);
|
||||
n.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -828,9 +828,9 @@ namespace TKS.OutGame
|
|||
/// <summary>병종 배지(병종 색 칩 + 글자 · 신규 제작 cls_* 아이콘이 오면 아이콘).</summary>
|
||||
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);
|
||||
string icon = TypeIconId(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);
|
||||
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 = TypeSymbolId(t);
|
||||
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);
|
||||
return b;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,8 +219,11 @@ namespace TKS
|
|||
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.preserveAspect = true;
|
||||
// 병과 = 단색 심볼만(배경 틀 없음 · PD 2026-11-09)
|
||||
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.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);
|
||||
|
|
@ -288,8 +291,7 @@ namespace TKS
|
|||
portraitArt.sprite = sp;
|
||||
portraitArt.enabled = sp != null;
|
||||
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.TypeIconId(u.Type));
|
||||
var tsp = UISkin.Icon(OutGame.UiKit.TypeSymbolId(u.Type));
|
||||
if (typeIcon != null && tsp != null) typeIcon.sprite = tsp;
|
||||
}
|
||||
nameText.text = u.DisplayName + (u.IsBoss ? " <size=24>장수</size>" : "");
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
새로 그리지 않는다 — 확정 납품 메달 아이콘(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)의 투명도 = 형태.
|
||||
형태를 채움색으로 칠하고, 원본의 진한 안쪽 선(눈 · 칼날 경계 등)만 선 색으로 남긴 뒤 바깥 테두리를 두른다.
|
||||
기본 = 단색(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>]
|
||||
출력 = <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))
|
||||
|
||||
|
||||
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")
|
||||
bb = src.getchannel("A").point(lambda v: 255 if v > 40 else 0).getbbox()
|
||||
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)
|
||||
lines = Image.new("L", (size, size), 0)
|
||||
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.putalpha(canvas.filter(ImageFilter.MaxFilter(stroke * 2 + 1))) # 바깥 테두리
|
||||
body = Image.new("RGBA", (size, size), fill + (255,))
|
||||
|
|
@ -61,15 +67,15 @@ def main():
|
|||
ap.add_argument("--line", default="#2A2230")
|
||||
ap.add_argument("--out", default="Assets/UI/Symbols")
|
||||
ap.add_argument("--preview", default="")
|
||||
ap.add_argument("--outline", action="store_true", help="테두리 + 진한 안쪽 선(구 방식) · 기본 = 단색")
|
||||
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 = 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))
|
||||
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:
|
||||
|
|
|
|||