191 lines
11 KiB
C#
191 lines
11 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
|
// WLDungeonSceneSetup.cs — 던전 씬(아레나 복사본)을 **런타임**에 전투 가능한 맵으로 만든다
|
|
//
|
|
// PD 지시 「던전은 지금 만든 데모 씬을 쓰면 될 것 같아」 + Lead 「WL_ArenaProto 복사본 2개, 원본은 룩 개발용」
|
|
//
|
|
// ■ 아레나 데모 씬에 없는 것(실측) — 전부 여기서 런타임에 채운다. 씬 파일은 룩 그대로 둔다.
|
|
// ① NavMesh : 룩 씬이라 베이크 데이터가 없다 → `NavMeshSurface` 를 붙여 런타임 베이크
|
|
// ② 몬스터 스포너: `StageSpawner` 는 `MobControlMgr.SpawnerID` 로 몹을 찾는다(StageSpawner.cs:20)
|
|
// → 스테이지 표가 요구하는 SpawnerID 로 스포너를 만든다(테이블 수정 0 · 몹 종류는 MonsterAppear 표가 정한다)
|
|
// ③ LoadMapMgr : `PCInfo.Make_Actors:24` 가 부른다 → 섬과 같은 파생 노드로 채운다
|
|
// ④ 중복 정리 : 데모 리그(ArenaWalkerRig)·여분 카메라/AudioListener 해제
|
|
//
|
|
// ■ 그 다음 `StageDirector.Enter(stageIndex, "dungeon")` — #815 자산을 그대로 쓴다(코드 0줄).
|
|
//
|
|
// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using TMPro;
|
|
using Unity.AI.Navigation;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using WL.Combat.Stage;
|
|
|
|
namespace WL.Island
|
|
{
|
|
/// <summary>던전 씬 준비 + 스테이지 입장. 정적.</summary>
|
|
public static class WLDungeonSceneSetup
|
|
{
|
|
/// <summary>입장 요청이 걸린 던전(씬 로드 전에 세팅된다).</summary>
|
|
public static WLDungeonDef Pending;
|
|
/// <summary>지금 돌고 있는 던전(스테이지 이벤트를 우리 것으로 가려내는 기준).</summary>
|
|
public static WLDungeonDef Current;
|
|
|
|
public static int Prepared, NavBakes, SpawnersMade, EnterCalls;
|
|
public static string LastLog = "";
|
|
|
|
static readonly List<MobControlMgr> s_made = new List<MobControlMgr>(4);
|
|
|
|
public static IEnumerator Co_Prepare(WLIslandSettings cfg, Scene scene, WLDungeonDef d)
|
|
{
|
|
Prepared++;
|
|
Current = d;
|
|
Pending = null;
|
|
yield return null; // 씬 안 오브젝트들의 Awake 를 끝까지 돌린다
|
|
|
|
WLIslandSceneSetup.EnsureDungeonMapNodes(cfg, scene, d);
|
|
FixDuplicates(cfg, scene);
|
|
|
|
// ① NavMesh
|
|
if (cfg.bakeDungeonNavMesh != 0) BakeNavMesh(cfg, scene);
|
|
|
|
// 🔴 단독 Play(씬만 열고 Play = 룩 확인 경로)에는 테이블·ServerData 가 없다.
|
|
// 스포너·스테이지는 원본 테이블을 타므로 여기서 멈춘다 — 씬 룩만 확인한다.
|
|
if (InGameInfo.Ins == null)
|
|
{
|
|
LastLog = "던전 씬 단독 Play — 룩만 확인(스포너·스테이지 건너뜀)";
|
|
WLIslandBridge.Log(cfg, LastLog);
|
|
yield break;
|
|
}
|
|
|
|
// ② 스포너 — 스테이지 표가 요구하는 ID 로만 만든다
|
|
s_made.Clear();
|
|
var def = StageDefOf(d);
|
|
if (cfg.spawnDungeonSpawners != 0 && def != null) MakeSpawners(cfg, scene, def);
|
|
|
|
// ③ 스포너 로딩(첫 스폰)이 끝나기를 기다린다 — 안 기다리면 리젠 차단이 첫 스폰을 죽인다(StageSpawner.cs 가드)
|
|
float t = 0f;
|
|
while (t < 8f && !AllLoaded()) { t += Time.unscaledDeltaTime; yield return null; }
|
|
|
|
// ④ PC 가 생길 때까지 기다린다(Make_Actors 는 Addressables 비동기다)
|
|
t = 0f;
|
|
while (t < 8f && !(MyValue.bMyPC && MyValue.MyPC != null)) { t += Time.unscaledDeltaTime; yield return null; }
|
|
|
|
if (cfg.dungeonEnterDelay > 0f) yield return new WaitForSecondsRealtime(cfg.dungeonEnterDelay);
|
|
|
|
EnterCalls++;
|
|
bool ok = StageDirector.Enter(d.stageIndex, "dungeon " + d.id);
|
|
LastLog = "던전 준비 완료 — " + d.displayName + " · 스포너 " + s_made.Count +
|
|
" · NavMesh " + NavBakes + " · Enter(" + d.stageIndex + ")=" + ok;
|
|
WLIslandBridge.Log(cfg, LastLog);
|
|
}
|
|
|
|
public static StageDef StageDefOf(WLDungeonDef d)
|
|
{
|
|
var t = WLStageTable.Instance;
|
|
return t != null ? t.Get(d.stageIndex) : null;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────
|
|
// ① NavMesh 런타임 베이크
|
|
// ─────────────────────────────────────────────────────────────────
|
|
static void BakeNavMesh(WLIslandSettings cfg, Scene scene)
|
|
{
|
|
var surf = Object.FindFirstObjectByType<NavMeshSurface>();
|
|
if (surf == null)
|
|
{
|
|
var go = new GameObject("~WL_DungeonNavMesh");
|
|
SceneManager.MoveGameObjectToScene(go, scene);
|
|
surf = go.AddComponent<NavMeshSurface>();
|
|
}
|
|
// 🔴 기본값(RenderMeshes)은 메시마다 Read/Write 를 요구해 콘솔 에러 100건이 난다(실측).
|
|
// 콜라이더 기준으로 베이크하면 임포트 설정을 하나도 건드리지 않고 조용히 끝난다.
|
|
surf.useGeometry = UnityEngine.AI.NavMeshCollectGeometry.PhysicsColliders;
|
|
surf.BuildNavMesh();
|
|
NavBakes++;
|
|
|
|
UnityEngine.AI.NavMeshHit hit;
|
|
var def = StageDefOf(WLDungeonSceneSetup.Current);
|
|
Vector3 probe = def != null ? def.anchor : Vector3.zero;
|
|
bool onMesh = UnityEngine.AI.NavMesh.SamplePosition(probe, out hit, 6f, UnityEngine.AI.NavMesh.AllAreas);
|
|
WLIslandBridge.Log(cfg, "던전 NavMesh 런타임 베이크 완료 — 앵커 " + probe.ToString("F1") +
|
|
" 위 NavMesh " + (onMesh ? ("있음(" + hit.position.ToString("F2") + ")") : "🔴 없음"));
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────
|
|
// ② 스포너 생성 (테이블 수정 0 — MonsterAppear 표의 기존 SpawnerID 를 그대로 쓴다)
|
|
// ─────────────────────────────────────────────────────────────────
|
|
static void MakeSpawners(WLIslandSettings cfg, Scene scene, StageDef def)
|
|
{
|
|
if (def.waves == null) return;
|
|
for (int i = 0; i < def.waves.Length; i++)
|
|
{
|
|
var w = def.waves[i];
|
|
if (w == null || w.spawnerId == 0) continue;
|
|
if (StageSpawner.Find(w.spawnerId) != null) continue; // 이미 씬에 있으면 그대로 쓴다
|
|
|
|
var go = new GameObject("~WL_Spawner_" + w.spawnerId);
|
|
go.SetActive(false); // 🔴 필드부터 채우고 깨운다
|
|
SceneManager.MoveGameObjectToScene(go, scene);
|
|
go.transform.position = def.anchor;
|
|
|
|
var labelGo = new GameObject("lv");
|
|
labelGo.transform.SetParent(go.transform, false);
|
|
labelGo.transform.localPosition = new Vector3(0f, 2.2f, 0f);
|
|
var tmp = labelGo.AddComponent<TextMeshPro>();
|
|
tmp.text = "Lv.???";
|
|
tmp.fontSize = 2.4f;
|
|
tmp.alignment = TextAlignmentOptions.Center;
|
|
labelGo.SetActive(false); // 던전 바닥에 레벨표가 뜨지 않게
|
|
|
|
var mgr = go.AddComponent<MobControlMgr>();
|
|
mgr.SpawnerID = w.spawnerId;
|
|
mgr.MobGenRange = cfg.dungeonSpawnerRange;
|
|
mgr.text_lv = tmp;
|
|
|
|
go.SetActive(true);
|
|
s_made.Add(mgr);
|
|
SpawnersMade++;
|
|
}
|
|
WLIslandBridge.Log(cfg, "던전 스포너 " + s_made.Count + "개 생성(앵커 " + def.anchor + ")");
|
|
}
|
|
|
|
static bool AllLoaded()
|
|
{
|
|
for (int i = 0; i < s_made.Count; i++)
|
|
if (s_made[i] == null || !s_made[i].Get_LoadComplete()) return false;
|
|
return s_made.Count > 0;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────
|
|
// ④ 중복 정리 — 데모 리그·여분 카메라
|
|
// ─────────────────────────────────────────────────────────────────
|
|
public static void FixDuplicates(WLIslandSettings cfg, Scene scene)
|
|
{
|
|
if (cfg.fixDungeonDuplicates == 0) return;
|
|
if (InGameInfo.Ins == null) return; // 단독 Play(룩 확인)에서는 데모 리그가 유일한 볼거리다
|
|
|
|
int rigs = 0, cams = 0, listeners = 0;
|
|
|
|
var walkers = Object.FindObjectsByType<WL.Look.Arena.ArenaWalker>(FindObjectsSortMode.None);
|
|
for (int i = 0; i < walkers.Length; i++)
|
|
if (walkers[i] != null) { walkers[i].gameObject.SetActive(false); rigs++; }
|
|
|
|
var allCams = Object.FindObjectsByType<Camera>(FindObjectsSortMode.None);
|
|
for (int i = 0; i < allCams.Length; i++)
|
|
{
|
|
var c = allCams[i];
|
|
if (c == null || c.gameObject.scene != scene) continue;
|
|
if (c == Camera.main) continue;
|
|
c.enabled = false; cams++;
|
|
var al = c.GetComponent<AudioListener>();
|
|
if (al != null && al.enabled) { al.enabled = false; listeners++; }
|
|
}
|
|
|
|
WLIslandBridge.Log(cfg, "던전 중복 정리 — 데모 리그 " + rigs + " · 카메라 " + cams + " · 리스너 " + listeners);
|
|
}
|
|
}
|
|
}
|