- Notion 동기화 기능 추가: - NotionSyncSettings.cs: Notion API 설정 ScriptableObject - NotionBackgroundSync.cs: Notion API 연동 및 동기화 윈도우 - 배경 씬 정보를 Notion 데이터베이스에 자동 동기화 - Git Raw URL을 통한 썸네일 이미지 연동 - Git 커밋 상태 확인 및 경고 표시 - 배경 씬 로더 버그 수정: - 리컴파일 후 배경 씬 중복 로드 문제 해결 - OnFocus 콜백으로 상태 동기화 강화 - 중복 씬 자동 감지 및 언로드 - 썸네일 캡처 개선: - 기본 해상도 1920x1080 (16:9) - 에디터에서 1:1 중앙 크롭 표시 - 캡처 후 자동 Notion 동기화 옵션 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
470 lines
16 KiB
C#
470 lines
16 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;
|
|
}
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
// 시작 시 현재 상태 동기화
|
|
SyncCurrentSceneStatus();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 로드된 배경 씬 상태 동기화 (리컴파일/재시작 후 복원용)
|
|
/// </summary>
|
|
public void SyncCurrentSceneStatus()
|
|
{
|
|
if (sceneDatabase == null) return;
|
|
|
|
var loadedBackgroundScenes = new List<Scene>();
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 특정 씬이 이미 로드되어 있는지 확인
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 로드된 모든 배경 씬 언로드
|
|
/// </summary>
|
|
private void UnloadAllBackgroundScenesImmediate()
|
|
{
|
|
if (sceneDatabase == null) return;
|
|
|
|
var scenesToUnload = new List<Scene>();
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
|
|
// 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<Scene>();
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 씬 로드 취소
|
|
/// </summary>
|
|
public void CancelLoad()
|
|
{
|
|
if (_currentOperation != null)
|
|
{
|
|
// Unity에서는 씬 로드 취소가 직접적으로 불가능
|
|
// 대신 로드 완료 후 즉시 언로드
|
|
_isLoading = false;
|
|
}
|
|
}
|
|
}
|
|
}
|