Projext/Assets/02_Scripts/Systems/Scene/DungeonSceneSetup.cs

498 lines
32 KiB
C#
Raw Normal View History

using UnityEngine; // 유니티 엔진의 기본 기능을 불러올거에요 -> UnityEngine을
using UnityEngine.SceneManagement; // 씬 관리 기능을 불러올거에요 -> SceneManager를
using UnityEngine.UI; // UI 기능을 불러올거에요 -> Image 컴포넌트 탐색용
using TMPro; // TMP 기능을 불러올거에요 -> TextMeshProUGUI 탐색용
using System.Collections; // 코루틴을 사용할거에요 -> IEnumerator를
// ══════════════════════════════════════════════════════════════════
// DungeonSceneSetup — 던전/보스 씬 초기 설정 관리자
//
// [역할]
// 던전 또는 보스 씬이 로드될 때 필요한 초기 설정을 수행합니다.
// - DontDestroyOnLoad 플레이어가 정상 존재하는지 확인
// - DontDestroyOnLoad 카메라/UI가 정상 존재하는지 확인
// - 에디터에서 씬을 직접 Play할 때 플레이어/카메라/UI 프리팹 자동 생성
// - 던전 전용 환경 설정 (안개, 라이팅, BGM 등)
//
// [메인 씬 캐릭터 변경 사항 자동 반영 원리]
// ★ 핵심: 플레이어 오브젝트 자체가 DontDestroyOnLoad로 살아있기 때문에
// 메인 씬에서 장비, 스탯, 스킬 등을 바꾸면 그 오브젝트가 그대로
// 던전 씬으로 넘어옵니다. 별도 저장/로드 로직이 필요 없습니다.
// 카메라와 UI도 동일하게 DontDestroyOnLoad로 유지됩니다.
//
// [사용법]
// 1. 던전/보스 씬에 빈 오브젝트 "DungeonManager" 생성
// 2. 이 스크립트 부착
// 3. fallbackPlayerPrefab에 Player 프리팹 연결
// 4. fallbackCameraPrefab에 Main Camera 프리팹 연결 (또는 Virtual Camera)
// 5. fallbackUIPrefabs 배열에 UI Canvas 프리팹들 연결
// 6. PlayerSpawnPoint도 씬에 배치 (스폰 위치 지정)
// ══════════════════════════════════════════════════════════════════
public class DungeonSceneSetup : MonoBehaviour // 클래스를 정의할거에요 -> 던전 씬 초기화 관리자를
{
// ─────────────────────────────────────────────────────────────
// Inspector 설정 — 플레이어
// ─────────────────────────────────────────────────────────────
[Header("=== 플레이어 폴백 (에디터 전용) ===")]
[Tooltip("에디터에서 씬을 직접 Play할 때 사용할 Player 프리팹\n" +
"정상적인 게임 흐름(메인→던전)에서는 사용되지 않습니다")]
[SerializeField] private GameObject fallbackPlayerPrefab; // 변수를 선언할거에요 -> 폴백 플레이어 프리팹을
[Header("=== 플레이어 태그 ===")]
[SerializeField] private string playerTag = "Player"; // 변수를 선언할거에요 -> 플레이어 태그를
// ─────────────────────────────────────────────────────────────
// Inspector 설정 — 카메라
// ─────────────────────────────────────────────────────────────
[Header("=== 카메라 폴백 (에디터 전용) ===")]
[Tooltip("에디터에서 씬을 직접 Play할 때 사용할 Main Camera 프리팹\n" +
"※ Main Camera 태그가 설정된 카메라 프리팹을 연결하세요")]
[SerializeField] private GameObject fallbackCameraPrefab; // 변수를 선언할거에요 -> 폴백 메인 카메라 프리팹을
[Tooltip("에디터에서 씬을 직접 Play할 때 사용할 Virtual Camera 프리팹\n" +
"※ Cinemachine Virtual Camera 프리팹을 연결하세요")]
[SerializeField] private GameObject fallbackVirtualCameraPrefab; // 변수를 선언할거에요 -> 폴백 가상 카메라 프리팹을
// ─────────────────────────────────────────────────────────────
// Inspector 설정 — UI
// ─────────────────────────────────────────────────────────────
[Header("=== UI 폴백 (에디터 전용) ===")]
[Tooltip("에디터에서 씬을 직접 Play할 때 사용할 UI Canvas 프리팹들\n" +
"예: Player Canvas, StopCanvas, SettingsCanvas, DeathUICanvas 등\n" +
"정상적인 게임 흐름에서는 DontDestroyOnLoad로 넘어오므로 사용되지 않습니다")]
[SerializeField] private GameObject[] fallbackUIPrefabs; // 변수를 선언할거에요 -> 폴백 UI 프리팹 배열을
// ─────────────────────────────────────────────────────────────
// Inspector 설정 — 씬 복귀
// ─────────────────────────────────────────────────────────────
[Header("=== 씬 복귀 설정 ===")]
[Tooltip("플레이어가 돌아갈 메인 씬 이름")]
[SerializeField] private string mainSceneName = "MainScene"; // 변수를 선언할거에요 -> 메인 씬 이름을
// ─────────────────────────────────────────────────────────────
// Inspector 설정 — 던전 환경
// ─────────────────────────────────────────────────────────────
[Header("=== 던전 환경 (선택) ===")]
[Tooltip("던전 씬 로드 시 안개 활성화 여부")]
[SerializeField] private bool enableFog = true; // 변수를 선언할거에요 -> 안개 활성화 여부를
[SerializeField] private Color fogColor = new Color(0.05f, 0.03f, 0.08f); // 변수를 선언할거에요 -> 안개 색상을 (어두운 보라)
[SerializeField] private float fogDensity = 0.02f; // 변수를 선언할거에요 -> 안개 밀도를
[Tooltip("던전 씬 진입 시 재생할 앰비언트 라이트 색상")]
[SerializeField] private Color ambientColor = new Color(0.1f, 0.08f, 0.15f); // 변수를 선언할거에요 -> 앰비언트 색상을
// ─────────────────────────────────────────────────────────────
// 내부 상태
// ─────────────────────────────────────────────────────────────
private GameObject _cachedPlayer; // 변수를 선언할거에요 -> 찾은 플레이어 캐시를
private bool _isDirectPlay = false; // 변수를 선언할거에요 -> 직접 Play 모드인지 여부를
// ─────────────────────────────────────────────────────────────
// 초기화 — 씬 로드 시 자동 실행
// ─────────────────────────────────────────────────────────────
private void Awake() // Awake에서 실행할거에요 -> 가장 먼저 초기화를
{
EnsurePlayerExists(); // 실행할거에요 -> 플레이어 존재 확인을
// 직접 Play일 때만 카메라/UI 폴백 생성 (메인씬에서 넘어왔으면 이미 DontDestroyOnLoad에 있음)
if (_isDirectPlay) // 조건이 맞으면 실행할거에요 -> 직접 Play 모드면
{
EnsureCameraExists(); // 실행할거에요 -> 카메라 존재 확인을
EnsureUIExists(); // 실행할거에요 -> UI 존재 확인을
}
ApplyDungeonEnvironment(); // 실행할거에요 -> 던전 환경 설정을
}
/// <summary>
/// Start에서 실행 — Awake에서 모든 오브젝트 생성 후,
/// UI와 카메라의 플레이어 참조를 자동으로 연결합니다.
/// Awake → 오브젝트 생성 / Start → 참조 연결 (타이밍 안전)
/// </summary>
private void Start() // Start에서 실행할거에요 -> 참조 연결을 (Awake 이후)
{
if (_isDirectPlay && _cachedPlayer != null) // 조건이 맞으면 실행할거에요 -> 직접 Play이고 플레이어가 있으면
{
LinkAllUIToPlayer(); // 실행할거에요 -> 모든 UI에 플레이어 참조 연결을
LinkCanvasRenderCameras(); // 실행할거에요 -> Canvas의 Render Camera 연결을
StartCoroutine(DelayedUIRefresh()); // 코루틴을 시작할거에요 -> 1프레임 뒤 UI 강제 갱신을 (타이밍 안전장치)
}
}
/// <summary>
/// 모든 Start()가 실행된 뒤 UI를 한번 더 갱신합니다.
/// PlayerHealth.Start()에서 CurrentHP가 초기화되기 전에
/// LinkAllUIToPlayer()가 먼저 실행될 수 있어서 필요합니다.
/// </summary>
private IEnumerator DelayedUIRefresh() // 코루틴을 정의할거에요 -> 지연 UI 갱신을
{
yield return null; // 기다릴거에요 -> 1프레임 (모든 Start() 완료 보장)
if (_cachedPlayer == null) yield break; // 조건이 맞으면 중단할거에요 -> 플레이어 없으면
// ── HP바 강제 갱신 ──
PlayerHealth playerHealth = _cachedPlayer.GetComponent<PlayerHealth>(); // 가져올거에요 -> 플레이어 체력을
if (playerHealth != null) // 조건이 맞으면 실행할거에요 -> 체력 스크립트가 있으면
{
playerHealth.RefreshHealthUI(); // 실행할거에요 -> HP 이벤트를 다시 발생시켜 UI 갱신
Debug.Log("[DungeonSetup] 지연 갱신: HP UI 강제 Refresh 완료"); // 로그를 찍을거에요
}
// ── 레벨 시스템 강제 갱신 ──
PlayerLevelSystem levelSys = FindObjectOfType<PlayerLevelSystem>(true); // 찾을거에요 -> 레벨 시스템을
if (levelSys != null) // 조건이 맞으면 실행할거에요 -> 레벨 시스템이 있으면
{
levelSys.ForceRefreshUI(); // 실행할거에요 -> 레벨 UI 강제 갱신을
Debug.Log("[DungeonSetup] 지연 갱신: 레벨 UI 강제 Refresh 완료"); // 로그를 찍을거에요
}
// ── HPUibar 강제 갱신 (텍스트 포함) ──
HPUibar hpBar = FindObjectOfType<HPUibar>(true); // 찾을거에요 -> HP바를
if (hpBar != null) // 조건이 맞으면 실행할거에요 -> HP바가 있으면
{
hpBar.ForceRefresh(); // 실행할거에요 -> HP바 강제 갱신을
Debug.Log("[DungeonSetup] 지연 갱신: HPUibar 강제 Refresh 완료"); // 로그를 찍을거에요
}
Debug.Log("[DungeonSetup] ★ 지연 UI 갱신 완료 (모든 Start() 이후)"); // 로그를 찍을거에요
}
// ─────────────────────────────────────────────────────────────
// 플레이어 폴백 생성
// ─────────────────────────────────────────────────────────────
/// <summary>
/// DontDestroyOnLoad 플레이어가 존재하는지 확인합니다.
/// 에디터에서 던전 씬을 직접 Play할 때만 폴백 프리팹을 생성합니다.
/// 정상 흐름(메인→던전)에서는 이미 플레이어가 DontDestroyOnLoad에 있습니다.
/// </summary>
private void EnsurePlayerExists() // 함수를 정의할거에요 -> 플레이어 존재 보장을
{
// 1단계: 태그로 기존 플레이어 검색
_cachedPlayer = GameObject.FindGameObjectWithTag(playerTag); // 찾을거에요 -> 플레이어를
if (_cachedPlayer != null) // 조건이 맞으면 실행할거에요 -> 이미 플레이어가 있으면
{
_isDirectPlay = false; // 설정할거에요 -> 메인 씬에서 정상 진입한 것으로 판단
Debug.Log($"[DungeonSetup] 플레이어 발견: {_cachedPlayer.name} — 메인 씬 상태 그대로 유지됩니다"); // 로그를 찍을거에요
return; // 중단할거에요 -> 정상 상태이므로 추가 작업 불필요
}
// 2단계: 플레이어가 없음 (에디터에서 직접 Play한 경우)
_isDirectPlay = true; // 설정할거에요 -> 직접 Play 모드로 판단
Debug.LogWarning("[DungeonSetup] 플레이어를 찾을 수 없습니다! 에디터 테스트 모드로 폴백 생성합니다."); // 경고를 찍을거에요
if (fallbackPlayerPrefab != null) // 조건이 맞으면 실행할거에요 -> 폴백 프리팹이 설정되어 있으면
{
// 폴백 플레이어 생성 (PlayerSpawnPoint 위치 또는 원점)
Vector3 spawnPos = Vector3.zero; // 기본 위치를 원점으로 설정할거에요
Quaternion spawnRot = Quaternion.identity; // 기본 회전을 무회전으로 설정할거에요
// PlayerSpawnPoint가 있으면 그 위치 사용
PlayerSpawnPoint spawnPoint = FindObjectOfType<PlayerSpawnPoint>(); // 찾을거에요 -> 스폰 포인트를
if (spawnPoint != null) // 조건이 맞으면 실행할거에요 -> 스폰 포인트가 있으면
{
spawnPos = spawnPoint.transform.position; // 설정할거에요 -> 스폰 위치로
spawnRot = spawnPoint.transform.rotation; // 설정할거에요 -> 스폰 회전으로
}
_cachedPlayer = Instantiate(fallbackPlayerPrefab, spawnPos, spawnRot); // 생성할거에요 -> 폴백 플레이어를
_cachedPlayer.name = "Player (Fallback)"; // 이름을 설정할거에요 -> 폴백임을 명시
Debug.Log($"[DungeonSetup] 폴백 플레이어 생성 완료: {spawnPos}"); // 로그를 찍을거에요
}
else // 폴백 프리팹도 없으면
{
Debug.LogError("[DungeonSetup] fallbackPlayerPrefab이 설정되지 않았습니다! " +
"Inspector에서 Player 프리팹을 연결하거나, 메인 씬에서 정상 진입하세요."); // 에러를 찍을거에요
}
}
// ─────────────────────────────────────────────────────────────
// 카메라 폴백 생성
// ─────────────────────────────────────────────────────────────
/// <summary>
/// 카메라가 존재하는지 확인합니다.
/// 에디터에서 씬을 직접 Play할 때만 폴백 카메라를 생성합니다.
/// 메인 카메라 → 가상 카메라 순서로 생성합니다.
/// </summary>
private void EnsureCameraExists() // 함수를 정의할거에요 -> 카메라 존재 보장을
{
// ── 1. 메인 카메라 확인 및 폴백 생성 ──
if (Camera.main == null) // 조건이 맞으면 실행할거에요 -> 메인 카메라가 없으면
{
if (fallbackCameraPrefab != null) // 조건이 맞으면 실행할거에요 -> 폴백 프리팹이 있으면
{
GameObject cam = Instantiate(fallbackCameraPrefab); // 생성할거에요 -> 폴백 메인 카메라를
cam.name = "Main Camera (Fallback)"; // 이름을 설정할거에요 -> 폴백임을 명시
Debug.Log("[DungeonSetup] 폴백 메인 카메라 생성 완료"); // 로그를 찍을거에요
}
else // 프리팹이 없으면
{
Debug.LogError("[DungeonSetup] fallbackCameraPrefab이 설정되지 않았습니다! " +
"Inspector에서 Main Camera 프리팹을 연결하세요."); // 에러를 찍을거에요
}
}
else // 카메라가 이미 있으면
{
Debug.Log("[DungeonSetup] 메인 카메라 이미 존재 — 스킵"); // 로그를 찍을거에요
}
// ── 2. 가상 카메라 (Cinemachine) 확인 및 폴백 생성 ──
if (fallbackVirtualCameraPrefab != null) // 조건이 맞으면 실행할거에요 -> 가상 카메라 프리팹이 설정되어 있으면
{
// Cinemachine.CinemachineVirtualCamera 타입으로 검색
var existingVCam = FindObjectOfType<Cinemachine.CinemachineVirtualCamera>(); // 찾을거에요 -> 기존 가상 카메라를
if (existingVCam == null) // 조건이 맞으면 실행할거에요 -> 가상 카메라가 없으면
{
GameObject vCam = Instantiate(fallbackVirtualCameraPrefab); // 생성할거에요 -> 폴백 가상 카메라를
vCam.name = "Virtual Camera (Fallback)"; // 이름을 설정할거에요 -> 폴백임을 명시
// ★ 폴백 가상 카메라에 플레이어 자동 연결
LinkVirtualCameraToPlayer(vCam); // 실행할거에요 -> 카메라와 플레이어 연결을
Debug.Log("[DungeonSetup] 폴백 가상 카메라 생성 완료"); // 로그를 찍을거에요
}
else // 이미 있으면
{
Debug.Log("[DungeonSetup] 가상 카메라 이미 존재 — 스킵"); // 로그를 찍을거에요
}
}
}
/// <summary>
/// 폴백 가상 카메라의 Follow/LookAt을 플레이어에 자동 연결합니다.
/// 프리팹에서 새로 생성하면 참조가 끊어지기 때문에 수동으로 연결해야 합니다.
/// </summary>
private void LinkVirtualCameraToPlayer(GameObject vCamObj) // 함수를 정의할거에요 -> 가상 카메라 타겟 연결을
{
if (_cachedPlayer == null) // 조건이 맞으면 실행할거에요 -> 플레이어가 없으면
{
Debug.LogWarning("[DungeonSetup] 플레이어가 없어서 카메라 Follow 연결을 건너뜁니다."); // 경고를 찍을거에요
return; // 중단할거에요
}
// 가상 카메라 컴포넌트 가져오기
var vCam = vCamObj.GetComponent<Cinemachine.CinemachineVirtualCamera>(); // 가져올거에요 -> CinemachineVirtualCamera를
if (vCam == null) // 조건이 맞으면 실행할거에요 -> 컴포넌트가 없으면
{
Debug.LogWarning("[DungeonSetup] VirtualCamera 컴포넌트를 찾을 수 없습니다."); // 경고를 찍을거에요
return; // 중단할거에요
}
// Follow와 LookAt에 플레이어 Transform 연결
vCam.Follow = _cachedPlayer.transform; // 연결할거에요 -> Follow 타겟을 플레이어로
vCam.LookAt = _cachedPlayer.transform; // 연결할거에요 -> LookAt 타겟을 플레이어로
Debug.Log($"[DungeonSetup] 가상 카메라 Follow/LookAt → {_cachedPlayer.name} 연결 완료"); // 로그를 찍을거에요
}
// ─────────────────────────────────────────────────────────────
// UI 폴백 생성
// ─────────────────────────────────────────────────────────────
/// <summary>
/// UI Canvas들이 존재하는지 확인합니다.
/// 에디터에서 씬을 직접 Play할 때만 폴백 UI를 생성합니다.
/// fallbackUIPrefabs 배열에 등록된 프리팹을 순차 생성합니다.
/// </summary>
private void EnsureUIExists() // 함수를 정의할거에요 -> UI 존재 보장을
{
if (fallbackUIPrefabs == null || fallbackUIPrefabs.Length == 0) // 조건이 맞으면 실행할거에요 -> 폴백 UI가 없으면
{
Debug.LogWarning("[DungeonSetup] fallbackUIPrefabs가 비어있습니다. UI 없이 진행합니다."); // 경고를 찍을거에요
return; // 중단할거에요
}
for (int i = 0; i < fallbackUIPrefabs.Length; i++) // 반복할거에요 -> 각 UI 프리팹마다
{
if (fallbackUIPrefabs[i] == null) // 조건이 맞으면 실행할거에요 -> 빈 슬롯이면
{
Debug.LogWarning($"[DungeonSetup] fallbackUIPrefabs[{i}]가 비어있습니다 — 건너뜁니다."); // 경고를 찍을거에요
continue; // 다음으로 넘어갈거에요
}
// 같은 이름의 오브젝트가 이미 있는지 확인 (DontDestroyOnLoad에서 넘어왔을 수 있음)
string prefabName = fallbackUIPrefabs[i].name; // 가져올거에요 -> 프리팹 이름을
GameObject existing = GameObject.Find(prefabName); // 찾을거에요 -> 같은 이름의 기존 오브젝트를
if (existing != null) // 조건이 맞으면 실행할거에요 -> 이미 존재하면
{
Debug.Log($"[DungeonSetup] UI '{prefabName}' 이미 존재 — 스킵"); // 로그를 찍을거에요
continue; // 다음으로 넘어갈거에요
}
// 폴백 UI 생성
GameObject uiInstance = Instantiate(fallbackUIPrefabs[i]); // 생성할거에요 -> 폴백 UI를
uiInstance.name = $"{prefabName} (Fallback)"; // 이름을 설정할거에요 -> 폴백임을 명시
Debug.Log($"[DungeonSetup] 폴백 UI 생성 완료: {prefabName}"); // 로그를 찍을거에요
}
}
// ─────────────────────────────────────────────────────────────
// UI ↔ Player 자동 연결 (폴백 모드 전용)
// ─────────────────────────────────────────────────────────────
/// <summary>
/// 씬에 존재하는 모든 UI 스크립트의 플레이어 참조를 자동 연결합니다.
/// 폴백으로 프리팹을 새로 생성하면 씬 참조가 끊기기 때문에 필요합니다.
/// 대상: HPUibar, PlayerStatsUI, ArrowRangeUI, PlayerLevelSystem
/// </summary>
private void LinkAllUIToPlayer() // 함수를 정의할거에요 -> 모든 UI에 플레이어 연결을
{
if (_cachedPlayer == null) // 조건이 맞으면 실행할거에요 -> 플레이어가 없으면
{
Debug.LogWarning("[DungeonSetup] 플레이어가 없어서 UI 연결을 건너뜁니다."); // 경고를 찍을거에요
return; // 중단할거에요
}
// 플레이어의 핵심 컴포넌트들을 미리 캐싱 (GetComponent 최소화)
Stats playerStats = _cachedPlayer.GetComponent<Stats>(); // 가져올거에요 -> 플레이어 스탯을
PlayerHealth playerHealth = _cachedPlayer.GetComponent<PlayerHealth>(); // 가져올거에요 -> 플레이어 체력을
PlayerAttack playerAttack = _cachedPlayer.GetComponent<PlayerAttack>(); // 가져올거에요 -> 플레이어 공격을
// ── 1. HPUibar — targetObject 연결 ──
HPUibar hpBar = FindObjectOfType<HPUibar>(true); // 찾을거에요 -> HP UI바를 (비활성 포함)
if (hpBar != null) // 조건이 맞으면 실행할거에요 -> HP바가 있으면
{
hpBar.SetTargetObject(_cachedPlayer); // 연결할거에요 -> HP바의 타겟을 플레이어로
Debug.Log("[DungeonSetup] HPUibar → Player 연결 완료"); // 로그를 찍을거에요
}
// ── 2. PlayerStatsUI — playerStats 연결 ──
PlayerStatsUI statsUI = FindObjectOfType<PlayerStatsUI>(true); // 찾을거에요 -> 스탯 UI를 (비활성 포함)
if (statsUI != null && playerStats != null) // 조건이 맞으면 실행할거에요 -> 둘 다 있으면
{
statsUI.SetPlayerStats(playerStats); // 연결할거에요 -> 스탯 UI에 플레이어 스탯을
Debug.Log("[DungeonSetup] PlayerStatsUI → Stats 연결 완료"); // 로그를 찍을거에요
}
// ── 3. ArrowRangeUI (CrossHairUI) — attackScript 연결 ──
ArrowRangeUI crosshair = FindObjectOfType<ArrowRangeUI>(true); // 찾을거에요 -> 조준선 UI를 (비활성 포함)
if (crosshair != null && playerAttack != null) // 조건이 맞으면 실행할거에요 -> 둘 다 있으면
{
crosshair.SetAttackScript(playerAttack); // 연결할거에요 -> 조준선에 공격 스크립트를
Debug.Log("[DungeonSetup] ArrowRangeUI → PlayerAttack 연결 완료"); // 로그를 찍을거에요
}
// ── 4. PlayerLevelSystem — stats, pHealth 연결 ──
PlayerLevelSystem levelSys = FindObjectOfType<PlayerLevelSystem>(true); // 찾을거에요 -> 레벨 시스템을 (비활성 포함)
if (levelSys != null) // 조건이 맞으면 실행할거에요 -> 레벨 시스템이 있으면
{
levelSys.SetPlayerReferences(playerStats, playerHealth); // 연결할거에요 -> 레벨 시스템에 플레이어 참조를
Debug.Log("[DungeonSetup] PlayerLevelSystem → Stats, PlayerHealth 연결 완료"); // 로그를 찍을거에요
}
Debug.Log("[DungeonSetup] ★ 모든 UI ↔ Player 연결 완료"); // 로그를 찍을거에요
}
/// <summary>
/// Screen Space - Camera 모드의 Canvas에 Render Camera를 자동 연결합니다.
/// 폴백으로 생성하면 Camera 참조가 None이 되기 때문에 필요합니다.
/// </summary>
private void LinkCanvasRenderCameras() // 함수를 정의할거에요 -> Canvas 카메라 연결을
{
Camera mainCam = Camera.main; // 가져올거에요 -> 현재 메인 카메라를
if (mainCam == null) // 조건이 맞으면 실행할거에요 -> 메인 카메라가 없으면
{
Debug.LogWarning("[DungeonSetup] 메인 카메라가 없어서 Canvas 카메라 연결을 건너뜁니다."); // 경고를 찍을거에요
return; // 중단할거에요
}
// 씬에 있는 모든 Canvas 검색 (비활성 포함)
Canvas[] allCanvas = FindObjectsOfType<Canvas>(true); // 찾을거에요 -> 모든 Canvas를
for (int i = 0; i < allCanvas.Length; i++) // 반복할거에요 -> 각 Canvas마다
{
Canvas canvas = allCanvas[i]; // 가져올거에요 -> 현재 Canvas를
// Screen Space - Camera 모드인데 카메라가 없으면 연결
if (canvas.renderMode == RenderMode.ScreenSpaceCamera && canvas.worldCamera == null) // 조건이 맞으면 -> 카메라 모드인데 카메라 없으면
{
canvas.worldCamera = mainCam; // 연결할거에요 -> 메인 카메라를 Render Camera로
Debug.Log($"[DungeonSetup] Canvas '{canvas.name}' → Render Camera 연결 완료"); // 로그를 찍을거에요
}
}
}
// ─────────────────────────────────────────────────────────────
// 던전 환경 설정
// ─────────────────────────────────────────────────────────────
/// <summary>
/// 던전 환경 설정을 적용합니다 (안개, 앰비언트 라이트 등)
/// </summary>
private void ApplyDungeonEnvironment() // 함수를 정의할거에요 -> 던전 환경을
{
// 안개 설정
if (enableFog) // 조건이 맞으면 실행할거에요 -> 안개를 켤 거면
{
RenderSettings.fog = true; // 활성화할거에요 -> 안개를
RenderSettings.fogColor = fogColor; // 설정할거에요 -> 안개 색상을
RenderSettings.fogDensity = fogDensity; // 설정할거에요 -> 안개 밀도를
RenderSettings.fogMode = FogMode.Exponential; // 설정할거에요 -> 안개 모드를 지수형으로
}
// 앰비언트 라이트 설정
RenderSettings.ambientLight = ambientColor; // 설정할거에요 -> 앰비언트 색상을
Debug.Log("[DungeonSetup] 던전 환경 설정 적용 완료"); // 로그를 찍을거에요
}
// ─────────────────────────────────────────────────────────────
// 공개 API — 다른 스크립트에서 호출 가능
// ─────────────────────────────────────────────────────────────
/// <summary>
/// 메인 씬으로 복귀합니다.
/// 던전 클리어 시 또는 복귀 포탈에서 호출하세요.
/// 플레이어 상태는 DontDestroyOnLoad이므로 자동 유지됩니다.
/// </summary>
public void ReturnToMainScene() // 함수를 정의할거에요 -> 메인 씬 복귀를
{
if (string.IsNullOrEmpty(mainSceneName)) // 조건이 맞으면 실행할거에요 -> 씬 이름이 없으면
{
Debug.LogError("[DungeonSetup] mainSceneName이 설정되지 않았습니다!"); // 에러를 찍을거에요
return; // 중단할거에요
}
Debug.Log($"[DungeonSetup] 메인 씬으로 복귀: {mainSceneName}"); // 로그를 찍을거에요
if (SceneLoader.Instance != null) // SceneLoader가 있으면 페이드 전환
{
SceneLoader.Instance.LoadSceneWithFade(mainSceneName); // 페이드 로드
}
else // 없으면 직접 로드
{
SceneManager.LoadScene(mainSceneName); // 직접 로드
}
}
// ─────────────────────────────────────────────────────────────
// Gizmo — 씬 뷰에서 던전 매니저 위치 표시
// ─────────────────────────────────────────────────────────────
private void OnDrawGizmos() // 씬 뷰에서 그릴거에요 -> 매니저 위치를
{
Gizmos.color = new Color(0.6f, 0.2f, 0.8f, 0.5f); // 보라색 반투명
Gizmos.DrawWireSphere(transform.position, 1f); // 원 그리기
#if UNITY_EDITOR
UnityEditor.Handles.Label(transform.position + Vector3.up * 1.5f, // 라벨 위치
"Dungeon Manager", // 라벨 텍스트
new GUIStyle { // 스타일
normal = { textColor = new Color(0.8f, 0.4f, 1f) }, // 보라 텍스트
fontSize = 12, // 글자 크기
fontStyle = FontStyle.Bold // 볼드
});
#endif
}
}