Project_WL/Assets/WL/Island/WLIslandUi.cs

170 lines
6.8 KiB
C#

// ─────────────────────────────────────────────────────────────────────────────
// WLIslandUi.cs — 던전 입구의 이름표·「들어가기」 버튼 · 더미 머티리얼
//
// 발주서 WL-816d §1-② 「플레이어가 가까이 가면 『들어가기』가 뜨게 하라」
// 🔴 기존 UI 프리팹·Canvas 를 고치지 않는다 — 필요한 것만 런타임에 만들고, 떠날 때 지운다.
// 🔴 배경/캐릭터 머티리얼은 읽지도 고치지도 않는다(816a 소유). 더미용 머티리얼만 새로 만든다.
// ─────────────────────────────────────────────────────────────────────────────
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace WL.Island
{
/// <summary>더미 전용 머티리얼 공장(기존 머티리얼은 건드리지 않는다).</summary>
public static class WLIslandLook
{
static readonly Dictionary<int, Material> s_cache = new Dictionary<int, Material>(8);
static Shader s_shader;
public static Shader LitShader
{
get
{
if (s_shader != null) return s_shader;
s_shader = Shader.Find("Universal Render Pipeline/Lit");
if (s_shader == null) s_shader = Shader.Find("Universal Render Pipeline/Simple Lit");
if (s_shader == null) s_shader = Shader.Find("Sprites/Default");
return s_shader;
}
}
public static Material GateMaterial(Color c)
{
int key = ((Color32)c).GetHashCode();
Material m;
if (s_cache.TryGetValue(key, out m) && m != null) return m;
if (LitShader == null) return null;
m = new Material(LitShader) { name = "WL_GateDummy", hideFlags = HideFlags.DontSave };
if (m.HasProperty("_BaseColor")) m.SetColor("_BaseColor", c);
if (m.HasProperty("_Color")) m.SetColor("_Color", c);
if (m.HasProperty("_Smoothness")) m.SetFloat("_Smoothness", 0.15f);
s_cache[key] = m;
return m;
}
}
/// <summary>입구 위에 뜨는 3D 이름표.</summary>
public static class WLIslandLabel
{
public static void Attach(Transform parent, Vector3 localPos, string text)
{
var go = new GameObject("label");
go.transform.SetParent(parent, false);
go.transform.localPosition = localPos;
var tmp = go.AddComponent<TextMeshPro>();
tmp.text = text;
tmp.fontSize = 3.2f;
tmp.alignment = TextAlignmentOptions.Center;
tmp.color = Color.white;
var rt = tmp.GetComponent<RectTransform>();
if (rt != null) rt.sizeDelta = new Vector2(6f, 1.4f);
go.AddComponent<WLIslandBillboard>();
}
}
/// <summary>이름표가 항상 카메라를 본다.</summary>
public sealed class WLIslandBillboard : MonoBehaviour
{
void LateUpdate()
{
var cam = Camera.main;
if (cam == null) return;
transform.rotation = Quaternion.LookRotation(transform.position - cam.transform.position, Vector3.up);
}
}
/// <summary>화면 아래 「들어가기」 — 런타임 생성 Canvas 1개(기존 UI 무변경).</summary>
public static class WLIslandPrompt
{
static GameObject s_root;
static TextMeshProUGUI s_text;
static WLDungeonGate s_target;
public static bool Visible { get { return s_root != null && s_root.activeSelf; } }
public static string LastText = "";
public static void Show(WLIslandSettings cfg, string text, WLDungeonGate gate)
{
Ensure(cfg);
s_target = gate;
LastText = text;
if (s_text != null) s_text.text = text;
if (s_root != null) s_root.SetActive(true);
}
public static void Hide()
{
s_target = null;
if (s_root != null) s_root.SetActive(false);
}
static void Ensure(WLIslandSettings cfg)
{
if (s_root != null) return;
s_root = new GameObject("~WL_IslandPrompt");
Object.DontDestroyOnLoad(s_root);
var canvas = s_root.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 9000;
var scaler = s_root.AddComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1080f, 1920f);
scaler.matchWidthOrHeight = 0.5f;
s_root.AddComponent<GraphicRaycaster>();
if (Object.FindFirstObjectByType<EventSystem>() == null)
{
var es = new GameObject("~WL_EventSystem");
Object.DontDestroyOnLoad(es);
es.AddComponent<EventSystem>();
es.AddComponent<StandaloneInputModule>();
}
// 버튼
var btnGo = new GameObject("btn_enter", typeof(RectTransform));
btnGo.transform.SetParent(s_root.transform, false);
var brt = btnGo.GetComponent<RectTransform>();
brt.anchorMin = new Vector2(0.5f, 0f);
brt.anchorMax = new Vector2(0.5f, 0f);
brt.pivot = new Vector2(0.5f, 0f);
brt.anchoredPosition = new Vector2(0f, 260f);
brt.sizeDelta = new Vector2(520f, 150f);
var img = btnGo.AddComponent<Image>();
img.color = new Color(0.13f, 0.09f, 0.20f, 0.92f);
var btn = btnGo.AddComponent<Button>();
btn.targetGraphic = img;
btn.onClick.AddListener(OnClick);
var txtGo = new GameObject("text", typeof(RectTransform));
txtGo.transform.SetParent(btnGo.transform, false);
var trt = txtGo.GetComponent<RectTransform>();
trt.anchorMin = Vector2.zero; trt.anchorMax = Vector2.one;
trt.offsetMin = Vector2.zero; trt.offsetMax = Vector2.zero;
s_text = txtGo.AddComponent<TextMeshProUGUI>();
s_text.fontSize = 44f;
s_text.alignment = TextAlignmentOptions.Center;
s_text.color = Color.white;
s_text.text = cfg != null ? cfg.enterButtonText : "들어가기";
s_root.SetActive(false);
}
static void OnClick()
{
var g = s_target;
if (g != null) g.Enter();
}
}
}