2026-11-06 14:20:57 +00:00
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.UI;
|
|
|
|
|
|
|
|
|
|
namespace TKS
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// UI 테마 적용 도우미(PD #1100) — 아웃게임 UiKit · 전투 HUD · 월드맵 팝업이 같이 쓴다.
|
|
|
|
|
/// 테마가 없으면 모든 함수가 false/null 을 돌려주고 호출부는 기존 도형으로 그린다.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static class UISkin
|
|
|
|
|
{
|
|
|
|
|
public const string OverlayName = "Gloss";
|
|
|
|
|
|
|
|
|
|
public static UITheme Theme
|
|
|
|
|
{
|
|
|
|
|
get { return UITheme.Current; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool On
|
|
|
|
|
{
|
|
|
|
|
get { return Theme != null; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Color Col(string id, Color fallback)
|
|
|
|
|
{
|
|
|
|
|
Color c;
|
|
|
|
|
return Theme != null && Theme.TryColor(id, out c) ? c : fallback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Font TitleFont
|
|
|
|
|
{
|
|
|
|
|
get { return Theme != null && Theme.titleFont != null ? Theme.titleFont : TextUtil.Font; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool Has(string skinId)
|
|
|
|
|
{
|
|
|
|
|
return Theme != null && Theme.Skin(skinId) != null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>이미지에 스킨(9-slice 바탕 + 덮개) 적용 · 색은 호출부(img.color)가 정한다.</summary>
|
|
|
|
|
public static bool Apply(Image img, string skinId)
|
|
|
|
|
{
|
|
|
|
|
var s = Theme != null ? Theme.Skin(skinId) : null;
|
|
|
|
|
if (s == null || img == null) return false;
|
|
|
|
|
var sp = s.Sprite;
|
|
|
|
|
img.sprite = sp;
|
|
|
|
|
img.type = sp.border != Vector4.zero ? Image.Type.Sliced : Image.Type.Simple;
|
|
|
|
|
img.pixelsPerUnitMultiplier = s.ppu > 0f ? s.ppu : 1f;
|
|
|
|
|
img.fillCenter = true;
|
|
|
|
|
var old = img.transform.Find(OverlayName);
|
|
|
|
|
if (old != null) Object.Destroy(old.gameObject);
|
|
|
|
|
if (s.overlay != null) Overlay(img.transform, s.overlay, s.inset, s.ppu).color = new Color(1f, 1f, 1f, s.overlayAlpha);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>부모를 꽉 채우는 덮개(광택 등 · 첫 자식 = 내용 아래) · inset = 왼 · 위 · 오 · 아래.</summary>
|
|
|
|
|
public static Image Overlay(Transform parent, Sprite sp, Vector4 inset, float ppu = 1f)
|
|
|
|
|
{
|
|
|
|
|
var go = new GameObject(OverlayName, typeof(RectTransform));
|
|
|
|
|
go.transform.SetParent(parent, false);
|
|
|
|
|
go.transform.SetAsFirstSibling();
|
|
|
|
|
var rt = (RectTransform)go.transform;
|
|
|
|
|
rt.anchorMin = Vector2.zero;
|
|
|
|
|
rt.anchorMax = Vector2.one;
|
|
|
|
|
rt.offsetMin = new Vector2(inset.x, inset.w);
|
|
|
|
|
rt.offsetMax = new Vector2(-inset.z, -inset.y);
|
|
|
|
|
var img = go.AddComponent<Image>();
|
|
|
|
|
img.sprite = sp;
|
|
|
|
|
img.type = sp.border != Vector4.zero ? Image.Type.Sliced : Image.Type.Simple;
|
|
|
|
|
img.pixelsPerUnitMultiplier = ppu > 0f ? ppu : 1f;
|
|
|
|
|
img.preserveAspect = sp.border == Vector4.zero;
|
|
|
|
|
img.raycastTarget = false;
|
|
|
|
|
return img;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Sprite Icon(string id)
|
|
|
|
|
{
|
|
|
|
|
return Theme != null ? Theme.Icon(id) : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>아이콘 이미지(테마에 id 가 없으면 null) · 비율 유지 · 표 tint 적용.</summary>
|
|
|
|
|
public static Image IconImage(Transform parent, string id, Vector2 anchor, Vector2 pivot, Vector2 pos, Vector2 size)
|
|
|
|
|
{
|
|
|
|
|
var e = Theme != null ? Theme.IconEntryOf(id) : null;
|
|
|
|
|
if (e == null) return null;
|
|
|
|
|
var go = new GameObject("Icon_" + id, 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;
|
|
|
|
|
var img = go.AddComponent<Image>();
|
|
|
|
|
img.sprite = e.Sprite;
|
|
|
|
|
img.color = e.samguk != null ? Color.white : e.tint;
|
|
|
|
|
img.preserveAspect = true;
|
|
|
|
|
img.raycastTarget = false;
|
|
|
|
|
return img;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>글자 외곽선(팩 버튼 글자처럼 굵은 어두운 선).</summary>
|
|
|
|
|
public static void Stroke(Graphic g, Color c, float d = 2.5f)
|
|
|
|
|
{
|
|
|
|
|
if (g == null || g.GetComponent<Outline>() != null) return;
|
|
|
|
|
var t = g as Text;
|
|
|
|
|
if (t != null && t.fontSize < 40) d = Mathf.Min(d, t.fontSize < 30 ? 1.5f : 2f); // 작은 글자는 얇게(굵으면 한글이 뭉개짐)
|
|
|
|
|
var o = g.gameObject.AddComponent<Outline>();
|
|
|
|
|
o.effectColor = c;
|
|
|
|
|
o.effectDistance = new Vector2(d, -d);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static readonly Dictionary<string, Sprite> worldCache = new Dictionary<string, Sprite>();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 월드 공간(SpriteRenderer · 9-slice)용 스킨 사본 — 같은 텍스처를 ppu 만 바꿔 다시 만든다(체력 바처럼 월드 크기가 작은 막대가 테두리보다 작아 깨지지 않게).
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static Sprite WorldSprite(string skinId, float ppu)
|
|
|
|
|
{
|
|
|
|
|
var s = Theme != null ? Theme.Skin(skinId) : null;
|
|
|
|
|
if (s == null) return null;
|
|
|
|
|
string key = skinId + "@" + ppu;
|
|
|
|
|
Sprite sp;
|
|
|
|
|
if (worldCache.TryGetValue(key, out sp) && sp != null) return sp;
|
|
|
|
|
var src = s.Sprite;
|
|
|
|
|
// SpriteRenderer 9-slice 는 가운데 칸이 0 px 이면 가운데를 그리지 않는다(팩 슬라이더 = 테두리가 전부) → 가운데 2 px 를 남긴다
|
|
|
|
|
var b = src.border;
|
|
|
|
|
float w = src.rect.width, h = src.rect.height;
|
|
|
|
|
if (b.x + b.z > w - 2f)
|
|
|
|
|
{
|
|
|
|
|
float k = Mathf.Max(0f, w - 2f) / Mathf.Max(1f, b.x + b.z);
|
|
|
|
|
b.x = Mathf.Floor(b.x * k);
|
|
|
|
|
b.z = Mathf.Floor(b.z * k);
|
|
|
|
|
}
|
|
|
|
|
if (b.y + b.w > h - 2f)
|
|
|
|
|
{
|
|
|
|
|
float k = Mathf.Max(0f, h - 2f) / Mathf.Max(1f, b.y + b.w);
|
|
|
|
|
b.y = Mathf.Floor(b.y * k);
|
|
|
|
|
b.w = Mathf.Floor(b.w * k);
|
|
|
|
|
}
|
2026-11-09 16:05:47 +00:00
|
|
|
// 아틀라스에 묶인 스프라이트(PD 2026-11-10 UI 아틀라스) = texture 가 아틀라스 장 → 그 장 안의 자리(textureRect)로 잘라야 한다(rect = 원본 기준)
|
|
|
|
|
var area = src.packed ? src.textureRect : src.rect;
|
|
|
|
|
sp = Sprite.Create(src.texture, area, new Vector2(0.5f, 0.5f), ppu, 0, SpriteMeshType.FullRect, b);
|
2026-11-06 14:20:57 +00:00
|
|
|
sp.name = key;
|
|
|
|
|
sp.hideFlags = HideFlags.DontSave;
|
|
|
|
|
worldCache[key] = sp;
|
|
|
|
|
return sp;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|