VSClone/Assets/Scripts/View/UI.cs

234 lines
12 KiB
C#
Raw Permalink 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.Globalization;
using System.Numerics;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using Vector2 = UnityEngine.Vector2;
using Vector3 = UnityEngine.Vector3;
using Vector4 = UnityEngine.Vector4;
// 코드로 만드는 uGUI 도우미 (기준 해상도 1080×1920 · 원작 GamePlayUI 좌표계와 같음)
namespace VS.View
{
public static class Pal
{
public static readonly Color Ink = new Color32(44, 48, 80, 255); // 남색 잉크(GUI Pro-SuperCasual 테두리 계열)
public static readonly Color Navy = new Color32(38, 44, 72, 255); // 어두운 판·탭 바
public static readonly Color NavyLight = new Color32(78, 90, 136, 255); // 선택된 탭
public static readonly Color Outline = new Color(0.14f, 0.16f, 0.28f, 0.95f);
public static readonly Color White = Color.white;
public static readonly Color Pink = new Color32(255, 105, 170, 255);
public static readonly Color PinkDark = new Color32(214, 62, 132, 255);
public static readonly Color Purple = new Color32(126, 87, 214, 255);
public static readonly Color PurpleDark = new Color32(84, 56, 160, 255);
public static readonly Color Yellow = new Color32(255, 206, 64, 255);
public static readonly Color Green = new Color32(78, 200, 120, 255);
public static readonly Color GreenDark = new Color32(42, 150, 84, 255);
public static readonly Color Gray = new Color32(170, 170, 185, 255);
public static readonly Color Panel = new Color32(255, 255, 255, 255);
public static readonly Color PanelEdge = new Color32(236, 240, 252, 255);
public static readonly Color Row = new Color32(241, 243, 252, 255); // 팝업 안 줄(행) 바탕
public static readonly Color Dim = new Color(0.08f, 0.04f, 0.12f, 0.62f);
public static readonly Color Blue = new Color32(84, 170, 255, 255);
public static readonly Color Orange = new Color32(255, 150, 60, 255);
public static readonly Color Red = new Color32(240, 72, 84, 255);
}
public static class Fmt
{
// NSHTool.Utilities.NumberShortFormatter (.cctor RVA 0x252D820) 접미사 표 그대로 · 소수점 = '.'(원작은 vi-VN ',')
static readonly string[] Suf = { "", "k", "M", "B", "T", "Qa", "Qi", "Sx", "Sp", "Oc" };
static decimal Trunc(decimal d, int n) { decimal p = 1m; for (int i = 0; i < n; i++) p *= 10m; return decimal.Truncate(d * p) / p; }
public static string Num(BigInteger v, int decimals = 1)
{
if (v < 0) return "-" + Num(-v, decimals);
if (v < 1000) return v.ToString(CultureInfo.InvariantCulture);
int e = 0;
BigInteger t = v;
while (t >= 1000 && e < Suf.Length - 1) { t /= 1000; e++; }
decimal d = (decimal)(v / BigInteger.Pow(1000, Math.Max(0, e - 1))) / 1000m;
if (d >= 1000m && e == Suf.Length - 1) return t.ToString(CultureInfo.InvariantCulture) + Suf[e];
decimal r = Trunc(d, decimals);
return r.ToString("0." + new string('#', decimals), CultureInfo.InvariantCulture) + Suf[e];
}
public static string Num(decimal v, int decimals = 1)
{
if (v < 1000m)
{
if (v < 10m) return Trunc(v, 2).ToString("0.##", CultureInfo.InvariantCulture);
return Trunc(v, 1).ToString("0.#", CultureInfo.InvariantCulture);
}
return Num(new BigInteger(decimal.Truncate(v)), decimals);
}
public static string Time(double sec)
{
if (sec < 0) sec = 0;
int s = (int)Math.Ceiling(sec);
if (s >= 3600) return $"{s / 3600}:{(s / 60) % 60:00}:{s % 60:00}";
return $"{s / 60:00}:{s % 60:00}";
}
}
public static class UI
{
static Font font;
public static Font Font
{
get
{
if (font == null) font = Resources.Load<Font>("Fonts/MaplestoryBold");
if (font == null) font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
return font;
}
}
public static RectTransform Rect(string name, Transform parent)
{
var go = new GameObject(name, typeof(RectTransform));
go.layer = 5;
var rt = (RectTransform)go.transform;
rt.SetParent(parent, false);
return rt;
}
public static RectTransform Place(this RectTransform rt, Vector2 aMin, Vector2 aMax, Vector2 pos, Vector2 size, Vector2? pivot = null)
{
rt.anchorMin = aMin; rt.anchorMax = aMax; rt.pivot = pivot ?? new Vector2(0.5f, 0.5f);
rt.anchoredPosition = pos; rt.sizeDelta = size;
return rt;
}
public static RectTransform Fill(this RectTransform rt, float l = 0, float b = 0, float r = 0, float t = 0)
{
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;
}
public static Image Img(Transform parent, string name, Sprite sp, Color c, bool raycast = false)
{
var rt = Rect(name, parent);
var im = rt.gameObject.AddComponent<Image>();
im.sprite = sp; im.color = c; im.raycastTarget = raycast;
if (sp != null && sp.border != Vector4.zero) im.type = Image.Type.Sliced;
return im;
}
public static Text Txt(Transform parent, string name, string s, int size, Color c, TextAnchor a = TextAnchor.MiddleCenter, bool outline = false)
{
var rt = Rect(name, parent);
var t = rt.gameObject.AddComponent<Text>();
t.font = Font; t.text = s; t.fontSize = size; t.color = c; t.alignment = a;
t.raycastTarget = false; t.horizontalOverflow = HorizontalWrapMode.Wrap; t.verticalOverflow = VerticalWrapMode.Truncate;
t.resizeTextForBestFit = true; t.resizeTextMinSize = Math.Max(10, size / 2); t.resizeTextMaxSize = size;
if (outline) { var o = rt.gameObject.AddComponent<Outline>(); o.effectColor = Pal.Outline; float d = Mathf.Clamp(size / 15f, 1.5f, 3f); o.effectDistance = new Vector2(d, -d); } // 작은 글자는 얇게
return t;
}
/// 아이콘 그림 (비율 유지)
public static Image Icon(Transform parent, string name, Sprite sp, Color c)
{
var im = Img(parent, name, sp, c);
im.preserveAspect = true;
return im;
}
public static Button Btn(Transform parent, string name, Sprite sp, Color c, UnityAction onClick)
{
var im = Img(parent, name, sp, c, true);
var b = im.gameObject.AddComponent<Button>();
var cb = b.colors; cb.pressedColor = new Color(0.85f, 0.85f, 0.85f); cb.disabledColor = new Color(0.62f, 0.62f, 0.66f, 1f); b.colors = cb;
if (onClick != null) b.onClick.AddListener(onClick);
im.gameObject.AddComponent<PressScale>();
return b;
}
/// 글자 버튼 (배경 + 글자)
/// 팩이 있으면 색이 구워진 버튼 그림(흰 틴트) · 없으면 둥근 판 + 색
public static Button TextBtn(Transform parent, string name, string label, Color bg, int size, UnityAction onClick, out Text text)
{
var sp = Skin.Button(bg);
var b = Btn(parent, name, sp ?? Art.Round, sp != null ? Color.white : bg, onClick);
text = Txt(b.transform, "Label", label, size, Color.white, TextAnchor.MiddleCenter, true);
text.rectTransform.Fill(10, sp != null ? 16 : 6, 10, 6); // 팩 버튼은 아래 그림자 두께만큼 글자를 올림
return b;
}
/// 버튼 색 바꾸기 (팩 = 그림 교체 · 코드 그림 = 색)
public static void SetBtnColor(Button b, Color c)
{
var im = b.GetComponent<Image>();
var sp = Skin.Button(c);
if (sp != null) { im.sprite = sp; im.color = Color.white; } else { im.sprite = Art.Round; im.color = c; }
}
/// 가로 막대 — small = 유닛 체력 같은 작은 막대(테두리 얇은 그림)
public static Image Bar(Transform parent, string name, Color bg, Color fg, out Image fill, bool small = false)
{
var back = Img(parent, name, small ? Skin.HpBg : Skin.BarBg, bg);
fill = Img(back.transform, "Fill", small ? Skin.HpFill : Skin.BarFill, fg);
fill.type = Image.Type.Filled; fill.fillMethod = Image.FillMethod.Horizontal; fill.fillAmount = 0;
fill.preserveAspect = false;
int pad = Skin.On ? (small ? 3 : 5) : 4;
fill.rectTransform.Fill(pad, pad, pad, pad);
return back;
}
/// 빨간 알림 점 (흰 테두리) — 오른쪽 위
public static Image Dot(Transform parent, string name, float size = 36f)
{
var d = Img(parent, name, Skin.CirclePlain, Pal.Red);
d.rectTransform.Place(new Vector2(1, 1), new Vector2(1, 1), new Vector2(-6, -6), new Vector2(size, size));
if (Skin.DotBorder != null) Img(d.transform, "Border", Skin.DotBorder, Color.white).rectTransform.Fill(-3, -3, -3, -3);
return d;
}
public static ScrollRect Scroll(Transform parent, string name, out RectTransform content, float spacing = 16f)
{
var root = Img(parent, name, Art.Square, new Color(1, 1, 1, 0), true);
var sr = root.gameObject.AddComponent<ScrollRect>();
var vp = Rect("Viewport", root.transform).Fill();
vp.gameObject.AddComponent<RectMask2D>();
content = Rect("Content", vp);
content.anchorMin = new Vector2(0, 1); content.anchorMax = new Vector2(1, 1); content.pivot = new Vector2(0.5f, 1);
content.anchoredPosition = Vector2.zero; content.sizeDelta = Vector2.zero;
var vl = content.gameObject.AddComponent<VerticalLayoutGroup>();
vl.spacing = spacing; vl.childControlHeight = true; vl.childControlWidth = true; vl.childForceExpandHeight = false; vl.childForceExpandWidth = true;
vl.padding = new RectOffset(8, 8, 8, 24);
var fit = content.gameObject.AddComponent<ContentSizeFitter>();
fit.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
sr.viewport = vp; sr.content = content; sr.horizontal = false; sr.vertical = true;
sr.movementType = ScrollRect.MovementType.Elastic; sr.scrollSensitivity = 30f;
return sr;
}
public static void SetLayoutHeight(Component c, float h)
{
var le = c.GetComponent<LayoutElement>() ?? c.gameObject.AddComponent<LayoutElement>();
le.preferredHeight = h; le.minHeight = h;
}
}
/// 버튼 눌림 크기 반응
public class PressScale : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler
{
Vector3 baseScale = Vector3.one; float target = 1f;
void Awake() { baseScale = transform.localScale; }
public void OnPointerDown(PointerEventData e) { target = 0.93f; }
public void OnPointerUp(PointerEventData e) { target = 1f; }
public void OnPointerExit(PointerEventData e) { target = 1f; }
void Update()
{
float s = transform.localScale.x / Mathf.Max(0.0001f, baseScale.x);
s = Mathf.MoveTowards(s, target, Time.unscaledDeltaTime * 2.5f);
transform.localScale = baseScale * s;
}
}
}