[WL-816d] 섬×던전 왕복 1차 배선 — WL/Island 새 파일 6개 + 던전 씬 2개 + 표 행 9개 (#816)
- 새 파일 Assets/WL/Island/** : 설정 SO · 브리지 · 섬 씬 런타임 준비 · 던전 입구 더미 · 던전 씬 런타임 준비 · UI - 던전 씬 = WL_ArenaProto 복사본 2개(원본은 룩 개발용으로 그대로) - 표: BattleMapConfig 5행(섬 900 · 던전 901~904) · WLStageTable 4행(인덱스 10~13 · 앵커 0,0,0) - 값 2개(816c 실측): autoEnterOnMapEnter 1→0 · 클리어/실패 버튼 「로비로」→「섬으로」 - 원본 수정 = NewGameUI.cs:36 1줄(StartMapId 훅)뿐 · FarmingIsland 0줄 · ProjectSettings 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
07661d6164
commit
c2972b5264
File diff suppressed because one or more lines are too long
|
|
@ -33,7 +33,7 @@ public class NewGameUI : MonoBehaviourSingletonTemplate<NewGameUI>
|
||||||
Set_TestUI();
|
Set_TestUI();
|
||||||
|
|
||||||
m_GuideQuestUI.Set();
|
m_GuideQuestUI.Set();
|
||||||
InGameInfo.Ins.Load_Map(-1);
|
InGameInfo.Ins.Load_Map(WL.Island.WLIslandSettings.StartMapId(-1)); // #816 1줄 훅 — 섬에서 시작(끄면 -1 그대로)
|
||||||
var cameraData = Camera.main.GetUniversalAdditionalCameraData();
|
var cameraData = Camera.main.GetUniversalAdditionalCameraData();
|
||||||
cameraData.cameraStack.Add(UICamera);
|
cameraData.cameraStack.Add(UICamera);
|
||||||
// OffLineReward // TODO 정인호 : 오프라인 보상 설정
|
// OffLineReward // TODO 정인호 : 오프라인 보상 설정
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ MonoBehaviour:
|
||||||
pollSeconds: 0.1
|
pollSeconds: 0.1
|
||||||
tickIntervalSeconds: 1
|
tickIntervalSeconds: 1
|
||||||
pauseTimerWhileWorldHeld: 1
|
pauseTimerWhileWorldHeld: 1
|
||||||
autoEnterOnMapEnter: 1
|
autoEnterOnMapEnter: 0
|
||||||
warpPlayerToAnchor: 1
|
warpPlayerToAnchor: 1
|
||||||
useBarrierHook: 1
|
useBarrierHook: 1
|
||||||
clearBarrierOnExit: 1
|
clearBarrierOnExit: 1
|
||||||
|
|
@ -41,4 +41,4 @@ MonoBehaviour:
|
||||||
suppressRunDirector: 1
|
suppressRunDirector: 1
|
||||||
disableRunGuide: 1
|
disableRunGuide: 1
|
||||||
resetRunDirectorOnEnter: 1
|
resetRunDirectorOnEnter: 1
|
||||||
toLobbyCallsReturnToLobby: 1
|
toLobbyCallsReturnToLobby: 0
|
||||||
|
|
|
||||||
|
|
@ -431,3 +431,105 @@ MonoBehaviour:
|
||||||
recommendPower: 0
|
recommendPower: 0
|
||||||
telegraphScale: 0.85
|
telegraphScale: 0.85
|
||||||
timeLimitOverrideSeconds: 0
|
timeLimitOverrideSeconds: 0
|
||||||
|
- id: 11
|
||||||
|
mapId: 901
|
||||||
|
anchor: {x: 0, y: 0, z: 0}
|
||||||
|
startRadius: 2
|
||||||
|
barrierRadius: 13
|
||||||
|
barrierShape: 0
|
||||||
|
waves:
|
||||||
|
- spawnerId: 813001
|
||||||
|
monsterIds:
|
||||||
|
- 101
|
||||||
|
- 102
|
||||||
|
- 103
|
||||||
|
- 104
|
||||||
|
count: 3
|
||||||
|
elite: 0
|
||||||
|
boss: 0
|
||||||
|
mode: 0
|
||||||
|
delaySec: 0
|
||||||
|
isBoss: 0
|
||||||
|
bossMonsterId: 0
|
||||||
|
clearRewardCards: 1
|
||||||
|
goldMultiplier: 1
|
||||||
|
recommendPower: 0
|
||||||
|
telegraphScale: 1.2
|
||||||
|
timeLimitOverrideSeconds: 0
|
||||||
|
- id: 12
|
||||||
|
mapId: 902
|
||||||
|
anchor: {x: 0, y: 0, z: 0}
|
||||||
|
startRadius: 2
|
||||||
|
barrierRadius: 13
|
||||||
|
barrierShape: 0
|
||||||
|
waves:
|
||||||
|
- spawnerId: 813003
|
||||||
|
monsterIds:
|
||||||
|
- 173
|
||||||
|
- 174
|
||||||
|
- 175
|
||||||
|
- 146
|
||||||
|
- 147
|
||||||
|
- 151
|
||||||
|
count: 5
|
||||||
|
elite: 0
|
||||||
|
boss: 0
|
||||||
|
mode: 0
|
||||||
|
delaySec: 0
|
||||||
|
isBoss: 0
|
||||||
|
bossMonsterId: 0
|
||||||
|
clearRewardCards: 1
|
||||||
|
goldMultiplier: 1.5
|
||||||
|
recommendPower: 0
|
||||||
|
telegraphScale: 1.1
|
||||||
|
timeLimitOverrideSeconds: 0
|
||||||
|
- id: 13
|
||||||
|
mapId: 903
|
||||||
|
anchor: {x: 0, y: 0, z: 0}
|
||||||
|
startRadius: 2
|
||||||
|
barrierRadius: 13
|
||||||
|
barrierShape: 0
|
||||||
|
waves:
|
||||||
|
- spawnerId: 813002
|
||||||
|
monsterIds:
|
||||||
|
- 168
|
||||||
|
- 169
|
||||||
|
- 170
|
||||||
|
- 171
|
||||||
|
- 172
|
||||||
|
count: 8
|
||||||
|
elite: 0
|
||||||
|
boss: 0
|
||||||
|
mode: 0
|
||||||
|
delaySec: 0
|
||||||
|
isBoss: 0
|
||||||
|
bossMonsterId: 0
|
||||||
|
clearRewardCards: 1
|
||||||
|
goldMultiplier: 2
|
||||||
|
recommendPower: 0
|
||||||
|
telegraphScale: 1
|
||||||
|
timeLimitOverrideSeconds: 0
|
||||||
|
- id: 14
|
||||||
|
mapId: 904
|
||||||
|
anchor: {x: 0, y: 0, z: 0}
|
||||||
|
startRadius: 2
|
||||||
|
barrierRadius: 13
|
||||||
|
barrierShape: 0
|
||||||
|
waves:
|
||||||
|
- spawnerId: 813004
|
||||||
|
monsterIds:
|
||||||
|
- 118
|
||||||
|
- 119
|
||||||
|
- 120
|
||||||
|
count: 10
|
||||||
|
elite: 0
|
||||||
|
boss: 0
|
||||||
|
mode: 0
|
||||||
|
delaySec: 0
|
||||||
|
isBoss: 0
|
||||||
|
bossMonsterId: 0
|
||||||
|
clearRewardCards: 1
|
||||||
|
goldMultiplier: 3
|
||||||
|
recommendPower: 0
|
||||||
|
telegraphScale: 1
|
||||||
|
timeLimitOverrideSeconds: 0
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d71b67e86ed35994183ad17a30807fd9
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 736faf4adc74e2343aefce8a1281a933
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ac3fb08ed9e62af4295fd6ddc45d9ef8
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 8d816d01a1b2c3d4e5f60708090a0b01, type: 3}
|
||||||
|
m_Name: WLIslandSettings
|
||||||
|
m_EditorClassIdentifier: Assembly-CSharp::WL.Island.WLIslandSettings
|
||||||
|
enabled_: 1
|
||||||
|
verboseLog: 1
|
||||||
|
startOnIsland: 1
|
||||||
|
islandMapId: 900
|
||||||
|
islandSceneName: Level01
|
||||||
|
hideWlPcOnIsland: 1
|
||||||
|
fixIslandDuplicates: 1
|
||||||
|
islandSaveKey:
|
||||||
|
swapIslandPlayer: 1
|
||||||
|
pcPrefabPath: Assets/Res_Addr/PC/LH_M05.prefab
|
||||||
|
islandPcScale: 0.7
|
||||||
|
mirrorFiAnimator: 1
|
||||||
|
fiPlayerModelName: Stickman
|
||||||
|
interactRadius: 3.5
|
||||||
|
promptFormat: "{0} 들어가기"
|
||||||
|
enterButtonText: "들어가기"
|
||||||
|
enterKey: 101
|
||||||
|
gateHeight: 3.2
|
||||||
|
gateWidth: 2.4
|
||||||
|
rewardEnabled: 1
|
||||||
|
goldPerKill: 12
|
||||||
|
goldClearBonus: 60
|
||||||
|
goldBossBonus: 120
|
||||||
|
applyGoldMultiplier: 1
|
||||||
|
goldOnFail: 0
|
||||||
|
pendingPrefsKey: WL_Island_PendingGold
|
||||||
|
bakeDungeonNavMesh: 1
|
||||||
|
spawnDungeonSpawners: 1
|
||||||
|
dungeonSpawnerRange: 6
|
||||||
|
fixDungeonDuplicates: 1
|
||||||
|
dungeonEnterDelay: 1.5
|
||||||
|
dungeons:
|
||||||
|
- enabled_: 1
|
||||||
|
id: 1
|
||||||
|
displayName: "던전 1"
|
||||||
|
sceneName: WL_Dungeon01
|
||||||
|
mapId: 901
|
||||||
|
stageIndex: 10
|
||||||
|
gatePosition: {x: -3, y: 0, z: 3}
|
||||||
|
gateYaw: 180
|
||||||
|
gateScale: 1
|
||||||
|
gateColor: {r: 0.45, g: 0.32, b: 0.62, a: 1}
|
||||||
|
gatePrefabPath:
|
||||||
|
- enabled_: 1
|
||||||
|
id: 2
|
||||||
|
displayName: "던전 2"
|
||||||
|
sceneName: WL_Dungeon02
|
||||||
|
mapId: 902
|
||||||
|
stageIndex: 11
|
||||||
|
gatePosition: {x: 10.5, y: 0, z: -3}
|
||||||
|
gateYaw: 0
|
||||||
|
gateScale: 1
|
||||||
|
gateColor: {r: 0.28, g: 0.5, b: 0.66, a: 1}
|
||||||
|
gatePrefabPath:
|
||||||
|
- enabled_: 1
|
||||||
|
id: 3
|
||||||
|
displayName: "던전 3"
|
||||||
|
sceneName: WL_Dungeon01
|
||||||
|
mapId: 903
|
||||||
|
stageIndex: 12
|
||||||
|
gatePosition: {x: -3, y: 0, z: 10.5}
|
||||||
|
gateYaw: 180
|
||||||
|
gateScale: 1
|
||||||
|
gateColor: {r: 0.62, g: 0.42, b: 0.24, a: 1}
|
||||||
|
gatePrefabPath:
|
||||||
|
- enabled_: 1
|
||||||
|
id: 4
|
||||||
|
displayName: "던전 4"
|
||||||
|
sceneName: WL_Dungeon02
|
||||||
|
mapId: 904
|
||||||
|
stageIndex: 13
|
||||||
|
gatePosition: {x: 10.5, y: 0, z: 10.5}
|
||||||
|
gateYaw: 0
|
||||||
|
gateScale: 1
|
||||||
|
gateColor: {r: 0.55, g: 0.24, b: 0.3, a: 1}
|
||||||
|
gatePrefabPath:
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8d816d01a1b2c3d4e5f60708090a0c01
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c1c85be0c23fb5a4ba3db7b9b8308fd5
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 172583605b6b4e07b3c1ce90024712a4
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: aaee46d680464b5980cd354d100a0544
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8d816d01a1b2c3d4e5f60708090a0b04
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// ② 스포너 — 스테이지 표가 요구하는 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 existing = Object.FindFirstObjectByType<NavMeshSurface>();
|
||||||
|
if (existing != null) { existing.BuildNavMesh(); NavBakes++; return; }
|
||||||
|
|
||||||
|
var go = new GameObject("~WL_DungeonNavMesh");
|
||||||
|
SceneManager.MoveGameObjectToScene(go, scene);
|
||||||
|
var surf = go.AddComponent<NavMeshSurface>();
|
||||||
|
surf.collectObjects = CollectObjects.All;
|
||||||
|
surf.useGeometry = NavMeshCollectGeometry.RenderMeshes; // 아레나 바닥에 콜라이더가 없을 수 있다
|
||||||
|
surf.BuildNavMesh();
|
||||||
|
NavBakes++;
|
||||||
|
WLIslandBridge.Log(cfg, "던전 NavMesh 런타임 베이크 완료");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// ② 스포너 생성 (테이블 수정 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8d816d01a1b2c3d4e5f60708090a0b06
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,298 @@
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// WLIslandBridge.cs — 섬 ↔ 던전 왕복의 배선 (원본 코드 0줄 · 새 Manager/Singleton 0)
|
||||||
|
//
|
||||||
|
// PD 지시 #816 · 발주서 WL-816d §1-④ / §1-B · 816c 설계서 §B-1 표(1~6단계)
|
||||||
|
//
|
||||||
|
// ■ 한 바퀴 (설계서 §B-1 을 그대로 구현)
|
||||||
|
// ① 섬 진입 : Load_Map(islandMapId) → 섬 씬(Level01)이 Additive 로 붙는다
|
||||||
|
// ② 던전 입구 : WLDungeonGate(더미)에 다가가면 「들어가기」
|
||||||
|
// ③ 던전 입장 : Load_Map(dungeon.mapId) → 던전 씬 → StageDirector.Enter(stageIndex)
|
||||||
|
// ④ 보상 확정 : StageEvents.StageCleared 구독 → StageDef.goldMultiplier 로 코인 계산 → **보류**
|
||||||
|
// ⑤ 섬 복귀 : StageExited(toLobby) → Load_Map(islandMapId)
|
||||||
|
// ⑥ 지급 : 섬 씬 로드 후 IslandManager.Instance.Coin += 보류분
|
||||||
|
// (816c 실측: IslandManager.Awake 에서 로드가 끝나 있어 Start 전에 더해도 안전)
|
||||||
|
//
|
||||||
|
// ■ 중복 정리(설계서 §B-2) — 전부 **런타임**에 한다. FI 씬·코드는 0줄.
|
||||||
|
// 섬 : WL 전투 PC(Actor) 숨김 · FI 카메라/AudioListener 중복 해제
|
||||||
|
// 던전: 아레나 데모 리그(ArenaWalkerRig)·여분 카메라 해제
|
||||||
|
//
|
||||||
|
// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다.
|
||||||
|
// 🔴 어셈블리 주의: Assets/WL/Island/ 에 .asmdef 를 만들지 말 것.
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
using WL.Combat.Stage;
|
||||||
|
|
||||||
|
namespace WL.Island
|
||||||
|
{
|
||||||
|
/// <summary>섬 ↔ 던전 왕복. 정적 진입 + 숨김 러너 1개(코루틴·입력용).</summary>
|
||||||
|
public static class WLIslandBridge
|
||||||
|
{
|
||||||
|
// ── 진단(프로브·보고가 읽는다)
|
||||||
|
public static int IslandLoads, DungeonLoads, ClearGrants, FailGrants, CoinGranted, GatesSpawned;
|
||||||
|
public static string LastLog = "";
|
||||||
|
|
||||||
|
static bool s_installed;
|
||||||
|
static int s_pendingGold;
|
||||||
|
static WLIslandRunner s_runner;
|
||||||
|
static readonly List<WLDungeonGate> s_gates = new List<WLDungeonGate>(8);
|
||||||
|
|
||||||
|
/// <summary>보류 중인 보상(섬에 도착하면 더해진다).</summary>
|
||||||
|
public static int PendingGold { get { return s_pendingGold; } }
|
||||||
|
|
||||||
|
/// <summary>현재 씬에 살아 있는 던전 입구들.</summary>
|
||||||
|
public static List<WLDungeonGate> Gates { get { return s_gates; } }
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// 설치 — 씬 하나 없이 자동으로 걸린다(원본 훅 0줄)
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
||||||
|
public static void Install()
|
||||||
|
{
|
||||||
|
if (s_installed) return;
|
||||||
|
var cfg = WLIslandSettings.Instance;
|
||||||
|
if (cfg == null || cfg.enabled_ == 0) return;
|
||||||
|
s_installed = true;
|
||||||
|
|
||||||
|
s_pendingGold = PlayerPrefs.GetInt(cfg.pendingPrefsKey, 0);
|
||||||
|
|
||||||
|
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||||
|
StageEvents.StageCleared.Add(OnStageCleared);
|
||||||
|
StageEvents.StageFailed.Add(OnStageFailed);
|
||||||
|
StageEvents.StageExited.Add(OnStageExited);
|
||||||
|
|
||||||
|
EnsureRunner();
|
||||||
|
|
||||||
|
// 이미 열려 있는 씬(에디터에서 씬을 직접 열고 Play 한 경우)도 한 번 훑는다.
|
||||||
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||||
|
{
|
||||||
|
var sc = SceneManager.GetSceneAt(i);
|
||||||
|
if (sc.isLoaded) OnSceneLoaded(sc, LoadSceneMode.Additive);
|
||||||
|
}
|
||||||
|
Log(cfg, "설치 완료 — 보류 보상 " + s_pendingGold);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EnsureRunner()
|
||||||
|
{
|
||||||
|
if (s_runner != null) return;
|
||||||
|
var go = new GameObject("~WLIslandRunner");
|
||||||
|
go.hideFlags = HideFlags.HideAndDontSave;
|
||||||
|
Object.DontDestroyOnLoad(go);
|
||||||
|
s_runner = go.AddComponent<WLIslandRunner>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// 씬 로드
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||||
|
{
|
||||||
|
var cfg = WLIslandSettings.Instance;
|
||||||
|
if (cfg == null || cfg.enabled_ == 0) return;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(cfg.islandSceneName) && scene.name == cfg.islandSceneName)
|
||||||
|
{
|
||||||
|
IslandLoads++;
|
||||||
|
EnsureRunner();
|
||||||
|
// 🔴 LoadMapMgr 는 **PCInfo.Make_Actors 보다 먼저** 있어야 한다(같은 프레임).
|
||||||
|
WLIslandSceneSetup.EnsureMapNodes(cfg, scene);
|
||||||
|
s_runner.StartCoroutine(Co_IslandReady(cfg, scene));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dg = cfg.FindBySceneName(scene.name);
|
||||||
|
if (dg != null)
|
||||||
|
{
|
||||||
|
DungeonLoads++;
|
||||||
|
EnsureRunner();
|
||||||
|
s_runner.StartCoroutine(WLDungeonSceneSetup.Co_Prepare(cfg, scene, dg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static IEnumerator Co_IslandReady(WLIslandSettings cfg, Scene scene)
|
||||||
|
{
|
||||||
|
// 같은 프레임에는 FI 매니저들의 Awake 가 아직 안 끝났을 수 있다 — 한 프레임 기다린다.
|
||||||
|
yield return null;
|
||||||
|
|
||||||
|
WLIslandSceneSetup.FixDuplicates(cfg, scene);
|
||||||
|
WLIslandSceneSetup.SwapPlayerVisual(cfg, scene);
|
||||||
|
SpawnGates(cfg, scene);
|
||||||
|
|
||||||
|
// 보류 보상 지급(⑥) — IslandManager 가 살아날 때까지 잠깐 기다린다.
|
||||||
|
float t = 0f;
|
||||||
|
while (t < 5f && !WLIslandCoin.Ready) { t += Time.unscaledDeltaTime; yield return null; }
|
||||||
|
GrantPending(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// 던전 입구 배치
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
static void SpawnGates(WLIslandSettings cfg, Scene scene)
|
||||||
|
{
|
||||||
|
s_gates.Clear();
|
||||||
|
if (cfg.dungeons == null) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < cfg.dungeons.Length; i++)
|
||||||
|
{
|
||||||
|
var d = cfg.dungeons[i];
|
||||||
|
if (d == null || d.enabled_ == 0) continue;
|
||||||
|
var gate = WLDungeonGate.Create(cfg, d, scene);
|
||||||
|
if (gate != null) { s_gates.Add(gate); GatesSpawned++; }
|
||||||
|
}
|
||||||
|
Log(cfg, "던전 입구 " + s_gates.Count + "개 배치");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// 던전 입장 / 복귀
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
public static void EnterDungeon(WLDungeonDef d)
|
||||||
|
{
|
||||||
|
var cfg = WLIslandSettings.Instance;
|
||||||
|
if (cfg == null || d == null) return;
|
||||||
|
|
||||||
|
EnsureRunner();
|
||||||
|
WLDungeonSceneSetup.Pending = d;
|
||||||
|
|
||||||
|
if (InGameInfo.Ins != null)
|
||||||
|
{
|
||||||
|
Log(cfg, "던전 입장 — " + d.displayName + " (map " + d.mapId + " · stage " + d.stageIndex + ")");
|
||||||
|
InGameInfo.Ins.Load_Map(d.mapId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 에디터에서 섬 씬만 열고 Play 한 경우(로그인 없는 검증 경로).
|
||||||
|
Log(cfg, "던전 입장(단독) — " + d.sceneName);
|
||||||
|
SceneManager.LoadScene(d.sceneName, LoadSceneMode.Single);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>던전에서 섬으로 돌아간다(클리어·실패·중단 공통).</summary>
|
||||||
|
public static void ReturnToIsland(string reason)
|
||||||
|
{
|
||||||
|
var cfg = WLIslandSettings.Instance;
|
||||||
|
if (cfg == null || cfg.enabled_ == 0) return;
|
||||||
|
|
||||||
|
if (InGameInfo.Ins != null)
|
||||||
|
{
|
||||||
|
Log(cfg, "섬으로 복귀(" + reason + ") — 보류 보상 " + s_pendingGold);
|
||||||
|
InGameInfo.Ins.Load_Map(cfg.islandMapId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log(cfg, "섬으로 복귀(단독 · " + reason + ")");
|
||||||
|
SceneManager.LoadScene(cfg.islandSceneName, LoadSceneMode.Single);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// 보상 (④ 확정 · ⑥ 지급)
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
static void OnStageCleared(in StageClearResult r)
|
||||||
|
{
|
||||||
|
var cfg = WLIslandSettings.Instance;
|
||||||
|
if (cfg == null || cfg.enabled_ == 0 || cfg.rewardEnabled == 0) return;
|
||||||
|
if (WLDungeonSceneSetup.Current == null) return; // 던전이 아닌 스테이지(#815 데모)는 건드리지 않는다
|
||||||
|
|
||||||
|
int gold = ComputeGold(cfg, r);
|
||||||
|
SetPending(cfg, gold);
|
||||||
|
ClearGrants++;
|
||||||
|
Log(cfg, "클리어 보상 확정 — " + gold + " 코인 (처치 " + r.killed + "/" + r.total +
|
||||||
|
" · 배수 " + MultiplierOf(r.index).ToString("F2") + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnStageFailed(in StageFailedEvent e)
|
||||||
|
{
|
||||||
|
var cfg = WLIslandSettings.Instance;
|
||||||
|
if (cfg == null || cfg.enabled_ == 0) return;
|
||||||
|
if (WLDungeonSceneSetup.Current == null) return;
|
||||||
|
|
||||||
|
SetPending(cfg, cfg.goldOnFail);
|
||||||
|
FailGrants++;
|
||||||
|
Log(cfg, "실패(" + e.reason + ") — 보상 " + cfg.goldOnFail);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnStageExited(in StageExitedEvent e)
|
||||||
|
{
|
||||||
|
var cfg = WLIslandSettings.Instance;
|
||||||
|
if (cfg == null || cfg.enabled_ == 0) return;
|
||||||
|
if (!e.toLobby) return;
|
||||||
|
if (WLDungeonSceneSetup.Current == null) return;
|
||||||
|
|
||||||
|
WLDungeonSceneSetup.Current = null;
|
||||||
|
ReturnToIsland(e.reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>표의 goldMultiplier(WLStageTable.cs:103) — 소비처가 없던 필드의 첫 소비처.</summary>
|
||||||
|
public static float MultiplierOf(int stageIndex)
|
||||||
|
{
|
||||||
|
var t = WLStageTable.Instance;
|
||||||
|
if (t == null) return 1f;
|
||||||
|
var def = t.Get(stageIndex);
|
||||||
|
if (def == null || def.goldMultiplier <= 0f) return 1f;
|
||||||
|
return def.goldMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ComputeGold(WLIslandSettings cfg, in StageClearResult r)
|
||||||
|
{
|
||||||
|
if (!r.win) return cfg.goldOnFail;
|
||||||
|
float g = cfg.goldClearBonus + cfg.goldPerKill * r.killed;
|
||||||
|
if (r.isBoss) g += cfg.goldBossBonus;
|
||||||
|
if (cfg.applyGoldMultiplier != 0) g *= MultiplierOf(r.index);
|
||||||
|
int v = Mathf.RoundToInt(g);
|
||||||
|
return v < 0 ? 0 : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetPending(WLIslandSettings cfg, int gold)
|
||||||
|
{
|
||||||
|
s_pendingGold = gold < 0 ? 0 : gold;
|
||||||
|
PlayerPrefs.SetInt(cfg.pendingPrefsKey, s_pendingGold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>보류분을 섬 코인에 더한다. 성공하면 보류를 비운다.</summary>
|
||||||
|
public static bool GrantPending(WLIslandSettings cfg)
|
||||||
|
{
|
||||||
|
if (cfg == null) cfg = WLIslandSettings.Instance;
|
||||||
|
if (cfg == null || s_pendingGold <= 0) return false;
|
||||||
|
|
||||||
|
int before;
|
||||||
|
if (!WLIslandCoin.TryGet(out before)) { Log(cfg, "보상 지급 보류 — IslandManager 없음(" + s_pendingGold + ")"); return false; }
|
||||||
|
|
||||||
|
if (!WLIslandCoin.Add(s_pendingGold)) return false;
|
||||||
|
|
||||||
|
CoinGranted += s_pendingGold;
|
||||||
|
int after; WLIslandCoin.TryGet(out after);
|
||||||
|
Log(cfg, "🔴 보상 지급 — 코인 " + before + " → " + after + " (+" + s_pendingGold + ")");
|
||||||
|
s_pendingGold = 0;
|
||||||
|
PlayerPrefs.SetInt(cfg.pendingPrefsKey, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>검증용: 보상분을 직접 주입한다(전투 없이 ④⑤⑥ 경로만 태운다).</summary>
|
||||||
|
public static void DebugSetPending(int gold)
|
||||||
|
{
|
||||||
|
var cfg = WLIslandSettings.Instance;
|
||||||
|
if (cfg == null) return;
|
||||||
|
SetPending(cfg, gold);
|
||||||
|
Log(cfg, "검증 주입 — 보류 보상 " + gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Log(WLIslandSettings cfg, string msg)
|
||||||
|
{
|
||||||
|
LastLog = msg;
|
||||||
|
if (cfg != null && cfg.verboseLog != 0) Debug.Log("[WL-816d] " + msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>코루틴·입력을 도맡는 숨김 러너(새 Manager 가 아니다 · 씬에 남지 않는다).</summary>
|
||||||
|
public sealed class WLIslandRunner : MonoBehaviour
|
||||||
|
{
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
var cfg = WLIslandSettings.Instance;
|
||||||
|
if (cfg == null || cfg.enabled_ == 0) return;
|
||||||
|
WLDungeonGate.TickAll(cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8d816d01a1b2c3d4e5f60708090a0b02
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,316 @@
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// WLIslandSceneSetup.cs — 섬 씬(FarmingIsland Level01)에 **런타임으로만** 얹는 것들
|
||||||
|
//
|
||||||
|
// 발주서 WL-816d §1-① 「원본 씬은 수정 0 — 얹어야 하면 WL 새 파일이 런타임에 얹어라」
|
||||||
|
// 816c 설계서 §B-2 표(중복 정리) · §1-B-3(섬 캐릭터 = 우리 전투 캐릭터)
|
||||||
|
//
|
||||||
|
// ■ 하는 일 3가지 (FI 씬·FI 코드 0줄)
|
||||||
|
// ① 맵 노드 : `PCInfo.Make_Actors` 가 부르는 `LoadMapMgr.Ins` 를 섬 씬에도 만들어 준다.
|
||||||
|
// 🔴 MapData 는 **일부러 붙이지 않는다** — MapData.Start 가 RenderSettings(앰비언트·안개)를
|
||||||
|
// 덮어쓰는데 섬 배경/조명은 816a(gameplay) 소유라 건드리면 안 된다.
|
||||||
|
// ② 중복 정리 : WL 전투 PC(Actor) 숨김 · FI 카메라/AudioListener 중복 해제.
|
||||||
|
// ③ 캐릭터 교체 : FI 농부 메시를 끄고 그 자리에 치비(LH_M05)를 **자식으로** 붙인다.
|
||||||
|
// 조작·상호작용·NavMesh 는 FI `PlayerController` 그대로 — 모델만 바뀐다.
|
||||||
|
//
|
||||||
|
// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다.
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
using CryingSnow.FarmingIsland;
|
||||||
|
|
||||||
|
namespace WL.Island
|
||||||
|
{
|
||||||
|
/// <summary>섬 씬 런타임 준비. 정적 · 상태 없음.</summary>
|
||||||
|
public static class WLIslandSceneSetup
|
||||||
|
{
|
||||||
|
public const string MapNodesName = "~WL_IslandMapNodes";
|
||||||
|
public const string PcVisualName = "~WL_IslandPcVisual";
|
||||||
|
|
||||||
|
public static int MapNodesMade, PcHidden, CamerasFixed, ListenersFixed, VisualSwaps;
|
||||||
|
public static string LastSwapLog = "";
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// ① 맵 노드 — LoadMapMgr 자리 채우기
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
/// <summary>
|
||||||
|
/// 섬 씬에도 `LoadMapMgr` 를 만들어 둔다. 없으면 `PCInfo.Make_Actors:24` 가 죽은 참조를 건드린다.
|
||||||
|
/// 🔴 `PCInfo.Make_Actors` 보다 **먼저** 불려야 한다(= sceneLoaded 즉시 · 같은 프레임).
|
||||||
|
/// </summary>
|
||||||
|
public static void EnsureMapNodes(WLIslandSettings cfg, Scene scene)
|
||||||
|
{
|
||||||
|
if (InGameInfo.Ins == null) return; // 섬 씬만 단독 Play = 원본 흐름이 없다 → 필요 없다
|
||||||
|
if (LoadMapMgr.isIns && LoadMapMgr.Ins != null) return;
|
||||||
|
|
||||||
|
var go = new GameObject(MapNodesName);
|
||||||
|
SceneManager.MoveGameObjectToScene(go, scene);
|
||||||
|
// 🔴 원본 LoadMapMgr 를 그대로 붙이면 Awake 가 빈 주소로 Addressables 를 때려 에러가 난다.
|
||||||
|
// 파생 클래스가 Awake 만 갈아끼운다 = 원본 0줄 · 주소 로드 0 · Ins 만 채운다.
|
||||||
|
var mgr = go.AddComponent<WLIslandMapNodes>();
|
||||||
|
mgr.LoadSceneName = cfg.islandSceneName;
|
||||||
|
mgr.LoadMapPrefab = "";
|
||||||
|
MapNodesMade++;
|
||||||
|
WLIslandBridge.Log(cfg, "섬 맵 노드 생성(LoadMapMgr) — " + scene.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>던전 씬에도 같은 이유로 `LoadMapMgr` 자리를 채운다(맵 프리팹 없음 = 주소 로드 0).</summary>
|
||||||
|
public static void EnsureDungeonMapNodes(WLIslandSettings cfg, Scene scene, WLDungeonDef d)
|
||||||
|
{
|
||||||
|
if (InGameInfo.Ins == null) return;
|
||||||
|
if (LoadMapMgr.isIns && LoadMapMgr.Ins != null && LoadMapMgr.Ins.gameObject.scene == scene) return;
|
||||||
|
|
||||||
|
var go = new GameObject(MapNodesName);
|
||||||
|
SceneManager.MoveGameObjectToScene(go, scene);
|
||||||
|
var mgr = go.AddComponent<WLIslandMapNodes>();
|
||||||
|
mgr.LoadSceneName = d.sceneName;
|
||||||
|
mgr.LoadMapPrefab = "";
|
||||||
|
MapNodesMade++;
|
||||||
|
WLIslandBridge.Log(cfg, "던전 맵 노드 생성(LoadMapMgr) — " + scene.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// ② 중복 정리
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
public static void FixDuplicates(WLIslandSettings cfg, Scene scene)
|
||||||
|
{
|
||||||
|
if (cfg.fixIslandDuplicates == 0) return;
|
||||||
|
|
||||||
|
// WL 전투 PC 는 섬에서 숨긴다(플레이어 2명 방지) — 던전에 가면 다시 만들어진다.
|
||||||
|
if (cfg.hideWlPcOnIsland != 0 && MyValue.bMyPC && MyValue.MyPC != null)
|
||||||
|
{
|
||||||
|
MyValue.MyPC.gameObject.SetActive(false);
|
||||||
|
PcHidden++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FI 카메라·AudioListener 중복 — Additive 로 붙었을 때만 정리한다.
|
||||||
|
// · 섬 씬만 단독으로 열려 있으면(검증 Play) FI 카메라가 유일한 카메라라 끄면 안 된다.
|
||||||
|
if (SceneManager.sceneCount <= 1) return;
|
||||||
|
|
||||||
|
var listeners = Object.FindObjectsByType<AudioListener>(FindObjectsSortMode.None);
|
||||||
|
int alive = 0;
|
||||||
|
for (int i = 0; i < listeners.Length; i++)
|
||||||
|
{
|
||||||
|
if (listeners[i] == null || !listeners[i].enabled) continue;
|
||||||
|
alive++;
|
||||||
|
if (alive > 1 && listeners[i].gameObject.scene == scene) { listeners[i].enabled = false; ListenersFixed++; }
|
||||||
|
}
|
||||||
|
WLIslandBridge.Log(cfg, "중복 정리 — PC 숨김 " + PcHidden + " · AudioListener 해제 " + ListenersFixed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// ③ 캐릭터 교체 — FI 조작 그대로, 모델만 우리 치비로
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
public static void SwapPlayerVisual(WLIslandSettings cfg, Scene scene)
|
||||||
|
{
|
||||||
|
if (cfg.swapIslandPlayer == 0) return;
|
||||||
|
|
||||||
|
var fiPlayer = Object.FindFirstObjectByType<PlayerController>();
|
||||||
|
if (fiPlayer == null) { LastSwapLog = "FI PlayerController 없음 — 교체 건너뜀"; return; }
|
||||||
|
if (fiPlayer.transform.Find(PcVisualName) != null) return; // 이미 했다
|
||||||
|
|
||||||
|
var prefab = WLIslandAssets.LoadPrefab(cfg.pcPrefabPath);
|
||||||
|
if (prefab == null) { LastSwapLog = "치비 프리팹을 못 찾았다 — " + cfg.pcPrefabPath; return; }
|
||||||
|
|
||||||
|
// 🔴 비활성 부모 아래에서 인스턴스 → PCActor 등 전투 컴포넌트의 Awake 가 돌지 않는다.
|
||||||
|
var holder = new GameObject("~WLIslandSpawnHolder");
|
||||||
|
holder.SetActive(false);
|
||||||
|
var visual = Object.Instantiate(prefab, holder.transform);
|
||||||
|
visual.name = PcVisualName;
|
||||||
|
|
||||||
|
StripCombatComponents(visual);
|
||||||
|
|
||||||
|
visual.transform.SetParent(fiPlayer.transform, false);
|
||||||
|
visual.transform.localPosition = Vector3.zero;
|
||||||
|
visual.transform.localRotation = Quaternion.identity;
|
||||||
|
visual.transform.localScale = new Vector3(cfg.islandPcScale, cfg.islandPcScale, cfg.islandPcScale);
|
||||||
|
|
||||||
|
// FI 농부 메시 숨김(FI 오브젝트는 파괴하지 않는다 — 끄기만 한다 = 되돌리기 가능)
|
||||||
|
int hidden = HideFiModel(cfg, fiPlayer.gameObject, visual);
|
||||||
|
|
||||||
|
// 애니메이션은 FI 컨트롤러를 그대로 흘린다(둘 다 Humanoid → 리타깃)
|
||||||
|
var rig = visual.AddComponent<WLIslandPcRig>();
|
||||||
|
rig.Bind(fiPlayer.GetComponent<Animator>(), cfg);
|
||||||
|
|
||||||
|
Object.Destroy(holder);
|
||||||
|
visual.SetActive(true);
|
||||||
|
VisualSwaps++;
|
||||||
|
|
||||||
|
float h = MeasureHeight(visual);
|
||||||
|
LastSwapLog = "치비 교체 완료 — 스케일 " + visual.transform.localScale.x.ToString("F4") +
|
||||||
|
" · 월드 높이 " + h.ToString("F4") + " m · FI 메시 숨김 " + hidden + "개";
|
||||||
|
WLIslandBridge.Log(cfg, LastSwapLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>전투/이동 컴포넌트를 떼어 낸다 — 남기는 것은 Animator · 렌더러 · WL 룩/크기 보정뿐.</summary>
|
||||||
|
static void StripCombatComponents(GameObject root)
|
||||||
|
{
|
||||||
|
var mbs = root.GetComponentsInChildren<MonoBehaviour>(true);
|
||||||
|
for (int i = 0; i < mbs.Length; i++)
|
||||||
|
{
|
||||||
|
var mb = mbs[i];
|
||||||
|
if (mb == null) continue;
|
||||||
|
if (mb is WL.Character.WLPcScaleCompensator) continue; // 높이 1.1912 m 유지
|
||||||
|
if (mb is WL.Look.Character.WLCharacterLook) continue; // 팔레트 UV 도트화(814x) 유지
|
||||||
|
Object.DestroyImmediate(mb);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cc = root.GetComponent<CharacterController>(); if (cc != null) Object.DestroyImmediate(cc);
|
||||||
|
var agents = root.GetComponentsInChildren<UnityEngine.AI.NavMeshAgent>(true);
|
||||||
|
for (int i = 0; i < agents.Length; i++) if (agents[i] != null) Object.DestroyImmediate(agents[i]);
|
||||||
|
var obs = root.GetComponentsInChildren<UnityEngine.AI.NavMeshObstacle>(true);
|
||||||
|
for (int i = 0; i < obs.Length; i++) if (obs[i] != null) Object.DestroyImmediate(obs[i]);
|
||||||
|
var rbs = root.GetComponentsInChildren<Rigidbody>(true);
|
||||||
|
for (int i = 0; i < rbs.Length; i++) if (rbs[i] != null) Object.DestroyImmediate(rbs[i]);
|
||||||
|
var cols = root.GetComponentsInChildren<Collider>(true);
|
||||||
|
for (int i = 0; i < cols.Length; i++) if (cols[i] != null) Object.DestroyImmediate(cols[i]);
|
||||||
|
var srcs = root.GetComponentsInChildren<AudioSource>(true);
|
||||||
|
for (int i = 0; i < srcs.Length; i++) if (srcs[i] != null) Object.DestroyImmediate(srcs[i]);
|
||||||
|
var listeners = root.GetComponentsInChildren<AudioListener>(true);
|
||||||
|
for (int i = 0; i < listeners.Length; i++) if (listeners[i] != null) Object.DestroyImmediate(listeners[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int HideFiModel(WLIslandSettings cfg, GameObject fiPlayer, GameObject exclude)
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
if (!string.IsNullOrEmpty(cfg.fiPlayerModelName))
|
||||||
|
{
|
||||||
|
var t = FindChild(fiPlayer.transform, cfg.fiPlayerModelName, exclude.transform);
|
||||||
|
if (t != null) { t.gameObject.SetActive(false); return 1; }
|
||||||
|
}
|
||||||
|
var smrs = fiPlayer.GetComponentsInChildren<SkinnedMeshRenderer>(true);
|
||||||
|
for (int i = 0; i < smrs.Length; i++)
|
||||||
|
{
|
||||||
|
if (smrs[i] == null) continue;
|
||||||
|
if (smrs[i].transform.IsChildOf(exclude.transform)) continue;
|
||||||
|
smrs[i].enabled = false; n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Transform FindChild(Transform root, string name, Transform skip)
|
||||||
|
{
|
||||||
|
var all = root.GetComponentsInChildren<Transform>(true);
|
||||||
|
for (int i = 0; i < all.Length; i++)
|
||||||
|
{
|
||||||
|
if (all[i] == root || all[i] == skip) continue;
|
||||||
|
if (skip != null && all[i].IsChildOf(skip)) continue;
|
||||||
|
if (string.Equals(all[i].name, name, System.StringComparison.OrdinalIgnoreCase)) return all[i];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>월드 AABB 높이(실측 보고용).</summary>
|
||||||
|
public static float MeasureHeight(GameObject root)
|
||||||
|
{
|
||||||
|
var rs = root.GetComponentsInChildren<Renderer>(true);
|
||||||
|
bool any = false; Bounds b = new Bounds();
|
||||||
|
for (int i = 0; i < rs.Length; i++)
|
||||||
|
{
|
||||||
|
if (rs[i] == null) continue;
|
||||||
|
if (!any) { b = rs[i].bounds; any = true; }
|
||||||
|
else b.Encapsulate(rs[i].bounds);
|
||||||
|
}
|
||||||
|
return any ? b.size.y : 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 섬 씬 전용 `LoadMapMgr`. 원본은 Awake 에서 맵 프리팹을 Addressables 로 받아 오는데,
|
||||||
|
/// 섬은 씬 자체가 맵이라 받을 것이 없다 → Awake 만 갈아끼워 **주소 로드 0**·싱글턴만 채운다.
|
||||||
|
/// 🔴 원본 `LoadMapMgr.cs` 는 한 줄도 고치지 않는다(파생으로 해결).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class WLIslandMapNodes : LoadMapMgr
|
||||||
|
{
|
||||||
|
protected override void Awake() { Ins = this; isIns = true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>FI 애니메이터의 파라미터를 치비 애니메이터로 그대로 흘린다(Humanoid 리타깃).</summary>
|
||||||
|
public sealed class WLIslandPcRig : MonoBehaviour
|
||||||
|
{
|
||||||
|
Animator _src, _dst;
|
||||||
|
AnimatorControllerParameter[] _params;
|
||||||
|
|
||||||
|
public void Bind(Animator src, WLIslandSettings cfg)
|
||||||
|
{
|
||||||
|
_src = src;
|
||||||
|
_dst = GetComponent<Animator>();
|
||||||
|
if (_dst == null) _dst = GetComponentInChildren<Animator>(true);
|
||||||
|
if (_src == null || _dst == null) return;
|
||||||
|
|
||||||
|
if (cfg.mirrorFiAnimator != 0)
|
||||||
|
{
|
||||||
|
_dst.runtimeAnimatorController = _src.runtimeAnimatorController;
|
||||||
|
_dst.applyRootMotion = false;
|
||||||
|
_dst.updateMode = _src.updateMode;
|
||||||
|
_dst.cullingMode = AnimatorCullingMode.AlwaysAnimate;
|
||||||
|
_params = _src.parameters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LateUpdate()
|
||||||
|
{
|
||||||
|
if (_src == null || _dst == null || _params == null) return;
|
||||||
|
for (int i = 0; i < _params.Length; i++)
|
||||||
|
{
|
||||||
|
var p = _params[i];
|
||||||
|
switch (p.type)
|
||||||
|
{
|
||||||
|
case AnimatorControllerParameterType.Float: _dst.SetFloat(p.nameHash, _src.GetFloat(p.nameHash)); break;
|
||||||
|
case AnimatorControllerParameterType.Int: _dst.SetInteger(p.nameHash, _src.GetInteger(p.nameHash)); break;
|
||||||
|
case AnimatorControllerParameterType.Bool: _dst.SetBool(p.nameHash, _src.GetBool(p.nameHash)); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>섬 코인(= `IslandManager.Coin` · FI 의 유일한 재화)에 닿는 유일한 창구.</summary>
|
||||||
|
public static class WLIslandCoin
|
||||||
|
{
|
||||||
|
public static bool Ready { get { return IslandManager.Instance != null; } }
|
||||||
|
|
||||||
|
public static bool TryGet(out int coin)
|
||||||
|
{
|
||||||
|
coin = 0;
|
||||||
|
var m = IslandManager.Instance;
|
||||||
|
if (m == null) return false;
|
||||||
|
coin = m.Coin;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Add(int amount)
|
||||||
|
{
|
||||||
|
var m = IslandManager.Instance;
|
||||||
|
if (m == null) return false;
|
||||||
|
m.Coin += amount; // setter 가 OnCoinChanged 를 쏜다 → CoinDisplay 가 갱신된다
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>프리팹 로드 — 실기(Addressables)와 에디터 단독 Play 양쪽을 덮는다.</summary>
|
||||||
|
public static class WLIslandAssets
|
||||||
|
{
|
||||||
|
static readonly Dictionary<string, GameObject> s_cache = new Dictionary<string, GameObject>(4);
|
||||||
|
|
||||||
|
public static GameObject LoadPrefab(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path)) return null;
|
||||||
|
GameObject go;
|
||||||
|
if (s_cache.TryGetValue(path, out go) && go != null) return go;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
go = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||||
|
if (go != null) { s_cache[path] = go; return go; }
|
||||||
|
#endif
|
||||||
|
// 빌드에서는 Addressables 동기 로드가 필요하다(섬 진입은 이미 로딩 화면 안이다).
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var h = UnityEngine.AddressableAssets.Addressables.LoadAssetAsync<GameObject>(path);
|
||||||
|
go = h.WaitForCompletion();
|
||||||
|
if (go != null) s_cache[path] = go;
|
||||||
|
}
|
||||||
|
catch (System.Exception ex) { Debug.LogWarning("[WL-816d] 프리팹 로드 실패 " + path + " — " + ex.Message); }
|
||||||
|
return go;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8d816d01a1b2c3d4e5f60708090a0b03
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,237 @@
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// WLIslandSettings.cs — 섬(FarmingIsland) × 던전(아레나 복사본) 왕복의 단일 출처(SOT · C45)
|
||||||
|
//
|
||||||
|
// PD 지시 #816 (2026-09-13) · 발주서 WL-816d §1 / §1-B + Lead 추가 지시 2건
|
||||||
|
// SOT = `공유/기획팀_산출물/WL/게임구조_2026-09-13/FarmingIsland_기반_전환설계_v1.md`(816c 실측)
|
||||||
|
//
|
||||||
|
// ■ 이 에셋 하나로 되돌린다(C8)
|
||||||
|
// enabled_ = 0 → 섬·던전 진입이 전부 사라지고 지금 게임 그대로(원본 1줄 훅도 -1 을 그대로 돌려준다).
|
||||||
|
// startOnIsland = 0 → 로그인 후 로비(WL_Nature)로 간다 = 현재 동작 100 %.
|
||||||
|
//
|
||||||
|
// ■ 던전을 늘리는 법 = **표 한 줄**
|
||||||
|
// dungeons[] 에 행 하나(씬 이름 · 맵 ID · 스테이지 인덱스 · 섬 위의 입구 좌표)를 추가하면
|
||||||
|
// 섬에 입구 더미가 하나 더 생기고, 그 문으로 들어가면 그 스테이지가 돈다. 코드 수정 0.
|
||||||
|
//
|
||||||
|
// 🔴 원본(Assets/Script/** · Assets/FarmingIsland/**)은 이 기능을 위해 고치지 않는다.
|
||||||
|
// 유일한 예외 = `NewGameUI.cs:36` **1줄**(발주서가 허용한 1줄 훅 상한) — StartMapId() 하나만 부른다.
|
||||||
|
// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다(ErrorLogHookManager 가 플레이어 팝업을 띄운다).
|
||||||
|
// 🔴 어셈블리 주의: Assets/WL/Island/ 에 .asmdef 를 만들지 말 것.
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace WL.Island
|
||||||
|
{
|
||||||
|
/// <summary>던전 1개 = 이 표의 한 행. 씬·스테이지·입구 위치가 전부 값이다.</summary>
|
||||||
|
[Serializable]
|
||||||
|
public class WLDungeonDef
|
||||||
|
{
|
||||||
|
[Tooltip("0 이면 이 행은 없는 것으로 친다(입구도 안 생긴다).")]
|
||||||
|
public int enabled_ = 1;
|
||||||
|
|
||||||
|
[Tooltip("던전 번호(로그·이름표용).")]
|
||||||
|
public int id = 1;
|
||||||
|
|
||||||
|
[Tooltip("입구 위에 뜨는 이름.")]
|
||||||
|
public string displayName = "던전 1";
|
||||||
|
|
||||||
|
[Tooltip("던전 씬 이름(빌드 씬 목록에 등록돼 있어야 한다). 예: WL_Dungeon01")]
|
||||||
|
public string sceneName = "WL_Dungeon01";
|
||||||
|
|
||||||
|
[Tooltip("BattleMapConfig.json 의 n_MapID — Load_Map(이 값) 으로 들어간다.")]
|
||||||
|
public int mapId = 901;
|
||||||
|
|
||||||
|
[Tooltip("WLStageTable 의 표 인덱스(0부터). 던전마다 달라야 보상·몹 수가 갈린다.")]
|
||||||
|
public int stageIndex = 10;
|
||||||
|
|
||||||
|
[Tooltip("섬 위 입구 더미의 월드 좌표.")]
|
||||||
|
public Vector3 gatePosition = Vector3.zero;
|
||||||
|
|
||||||
|
[Tooltip("입구 더미의 Y 회전(도).")]
|
||||||
|
public float gateYaw;
|
||||||
|
|
||||||
|
[Tooltip("입구 더미 크기 배수.")]
|
||||||
|
public float gateScale = 1f;
|
||||||
|
|
||||||
|
[Tooltip("입구 더미 색(더미라서 색으로만 구분한다 · 추후 프리팹으로 교체).")]
|
||||||
|
public Color gateColor = new Color(0.45f, 0.32f, 0.62f, 1f);
|
||||||
|
|
||||||
|
[Tooltip("비우면 내장 더미(문틀+포털판)를 만든다. 채우면 그 프리팹을 대신 놓는다(추후 교체용).")]
|
||||||
|
public string gatePrefabPath = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
[CreateAssetMenu(fileName = "WLIslandSettings", menuName = "WL/Island Settings", order = 340)]
|
||||||
|
public sealed class WLIslandSettings : ScriptableObject
|
||||||
|
{
|
||||||
|
public const string ResourcePath = "WL/WLIslandSettings";
|
||||||
|
|
||||||
|
static WLIslandSettings s_inst;
|
||||||
|
static bool s_tried;
|
||||||
|
|
||||||
|
/// <summary>설정 에셋(없으면 null = 기능 전체 무동작).</summary>
|
||||||
|
public static WLIslandSettings Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!s_tried) { s_inst = Resources.Load<WLIslandSettings>(ResourcePath); s_tried = true; }
|
||||||
|
return s_inst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Invalidate() { s_inst = null; s_tried = false; }
|
||||||
|
|
||||||
|
/// <summary>기능이 살아 있는가.</summary>
|
||||||
|
public static bool Enabled { get { var c = Instance; return c != null && c.enabled_ != 0; } }
|
||||||
|
|
||||||
|
// ───────────────────────────────── 전체 스위치
|
||||||
|
[Header("되돌리기 (PD 1순위)")]
|
||||||
|
[Tooltip("0 이면 섬·던전이 전부 사라진다 = 지금 게임 그대로.")]
|
||||||
|
public int enabled_ = 1;
|
||||||
|
|
||||||
|
[Tooltip("1 이면 진행 상황을 Console 에 남긴다.")]
|
||||||
|
public int verboseLog = 1;
|
||||||
|
|
||||||
|
// ───────────────────────────────── 섬
|
||||||
|
[Header("섬 (FarmingIsland Level01)")]
|
||||||
|
[Tooltip("1 이면 로그인 후 로비 대신 섬에서 시작한다(PD 2026-09-13). 0 이면 지금처럼 로비.")]
|
||||||
|
public int startOnIsland = 1;
|
||||||
|
|
||||||
|
[Tooltip("BattleMapConfig.json 에 넣은 섬 행의 n_MapID.")]
|
||||||
|
public int islandMapId = 900;
|
||||||
|
|
||||||
|
[Tooltip("섬 씬 이름(BattleMapConfig 의 s_Scene 과 같아야 한다).")]
|
||||||
|
public string islandSceneName = "Level01";
|
||||||
|
|
||||||
|
[Tooltip("섬에서 WL 전투 PC(Actor)를 숨긴다 — 섬은 FI 플레이어가 걷는다(플레이어 2명 방지).")]
|
||||||
|
public int hideWlPcOnIsland = 1;
|
||||||
|
|
||||||
|
[Tooltip("섬 씬의 FI 카메라/AudioListener 중복을 정리한다(Additive 로 붙었을 때만).")]
|
||||||
|
public int fixIslandDuplicates = 1;
|
||||||
|
|
||||||
|
[Tooltip("FI 세이브 파일 이름을 이 값으로 고정한다(빈 문자열 = 원본 그대로 = 활성 씬 이름). " +
|
||||||
|
"816c §A-3: 섹터마다 세이브가 갈리는 문제의 최소 수정 — 다만 원본 0줄 원칙 때문에 이번엔 기본 빈칸(미적용).")]
|
||||||
|
public string islandSaveKey = "";
|
||||||
|
|
||||||
|
// ───────────────────────────────── 섬의 플레이어 = 우리 전투 캐릭터
|
||||||
|
[Header("섬 캐릭터 교체 (PD: 전투 캐릭터로 통일)")]
|
||||||
|
[Tooltip("1 이면 FI 농부(Stickman) 대신 우리 치비 캐릭터가 섬을 걷는다. 조작·상호작용은 FI 것 그대로.")]
|
||||||
|
public int swapIslandPlayer = 1;
|
||||||
|
|
||||||
|
[Tooltip("섬에서 쓸 PC 프리팹 경로(Addressables 주소 겸 에디터 경로).")]
|
||||||
|
public string pcPrefabPath = "Assets/Res_Addr/PC/LH_M05.prefab";
|
||||||
|
|
||||||
|
[Tooltip("치비 루트에 넣을 스케일. 프리팹의 WLPcScaleCompensator 가 ×1.5038 해서 " +
|
||||||
|
"0.7 × 1.5038 = 1.0527 → 월드 높이 1.1912 m(= 인게임 PC 와 동일 · 814n 실측).")]
|
||||||
|
public float islandPcScale = 0.7f;
|
||||||
|
|
||||||
|
[Tooltip("1 이면 FI 애니메이터의 파라미터를 치비 애니메이터로 그대로 흘린다(둘 다 Humanoid · 리타깃). " +
|
||||||
|
"FI 컨트롤러를 그대로 쓰므로 걷기·농사 동작이 전부 따라온다.")]
|
||||||
|
public int mirrorFiAnimator = 1;
|
||||||
|
|
||||||
|
[Tooltip("FI 농부 메시를 숨길 때 쓰는 자식 이름(대소문자 무시 · 비우면 SkinnedMeshRenderer 를 전부 끈다).")]
|
||||||
|
public string fiPlayerModelName = "Stickman";
|
||||||
|
|
||||||
|
// ───────────────────────────────── 던전 입구
|
||||||
|
[Header("던전 입구(더미)")]
|
||||||
|
[Tooltip("플레이어가 이 거리(m) 안에 들어오면 「들어가기」가 뜬다.")]
|
||||||
|
public float interactRadius = 3.5f;
|
||||||
|
|
||||||
|
[Tooltip("안내 문구 — {0} = 던전 이름.")]
|
||||||
|
public string promptFormat = "{0}\n들어가기";
|
||||||
|
|
||||||
|
[Tooltip("버튼 문구.")]
|
||||||
|
public string enterButtonText = "들어가기";
|
||||||
|
|
||||||
|
[Tooltip("키보드로도 들어갈 수 있게(에디터 검증용).")]
|
||||||
|
public KeyCode enterKey = KeyCode.E;
|
||||||
|
|
||||||
|
[Tooltip("입구 더미 기본 높이(m).")]
|
||||||
|
public float gateHeight = 3.2f;
|
||||||
|
|
||||||
|
[Tooltip("입구 더미 기본 폭(m).")]
|
||||||
|
public float gateWidth = 2.4f;
|
||||||
|
|
||||||
|
// ───────────────────────────────── 보상
|
||||||
|
[Header("보상 (던전 클리어 → 섬 코인)")]
|
||||||
|
[Tooltip("0 이면 보상을 주지 않는다.")]
|
||||||
|
public int rewardEnabled = 1;
|
||||||
|
|
||||||
|
[Tooltip("처치 1마리당 코인.")]
|
||||||
|
public int goldPerKill = 12;
|
||||||
|
|
||||||
|
[Tooltip("클리어 기본 코인.")]
|
||||||
|
public int goldClearBonus = 60;
|
||||||
|
|
||||||
|
[Tooltip("보스 스테이지 추가 코인.")]
|
||||||
|
public int goldBossBonus = 120;
|
||||||
|
|
||||||
|
[Tooltip("1 이면 위 합계에 StageDef.goldMultiplier(WLStageTable.cs:103)를 곱한다.")]
|
||||||
|
public int applyGoldMultiplier = 1;
|
||||||
|
|
||||||
|
[Tooltip("실패(사망·시간 초과) 시 주는 코인. PD 지시 = 0.")]
|
||||||
|
public int goldOnFail = 0;
|
||||||
|
|
||||||
|
[Tooltip("보류 보상을 남기는 PlayerPrefs 키(플레이 중 씬 전환을 건너 살아남게).")]
|
||||||
|
public string pendingPrefsKey = "WL_Island_PendingGold";
|
||||||
|
|
||||||
|
// ───────────────────────────────── 던전 씬 런타임 준비
|
||||||
|
[Header("던전 씬 준비(런타임)")]
|
||||||
|
[Tooltip("1 이면 던전 씬에 NavMesh 를 런타임 베이크한다(아레나 복사본에는 베이크 데이터가 없다).")]
|
||||||
|
public int bakeDungeonNavMesh = 1;
|
||||||
|
|
||||||
|
[Tooltip("1 이면 스테이지 표가 요구하는 SpawnerID 로 스포너를 런타임 생성한다.")]
|
||||||
|
public int spawnDungeonSpawners = 1;
|
||||||
|
|
||||||
|
[Tooltip("생성한 스포너의 MobGenRange(m).")]
|
||||||
|
public float dungeonSpawnerRange = 6f;
|
||||||
|
|
||||||
|
[Tooltip("1 이면 아레나 복사본의 데모 리그(ArenaWalkerRig)·여분 카메라를 끈다(플레이어·카메라 중복 방지).")]
|
||||||
|
public int fixDungeonDuplicates = 1;
|
||||||
|
|
||||||
|
[Tooltip("던전 입장 후 StageDirector.Enter 를 부르기까지 기다리는 초(맵·PC 로딩 여유).")]
|
||||||
|
public float dungeonEnterDelay = 1.5f;
|
||||||
|
|
||||||
|
// ───────────────────────────────── 던전 표
|
||||||
|
[Header("던전 표 — 한 줄 = 던전 하나")]
|
||||||
|
public WLDungeonDef[] dungeons = new WLDungeonDef[0];
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// 원본 1줄 훅이 부르는 유일한 진입점 (NewGameUI.cs:36)
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 로그인 후 처음 들어갈 맵 ID. 기능이 꺼져 있으면 <paramref name="original"/>(= -1 로비)를 그대로 돌려준다.
|
||||||
|
/// 🔴 원본 수정은 이 호출 **1줄**이 전부다.
|
||||||
|
/// </summary>
|
||||||
|
public static int StartMapId(int original)
|
||||||
|
{
|
||||||
|
var c = Instance;
|
||||||
|
if (c == null || c.enabled_ == 0 || c.startOnIsland == 0) return original;
|
||||||
|
return c.islandMapId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>맵 ID 로 던전 행을 찾는다(없으면 null).</summary>
|
||||||
|
public WLDungeonDef FindByMapId(int mapId)
|
||||||
|
{
|
||||||
|
if (dungeons == null) return null;
|
||||||
|
for (int i = 0; i < dungeons.Length; i++)
|
||||||
|
if (dungeons[i] != null && dungeons[i].enabled_ != 0 && dungeons[i].mapId == mapId) return dungeons[i];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>씬 이름으로 던전 행을 찾는다(같은 씬을 여러 던전이 써도 되므로 맵 ID 우선).</summary>
|
||||||
|
public WLDungeonDef FindBySceneName(string sceneName)
|
||||||
|
{
|
||||||
|
if (dungeons == null || string.IsNullOrEmpty(sceneName)) return null;
|
||||||
|
for (int i = 0; i < dungeons.Length; i++)
|
||||||
|
if (dungeons[i] != null && dungeons[i].enabled_ != 0 && sceneName.Equals(dungeons[i].sceneName)) return dungeons[i];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log(string msg)
|
||||||
|
{
|
||||||
|
if (verboseLog != 0) Debug.Log("[WL-816d] " + msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8d816d01a1b2c3d4e5f60708090a0b01
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// 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 };
|
||||||
|
m.SetColor("_BaseColor", c);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8d816d01a1b2c3d4e5f60708090a0b05
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -87,13 +87,13 @@ MonoBehaviour:
|
||||||
nextStageText: "\uB2E4\uC74C \uC2A4\uD14C\uC774\uC9C0"
|
nextStageText: "\uB2E4\uC74C \uC2A4\uD14C\uC774\uC9C0"
|
||||||
restartFromFirstText: "\uCC98\uC74C\uBD80\uD130"
|
restartFromFirstText: "\uCC98\uC74C\uBD80\uD130"
|
||||||
showRestartOnLastStage: 1
|
showRestartOnLastStage: 1
|
||||||
toLobbyText: "\uB85C\uBE44\uB85C"
|
toLobbyText: "섬으로"
|
||||||
stageFailEnabled: 1
|
stageFailEnabled: 1
|
||||||
failDeathMessage: "\uC4F0\uB7EC\uC84C\uC2B5\uB2C8\uB2E4.\n\uB2E4\uC2DC \uB3C4\uC804\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?"
|
failDeathMessage: "\uC4F0\uB7EC\uC84C\uC2B5\uB2C8\uB2E4.\n\uB2E4\uC2DC \uB3C4\uC804\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?"
|
||||||
failTimeoutMessage: "\uC2DC\uAC04 \uCD08\uACFC!\n\uB0A8\uC740 \uC801 {0}\uB9C8\uB9AC"
|
failTimeoutMessage: "\uC2DC\uAC04 \uCD08\uACFC!\n\uB0A8\uC740 \uC801 {0}\uB9C8\uB9AC"
|
||||||
failAbortedMessage: "\uC2A4\uD14C\uC774\uC9C0\uAC00 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."
|
failAbortedMessage: "\uC2A4\uD14C\uC774\uC9C0\uAC00 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."
|
||||||
failRetryText: "\uB2E4\uC2DC \uB3C4\uC804"
|
failRetryText: "\uB2E4\uC2DC \uB3C4\uC804"
|
||||||
failLobbyText: "\uB85C\uBE44\uB85C"
|
failLobbyText: "섬으로"
|
||||||
suppressResultOnFail: 1
|
suppressResultOnFail: 1
|
||||||
lobbyMode: 0
|
lobbyMode: 0
|
||||||
titleSceneName: Title
|
titleSceneName: Title
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue