user 7a5d1dbe8f Refactor: 배경 씬 로더 시스템 개선 및 폴더 구조 정리
배경 씬 로더 기능 개선:
- BackgroundSceneDatabase 에셋 추가
- 플레이 모드 언로드 시 MarkSceneDirty 오류 수정
- Directional Light 및 NiloToonOverrider 백업/복원 기능
- 빌드 세팅 자동 추가 기능

배경 폴더 구조 정리:
- [초금비]방 → [공용]방 이름 변경
- [공용]루프탑 카페 씬 구조 정리 (Day/Night 씬 통합)
- 미사용 배경 삭제: [공용]교실, [공용]농구장

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 00:00:35 +09:00

346 lines
11 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
namespace Streamingle.Background
{
/// <summary>
/// 배경 씬 로더 - 에디터/런타임 모두에서 안정적으로 동작
/// </summary>
public class BackgroundSceneLoader : MonoBehaviour
{
private static BackgroundSceneLoader _instance;
public static BackgroundSceneLoader Instance
{
get
{
if (_instance == null)
{
var go = new GameObject("[BackgroundSceneLoader]");
_instance = go.AddComponent<BackgroundSceneLoader>();
DontDestroyOnLoad(go);
}
return _instance;
}
}
[SerializeField] private BackgroundSceneDatabase sceneDatabase;
private Scene? _currentBackgroundScene;
private bool _isLoading;
private AsyncOperation _currentOperation;
// 기존 씬 라이팅 백업
private List<LightBackup> _originalDirectionalLights = new List<LightBackup>();
private List<ComponentBackup> _originalNiloToonOverriders = new List<ComponentBackup>();
private bool _hasLightingBackup;
private class LightBackup
{
public Light light;
public bool wasEnabled;
}
private class ComponentBackup
{
public MonoBehaviour component;
public bool wasEnabled;
}
public event Action<BackgroundSceneInfo> OnSceneLoadStarted;
public event Action<BackgroundSceneInfo> OnSceneLoadCompleted;
public event Action<string> OnSceneUnloaded;
public event Action<float> OnLoadProgress;
public event Action<string> 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;
}
}
/// <summary>
/// 현재 활성 씬의 Directional Light와 NiloToonOverrider 백업 및 비활성화
/// </summary>
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<Light>(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<MonoBehaviour>(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}개");
}
}
/// <summary>
/// 백업된 라이팅 복원
/// </summary>
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;
}
/// <summary>
/// 배경 씬 로드 (Additive)
/// </summary>
public void LoadScene(BackgroundSceneInfo sceneInfo, bool unloadCurrent = true)
{
if (sceneInfo == null)
{
OnError?.Invoke("씬 정보가 없습니다.");
return;
}
StartCoroutine(LoadSceneCoroutine(sceneInfo, unloadCurrent));
}
/// <summary>
/// 씬 경로로 로드
/// </summary>
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);
}
/// <summary>
/// 현재 배경 씬 언로드
/// </summary>
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);
}
/// <summary>
/// 씬 로드 취소
/// </summary>
public void CancelLoad()
{
if (_currentOperation != null)
{
// Unity에서는 씬 로드 취소가 직접적으로 불가능
// 대신 로드 완료 후 즉시 언로드
_isLoading = false;
}
}
}
}