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; } } /// /// 현재 활성 씬의 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; } _isLoading = true; OnSceneLoadStarted?.Invoke(sceneInfo); // 현재 씬 언로드 (새 배경 로드 시에는 라이팅 복원하지 않음) if (unloadCurrent && _currentBackgroundScene.HasValue && _currentBackgroundScene.Value.isLoaded) { var unloadOp = SceneManager.UnloadSceneAsync(_currentBackgroundScene.Value); if (unloadOp != null) { while (!unloadOp.isDone) { yield return null; } } OnSceneUnloaded?.Invoke(_currentBackgroundScene.Value.name); _currentBackgroundScene = null; } // 새 씬 로드 #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; } } } }