ThreeKingdomsSlingshot/Assets/Scripts/OutGame/UI/UiKit.cs

397 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace TKS.OutGame
{
/// <summary>
/// 아웃게임 와이어프레임 UI 도우미 — uGUI + Malgun Gothic(TextUtil) · 아트 없음 = 도형 + 글자 · 기준 1080×1920 · 색 = 목업 img03·06·08·11.
/// 위치 인자는 전부 「부모 안 기준점(anchor) · 피벗 · 기준점에서의 거리 · 크기」(UI px).
/// </summary>
public static class UiKit
{
public static readonly Color Bg = new Color(0.95f, 0.92f, 0.85f);
public static readonly Color Paper = new Color(0.99f, 0.97f, 0.92f);
public static readonly Color Header = new Color(0.92f, 0.87f, 0.78f);
public static readonly Color Ink = new Color(0.17f, 0.12f, 0.10f);
public static readonly Color InkSoft = new Color(0.45f, 0.39f, 0.34f);
public static readonly Color Dark = new Color(0.21f, 0.16f, 0.14f);
public static readonly Color Red = new Color(0.74f, 0.26f, 0.19f);
public static readonly Color Line = new Color(0.33f, 0.26f, 0.22f);
public static readonly Color Slot = new Color(0.80f, 0.76f, 0.70f);
public static readonly Color Coin = new Color(0.88f, 0.67f, 0.20f);
public static readonly Color Jade = new Color(0.27f, 0.60f, 0.52f);
public static readonly Color Grain = new Color(0.76f, 0.58f, 0.38f);
public static readonly Color Ok = new Color(0.36f, 0.62f, 0.31f);
public static readonly Color Star = new Color(0.93f, 0.68f, 0.13f);
public static readonly Color Disabled = new Color(0.66f, 0.62f, 0.58f);
public static readonly Color Purple = new Color(0.58f, 0.36f, 0.70f);
public static readonly Color White = Color.white;
static Sprite round;
/// <summary>둥근 모서리 9-slice(모서리 22 px).</summary>
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;
}
/// <summary>부모 전체에서 안쪽 여백(왼·오·위·아래)만 뺀 영역.</summary>
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;
}
/// <summary>위쪽 띠(부모 가로 전체 · 위에서 top 아래 · 높이 h).</summary>
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;
}
public static Image Paint(RectTransform rt, Color c, bool rounded = true, bool raycast = false)
{
var img = rt.gameObject.AddComponent<Image>();
img.color = c;
if (rounded)
{
img.sprite = Round;
img.type = Image.Type.Sliced;
}
img.raycastTarget = raycast;
return img;
}
public static Image Box(string name, Transform parent, Vector2 anchor, Vector2 pivot, Vector2 pos, Vector2 size, Color c, bool rounded = true)
{
return Paint(Rect(name, parent, anchor, pivot, pos, size), c, rounded);
}
public static Image Dot(string name, Transform parent, Vector2 anchor, Vector2 pivot, Vector2 pos, float d, Color c)
{
var img = Rect(name, parent, anchor, pivot, pos, new Vector2(d, d)).gameObject.AddComponent<Image>();
img.sprite = Circle;
img.color = c;
img.raycastTarget = false;
return img;
}
/// <summary>테두리(외곽선 효과 · 둥근 상자에 굵은 선처럼 보임).</summary>
public static void Border(Graphic g, Color c, float w = 4f)
{
var o = g.gameObject.AddComponent<Outline>();
o.effectColor = c;
o.effectDistance = new Vector2(w, -w);
o.useGraphicAlpha = false;
var o2 = g.gameObject.AddComponent<Outline>();
o2.effectColor = c;
o2.effectDistance = new Vector2(-w, w);
o2.useGraphicAlpha = false;
}
public static Text Label(Transform parent, string s, int size, Color c, TextAnchor align, Vector2 anchor, Vector2 pivot, Vector2 pos, Vector2 box, bool bold = true)
{
var rt = Rect("Text", parent, anchor, pivot, pos, box);
return Style(rt.gameObject.AddComponent<Text>(), s, size, c, align, bold);
}
/// <summary>부모 전체에 꽉 찬 글자(가운데 정렬 기본).</summary>
public static Text FillLabel(Transform parent, string s, int size, Color c, TextAnchor align = TextAnchor.MiddleCenter, bool bold = true, float pad = 0f)
{
var rt = Fill("Text", parent, pad, pad, 0f, 0f);
return Style(rt.gameObject.AddComponent<Text>(), s, size, c, align, bold);
}
static Text Style(Text t, string s, int size, Color c, TextAnchor align, bool bold)
{
t.font = TextUtil.Font;
t.fontSize = size;
t.fontStyle = bold ? FontStyle.Bold : FontStyle.Normal;
t.alignment = align;
t.color = c;
t.horizontalOverflow = HorizontalWrapMode.Wrap;
t.verticalOverflow = VerticalWrapMode.Overflow;
t.supportRichText = true;
t.raycastTarget = false;
t.text = s;
return t;
}
public static Button Btn(string name, Transform parent, string label, int fontSize, Color bg, Color fg, Vector2 anchor, Vector2 pivot, Vector2 pos, Vector2 size, Action onClick)
{
var img = Box(name, parent, anchor, pivot, pos, size, bg);
img.raycastTarget = true;
var b = img.gameObject.AddComponent<Button>();
var cb = b.colors;
cb.disabledColor = new Color(0.75f, 0.75f, 0.75f, 0.85f);
b.colors = cb;
if (onClick != null) b.onClick.AddListener(() => onClick());
FillLabel(img.transform, label, fontSize, fg, TextAnchor.MiddleCenter, true, 6f);
return b;
}
public static Text LabelOf(Button b)
{
return b.GetComponentInChildren<Text>(true);
}
/// <summary>둥근 막대 게이지 · 반환 = 채움(SetBar 로 0~1).</summary>
public static RectTransform Bar(string name, Transform parent, Vector2 anchor, Vector2 pivot, Vector2 pos, Vector2 size, Color bg, Color fg)
{
var back = Box(name, parent, anchor, pivot, pos, size, bg);
var fill = Fill("Fill", back.transform);
fill.anchorMax = new Vector2(0f, 1f);
Paint(fill, fg);
return fill;
}
public static void SetBar(RectTransform fill, float v)
{
v = Mathf.Clamp01(v);
fill.anchorMax = new Vector2(v, 1f);
fill.gameObject.SetActive(v > 0.001f);
}
/// <summary>세로 스크롤 격자 목록(칸 크기 cell · 열 columns) · 반환 = 내용(자식 = 칸).</summary>
public static RectTransform ScrollGrid(string name, Transform parent, float l, float r, float t, float b, Vector2 cell, Vector2 spacing, int columns, out ScrollRect scroll)
{
var vp = Fill(name, parent, l, r, t, b);
var vimg = vp.gameObject.AddComponent<Image>();
vimg.color = new Color(1f, 1f, 1f, 0f);
vimg.raycastTarget = true;
vp.gameObject.AddComponent<RectMask2D>();
var content = new GameObject("Content", typeof(RectTransform)).GetComponent<RectTransform>();
content.SetParent(vp, false);
content.anchorMin = new Vector2(0f, 1f);
content.anchorMax = new Vector2(1f, 1f);
content.pivot = new Vector2(0.5f, 1f);
content.offsetMin = content.offsetMax = Vector2.zero;
var grid = content.gameObject.AddComponent<GridLayoutGroup>();
grid.cellSize = cell;
grid.spacing = spacing;
grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
grid.constraintCount = columns;
grid.childAlignment = TextAnchor.UpperCenter;
grid.padding = new RectOffset(0, 0, 6, 12);
var fit = content.gameObject.AddComponent<ContentSizeFitter>();
fit.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
scroll = vp.gameObject.AddComponent<ScrollRect>();
scroll.viewport = vp;
scroll.content = content;
scroll.horizontal = false;
scroll.vertical = true;
scroll.movementType = ScrollRect.MovementType.Clamped;
scroll.scrollSensitivity = 60f;
return content;
}
public static void Clear(Transform t)
{
for (int i = t.childCount - 1; i >= 0; i--)
{
var c = t.GetChild(i).gameObject;
c.SetActive(false);
UnityEngine.Object.Destroy(c);
}
}
/// <summary>목록 칸(격자 자식) — 둥근 카드 + 클릭.</summary>
public static Image Cell(Transform content, string name, Color fill, Action onClick)
{
var go = new GameObject(name, typeof(RectTransform));
go.transform.SetParent(content, false);
var img = Paint((RectTransform)go.transform, fill);
img.raycastTarget = true;
if (onClick != null)
{
var b = go.AddComponent<Button>();
b.onClick.AddListener(() => onClick());
}
return img;
}
// ---------- 영웅 표시 ----------
public static Color TypeColor(UnitType t)
{
switch (t)
{
case UnitType.Infantry: return new Color(0.62f, 0.33f, 0.22f);
case UnitType.Spear: return new Color(0.30f, 0.50f, 0.30f);
case UnitType.Cavalry: return new Color(0.28f, 0.32f, 0.45f);
case UnitType.Archer: return new Color(0.70f, 0.52f, 0.20f);
case UnitType.Strategist: return Purple;
}
return Dark;
}
/// <summary>등급(승급 green… · 장비 white…) 표시 이름.</summary>
public static string GradeKo(string g)
{
switch (g)
{
case "white": return "흰";
case "green": return "녹";
case "blue": return "청";
case "purple": return "자";
case "orange": return "주";
case "gold": return "금";
}
return g;
}
public static Color GradeColor(string g)
{
switch (g)
{
case "white": return new Color(0.80f, 0.79f, 0.76f);
case "green": return new Color(0.38f, 0.64f, 0.33f);
case "blue": return new Color(0.29f, 0.48f, 0.78f);
case "purple": return Purple;
case "orange": return new Color(0.90f, 0.50f, 0.18f);
case "gold": return new Color(0.92f, 0.74f, 0.16f);
}
return Slot;
}
public static string Stars(int n, int max)
{
var sb = new System.Text.StringBuilder();
for (int i = 0; i < max; i++) sb.Append(i < n ? "★" : "☆");
return sb.ToString();
}
static readonly Dictionary<string, Sprite> artCache = new Dictionary<string, Sprite>();
/// <summary>일러 = Resources/Units/&lt;art&gt; front_idle 첫 프레임(없으면 다른 클립 첫 프레임 · 없으면 null = 도형).</summary>
public static Sprite ArtSprite(string art)
{
if (string.IsNullOrEmpty(art)) return null;
Sprite sp;
if (artCache.TryGetValue(art, out sp)) return sp;
var a = UnitArt.Load(art);
Sprite[] frames = null;
if (a != null && !a.Clips.TryGetValue("front_idle", out frames))
foreach (var kv in a.Clips)
{
frames = kv.Value;
break;
}
sp = frames != null && frames.Length > 0 ? frames[0] : null;
artCache[art] = sp;
return sp;
}
/// <summary>
/// 초상(틀 안을 마스크) — 일러가 있으면 그 그림(zoom &gt; 1 = 위쪽 얼굴 부분 확대) · 없으면 병종 색 원 + 병종 글자.
/// circle = 원형 틀(편성 자리) · false = 둥근 사각.
/// </summary>
public static RectTransform Portrait(string name, Transform parent, HeroDb.Hero h, Vector2 anchor, Vector2 pivot, Vector2 pos, Vector2 size, bool circle, float zoom, Color frame)
{
var rt = Rect(name, parent, anchor, pivot, pos, size);
var bg = rt.gameObject.AddComponent<Image>();
bg.color = frame;
bg.raycastTarget = false;
if (circle) bg.sprite = Circle;
else
{
bg.sprite = Round;
bg.type = Image.Type.Sliced;
}
var mask = rt.gameObject.AddComponent<Mask>();
mask.showMaskGraphic = frame.a > 0.01f;
if (frame.a <= 0.01f) bg.color = Color.white; // 투명 틀: 알파 0 그래픽은 스텐실을 안 써서 자식이 안 보임 → 불투명 + 그리지 않음
var sp = h != null ? ArtSprite(h.art) : null;
if (sp != null)
{
// zoom ≤ 1 = 전신 가운데 · zoom > 1 = 위쪽(얼굴) 확대
bool bust = zoom > 1f;
var a = bust
? Rect("Art", rt, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, size.y * 0.06f * zoom), size * zoom)
: Rect("Art", rt, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, size * zoom);
var img = a.gameObject.AddComponent<Image>();
img.sprite = sp;
img.preserveAspect = true;
img.raycastTarget = false;
}
else if (h != null)
{
float d = Mathf.Min(size.x, size.y) * 0.72f;
var dot = Dot("Shape", rt, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, d, TypeColor(h.type));
FillLabel(dot.transform, UnitTypeInfo.Mark(h.type), Mathf.RoundToInt(d * 0.5f), White);
}
return rt;
}
/// <summary>병종 배지(어두운 둥근 칸 + 글자).</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), Dark);
FillLabel(b.transform, UnitTypeInfo.Mark(t), Mathf.RoundToInt(h * 0.62f), White);
return b;
}
}
}