using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace TKS.OutGame { /// /// 아웃게임 UI 도우미 — uGUI + Malgun Gothic(TextUtil) · 기준 1080×1920 · 배치 = 확정 목업(img03·05·06·07·08·11·13·14·16·17). /// 부품·색 = UI 테마(PD #1100 · Layer Lab GUI Pro-SuperCasual 팩 · UISkin) · 테마가 없으면 기존 도형 와이어프레임. /// 위치 인자는 전부 「부모 안 기준점(anchor) · 피벗 · 기준점에서의 거리 · 크기」(UI px). /// public static class UiKit { // 팔레트 = 테마 색 id(ui_theme.csv) · 괄호 값 = 테마가 없을 때(구 와이어프레임) public static Color Bg { get { return UISkin.Col("bg", new Color(0.95f, 0.92f, 0.85f)); } } public static Color Paper { get { return UISkin.Col("paper", new Color(0.99f, 0.97f, 0.92f)); } } public static Color Header { get { return UISkin.Col("header", new Color(0.92f, 0.87f, 0.78f)); } } public static Color HeaderText { get { return UISkin.Col("header_text", Ink); } } public static Color HeaderSub { get { return UISkin.Col("header_sub", InkSoft); } } public static Color Ink { get { return UISkin.Col("ink", new Color(0.17f, 0.12f, 0.10f)); } } public static Color InkSoft { get { return UISkin.Col("ink_soft", new Color(0.45f, 0.39f, 0.34f)); } } public static Color Dark { get { return UISkin.Col("dark", new Color(0.21f, 0.16f, 0.14f)); } } public static Color Red { get { return UISkin.Col("red", new Color(0.74f, 0.26f, 0.19f)); } } public static Color Line { get { return UISkin.Col("line", new Color(0.33f, 0.26f, 0.22f)); } } public static Color Slot { get { return UISkin.Col("slot", new Color(0.80f, 0.76f, 0.70f)); } } public static Color Coin { get { return UISkin.Col("coin", new Color(0.88f, 0.67f, 0.20f)); } } public static Color Jade { get { return UISkin.Col("jade", new Color(0.27f, 0.60f, 0.52f)); } } public static Color Grain { get { return UISkin.Col("grain", new Color(0.76f, 0.58f, 0.38f)); } } public static Color Ok { get { return UISkin.Col("ok", new Color(0.36f, 0.62f, 0.31f)); } } public static Color Star { get { return UISkin.Col("star", new Color(0.93f, 0.68f, 0.13f)); } } public static Color Disabled { get { return UISkin.Col("disabled", new Color(0.66f, 0.62f, 0.58f)); } } public static Color Purple { get { return UISkin.Col("purple", new Color(0.58f, 0.36f, 0.70f)); } } public static Color BarColor { get { return UISkin.Col("bar", new Color(0.15f, 0.11f, 0.09f)); } } public static Color Stroke { get { return UISkin.Col("stroke", new Color(0.12f, 0.1f, 0.1f)); } } public static readonly Color White = Color.white; public static Color Col(string id, Color fallback) { return UISkin.Col(id, fallback); } static Sprite round; /// 둥근 모서리 9-slice(모서리 22 px). public static Sprite Round { get { if (round == null) round = MakeRound(64, 22); return round; } } public static Sprite Circle { get { return ProceduralSprites.Circle; } } static Sprite MakeRound(int size, int radius) { var tex = new Texture2D(size, size, TextureFormat.RGBA32, false); tex.wrapMode = TextureWrapMode.Clamp; tex.hideFlags = HideFlags.DontSave; var px = new Color[size * size]; for (int y = 0; y < size; y++) for (int x = 0; x < size; x++) { float cx = Mathf.Clamp(x + 0.5f, radius, size - radius); float cy = Mathf.Clamp(y + 0.5f, radius, size - radius); float d = Vector2.Distance(new Vector2(x + 0.5f, y + 0.5f), new Vector2(cx, cy)); px[y * size + x] = new Color(1f, 1f, 1f, Mathf.Clamp01(radius - d + 0.5f)); } tex.SetPixels(px); tex.Apply(); float b = radius + 2; var sp = Sprite.Create(tex, new Rect(0, 0, size, size), new Vector2(0.5f, 0.5f), 100f, 0, SpriteMeshType.FullRect, new Vector4(b, b, b, b)); sp.hideFlags = HideFlags.DontSave; return sp; } // ---------- 기본 ---------- public static RectTransform Rect(string name, Transform parent, Vector2 anchor, Vector2 pivot, Vector2 pos, Vector2 size) { var go = new GameObject(name, typeof(RectTransform)); go.transform.SetParent(parent, false); var rt = (RectTransform)go.transform; rt.anchorMin = rt.anchorMax = anchor; rt.pivot = pivot; rt.anchoredPosition = pos; rt.sizeDelta = size; return rt; } /// 부모 전체에서 안쪽 여백(왼·오·위·아래)만 뺀 영역. public static RectTransform Fill(string name, Transform parent, float l = 0f, float r = 0f, float t = 0f, float b = 0f) { var go = new GameObject(name, typeof(RectTransform)); go.transform.SetParent(parent, false); var rt = (RectTransform)go.transform; rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one; rt.pivot = new Vector2(0.5f, 0.5f); rt.offsetMin = new Vector2(l, b); rt.offsetMax = new Vector2(-r, -t); return rt; } /// 위쪽 띠(부모 가로 전체 · 위에서 top 아래 · 높이 h). public static RectTransform TopBand(string name, Transform parent, float top, float h, float l = 0f, float r = 0f) { var go = new GameObject(name, typeof(RectTransform)); go.transform.SetParent(parent, false); var rt = (RectTransform)go.transform; rt.anchorMin = new Vector2(0f, 1f); rt.anchorMax = new Vector2(1f, 1f); rt.pivot = new Vector2(0.5f, 1f); rt.offsetMin = new Vector2(l, -top - h); rt.offsetMax = new Vector2(-r, -top); return rt; } /// /// 칠하기 — rounded = 팩 카드(흰 바탕 + 굵은 외곽선 · 색 틴트 · 반투명이면 외곽선 없는 판) · false = 평면 띠. /// public static Image Paint(RectTransform rt, Color c, bool rounded = true, bool raycast = false) { var img = rt.gameObject.AddComponent(); img.color = c; if (rounded && !UISkin.Apply(img, c.a >= 0.95f ? "card" : "soft")) { img.sprite = Round; img.type = Image.Type.Sliced; } img.raycastTarget = raycast; return img; } /// 스킨 id 로 칠하기(없으면 둥근 도형). public static Image Skin(RectTransform rt, string skin, Color c, bool raycast = false) { var img = rt.gameObject.AddComponent(); img.color = c; if (!UISkin.Apply(img, skin)) { img.sprite = Round; img.type = Image.Type.Sliced; } img.raycastTarget = raycast; return img; } /// 부모를 채우는 스킨 층(안쪽 여백 왼·위·오·아래 · 음수 = 바깥) · 스킨이 없으면 꺼 둔다(UI 상업용 재설계 #1104). public static Image Layer(Transform p, string name, string skin, Color c, float l = 0f, float t = 0f, float r = 0f, float b = 0f) { var img = Skin(Fill(name, p, l, r, t, b), skin, c); img.enabled = UISkin.Has(skin); return img; } /// /// 섹션 패널(Fable 제안서 §3-2 · §5-1) = 그림자(아래 6) → 프레임(#3F4262) → 필드(안쪽 6 · #262840 · 마스크) [→ 문양 타일 5 %] [→ 바닥 그라데이션] → 윗면 광택 h14. /// 위치 = 부모 왼쪽 위 기준(x, y 아래로) · 반환 = 필드(내용은 필드 자식 · 필드 모양으로 잘림 · 잘리면 안 되는 내용은 패널 루트에). /// public static RectTransform SectionPanel(Transform parent, string name, float x, float y, float w, float h, bool pattern, bool ground, out RectTransform root) { root = Rect(name, parent, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(x, -y), new Vector2(w, h)); Layer(root, "Shadow", "panel_frame", Col("panel_shadow", new Color(0f, 0f, 0f, 0.35f)), 0f, 6f, 0f, -6f); Layer(root, "Frame", "panel_frame", Col("panel_frame", Paper)); var field = Layer(root, "Field", "panel_field", Col("panel_field", Dark), 6f, 6f, 6f, 6f); field.gameObject.AddComponent().showMaskGraphic = true; if (pattern && UISkin.Has("bg_pattern")) { var pat = Layer(field.transform, "Pattern", "bg_pattern", Col("panel_pattern", Color.clear)); pat.type = Image.Type.Tiled; } if (ground && UISkin.Has("scroll_fade")) { // 스프라이트 = 위 불투명 → 아래 투명(실측) → 뒤집어 바닥이 어둡게 var g = Skin(Rect("Ground", field.transform, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), Vector2.zero, new Vector2(0f, 100f)), "scroll_fade", Col("panel_ground", Color.clear)); g.rectTransform.anchorMin = new Vector2(0f, 0f); g.rectTransform.anchorMax = new Vector2(1f, 0f); g.rectTransform.sizeDelta = new Vector2(0f, 100f); g.rectTransform.localScale = new Vector3(1f, -1f, 1f); g.rectTransform.pivot = new Vector2(0.5f, 1f); g.rectTransform.anchoredPosition = Vector2.zero; } var lrt = Rect("Light", root, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -8f), new Vector2(0f, 14f)); lrt.anchorMin = new Vector2(0f, 1f); lrt.anchorMax = new Vector2(1f, 1f); lrt.sizeDelta = new Vector2(-24f, 14f); lrt.anchoredPosition = new Vector2(0f, -8f); Skin(lrt, "panel_light", Col("panel_light", Color.clear)).enabled = UISkin.Has("panel_light"); return (RectTransform)field.transform; } /// 섹션 제목 띠(팩 Title_Tapered02 · 패널 왼쪽 위 모서리 · 360×69 · 글자 36 흰 외곽선 · 왼쪽 여백 36). public static RectTransform SectionTitle(Transform panelRoot, string text, float w = 360f) { var band = Rect("Title", panelRoot, new Vector2(0f, 1f), new Vector2(0f, 1f), Vector2.zero, new Vector2(w, 69f)); if (UISkin.Has("band_title")) Skin(band, "band_title", Col("band_title", Dark)); var t = Title(Label(band, text, 36, White, TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(36f, 2f), new Vector2(w - 60f, 60f)), true); t.horizontalOverflow = HorizontalWrapMode.Overflow; return band; } /// 스크롤 위·아래 페이드(필드색 → 투명) — 목록이 패널 안에서 잘리는 느낌을 지움. public static void ScrollFades(Transform parent, float top, float bottom, float h = 40f, float side = 12f) { if (!UISkin.Has("scroll_fade")) return; var c = Col("panel_field", Dark); var a = Skin(Rect("FadeTop", parent, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -top), new Vector2(0f, h)), "scroll_fade", c); a.rectTransform.anchorMin = new Vector2(0f, 1f); a.rectTransform.anchorMax = new Vector2(1f, 1f); a.rectTransform.sizeDelta = new Vector2(-2f * side, h); var b = Skin(Rect("FadeBottom", parent, new Vector2(0.5f, 0f), new Vector2(0.5f, 1f), new Vector2(0f, bottom), new Vector2(0f, h)), "scroll_fade", c); b.rectTransform.anchorMin = new Vector2(0f, 0f); b.rectTransform.anchorMax = new Vector2(1f, 0f); b.rectTransform.sizeDelta = new Vector2(-2f * side, h); b.rectTransform.localScale = new Vector3(1f, -1f, 1f); } /// 아이템 칸 5층(팩 ItemFrame00~03 · 색 = prefix + bg · deco · light · glow · 테두리 gear_border) — 장비·보물·보상 칸 공용. public static void ItemSlot(Transform cell, string prefix, bool lit) { Layer(cell, "Glow", "item_glow", lit ? Col(prefix + "glow", White) : Color.clear, -8f, -8f, -8f, -8f); Layer(cell, "Bg", "item_bg", Col(prefix + "bg", White)); Layer(cell, "Deco", "item_deco", Col(prefix + "deco", White), 4f, 4f, 4f, 4f); var light = Layer(cell, "Light", "item_light", Col(prefix + "light", White), 8f, 6f, 8f, 0f); light.rectTransform.anchorMin = new Vector2(0f, 0.54f); // 윗면 빛 = 칸 높이 46 %(목업) Layer(cell, "Border", "item_border", Col("gear_border", Line)); } /// 아이콘·글자 그림자(테마 icon_shadow). public static void DropShadow(Graphic g, float d) { if (g == null) return; var s = g.gameObject.AddComponent(); s.effectColor = Col("icon_shadow", new Color(0f, 0f, 0f, 0.35f)); s.effectDistance = new Vector2(0f, -d); } // ---------- 공용 부품 ②(Fable 제안서 §4~§5 · 나머지 화면 #1104) ---------- /// 부모 왼쪽 위 기준 사각(x 오른쪽 · y 아래로). public static RectTransform At(Transform p, string name, float x, float y, float w, float h) { return Rect(name, p, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(x, -y), new Vector2(w, h)); } /// /// 스킨 버튼(§5-5 버튼 위계 · Primary btn_l_yellow·btn_l_green·btn_l_red · Secondary btn_sky·btn_s_green·btn_purple · Tertiary round08) — /// 틀 + 틴트 + 글자(제목 폰트 · 밝은 글자만 외곽선 · 팩 버튼 아래 턱만큼 올림) · 왼쪽 위 기준. /// public static Button SkinBtn(Transform p, string name, string skin, Color tint, string label, int size, Color fg, float x, float y, float w, float h, Action onClick) { var img = Skin(At(p, name, x, y, w, h), skin, tint, true); var b = img.gameObject.AddComponent