139 lines
6.2 KiB
C#
139 lines
6.2 KiB
C#
|
|
using UnityEngine;
|
||
|
|
using UnityEngine.UI;
|
||
|
|
|
||
|
|
namespace TKS
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// UI 캔버스 갱신 등급(PD 2026-11-10 「리소스·정보가 얼마나 자주 바뀌는지에 따라 캔버스를 나누어 드로우콜·재구성 최소화」).
|
||
|
|
/// Static = 화면을 열 때만 바뀜(바탕 · 틀 · 고정 글자) · Event = 플레이어 조작·게임 사건 때 바뀜(값 · 선택 · 턴 · 팝업) ·
|
||
|
|
/// Frame = 매 프레임 바뀜(호흡 · 튀김 · 스크롤 · 미니맵 · 끌기 · 커서).
|
||
|
|
/// </summary>
|
||
|
|
public enum UiRate
|
||
|
|
{
|
||
|
|
Static,
|
||
|
|
Event,
|
||
|
|
Frame
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>캔버스 1장의 등급 표시(검증 보고 `UiLayerProbe` · 규칙 테스트가 읽는다).</summary>
|
||
|
|
[DisallowMultipleComponent]
|
||
|
|
public class UiLayerInfo : MonoBehaviour
|
||
|
|
{
|
||
|
|
public UiRate rate;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 캔버스 층 만들기 — 루트 캔버스(화면 단위 · 정렬 순서 · 평면 거리) + 중첩 캔버스(등급별 분리).
|
||
|
|
/// 규칙(프로젝트 CLAUDE.md 「UI 캔버스·아틀라스 구조」):
|
||
|
|
/// ① 중첩 캔버스는 overrideSorting = false(그리는 순서 · RectMask2D/Mask 자르기 유지) ② 눌리는 그래픽이 있는 층만 GraphicRaycaster
|
||
|
|
/// ③ 매 프레임 바뀌는 것은 가장 작은 단위로 자기 캔버스(UiPulse · UiPop 은 스스로 분리) ④ 캔버스 1장 = 최소 1 드로우콜이므로 등급이 같은 것은 합친다.
|
||
|
|
/// </summary>
|
||
|
|
public static class UiLayer
|
||
|
|
{
|
||
|
|
public static readonly Vector2 Reference = new Vector2(1080f, 1920f);
|
||
|
|
|
||
|
|
/// <summary>화면 단위 루트 캔버스(Screen Space - Camera · 카메라 캡처에도 찍힘).</summary>
|
||
|
|
public static Canvas Root(GameObject go, Camera cam, int order, float plane, Vector2 reference, UiRate rate, bool raycast = true)
|
||
|
|
{
|
||
|
|
var c = go.GetComponent<Canvas>();
|
||
|
|
if (c == null) c = go.AddComponent<Canvas>();
|
||
|
|
c.renderMode = RenderMode.ScreenSpaceCamera;
|
||
|
|
c.worldCamera = cam;
|
||
|
|
c.planeDistance = plane;
|
||
|
|
c.sortingOrder = order;
|
||
|
|
var sc = go.GetComponent<CanvasScaler>();
|
||
|
|
if (sc == null) sc = go.AddComponent<CanvasScaler>();
|
||
|
|
sc.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||
|
|
sc.referenceResolution = reference;
|
||
|
|
sc.matchWidthOrHeight = 0.5f;
|
||
|
|
if (raycast && go.GetComponent<GraphicRaycaster>() == null) go.AddComponent<GraphicRaycaster>();
|
||
|
|
Tag(go, rate);
|
||
|
|
return c;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 이미 있는 UI 오브젝트를 중첩 캔버스로 분리(자식 포함 · 위치·순서 그대로).
|
||
|
|
/// raycast = true/false 지정 · null = 지금 자식 중 raycastTarget 그래픽이 있으면 붙임(나중에 눌리는 자식을 넣을 층은 true 로 지정).
|
||
|
|
/// </summary>
|
||
|
|
public static Canvas Nest(GameObject go, UiRate rate, bool? raycast = null)
|
||
|
|
{
|
||
|
|
if (go == null) return null;
|
||
|
|
var c = go.GetComponent<Canvas>();
|
||
|
|
if (c == null) c = go.AddComponent<Canvas>();
|
||
|
|
c.overrideSorting = false;
|
||
|
|
var root = TopCanvas(go.transform.parent);
|
||
|
|
if (root != null)
|
||
|
|
{
|
||
|
|
// 중첩 캔버스의 이벤트 카메라 = 루트와 같게(GraphicRaycaster 가 이 값을 읽음 · 꺼진 상태에서도 부모 사슬로 찾음)
|
||
|
|
c.worldCamera = root.worldCamera;
|
||
|
|
c.additionalShaderChannels = root.additionalShaderChannels;
|
||
|
|
}
|
||
|
|
bool needRay = raycast ?? HasRaycastTarget(go);
|
||
|
|
var gr = go.GetComponent<GraphicRaycaster>();
|
||
|
|
if (needRay && gr == null) go.AddComponent<GraphicRaycaster>();
|
||
|
|
else if (!needRay && gr != null) Object.Destroy(gr);
|
||
|
|
Tag(go, rate);
|
||
|
|
return c;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static Canvas Nest(Component c, UiRate rate, bool? raycast = null)
|
||
|
|
{
|
||
|
|
return c != null ? Nest(c.gameObject, rate, raycast) : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 매 프레임 바뀌는 요소를 Frame 캔버스로 — 바로 위 부모가 이미 Frame 층(움직이는 것끼리 모은 그룹)이면 그 층을 쓰고, 아니면 자기 캔버스.
|
||
|
|
/// 캔버스 밖(아직 부모 없음)이면 아무것도 하지 않는다.
|
||
|
|
/// </summary>
|
||
|
|
public static void Isolate(GameObject go)
|
||
|
|
{
|
||
|
|
if (go == null || go.GetComponent<Canvas>() != null) return;
|
||
|
|
var p = go.transform.parent;
|
||
|
|
if (p == null || TopCanvas(p) == null) return;
|
||
|
|
var tag = p.GetComponent<UiLayerInfo>();
|
||
|
|
if (tag != null && tag.rate == UiRate.Frame && p.GetComponent<Canvas>() != null) return;
|
||
|
|
Nest(go, UiRate.Frame);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>부모를 꽉 채우는 빈 층(중첩 캔버스) — 같은 등급의 요소를 모아 담는다.</summary>
|
||
|
|
public static RectTransform Group(string name, Transform parent, UiRate rate, bool? raycast = null)
|
||
|
|
{
|
||
|
|
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.offsetMin = rt.offsetMax = Vector2.zero;
|
||
|
|
Nest(go, rate, raycast ?? false);
|
||
|
|
return rt;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>부모 사슬의 가장 위 캔버스(루트) — 꺼져 있어도 찾는다.</summary>
|
||
|
|
static Canvas TopCanvas(Transform t)
|
||
|
|
{
|
||
|
|
Canvas top = null;
|
||
|
|
for (; t != null; t = t.parent)
|
||
|
|
{
|
||
|
|
var c = t.GetComponent<Canvas>();
|
||
|
|
if (c != null) top = c;
|
||
|
|
}
|
||
|
|
return top;
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool HasRaycastTarget(GameObject go)
|
||
|
|
{
|
||
|
|
foreach (var g in go.GetComponentsInChildren<Graphic>(true))
|
||
|
|
if (g.raycastTarget)
|
||
|
|
return true;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void Tag(GameObject go, UiRate rate)
|
||
|
|
{
|
||
|
|
var t = go.GetComponent<UiLayerInfo>();
|
||
|
|
if (t == null) t = go.AddComponent<UiLayerInfo>();
|
||
|
|
t.rate = rate;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|