diff --git a/Assets/Data/UI/ui_theme.csv b/Assets/Data/UI/ui_theme.csv index 4103d300..544d9f53 100644 --- a/Assets/Data/UI/ui_theme.csv +++ b/Assets/Data/UI/ui_theme.csv @@ -498,3 +498,16 @@ skin,btn_green,Button/Button01_s_Green.png,,1.22,,,,Secondary 녹 h100(광고 color,stage_pattern,,,,,,#FFFFFF0D,상자 탭 무대 문양(흰 5 % · 검수2 결함 4) color,chest_select,,,,,,#FFFFFF,상자 행 선택 테두리(흰 · 금 틀과 구분 · 검수2 결함 5) 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,진영 원 배지 군 안쪽 diff --git a/Assets/Resources/UI/UITheme.asset b/Assets/Resources/UI/UITheme.asset index 211c2298..198d10f6 100644 --- a/Assets/Resources/UI/UITheme.asset +++ b/Assets/Resources/UI/UITheme.asset @@ -577,6 +577,22 @@ MonoBehaviour: color: {r: 1, g: 1, b: 1, a: 1} - id: chest_dim 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: - id: card pack: {fileID: 21300000, guid: 3070a6f23e6ca4273897acf1b7e8c5ce, type: 3} @@ -1776,6 +1792,26 @@ MonoBehaviour: pack: {fileID: 21300000, guid: 09135c198f8744fcd89493b169d11bd8, type: 3} samguk: {fileID: 0} 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 pack: {fileID: 0} samguk: {fileID: 21300000, guid: 8e5bf7eb1e300474785ff2781eeb5093, type: 3} diff --git a/Assets/Scripts/Editor/UIThemeImporter.cs b/Assets/Scripts/Editor/UIThemeImporter.cs index 406520b6..6250c9be 100644 --- a/Assets/Scripts/Editor/UIThemeImporter.cs +++ b/Assets/Scripts/Editor/UIThemeImporter.cs @@ -202,7 +202,7 @@ namespace TKS.EditorTools 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() diff --git a/Assets/Scripts/OutGame/UI/HeroScreens.cs b/Assets/Scripts/OutGame/UI/HeroScreens.cs index 35e85a10..c18c43c2 100644 --- a/Assets/Scripts/OutGame/UI/HeroScreens.cs +++ b/Assets/Scripts/OutGame/UI/HeroScreens.cs @@ -85,7 +85,7 @@ namespace TKS.OutGame public static readonly Vector2 CellSize = new Vector2(320f, 431f); public static readonly Vector2 Spacing = new Vector2(22f, 20f); /// 크기 표 size_px(머리 크기 기준 표준 키)가 카드에서 차지할 px — 배율 = UnitPx ÷ size_px(모든 유닛 머리 크기 같음) · 머리 끝 위치(카드 위에서 px). - const float UnitPx = 450f; + const float UnitPx = 360f; // 450 → 360 = 유닛 20 % 작게(PD 2026-11-09 「현재 비율보다 20 % 정도 작게」) const float HeadTop = 50f; public static Image Card(Transform content, OutGameSession s, HeroDb.Hero h, bool deployed, System.Action onTap) @@ -122,7 +122,7 @@ namespace TKS.OutGame } 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(); + 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(); art.preserveAspect = true; } art.sprite = sp; @@ -154,8 +154,8 @@ namespace TKS.OutGame bt.horizontalOverflow = HorizontalWrapMode.Overflow; } // 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); - UiKit.DropShadow(flag, 3f); + // 진영 = 원 배지(위·촉·오·군 · PD 2026-11-09 · 구 세력 깃발 대체) + 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); name.resizeTextForBestFit = true; 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)); 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); 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; @@ -226,6 +226,7 @@ namespace TKS.OutGame Text filterText; Text countText; int ownedCards; + UiAutoGrid autoGrid; 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); UiKit.DropShadow(arrow, 3f); - var body = Body(); - float side = (OutGameRoot.Reference.x - CollectionCards.CellSize.x * 3f - CollectionCards.Spacing.x * 2f) / 2f; - content = UiKit.ScrollGrid("List", body, side, side, 20f, 0f, CollectionCards.CellSize, CollectionCards.Spacing, 3, out scroll); + // 본문 = 화면 폭 전체(해상도 대응 · PD 2026-11-09) · 열 수·카드 배율 = UiAutoGrid(기준 1080 = 4열 · 좁으면 3 · 넓으면 5 이상) + var body = UiKit.Fill("ListBody", Rt, 0f, 0f, OutGameRoot.TopBarH + OutGameRoot.HeaderH, OutGameRoot.TabBarH); + content = UiKit.ScrollGrid("List", body, 24f, 24f, 20f, 0f, CollectionCards.CellSize, CollectionCards.Spacing, 4, out scroll); + autoGrid = content.gameObject.AddComponent(); + autoGrid.design = CollectionCards.CellSize; + } + + /// 열 수(해상도 대응 · 테스트·캡처). + public int Columns + { + get { return autoGrid != null ? autoGrid.Columns : 0; } + } + + /// 격자 칸(holder) — 원래 크기 카드를 UiAutoGrid 배율로 넣는다. + Transform Slot() + { + var go = new GameObject("Slot", typeof(RectTransform)); + go.transform.SetParent(content, false); + return go.transform; } public void SetFilter(bool soldier) @@ -284,14 +301,15 @@ namespace TKS.OutGame foreach (var h in owned) { 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++; } 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.Empty(content); + for (int i = owned.Count; i < total; i++) CollectionCards.EmptyIn(Slot(), autoGrid.Scale); countText.text = owned.Count + "/" + total; + autoGrid.Apply(); } /// 보유 카드 수(빈 카드 제외). diff --git a/Assets/Scripts/OutGame/UI/UiAutoGrid.cs b/Assets/Scripts/OutGame/UI/UiAutoGrid.cs new file mode 100644 index 00000000..bfe2a955 --- /dev/null +++ b/Assets/Scripts/OutGame/UI/UiAutoGrid.cs @@ -0,0 +1,58 @@ +using UnityEngine; +using UnityEngine.UI; + +namespace TKS.OutGame +{ + /// + /// 해상도 대응 카드 격자(PD 2026-11-09 「한 줄 최대 4명 · 가로가 길면 4명 이상 · 좁으면 3명 · 자동 리사이징」) — + /// 폭에 맞춰 열 수 = round((폭 + 간격) ÷ (목표 칸 폭 + 간격)) · 최소 minColumns · 칸 폭 = 폭을 열 수로 나눔(세로 = 원래 비율) · + /// 각 칸(holder)의 첫 자식 = 원래 크기(design) 카드 → 배율 Scale 로 맞춤. 기준 1080 폭(좌우 24) = 4열. + /// + [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(); + if (Scale <= 0f) Scale = targetWidth / design.x; + } + + void OnRectTransformDimensionsChange() + { + Apply(); + } + + /// 지금 폭으로 열 수·칸 크기·카드 배율을 다시 계산(칸을 새로 넣은 뒤에도 부른다). + 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; + } + } +} diff --git a/Assets/Scripts/OutGame/UI/UiAutoGrid.cs.meta b/Assets/Scripts/OutGame/UI/UiAutoGrid.cs.meta new file mode 100644 index 00000000..393165c7 --- /dev/null +++ b/Assets/Scripts/OutGame/UI/UiAutoGrid.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 489991b2f3bb99e4ba6e6bc028da049f \ No newline at end of file diff --git a/Assets/Scripts/OutGame/UI/UiKit.cs b/Assets/Scripts/OutGame/UI/UiKit.cs index cfa2052c..86707ffe 100644 --- a/Assets/Scripts/OutGame/UI/UiKit.cs +++ b/Assets/Scripts/OutGame/UI/UiKit.cs @@ -411,6 +411,41 @@ namespace TKS.OutGame return t; } + /// 병과 단색 심볼 id(PD 2026-11-09 · 말 · 검 · 활 · 창 · 깃털부채) — 없으면 기존 병과 메달 아이콘. + public static string TypeSymbolId(UnitType t) + { + string id = "cls_sym_" + TypeKey(t); + return UISkin.Icon(id) != null ? id : TypeIconId(t); + } + + /// 진영 글자(위 · 촉 · 오 · 군) · 키 = heroes.csv faction(shu · wei · wu · other). + public static string FactionKo(string f) + { + switch (f) + { + case "shu": return "촉"; + case "wei": return "위"; + case "wu": return "오"; + } + return "군"; + } + + /// 진영 원 배지(PD 2026-11-09 「원형 프레임에 위·촉·오·군 · 촉 녹 · 위 청 · 오 적 · 군 짙은 회색」) = 원 틀(fbadge_*) + 안쪽 원(fbadge_*_in) + 흰 글자. + 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(); + 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; + } + /// 아이콘(테마 id · 없으면 null). public static Image Icon(Transform parent, string id, Vector2 anchor, Vector2 pivot, Vector2 pos, float size) { diff --git a/Assets/Scripts/OutGame/UICaptureDriver.cs b/Assets/Scripts/OutGame/UICaptureDriver.cs index e8706877..ef9d8625 100644 --- a/Assets/Scripts/OutGame/UICaptureDriver.cs +++ b/Assets/Scripts/OutGame/UICaptureDriver.cs @@ -78,8 +78,24 @@ namespace TKS.OutGame if (sid == id || sh == null || shs == null || sh.gear.Count == 0) continue; 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); 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); yield return Shot(cam, "hero_detail", W, H); // 영웅 상세 등급 변형(UI 상업용 재설계 ① · 목업 hero_detail_5등급 대조 · 메모리 세이브만) = 그 영웅 장비 세트의 등급 순서대로 diff --git a/Assets/UI/Symbols.meta b/Assets/UI/Symbols.meta new file mode 100644 index 00000000..ea87d826 --- /dev/null +++ b/Assets/UI/Symbols.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: abcec5e654e973d49a8e4be2b67eb6fe +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UI/Symbols/cls_sym_archer.png b/Assets/UI/Symbols/cls_sym_archer.png new file mode 100644 index 00000000..f5b6d71d Binary files /dev/null and b/Assets/UI/Symbols/cls_sym_archer.png differ diff --git a/Assets/UI/Symbols/cls_sym_archer.png.meta b/Assets/UI/Symbols/cls_sym_archer.png.meta new file mode 100644 index 00000000..ddaf4ed6 --- /dev/null +++ b/Assets/UI/Symbols/cls_sym_archer.png.meta @@ -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: diff --git a/Assets/UI/Symbols/cls_sym_cavalry.png b/Assets/UI/Symbols/cls_sym_cavalry.png new file mode 100644 index 00000000..4c96e8d0 Binary files /dev/null and b/Assets/UI/Symbols/cls_sym_cavalry.png differ diff --git a/Assets/UI/Symbols/cls_sym_cavalry.png.meta b/Assets/UI/Symbols/cls_sym_cavalry.png.meta new file mode 100644 index 00000000..0f30e18e --- /dev/null +++ b/Assets/UI/Symbols/cls_sym_cavalry.png.meta @@ -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: diff --git a/Assets/UI/Symbols/cls_sym_infantry.png b/Assets/UI/Symbols/cls_sym_infantry.png new file mode 100644 index 00000000..02449bfe Binary files /dev/null and b/Assets/UI/Symbols/cls_sym_infantry.png differ diff --git a/Assets/UI/Symbols/cls_sym_infantry.png.meta b/Assets/UI/Symbols/cls_sym_infantry.png.meta new file mode 100644 index 00000000..986c0e27 --- /dev/null +++ b/Assets/UI/Symbols/cls_sym_infantry.png.meta @@ -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: diff --git a/Assets/UI/Symbols/cls_sym_spear.png b/Assets/UI/Symbols/cls_sym_spear.png new file mode 100644 index 00000000..dc40b226 Binary files /dev/null and b/Assets/UI/Symbols/cls_sym_spear.png differ diff --git a/Assets/UI/Symbols/cls_sym_spear.png.meta b/Assets/UI/Symbols/cls_sym_spear.png.meta new file mode 100644 index 00000000..3c35fc7d --- /dev/null +++ b/Assets/UI/Symbols/cls_sym_spear.png.meta @@ -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: diff --git a/Assets/UI/Symbols/cls_sym_strategist.png b/Assets/UI/Symbols/cls_sym_strategist.png new file mode 100644 index 00000000..7eb659be Binary files /dev/null and b/Assets/UI/Symbols/cls_sym_strategist.png differ diff --git a/Assets/UI/Symbols/cls_sym_strategist.png.meta b/Assets/UI/Symbols/cls_sym_strategist.png.meta new file mode 100644 index 00000000..9312b5c4 --- /dev/null +++ b/Assets/UI/Symbols/cls_sym_strategist.png.meta @@ -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: diff --git a/Tools/class_symbols.py b/Tools/class_symbols.py new file mode 100644 index 00000000..c66b727d --- /dev/null +++ b/Tools/class_symbols.py @@ -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)의 투명도 = 형태. +형태를 채움색으로 칠하고, 원본의 진한 안쪽 선(눈 · 칼날 경계 등)만 선 색으로 남긴 뒤 바깥 테두리를 두른다. +실행: -I -B Tools/class_symbols.py [--src ] [--fill #RRGGBB] [--line #RRGGBB] [--out Assets/UI/Symbols] [--preview ] +출력 = /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()