using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.SceneManagement; namespace Streamingle.Background { /// /// 배경 씬 로더 - 에디터/런타임 모두에서 안정적으로 동작 /// public class BackgroundSceneLoader : MonoBehaviour { private static BackgroundSceneLoader _instance; public static BackgroundSceneLoader Instance { get { if (_instance == null) { var go = new GameObject("[BackgroundSceneLoader]"); _instance = go.AddComponent(); DontDestroyOnLoad(go); } return _instance; } } [SerializeField] private BackgroundSceneDatabase sceneDatabase; private Scene? _currentBackgroundScene; private bool _isLoading; private AsyncOperation _currentOperation; // 기존 씬 라이팅 백업 private List _originalDirectionalLights = new List(); private List _originalNiloToonOverriders = new List(); private bool _hasLightingBackup; private class LightBackup { public Light light; public bool wasEnabled; } private class ComponentBackup { public MonoBehaviour component; public bool wasEnabled; } public event Action OnSceneLoadStarted; public event Action OnSceneLoadCompleted; public event Action OnSceneUnloaded; public event Action OnLoadProgress; public event Action OnError; public bool IsLoading => _isLoading; public Scene? CurrentBackgroundScene => _currentBackgroundScene; public string CurrentSceneName => _currentBackgroundScene.HasValue && _currentBackgroundScene.Value.IsValid() ? _currentBackgroundScene.Value.name : "없음"; public BackgroundSceneDatabase Database { get => sceneDatabase; set => sceneDatabase = value; } private void Awake() { if (_instance != null && _instance != this) { Destroy(gameObject); return; } _instance = this; DontDestroyOnLoad(gameObject); } private void OnDestroy() { if (_instance == this) { _instance = null; } } private void Start() { // 시작 시 현재 상태 동기화 SyncCurrentSceneStatus(); } /// /// 현재 로드된 배경 씬 상태 동기화 (리컴파일/재시작 후 복원용) /// public void SyncCurrentSceneStatus() { if (sceneDatabase == null) return; var loadedBackgroundScenes = new List(); for (int i = 0; i < SceneManager.sceneCount; i++) { var scene = SceneManager.GetSceneAt(i); if (!scene.isLoaded) continue; var sceneInfo = sceneDatabase.FindByPath(scene.path); if (sceneInfo != null) { loadedBackgroundScenes.Add(scene); } } // 중복 배경 씬 처리 (첫 번째만 유지) if (loadedBackgroundScenes.Count > 1) { UnityEngine.Debug.LogWarning($"[Runtime] 중복 배경 씬 감지: {loadedBackgroundScenes.Count}개. 첫 번째만 유지합니다."); for (int i = 1; i < loadedBackgroundScenes.Count; i++) { var duplicateScene = loadedBackgroundScenes[i]; UnityEngine.Debug.Log($"[Runtime] 중복 씬 언로드: {duplicateScene.name}"); SceneManager.UnloadSceneAsync(duplicateScene); } } // 상태 업데이트 if (loadedBackgroundScenes.Count > 0) { _currentBackgroundScene = loadedBackgroundScenes[0]; UnityEngine.Debug.Log($"[Runtime] 배경 씬 상태 동기화됨: {_currentBackgroundScene.Value.name}"); } else { _currentBackgroundScene = null; } } /// /// 특정 씬이 이미 로드되어 있는지 확인 /// private bool IsSceneAlreadyLoaded(string scenePath) { for (int i = 0; i < SceneManager.sceneCount; i++) { var scene = SceneManager.GetSceneAt(i); if (scene.path == scenePath && scene.isLoaded) { return true; } } return false; } /// /// 현재 로드된 모든 배경 씬 언로드 /// private void UnloadAllBackgroundScenesImmediate() { if (sceneDatabase == null) return; var scenesToUnload = new List(); for (int i = 0; i < SceneManager.sceneCount; i++) { var scene = SceneManager.GetSceneAt(i); if (!scene.isLoaded) continue; var sceneInfo = sceneDatabase.FindByPath(scene.path); if (sceneInfo != null) { scenesToUnload.Add(scene); } } foreach (var scene in scenesToUnload) { UnityEngine.Debug.Log($"[Runtime] 기존 배경 씬 언로드: {scene.name}"); SceneManager.UnloadSceneAsync(scene); } } /// /// 현재 활성 씬의 Directional Light와 NiloToonOverrider 백업 및 비활성화 /// private void BackupAndDisableOriginalLighting() { if (_hasLightingBackup) return; _originalDirectionalLights.Clear(); _originalNiloToonOverriders.Clear(); var activeScene = SceneManager.GetActiveScene(); if (!activeScene.isLoaded) return; var roots = activeScene.GetRootGameObjects(); foreach (var root in roots) { // Directional Light 찾기 var lights = root.GetComponentsInChildren(true); foreach (var light in lights) { if (light.type == LightType.Directional) { _originalDirectionalLights.Add(new LightBackup { light = light, wasEnabled = light.enabled }); light.enabled = false; } } // NiloToonCharacterMainLightOverrider 찾기 var niloToonOverriders = root.GetComponentsInChildren(true) .Where(mb => mb != null && mb.GetType().Name == "NiloToonCharacterMainLightOverrider"); foreach (var overrider in niloToonOverriders) { _originalNiloToonOverriders.Add(new ComponentBackup { component = overrider, wasEnabled = overrider.enabled }); overrider.enabled = false; } } _hasLightingBackup = true; int lightCount = _originalDirectionalLights.Count; int overriderCount = _originalNiloToonOverriders.Count; if (lightCount > 0 || overriderCount > 0) { UnityEngine.Debug.Log($"[Runtime] 기존 씬 라이팅 비활성화: Directional Light {lightCount}개, NiloToonOverrider {overriderCount}개"); } } /// /// 백업된 라이팅 복원 /// private void RestoreOriginalLighting() { if (!_hasLightingBackup) return; foreach (var backup in _originalDirectionalLights) { if (backup.light != null) { backup.light.enabled = backup.wasEnabled; } } foreach (var backup in _originalNiloToonOverriders) { if (backup.component != null) { backup.component.enabled = backup.wasEnabled; } } int lightCount = _originalDirectionalLights.Count(b => b.light != null); int overriderCount = _originalNiloToonOverriders.Count(b => b.component != null); if (lightCount > 0 || overriderCount > 0) { UnityEngine.Debug.Log($"[Runtime] 기존 씬 라이팅 복원: Directional Light {lightCount}개, NiloToonOverrider {overriderCount}개"); } _originalDirectionalLights.Clear(); _originalNiloToonOverriders.Clear(); _hasLightingBackup = false; } /// /// 배경 씬 로드 (Additive) /// public void LoadScene(BackgroundSceneInfo sceneInfo, bool unloadCurrent = true) { if (sceneInfo == null) { OnError?.Invoke("씬 정보가 없습니다."); return; } StartCoroutine(LoadSceneCoroutine(sceneInfo, unloadCurrent)); } /// /// 씬 경로로 로드 /// public void LoadScene(string scenePath, bool unloadCurrent = true) { if (sceneDatabase == null) { OnError?.Invoke("씬 데이터베이스가 설정되지 않았습니다."); return; } var sceneInfo = sceneDatabase.FindByPath(scenePath); if (sceneInfo == null) { OnError?.Invoke($"씬을 찾을 수 없습니다: {scenePath}"); return; } LoadScene(sceneInfo, unloadCurrent); } /// /// 현재 배경 씬 언로드 /// public void UnloadCurrentScene() { if (_currentBackgroundScene.HasValue && _currentBackgroundScene.Value.isLoaded) { StartCoroutine(UnloadSceneCoroutine(_currentBackgroundScene.Value)); } } private IEnumerator LoadSceneCoroutine(BackgroundSceneInfo sceneInfo, bool unloadCurrent) { if (_isLoading) { OnError?.Invoke("이미 씬을 로드 중입니다."); yield break; } // 1. 같은 씬이 이미 로드되어 있는지 확인 if (IsSceneAlreadyLoaded(sceneInfo.scenePath)) { UnityEngine.Debug.Log($"[Runtime] 이미 로드된 씬입니다: {sceneInfo.sceneName}"); _currentBackgroundScene = SceneManager.GetSceneByPath(sceneInfo.scenePath); OnSceneLoadCompleted?.Invoke(sceneInfo); yield break; } _isLoading = true; OnSceneLoadStarted?.Invoke(sceneInfo); // 2. 기존 모든 배경 씬 언로드 (새 배경 로드 시에는 라이팅 복원하지 않음) if (unloadCurrent) { // 모든 배경 씬 찾아서 언로드 var scenesToUnload = new List(); for (int i = 0; i < SceneManager.sceneCount; i++) { var scene = SceneManager.GetSceneAt(i); if (!scene.isLoaded) continue; var existingSceneInfo = sceneDatabase?.FindByPath(scene.path); if (existingSceneInfo != null) { scenesToUnload.Add(scene); } } foreach (var scene in scenesToUnload) { UnityEngine.Debug.Log($"[Runtime] 기존 배경 씬 언로드: {scene.name}"); var unloadOp = SceneManager.UnloadSceneAsync(scene); if (unloadOp != null) { while (!unloadOp.isDone) { yield return null; } } OnSceneUnloaded?.Invoke(scene.name); } _currentBackgroundScene = null; } // 3. 새 씬 로드 #if UNITY_EDITOR // 에디터에서는 EditorSceneManager 사용 (별도 처리 필요) if (!Application.isPlaying) { _isLoading = false; OnError?.Invoke("에디터 비플레이 모드에서는 EditorSceneManager를 사용하세요."); yield break; } #endif // 씬이 빌드 세팅에 있는지 확인 bool sceneInBuild = false; for (int i = 0; i < SceneManager.sceneCountInBuildSettings; i++) { string scenePath = SceneUtility.GetScenePathByBuildIndex(i); if (scenePath == sceneInfo.scenePath) { sceneInBuild = true; break; } } if (!sceneInBuild) { _isLoading = false; OnError?.Invoke($"씬이 빌드 세팅에 없습니다: {sceneInfo.scenePath}\n에디터에서는 Streamingle > Background Scene Loader를 사용하세요."); yield break; } // 기존 씬의 Directional Light와 NiloToonOverrider 비활성화 BackupAndDisableOriginalLighting(); _currentOperation = SceneManager.LoadSceneAsync(sceneInfo.scenePath, LoadSceneMode.Additive); if (_currentOperation == null) { _isLoading = false; RestoreOriginalLighting(); // 로드 실패 시 복원 OnError?.Invoke($"씬 로드 실패: {sceneInfo.scenePath}"); yield break; } while (!_currentOperation.isDone) { OnLoadProgress?.Invoke(_currentOperation.progress); yield return null; } // 로드된 씬 찾기 _currentBackgroundScene = SceneManager.GetSceneByPath(sceneInfo.scenePath); _isLoading = false; _currentOperation = null; OnSceneLoadCompleted?.Invoke(sceneInfo); } private IEnumerator UnloadSceneCoroutine(Scene scene) { string sceneName = scene.name; var unloadOp = SceneManager.UnloadSceneAsync(scene); if (unloadOp != null) { while (!unloadOp.isDone) { yield return null; } } _currentBackgroundScene = null; // 기존 씬의 Directional Light와 NiloToonOverrider 복원 RestoreOriginalLighting(); OnSceneUnloaded?.Invoke(sceneName); } /// /// 씬 로드 취소 /// public void CancelLoad() { if (_currentOperation != null) { // Unity에서는 씬 로드 취소가 직접적으로 불가능 // 대신 로드 완료 후 즉시 언로드 _isLoading = false; } } } }