Project_WL/Assets/WL/Island/WLDungeonGate.cs

152 lines
7.7 KiB
C#

// ─────────────────────────────────────────────────────────────────────────────
// 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
{
/// <summary>던전 입구 1개. 트리거가 아니라 거리 판정(FI 플레이어가 CharacterController 라 안전하다).</summary>
public sealed class WLDungeonGate : MonoBehaviour
{
public WLDungeonDef Def;
public Transform Player;
public bool InRange;
static readonly List<WLDungeonGate> s_all = new List<WLDungeonGate>(8);
static WLDungeonGate s_active;
/// <summary>지금 「들어가기」가 떠 있는 입구(없으면 null).</summary>
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<WLDungeonGate>();
gate.Def = d;
return gate;
}
/// <summary>내장 더미 — 문틀 2개 + 상인방 + 포털판 + 이름표. (PD: 더미로 충분하다)</summary>
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<Collider>();
if (col != null) Object.Destroy(col); // 더미 — 길을 막지 않는다(NavMesh 재베이크 회피)
var r = go.GetComponent<Renderer>();
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<CryingSnow.FarmingIsland.PlayerController>();
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);
}
}
}