// ───────────────────────────────────────────────────────────────────────────── // WLDungeonGate.cs — 섬 위의 던전 입구(**더미**) + 「들어가기」 // // PD 추가 지시(발주서 §1-B-1) 「던전 오브젝트는 우선 더미 오브젝트를 쓰고 추후 교체할게」 // → 모양에 시간을 쓰지 않는다. 단순 형태(문틀 + 포털판) + 이름표 + 「들어가기」면 끝이다. // → 교체는 값 하나 — `WLDungeonDef.gatePrefabPath` 에 프리팹 경로를 넣으면 그것이 대신 놓인다. // // ■ 룩 — 아레나(던전)와 같은 계열로 맞춘다(Lead 지시 3항). // `WLReferenceLookSettings` 가 지정한 Toon/URP 머티리얼을 찾아 쓰고, 없으면 URP/Lit 로 만든다. // 🔴 배경 머티리얼·조명은 816a 소유라 **읽기만** 한다(어떤 기존 머티리얼도 수정하지 않는다). // // 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다. // ───────────────────────────────────────────────────────────────────────────── using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; namespace WL.Island { /// 던전 입구 1개. 트리거가 아니라 거리 판정(FI 플레이어가 CharacterController 라 안전하다). public sealed class WLDungeonGate : MonoBehaviour { public WLDungeonDef Def; public Transform Player; public bool InRange; static readonly List s_all = new List(8); static WLDungeonGate s_active; /// 지금 「들어가기」가 떠 있는 입구(없으면 null). public static WLDungeonGate Active { get { return s_active; } } void OnEnable() { if (!s_all.Contains(this)) s_all.Add(this); } void OnDisable() { s_all.Remove(this); if (s_active == this) s_active = null; } // ───────────────────────────────────────────────────────────────── // 만들기 // ───────────────────────────────────────────────────────────────── public static WLDungeonGate Create(WLIslandSettings cfg, WLDungeonDef d, Scene scene) { GameObject root = null; if (!string.IsNullOrEmpty(d.gatePrefabPath)) { var prefab = WLIslandAssets.LoadPrefab(d.gatePrefabPath); if (prefab != null) { root = Object.Instantiate(prefab); float gs = d.gateScale <= 0f ? 1f : d.gateScale; root.transform.localScale = prefab.transform.localScale * gs; // WL-816y — 프리팹 입구도 이름표를 단다(더미에만 있던 것 · 위치는 값 gateHeight·gateScale) WLIslandLabel.Attach(root.transform, new Vector3(0f, cfg.gateHeight * gs * 1.15f, 0f), d.displayName); } } if (root == null) root = BuildDummy(cfg, d); root.name = "~WL_DungeonGate_" + d.id; root.transform.position = d.gatePosition; root.transform.rotation = Quaternion.Euler(0f, d.gateYaw, 0f); if (scene.IsValid()) SceneManager.MoveGameObjectToScene(root, scene); var gate = root.AddComponent(); gate.Def = d; return gate; } /// 내장 더미 — 문틀 2개 + 상인방 + 포털판 + 이름표. (PD: 더미로 충분하다) static GameObject BuildDummy(WLIslandSettings cfg, WLDungeonDef d) { float s = d.gateScale <= 0f ? 1f : d.gateScale; float h = cfg.gateHeight * s; float w = cfg.gateWidth * s; float t = 0.35f * s; var root = new GameObject("gate"); var frameMat = WLIslandLook.GateMaterial(d.gateColor * 0.55f); var portalMat = WLIslandLook.GateMaterial(d.gateColor); MakeBox(root.transform, "post_L", new Vector3(-w * 0.5f, h * 0.5f, 0f), new Vector3(t, h, t), frameMat); MakeBox(root.transform, "post_R", new Vector3(w * 0.5f, h * 0.5f, 0f), new Vector3(t, h, t), frameMat); MakeBox(root.transform, "lintel", new Vector3(0f, h + t * 0.5f, 0f), new Vector3(w + t, t, t), frameMat); MakeBox(root.transform, "portal", new Vector3(0f, h * 0.5f, 0f), new Vector3(w - t * 0.5f, h, t * 0.35f), portalMat); WLIslandLabel.Attach(root.transform, new Vector3(0f, h + t * 1.8f, 0f), d.displayName); return root; } static void MakeBox(Transform parent, string name, Vector3 pos, Vector3 size, Material mat) { var go = GameObject.CreatePrimitive(PrimitiveType.Cube); go.name = name; go.transform.SetParent(parent, false); go.transform.localPosition = pos; go.transform.localScale = size; var col = go.GetComponent(); if (col != null) Object.Destroy(col); // 더미 — 길을 막지 않는다(NavMesh 재베이크 회피) var r = go.GetComponent(); if (r != null && mat != null) r.sharedMaterial = mat; } // ───────────────────────────────────────────────────────────────── // 거리 판정 · 입력 (러너의 Update 에서 한 번만 돈다) // ───────────────────────────────────────────────────────────────── public static void TickAll(WLIslandSettings cfg) { if (s_all.Count == 0) { if (s_active != null) { s_active = null; WLIslandPrompt.Hide(); } return; } var player = FindPlayer(); WLDungeonGate best = null; float bestSq = cfg.interactRadius * cfg.interactRadius; for (int i = 0; i < s_all.Count; i++) { var g = s_all[i]; if (g == null) continue; g.Player = player; g.InRange = false; if (player == null) continue; float sq = (player.position - g.transform.position).sqrMagnitude; if (sq <= bestSq) { bestSq = sq; best = g; } } if (best != null) best.InRange = true; if (best != s_active) { s_active = best; if (best == null) WLIslandPrompt.Hide(); else WLIslandPrompt.Show(cfg, string.Format(cfg.promptFormat, best.Def.displayName), best); } if (s_active != null && Input.GetKeyDown(cfg.enterKey)) s_active.Enter(); } static Transform FindPlayer() { var fi = Object.FindFirstObjectByType(); if (fi != null) return fi.transform; if (MyValue.bMyPC && MyValue.MyPC != null) return MyValue.MyPC.transform; return null; } public void Enter() { WLIslandPrompt.Hide(); s_active = null; WLIslandBridge.EnterDungeon(Def); } } }