// ───────────────────────────────────────────────────────────────────────────── // 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 { /// 더미 전용 머티리얼 공장(기존 머티리얼은 건드리지 않는다). public static class WLIslandLook { static readonly Dictionary s_cache = new Dictionary(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; } } static Shader s_toon; static bool s_toonTried; /// 아레나·섬이 쓰는 Toon 셰이더(`Shader Graphs/Toon`). 없으면 null. public static Shader ToonShader { get { if (s_toonTried) return s_toon; s_toonTried = true; s_toon = Shader.Find("Shader Graphs/Toon"); return s_toon; } } /// /// 아레나와 같은 Toon 재질(WL-816g §2). 셰이더가 없으면 (URP/Lit)로 떨어진다. /// 🔴 `_BaseMap` 은 비워 둔다(기본 흰 텍스처) — 색은 `_DiffuseColor` 로만 준다. /// 실측 근거: `Farm_IslandTop_Demo.mat` 가 `_DiffuseColor`·`_ShadowDiffuseColor` 로 색을 준다. /// public static Material ToonMaterial(Color c, bool useToon) { if (!useToon) return GateMaterial(c); var sh = ToonShader; if (sh == null) return GateMaterial(c); int key = ((Color32)c).GetHashCode() ^ 0x7001; Material m; if (s_cache.TryGetValue(key, out m) && m != null) return m; m = new Material(sh) { name = "WL_PlatformToon", hideFlags = HideFlags.DontSave }; if (m.HasProperty("_DiffuseColor")) m.SetColor("_DiffuseColor", c); if (m.HasProperty("_ShadowDiffuseColor")) m.SetColor("_ShadowDiffuseColor", c * 0.55f); if (m.HasProperty("_BaseColor")) m.SetColor("_BaseColor", c); if (m.HasProperty("_MinimumDarkness")) m.SetFloat("_MinimumDarkness", 0.2f); s_cache[key] = m; return m; } 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; } } /// 입구 위에 뜨는 3D 이름표. public static class WLIslandLabel { public static void Attach(Transform parent, Vector3 localPos, string text) { var cfg = WLIslandSettings.Instance; var go = new GameObject("label"); go.transform.SetParent(parent, false); go.transform.localPosition = localPos; var tmp = go.AddComponent(); var f = WLIslandAssets.Font(cfg != null ? cfg.fontAssetPath : null); if (f != null) tmp.font = f; tmp.text = text; tmp.fontSize = 3.2f; tmp.alignment = TextAlignmentOptions.Center; tmp.color = Color.white; var rt = tmp.GetComponent(); if (rt != null) rt.sizeDelta = new Vector2(6f, 1.4f); go.AddComponent(); } } /// 이름표가 항상 카메라를 본다. 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); } } /// 화면 아래 「들어가기」 — 런타임 생성 Canvas 1개(기존 UI 무변경). 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.renderMode = RenderMode.ScreenSpaceOverlay; canvas.sortingOrder = 9000; var scaler = s_root.AddComponent(); scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.referenceResolution = new Vector2(1080f, 1920f); scaler.matchWidthOrHeight = 0.5f; s_root.AddComponent(); if (Object.FindFirstObjectByType() == null) { var es = new GameObject("~WL_EventSystem"); Object.DontDestroyOnLoad(es); es.AddComponent(); es.AddComponent(); } // 버튼 var btnGo = new GameObject("btn_enter", typeof(RectTransform)); btnGo.transform.SetParent(s_root.transform, false); var brt = btnGo.GetComponent(); 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(); img.color = new Color(0.13f, 0.09f, 0.20f, 0.92f); var btn = btnGo.AddComponent