using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.SceneManagement; namespace Streamingle.Background.Editor { /// /// 원래 라이팅 세팅을 저장하는 클래스 /// [Serializable] public class OriginalLightingSettings { // RenderSettings public Material skybox; public Light sun; public AmbientMode ambientMode; public Color ambientSkyColor; public Color ambientEquatorColor; public Color ambientGroundColor; public Color ambientLight; public float ambientIntensity; public Color subtractiveShadowColor; // Fog public bool fog; public Color fogColor; public FogMode fogMode; public float fogDensity; public float fogStartDistance; public float fogEndDistance; // 원래 씬의 라이트/볼륨 활성 상태 public List originalLights = new List(); public List originalVolumes = new List(); public List originalDirectionalLights = new List(); public List originalNiloToonOverriders = new List(); [Serializable] public class LightBackup { public Light light; public bool wasEnabled; } [Serializable] public class VolumeBackup { public Volume volume; public bool wasEnabled; } [Serializable] public class DirectionalLightBackup { public Light light; public bool wasEnabled; } [Serializable] public class ComponentBackup { public MonoBehaviour component; public bool wasEnabled; } public void Capture(Scene scene) { // RenderSettings 저장 skybox = RenderSettings.skybox; sun = RenderSettings.sun; ambientMode = RenderSettings.ambientMode; ambientSkyColor = RenderSettings.ambientSkyColor; ambientEquatorColor = RenderSettings.ambientEquatorColor; ambientGroundColor = RenderSettings.ambientGroundColor; ambientLight = RenderSettings.ambientLight; ambientIntensity = RenderSettings.ambientIntensity; subtractiveShadowColor = RenderSettings.subtractiveShadowColor; // Fog fog = RenderSettings.fog; fogColor = RenderSettings.fogColor; fogMode = RenderSettings.fogMode; fogDensity = RenderSettings.fogDensity; fogStartDistance = RenderSettings.fogStartDistance; fogEndDistance = RenderSettings.fogEndDistance; // 씬의 라이트/볼륨 상태 저장 originalLights.Clear(); originalVolumes.Clear(); originalDirectionalLights.Clear(); originalNiloToonOverriders.Clear(); var roots = scene.GetRootGameObjects(); foreach (var root in roots) { // [Background Lighting]은 제외 if (root.name == "[Background Lighting]") continue; var lights = root.GetComponentsInChildren(true); foreach (var light in lights) { originalLights.Add(new LightBackup { light = light, wasEnabled = light.enabled }); // Directional Light 별도 저장 if (light.type == LightType.Directional) { originalDirectionalLights.Add(new DirectionalLightBackup { light = light, wasEnabled = light.enabled }); } } var volumes = root.GetComponentsInChildren(true); foreach (var volume in volumes) { originalVolumes.Add(new VolumeBackup { volume = volume, wasEnabled = volume.enabled }); } // 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 }); } } } /// /// 배경 씬 로드 시 기존 씬의 Directional Light와 NiloToonOverrider 비활성화 /// public void DisableOriginalLighting() { // Directional Light 비활성화 foreach (var backup in originalDirectionalLights) { if (backup.light != null) { backup.light.enabled = false; } } // NiloToonCharacterMainLightOverrider 비활성화 foreach (var backup in originalNiloToonOverriders) { if (backup.component != null) { backup.component.enabled = false; } } int lightCount = originalDirectionalLights.Count(b => b.light != null); int overriderCount = originalNiloToonOverriders.Count(b => b.component != null); if (lightCount > 0 || overriderCount > 0) { UnityEngine.Debug.Log($"기존 씬 라이팅 비활성화: Directional Light {lightCount}개, NiloToonOverrider {overriderCount}개"); } } /// /// 배경 씬 언로드 시 기존 씬의 Directional Light와 NiloToonOverrider 복원 /// public void RestoreOriginalLighting() { // Directional Light 복원 foreach (var backup in originalDirectionalLights) { if (backup.light != null) { backup.light.enabled = backup.wasEnabled; } } // NiloToonCharacterMainLightOverrider 복원 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($"기존 씬 라이팅 복원: Directional Light {lightCount}개, NiloToonOverrider {overriderCount}개"); } } public void Restore() { // RenderSettings 복원 RenderSettings.skybox = skybox; RenderSettings.sun = sun; RenderSettings.ambientMode = ambientMode; RenderSettings.ambientSkyColor = ambientSkyColor; RenderSettings.ambientEquatorColor = ambientEquatorColor; RenderSettings.ambientGroundColor = ambientGroundColor; RenderSettings.ambientLight = ambientLight; RenderSettings.ambientIntensity = ambientIntensity; RenderSettings.subtractiveShadowColor = subtractiveShadowColor; // Fog 복원 RenderSettings.fog = fog; RenderSettings.fogColor = fogColor; RenderSettings.fogMode = fogMode; RenderSettings.fogDensity = fogDensity; RenderSettings.fogStartDistance = fogStartDistance; RenderSettings.fogEndDistance = fogEndDistance; // 라이트/볼륨 상태 복원 foreach (var backup in originalLights) { if (backup.light != null) { backup.light.enabled = backup.wasEnabled; } } foreach (var backup in originalVolumes) { if (backup.volume != null) { backup.volume.enabled = backup.wasEnabled; } } // Directional Light와 NiloToonOverrider 복원 RestoreOriginalLighting(); } } /// /// 배경 씬 로더 에디터 윈도우 /// public class BackgroundSceneLoaderWindow : EditorWindow { private const string BACKGROUND_PATH = "Assets/ResourcesData/Background"; private const string DATABASE_PATH = "Assets/Resources/Settings/BackgroundSceneDatabase.asset"; private const int THUMBNAIL_SIZE = 128; private const int GRID_PADDING = 10; private BackgroundSceneDatabase _database; private Dictionary> _categorizedScenes; private Dictionary _categoryFoldouts = new Dictionary(); private Dictionary _thumbnailCache = new Dictionary(); private Vector2 _scrollPosition; private string _searchFilter = ""; private string _selectedCategory = "전체"; private int _viewMode = 0; // 0: 그리드, 1: 리스트 private bool _isLoading; private float _loadProgress; private Scene? _loadedBackgroundScene; private string _currentSceneName = "없음"; // 라이팅 옵션 private bool _copyLighting = true; private bool _copySkybox = true; private bool _copyAmbient = true; private bool _copyFog = true; private bool _showLightingOptions = false; // 원래 라이팅 세팅 백업 private OriginalLightingSettings _originalLightingSettings = new OriginalLightingSettings(); private bool _hasBackup = false; private bool _autoRestoreOnUnload = true; private static readonly string[] VIEW_MODE_OPTIONS = { "그리드", "리스트" }; private static readonly Color SELECTED_COLOR = new Color(0.3f, 0.6f, 1f, 0.3f); private static readonly Color HOVER_COLOR = new Color(1f, 1f, 1f, 0.1f); [MenuItem("Streamingle/Background Scene Loader %#b")] public static void ShowWindow() { var window = GetWindow("배경 씬 로더"); window.minSize = new Vector2(400, 500); window.Show(); } private void OnEnable() { LoadOrCreateDatabase(); EditorApplication.playModeStateChanged += OnPlayModeStateChanged; UnityEditor.SceneManagement.EditorSceneManager.sceneOpened += OnSceneOpened; UnityEditor.SceneManagement.EditorSceneManager.sceneClosed += OnSceneClosed; // 리컴파일 후에도 현재 상태 복원 UpdateCurrentSceneStatus(); } private void OnFocus() { // 윈도우가 포커스될 때마다 상태 동기화 UpdateCurrentSceneStatus(); Repaint(); } private void OnDisable() { EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; UnityEditor.SceneManagement.EditorSceneManager.sceneOpened -= OnSceneOpened; UnityEditor.SceneManagement.EditorSceneManager.sceneClosed -= OnSceneClosed; ClearThumbnailCache(); } private void OnSceneOpened(Scene scene, UnityEditor.SceneManagement.OpenSceneMode mode) { // 씬이 열릴 때 현재 씬 상태 업데이트 UpdateCurrentSceneStatus(); // Background 폴더의 씬이면 데이터베이스에 있는지 확인하고 없으면 갱신 if (!string.IsNullOrEmpty(scene.path) && scene.path.Replace("\\", "/").Contains(BACKGROUND_PATH)) { var sceneInfo = _database?.FindByPath(scene.path); if (sceneInfo == null) { // 데이터베이스에 없으면 씬 목록 갱신 RefreshSceneList(); } } Repaint(); } private void OnSceneClosed(Scene scene) { UpdateCurrentSceneStatus(); Repaint(); } private void OnPlayModeStateChanged(PlayModeStateChange state) { if (state == PlayModeStateChange.EnteredEditMode || state == PlayModeStateChange.EnteredPlayMode) { // 씬 상태 업데이트 UpdateCurrentSceneStatus(); Repaint(); } } private void LoadOrCreateDatabase() { _database = AssetDatabase.LoadAssetAtPath(DATABASE_PATH); if (_database == null) { // Resources/Settings 폴더 확인/생성 string dirPath = Path.GetDirectoryName(DATABASE_PATH); if (!Directory.Exists(dirPath)) { Directory.CreateDirectory(dirPath); } _database = CreateInstance(); AssetDatabase.CreateAsset(_database, DATABASE_PATH); AssetDatabase.SaveAssets(); } RefreshSceneList(); } private void RefreshSceneList() { if (_database == null) return; _database.scenes.Clear(); if (!Directory.Exists(BACKGROUND_PATH)) { UnityEngine.Debug.LogWarning($"배경 폴더가 존재하지 않습니다: {BACKGROUND_PATH}"); return; } // Background 하위 폴더들 검색 var backgroundFolders = Directory.GetDirectories(BACKGROUND_PATH); foreach (var folderPath in backgroundFolders) { string folderName = Path.GetFileName(folderPath); string sceneFolderPath = Path.Combine(folderPath, "Scene"); if (!Directory.Exists(sceneFolderPath)) { // Scene 폴더가 없으면 루트에서 .unity 파일 검색 sceneFolderPath = folderPath; } // .unity 파일 검색 (재귀적으로) var sceneFiles = Directory.GetFiles(sceneFolderPath, "*.unity", SearchOption.AllDirectories); foreach (var sceneFile in sceneFiles) { // SkyBox 폴더의 씬은 제외 (스카이박스 참조용) if (sceneFile.Contains("SkyBox")) continue; string assetPath = sceneFile.Replace("\\", "/"); string sceneName = Path.GetFileNameWithoutExtension(sceneFile); string sceneDir = Path.GetDirectoryName(sceneFile); // 썸네일 경로 (같은 폴더에 [씬이름].png 또는 [씬이름].jpg) string thumbnailPath = FindThumbnail(sceneDir, sceneName); var sceneInfo = new BackgroundSceneInfo { sceneName = sceneName, scenePath = assetPath, categoryName = folderName, thumbnailPath = thumbnailPath }; _database.scenes.Add(sceneInfo); } } EditorUtility.SetDirty(_database); AssetDatabase.SaveAssets(); _categorizedScenes = _database.GetScenesByCategory(); // 카테고리 foldout 초기화 foreach (var category in _categorizedScenes.Keys) { if (!_categoryFoldouts.ContainsKey(category)) { _categoryFoldouts[category] = true; } } UnityEngine.Debug.Log($"배경 씬 {_database.scenes.Count}개를 로드했습니다."); } private string FindThumbnail(string directory, string sceneName) { string[] extensions = { ".png", ".jpg", ".jpeg" }; foreach (var ext in extensions) { string thumbnailPath = Path.Combine(directory, sceneName + ext); if (File.Exists(thumbnailPath)) { return thumbnailPath.Replace("\\", "/"); } } return null; } private Texture2D GetThumbnail(BackgroundSceneInfo sceneInfo) { if (string.IsNullOrEmpty(sceneInfo.thumbnailPath)) { return null; } if (_thumbnailCache.TryGetValue(sceneInfo.thumbnailPath, out var cached)) { return cached; } var texture = AssetDatabase.LoadAssetAtPath(sceneInfo.thumbnailPath); if (texture != null) { _thumbnailCache[sceneInfo.thumbnailPath] = texture; } return texture; } private void ClearThumbnailCache() { _thumbnailCache.Clear(); } /// /// 16:9 (또는 다른 비율) 이미지를 1:1로 중앙 크롭하여 표시 /// private void DrawCroppedThumbnail(Rect displayRect, Texture2D texture) { if (texture == null) return; float textureAspect = (float)texture.width / texture.height; // 정사각형이거나 세로가 더 긴 경우 기존 방식 if (textureAspect <= 1.0f) { GUI.DrawTexture(displayRect, texture, ScaleMode.ScaleToFit); return; } // 16:9 등 가로가 더 긴 경우: 중앙에서 정사각형으로 크롭 // UV 좌표 계산: 세로는 전체, 가로는 중앙 부분만 float cropWidth = (float)texture.height / texture.width; // 정사각형 비율 float uvOffsetX = (1f - cropWidth) / 2f; // 중앙 정렬 // texCoords: (x, y, width, height) - UV 공간에서의 영역 Rect texCoords = new Rect(uvOffsetX, 0f, cropWidth, 1f); GUI.DrawTextureWithTexCoords(displayRect, texture, texCoords); } /// /// 특정 썸네일 캐시 무효화 및 새로고침 /// public void RefreshThumbnail(string thumbnailPath) { if (string.IsNullOrEmpty(thumbnailPath)) return; // 캐시에서 제거 if (_thumbnailCache.ContainsKey(thumbnailPath)) { // 기존 텍스처 언로드 var oldTexture = _thumbnailCache[thumbnailPath]; if (oldTexture != null) { Resources.UnloadAsset(oldTexture); } _thumbnailCache.Remove(thumbnailPath); } // 텍스처 임포터 설정으로 강제 재임포트 var importer = AssetImporter.GetAtPath(thumbnailPath); if (importer != null) { importer.SaveAndReimport(); } else { // 임포터가 없으면 일반 방식 사용 AssetDatabase.ImportAsset(thumbnailPath, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport); } // 새로 로드 var texture = AssetDatabase.LoadAssetAtPath(thumbnailPath); if (texture != null) { _thumbnailCache[thumbnailPath] = texture; } Repaint(); } /// /// 모든 썸네일 새로고침 /// public void RefreshAllThumbnails() { // 기존 캐시의 텍스처들 언로드 foreach (var kvp in _thumbnailCache) { if (kvp.Value != null) { Resources.UnloadAsset(kvp.Value); } } _thumbnailCache.Clear(); // 모든 썸네일 경로를 수집하여 강제 재임포트 if (_database != null) { AssetDatabase.StartAssetEditing(); try { foreach (var scene in _database.scenes) { if (!string.IsNullOrEmpty(scene.thumbnailPath)) { var importer = AssetImporter.GetAtPath(scene.thumbnailPath); if (importer != null) { // dirty flag 설정하여 재임포트 강제 EditorUtility.SetDirty(importer); } AssetDatabase.ImportAsset(scene.thumbnailPath, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport); } } } finally { AssetDatabase.StopAssetEditing(); } } // 에셋 데이터베이스 전체 리프레시 AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate); Repaint(); UnityEngine.Debug.Log("모든 썸네일이 새로고침되었습니다."); } private void OnGUI() { DrawToolbar(); DrawContent(); DrawStatusBar(); } private void DrawToolbar() { EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); // 새로고침 버튼 if (GUILayout.Button(EditorGUIUtility.IconContent("Refresh"), EditorStyles.toolbarButton, GUILayout.Width(30))) { RefreshSceneList(); RefreshAllThumbnails(); } // 검색 필드 GUILayout.Label("검색:", GUILayout.Width(35)); _searchFilter = EditorGUILayout.TextField(_searchFilter, EditorStyles.toolbarSearchField, GUILayout.Width(150)); if (GUILayout.Button("X", EditorStyles.toolbarButton, GUILayout.Width(20))) { _searchFilter = ""; GUI.FocusControl(null); } GUILayout.FlexibleSpace(); // 카테고리 필터 GUILayout.Label("카테고리:", GUILayout.Width(55)); var categories = new List { "전체" }; if (_categorizedScenes != null) { categories.AddRange(_categorizedScenes.Keys.OrderBy(x => x)); } int selectedIndex = categories.IndexOf(_selectedCategory); if (selectedIndex < 0) selectedIndex = 0; selectedIndex = EditorGUILayout.Popup(selectedIndex, categories.ToArray(), EditorStyles.toolbarPopup, GUILayout.Width(100)); _selectedCategory = categories[selectedIndex]; // 뷰 모드 _viewMode = GUILayout.Toolbar(_viewMode, VIEW_MODE_OPTIONS, EditorStyles.toolbarButton, GUILayout.Width(100)); // 빌드 세팅 추가 버튼 if (GUILayout.Button("빌드 세팅", EditorStyles.toolbarButton, GUILayout.Width(70))) { ShowBuildSettingsMenu(); } EditorGUILayout.EndHorizontal(); } private void ShowBuildSettingsMenu() { var menu = new GenericMenu(); menu.AddItem(new GUIContent("모든 배경 씬 빌드 세팅에 추가"), false, AddAllScenesToBuildSettings); menu.AddItem(new GUIContent("빌드 세팅에 없는 씬만 추가"), false, AddMissingScenesToBuildSettings); menu.AddSeparator(""); menu.AddItem(new GUIContent("빌드 세팅에서 배경 씬 제거"), false, RemoveAllScenesFromBuildSettings); menu.AddSeparator(""); menu.AddItem(new GUIContent("빌드 세팅 열기"), false, () => EditorWindow.GetWindow(System.Type.GetType("UnityEditor.BuildPlayerWindow,UnityEditor"))); menu.ShowAsContext(); } private void AddAllScenesToBuildSettings() { if (_database == null || _database.scenes.Count == 0) { EditorUtility.DisplayDialog("오류", "추가할 배경 씬이 없습니다.", "확인"); return; } var currentScenes = EditorBuildSettings.scenes.ToList(); int addedCount = 0; foreach (var sceneInfo in _database.scenes) { // 이미 있는지 확인 bool exists = currentScenes.Any(s => s.path == sceneInfo.scenePath); if (!exists) { currentScenes.Add(new EditorBuildSettingsScene(sceneInfo.scenePath, true)); addedCount++; } } EditorBuildSettings.scenes = currentScenes.ToArray(); EditorUtility.DisplayDialog("완료", $"빌드 세팅에 {addedCount}개의 배경 씬이 추가되었습니다.\n" + $"(총 {_database.scenes.Count}개 중 {addedCount}개 새로 추가)", "확인"); UnityEngine.Debug.Log($"빌드 세팅에 배경 씬 {addedCount}개 추가됨"); } private void AddMissingScenesToBuildSettings() { if (_database == null || _database.scenes.Count == 0) { EditorUtility.DisplayDialog("오류", "추가할 배경 씬이 없습니다.", "확인"); return; } var currentScenes = EditorBuildSettings.scenes.ToList(); int addedCount = 0; foreach (var sceneInfo in _database.scenes) { bool exists = currentScenes.Any(s => s.path == sceneInfo.scenePath); if (!exists) { currentScenes.Add(new EditorBuildSettingsScene(sceneInfo.scenePath, true)); addedCount++; } } if (addedCount == 0) { EditorUtility.DisplayDialog("완료", "모든 배경 씬이 이미 빌드 세팅에 있습니다.", "확인"); return; } EditorBuildSettings.scenes = currentScenes.ToArray(); EditorUtility.DisplayDialog("완료", $"빌드 세팅에 {addedCount}개의 배경 씬이 추가되었습니다.", "확인"); UnityEngine.Debug.Log($"빌드 세팅에 배경 씬 {addedCount}개 추가됨"); } private void RemoveAllScenesFromBuildSettings() { if (_database == null || _database.scenes.Count == 0) { EditorUtility.DisplayDialog("오류", "제거할 배경 씬이 없습니다.", "확인"); return; } if (!EditorUtility.DisplayDialog("확인", "빌드 세팅에서 모든 배경 씬을 제거하시겠습니까?\n" + "이 작업은 런타임에서 배경 씬 로드를 불가능하게 만듭니다.", "제거", "취소")) { return; } var backgroundPaths = new HashSet(_database.scenes.Select(s => s.scenePath)); var currentScenes = EditorBuildSettings.scenes.ToList(); int removedCount = currentScenes.RemoveAll(s => backgroundPaths.Contains(s.path)); EditorBuildSettings.scenes = currentScenes.ToArray(); EditorUtility.DisplayDialog("완료", $"빌드 세팅에서 {removedCount}개의 배경 씬이 제거되었습니다.", "확인"); UnityEngine.Debug.Log($"빌드 세팅에서 배경 씬 {removedCount}개 제거됨"); } private void DrawContent() { if (_database == null || _categorizedScenes == null) { EditorGUILayout.HelpBox("데이터베이스를 로드하는 중...", MessageType.Info); return; } if (_database.scenes.Count == 0) { EditorGUILayout.HelpBox("배경 씬이 없습니다.\n새로고침 버튼을 눌러 씬을 검색하세요.", MessageType.Warning); return; } _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); var filteredScenes = GetFilteredScenes(); if (_viewMode == 0) { DrawGridView(filteredScenes); } else { DrawListView(filteredScenes); } EditorGUILayout.EndScrollView(); } private Dictionary> GetFilteredScenes() { var result = new Dictionary>(); foreach (var kvp in _categorizedScenes) { // 카테고리 필터 if (_selectedCategory != "전체" && kvp.Key != _selectedCategory) continue; var scenes = kvp.Value; // 검색 필터 if (!string.IsNullOrEmpty(_searchFilter)) { scenes = scenes.Where(s => s.sceneName.IndexOf(_searchFilter, StringComparison.OrdinalIgnoreCase) >= 0 || s.categoryName.IndexOf(_searchFilter, StringComparison.OrdinalIgnoreCase) >= 0 ).ToList(); } if (scenes.Count > 0) { result[kvp.Key] = scenes; } } return result; } private void DrawGridView(Dictionary> scenes) { float windowWidth = position.width - 30; float itemWidth = THUMBNAIL_SIZE + GRID_PADDING; float itemHeight = THUMBNAIL_SIZE + 30; int columnsCount = Mathf.Max(1, (int)(windowWidth / itemWidth)); foreach (var kvp in scenes.OrderBy(x => x.Key)) { string category = kvp.Key; var sceneList = kvp.Value; if (!_categoryFoldouts.ContainsKey(category)) _categoryFoldouts[category] = true; // 카테고리 헤더 _categoryFoldouts[category] = EditorGUILayout.Foldout(_categoryFoldouts[category], $"{category} ({sceneList.Count})", true, EditorStyles.foldoutHeader); if (!_categoryFoldouts[category]) continue; // 그리드 영역 계산 int rowCount = Mathf.CeilToInt((float)sceneList.Count / columnsCount); float gridHeight = rowCount * itemHeight; // 그리드 영역 확보 Rect gridRect = GUILayoutUtility.GetRect(windowWidth, gridHeight); gridRect.x += GRID_PADDING; // 아이템 그리기 for (int i = 0; i < sceneList.Count; i++) { int row = i / columnsCount; int col = i % columnsCount; Rect itemRect = new Rect( gridRect.x + col * itemWidth, gridRect.y + row * itemHeight, THUMBNAIL_SIZE, itemHeight ); DrawGridItem(sceneList[i], itemRect); } GUILayout.Space(GRID_PADDING); } } private void DrawGridItem(BackgroundSceneInfo sceneInfo, Rect rect) { // 배경 bool isCurrentScene = _currentSceneName == sceneInfo.sceneName; if (isCurrentScene) { EditorGUI.DrawRect(rect, SELECTED_COLOR); } else if (rect.Contains(Event.current.mousePosition)) { EditorGUI.DrawRect(rect, HOVER_COLOR); Repaint(); } // 썸네일 var thumbnailRect = new Rect(rect.x + 2, rect.y + 2, THUMBNAIL_SIZE - 4, THUMBNAIL_SIZE - 4); var thumbnail = GetThumbnail(sceneInfo); if (thumbnail != null) { // 16:9 이미지를 1:1로 중앙 크롭하여 표시 DrawCroppedThumbnail(thumbnailRect, thumbnail); } else { EditorGUI.DrawRect(thumbnailRect, new Color(0.2f, 0.2f, 0.2f)); GUI.Label(thumbnailRect, "No\nThumbnail", new GUIStyle(EditorStyles.centeredGreyMiniLabel) { wordWrap = true, alignment = TextAnchor.MiddleCenter }); } // 씬 이름 var labelRect = new Rect(rect.x, rect.y + THUMBNAIL_SIZE, THUMBNAIL_SIZE, 25); GUI.Label(labelRect, sceneInfo.sceneName, new GUIStyle(EditorStyles.miniLabel) { alignment = TextAnchor.MiddleCenter, wordWrap = true, clipping = TextClipping.Clip }); // 클릭 이벤트 if (Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition)) { if (Event.current.clickCount == 2) { LoadScene(sceneInfo); } else if (Event.current.button == 1) { ShowContextMenu(sceneInfo); } Event.current.Use(); } } private void DrawListView(Dictionary> scenes) { foreach (var kvp in scenes.OrderBy(x => x.Key)) { string category = kvp.Key; var sceneList = kvp.Value; if (!_categoryFoldouts.ContainsKey(category)) _categoryFoldouts[category] = true; _categoryFoldouts[category] = EditorGUILayout.Foldout(_categoryFoldouts[category], $"{category} ({sceneList.Count})", true, EditorStyles.foldoutHeader); if (!_categoryFoldouts[category]) continue; EditorGUI.indentLevel++; foreach (var sceneInfo in sceneList) { DrawListItem(sceneInfo); } EditorGUI.indentLevel--; GUILayout.Space(5); } } private void DrawListItem(BackgroundSceneInfo sceneInfo) { EditorGUILayout.BeginHorizontal(); bool isCurrentScene = _currentSceneName == sceneInfo.sceneName; // 썸네일 (작게) var thumbnail = GetThumbnail(sceneInfo); var thumbRect = GUILayoutUtility.GetRect(40, 40, GUILayout.Width(40), GUILayout.Height(40)); if (thumbnail != null) { // 16:9 이미지를 1:1로 중앙 크롭하여 표시 DrawCroppedThumbnail(thumbRect, thumbnail); } else { EditorGUI.DrawRect(thumbRect, new Color(0.2f, 0.2f, 0.2f)); } // 씬 정보 EditorGUILayout.BeginVertical(); var style = isCurrentScene ? EditorStyles.boldLabel : EditorStyles.label; EditorGUILayout.LabelField(sceneInfo.sceneName, style); EditorGUILayout.LabelField(sceneInfo.categoryName, EditorStyles.miniLabel); EditorGUILayout.EndVertical(); GUILayout.FlexibleSpace(); // 로드 버튼 if (GUILayout.Button(isCurrentScene ? "로드됨" : "로드", GUILayout.Width(60))) { LoadScene(sceneInfo); } // 메뉴 버튼 if (GUILayout.Button("...", GUILayout.Width(25))) { ShowContextMenu(sceneInfo); } EditorGUILayout.EndHorizontal(); // 구분선 var lineRect = GUILayoutUtility.GetRect(1, 1); EditorGUI.DrawRect(lineRect, new Color(0.5f, 0.5f, 0.5f, 0.3f)); } private void ShowContextMenu(BackgroundSceneInfo sceneInfo) { var menu = new GenericMenu(); menu.AddItem(new GUIContent("로드 (Additive)"), false, () => LoadScene(sceneInfo, LoadSceneMode.Additive)); menu.AddItem(new GUIContent("로드 (Single)"), false, () => LoadScene(sceneInfo, LoadSceneMode.Single)); menu.AddSeparator(""); menu.AddItem(new GUIContent("프로젝트에서 선택"), false, () => SelectInProject(sceneInfo)); menu.AddItem(new GUIContent("폴더 열기"), false, () => RevealInFinder(sceneInfo)); menu.AddSeparator(""); menu.AddItem(new GUIContent("썸네일 생성"), false, () => CreateThumbnail(sceneInfo)); menu.ShowAsContext(); } private void LoadScene(BackgroundSceneInfo sceneInfo, LoadSceneMode mode = LoadSceneMode.Additive) { if (_isLoading) { UnityEngine.Debug.LogWarning("이미 씬을 로드 중입니다."); return; } // 현재 씬 저장 확인 if (!Application.isPlaying && EditorSceneManager.GetActiveScene().isDirty) { if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) { return; } } try { _isLoading = true; if (Application.isPlaying) { // 플레이 모드: 런타임 로더 사용 LoadSceneRuntime(sceneInfo, mode); } else { // 에디터 모드: EditorSceneManager 사용 LoadSceneEditor(sceneInfo, mode); } } catch (Exception ex) { UnityEngine.Debug.LogError($"씬 로드 실패: {ex.Message}"); } finally { _isLoading = false; UpdateCurrentSceneStatus(); Repaint(); } } private void LoadSceneEditor(BackgroundSceneInfo sceneInfo, LoadSceneMode mode) { // 1. 같은 씬이 이미 로드되어 있는지 확인 if (IsSceneAlreadyLoaded(sceneInfo.scenePath)) { UnityEngine.Debug.Log($"[BackgroundSceneLoader] 이미 로드된 씬입니다: {sceneInfo.sceneName}"); var existingScene = SceneManager.GetSceneByPath(sceneInfo.scenePath); _loadedBackgroundScene = existingScene; _currentSceneName = sceneInfo.sceneName; return; } // 2. Additive 모드일 때 기존 모든 배경 씬 언로드 if (mode == LoadSceneMode.Additive) { UnloadAllBackgroundScenes(); } // 3. 씬 로드 var openMode = mode == LoadSceneMode.Additive ? OpenSceneMode.Additive : OpenSceneMode.Single; var scene = EditorSceneManager.OpenScene(sceneInfo.scenePath, openMode); if (mode == LoadSceneMode.Additive) { _loadedBackgroundScene = scene; // 라이팅 복사 옵션이 켜져 있으면 자동으로 라이팅 복사 if (_copyLighting) { var activeScene = SceneManager.GetActiveScene(); if (activeScene.isLoaded && activeScene.path != scene.path) { // 백업이 없으면 현재 라이팅 세팅 백업 if (!_hasBackup) { _originalLightingSettings.Capture(activeScene); _hasBackup = true; UnityEngine.Debug.Log("원래 라이팅 세팅이 백업되었습니다."); } // 기존 씬의 Directional Light와 NiloToonOverrider 비활성화 _originalLightingSettings.DisableOriginalLighting(); CopyLightingElements(scene, activeScene); UnityEngine.Debug.Log($"라이팅 자동 복사됨: {sceneInfo.sceneName}"); } } } _currentSceneName = sceneInfo.sceneName; UnityEngine.Debug.Log($"배경 씬 로드됨: {sceneInfo.sceneName}"); } private void LoadSceneRuntime(BackgroundSceneInfo sceneInfo, LoadSceneMode mode) { // 1. 같은 씬이 이미 로드되어 있는지 확인 if (IsSceneAlreadyLoaded(sceneInfo.scenePath)) { UnityEngine.Debug.Log($"[BackgroundSceneLoader] [PlayMode] 이미 로드된 씬입니다: {sceneInfo.sceneName}"); var existingScene = SceneManager.GetSceneByPath(sceneInfo.scenePath); _loadedBackgroundScene = existingScene; _currentSceneName = sceneInfo.sceneName; return; } // 2. Additive 모드일 때 기존 모든 배경 씬 언로드 if (mode == LoadSceneMode.Additive) { UnloadAllBackgroundScenes(); } // 3. 라이팅 백업 및 비활성화 if (mode == LoadSceneMode.Additive && _copyLighting) { var activeScene = SceneManager.GetActiveScene(); if (activeScene.isLoaded) { if (!_hasBackup) { _originalLightingSettings.Capture(activeScene); _hasBackup = true; UnityEngine.Debug.Log("[PlayMode] 원래 라이팅 세팅이 백업되었습니다."); } _originalLightingSettings.DisableOriginalLighting(); } } // 씬이 빌드 세팅에 있는지 확인 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) { // 빌드 세팅에 있으면 일반 로드 SceneManager.LoadSceneAsync(sceneInfo.scenePath, mode); } else { // 빌드 세팅에 없으면 에디터 전용 로드 UnityEngine.Debug.Log($"[PlayMode] 빌드 세팅에 없는 씬을 에디터 모드로 로드: {sceneInfo.sceneName}"); EditorSceneManager.LoadSceneInPlayMode(sceneInfo.scenePath, new LoadSceneParameters(mode)); } // 씬 로드 완료 후 처리를 위한 콜백 등록 SceneManager.sceneLoaded += OnRuntimeSceneLoaded; _currentSceneName = sceneInfo.sceneName; } private void OnRuntimeSceneLoaded(Scene scene, LoadSceneMode mode) { // 한 번만 실행 SceneManager.sceneLoaded -= OnRuntimeSceneLoaded; // 로드된 씬이 배경 씬인지 확인 var sceneInfo = _database?.FindByPath(scene.path); if (sceneInfo != null) { _loadedBackgroundScene = scene; // 라이팅 복사 if (_copyLighting && mode == LoadSceneMode.Additive) { var activeScene = SceneManager.GetActiveScene(); if (activeScene.isLoaded && activeScene.path != scene.path) { CopyLightingElements(scene, activeScene); UnityEngine.Debug.Log($"[PlayMode] 라이팅 자동 복사됨: {sceneInfo.sceneName}"); } } UnityEngine.Debug.Log($"[PlayMode] 배경 씬 로드됨: {sceneInfo.sceneName}"); } Repaint(); } private void UpdateCurrentSceneStatus() { if (_database == 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 = _database.FindByPath(scene.path); if (sceneInfo != null) { loadedBackgroundScenes.Add(scene); } } // 배경 씬이 여러 개 로드된 경우 경고 (첫 번째만 유지) if (loadedBackgroundScenes.Count > 1) { UnityEngine.Debug.LogWarning($"[BackgroundSceneLoader] 중복 배경 씬 감지: {loadedBackgroundScenes.Count}개. 첫 번째만 유지합니다."); // 첫 번째를 제외하고 모두 언로드 for (int i = 1; i < loadedBackgroundScenes.Count; i++) { var duplicateScene = loadedBackgroundScenes[i]; UnityEngine.Debug.Log($"[BackgroundSceneLoader] 중복 씬 언로드: {duplicateScene.name}"); if (Application.isPlaying) { SceneManager.UnloadSceneAsync(duplicateScene); } else { EditorSceneManager.CloseScene(duplicateScene, true); } } } // 상태 업데이트 if (loadedBackgroundScenes.Count > 0) { var firstScene = loadedBackgroundScenes[0]; _loadedBackgroundScene = firstScene; _currentSceneName = _database.FindByPath(firstScene.path)?.sceneName ?? firstScene.name; } else { _loadedBackgroundScene = null; _currentSceneName = "없음"; } } /// /// 현재 로드된 모든 배경 씬 언로드 /// private void UnloadAllBackgroundScenes() { if (_database == 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 = _database.FindByPath(scene.path); if (sceneInfo != null) { scenesToUnload.Add(scene); } } foreach (var scene in scenesToUnload) { UnityEngine.Debug.Log($"[BackgroundSceneLoader] 기존 배경 씬 언로드: {scene.name}"); if (Application.isPlaying) { SceneManager.UnloadSceneAsync(scene); } else { EditorSceneManager.CloseScene(scene, true); } } } /// /// 특정 씬이 이미 로드되어 있는지 확인 /// 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 SelectInProject(BackgroundSceneInfo sceneInfo) { var asset = AssetDatabase.LoadAssetAtPath(sceneInfo.scenePath); if (asset != null) { Selection.activeObject = asset; EditorGUIUtility.PingObject(asset); } } private void RevealInFinder(BackgroundSceneInfo sceneInfo) { EditorUtility.RevealInFinder(sceneInfo.scenePath); } private void CreateThumbnail(BackgroundSceneInfo sceneInfo) { // 씬 로드 후 Game 뷰 캡처 EditorUtility.DisplayDialog("썸네일 생성", "씬을 로드한 후 Game 뷰에서 원하는 앵글을 설정하고\n" + "Streamingle > Capture Thumbnail 메뉴를 사용하세요.", "확인"); } private void DrawStatusBar() { // 라이팅 옵션 펼침 _showLightingOptions = EditorGUILayout.Foldout(_showLightingOptions, "라이팅 복사 옵션", true); if (_showLightingOptions) { EditorGUI.indentLevel++; EditorGUILayout.BeginVertical(EditorStyles.helpBox); _copyLighting = EditorGUILayout.Toggle("라이팅 복사 활성화", _copyLighting); GUI.enabled = _copyLighting; EditorGUI.indentLevel++; _copySkybox = EditorGUILayout.Toggle("Skybox", _copySkybox); _copyAmbient = EditorGUILayout.Toggle("Ambient (환경광)", _copyAmbient); _copyFog = EditorGUILayout.Toggle("Fog (안개)", _copyFog); EditorGUI.indentLevel--; GUI.enabled = true; // 자동 복원 옵션 _autoRestoreOnUnload = EditorGUILayout.Toggle("배경 언로드 시 자동 복원", _autoRestoreOnUnload); // 백업 상태 표시 if (_hasBackup) { EditorGUILayout.HelpBox("원래 라이팅 세팅이 백업되었습니다. '원래대로 복원' 버튼으로 복원할 수 있습니다.", MessageType.Info); } EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("현재 배경에서 라이팅 복사")) { CopyLightingFromCurrentBackground(); } GUI.enabled = _hasBackup; if (GUILayout.Button("원래대로 복원")) { RestoreOriginalLighting(); } GUI.enabled = true; EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); EditorGUI.indentLevel--; } EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); // 현재 씬 상태 GUILayout.Label($"현재 배경: {_currentSceneName}"); GUILayout.FlexibleSpace(); // 언로드 버튼 GUI.enabled = _loadedBackgroundScene.HasValue && !_isLoading; if (GUILayout.Button("배경 언로드", EditorStyles.toolbarButton)) { UnloadCurrentBackground(); } GUI.enabled = true; // 로딩 상태 if (_isLoading) { GUILayout.Label("로드 중..."); } EditorGUILayout.EndHorizontal(); } private void CopyLightingFromCurrentBackground() { if (!_loadedBackgroundScene.HasValue || !_loadedBackgroundScene.Value.isLoaded) { EditorUtility.DisplayDialog("오류", "배경 씬이 로드되지 않았습니다.", "확인"); return; } var activeScene = SceneManager.GetActiveScene(); if (!activeScene.isLoaded) { EditorUtility.DisplayDialog("오류", "활성 씬이 없습니다.", "확인"); return; } // 같은 씬인지 확인 if (activeScene.path == _loadedBackgroundScene.Value.path) { EditorUtility.DisplayDialog("오류", "배경 씬과 활성 씬이 동일합니다.", "확인"); return; } // 백업이 없으면 현재 라이팅 세팅 백업 if (!_hasBackup) { _originalLightingSettings.Capture(activeScene); _hasBackup = true; UnityEngine.Debug.Log("원래 라이팅 세팅이 백업되었습니다."); } CopyLightingElements(_loadedBackgroundScene.Value, activeScene); EditorUtility.DisplayDialog("완료", "라이팅 세팅이 복사되었습니다.\n'원래대로 복원' 버튼으로 원래 세팅을 복원할 수 있습니다.", "확인"); } private void CopyLightingElements(Scene sourceScene, Scene targetScene) { var sourceRoots = sourceScene.GetRootGameObjects(); // RenderSettings 복사를 위해 소스 씬을 임시로 활성 씬으로 설정 var previousActiveScene = SceneManager.GetActiveScene(); SceneManager.SetActiveScene(sourceScene); // Skybox 복사 if (_copySkybox) { // 먼저 RenderSettings.skybox 확인 if (RenderSettings.skybox != null) { var sourceSkybox = RenderSettings.skybox; SceneManager.SetActiveScene(targetScene); RenderSettings.skybox = sourceSkybox; SceneManager.SetActiveScene(sourceScene); UnityEngine.Debug.Log($"Skybox 복사됨: {sourceSkybox.name}"); } else { // 카메라의 Skybox 컴포넌트 확인 (fallback) foreach (var root in sourceRoots) { var cameras = root.GetComponentsInChildren(true); foreach (var cam in cameras) { var skyboxComp = cam.GetComponent(); if (skyboxComp != null && skyboxComp.material != null) { SceneManager.SetActiveScene(targetScene); RenderSettings.skybox = skyboxComp.material; SceneManager.SetActiveScene(sourceScene); UnityEngine.Debug.Log($"Skybox 복사됨 (카메라): {skyboxComp.material.name}"); break; } } } } } // Ambient 복사 if (_copyAmbient) { var ambientMode = RenderSettings.ambientMode; var ambientSkyColor = RenderSettings.ambientSkyColor; var ambientEquatorColor = RenderSettings.ambientEquatorColor; var ambientGroundColor = RenderSettings.ambientGroundColor; var ambientLight = RenderSettings.ambientLight; var ambientIntensity = RenderSettings.ambientIntensity; var subtractiveShadowColor = RenderSettings.subtractiveShadowColor; SceneManager.SetActiveScene(targetScene); RenderSettings.ambientMode = ambientMode; RenderSettings.ambientSkyColor = ambientSkyColor; RenderSettings.ambientEquatorColor = ambientEquatorColor; RenderSettings.ambientGroundColor = ambientGroundColor; RenderSettings.ambientLight = ambientLight; RenderSettings.ambientIntensity = ambientIntensity; RenderSettings.subtractiveShadowColor = subtractiveShadowColor; SceneManager.SetActiveScene(sourceScene); UnityEngine.Debug.Log("Ambient 설정 복사됨"); } // Fog 복사 if (_copyFog) { var fog = RenderSettings.fog; var fogColor = RenderSettings.fogColor; var fogMode = RenderSettings.fogMode; var fogDensity = RenderSettings.fogDensity; var fogStartDistance = RenderSettings.fogStartDistance; var fogEndDistance = RenderSettings.fogEndDistance; SceneManager.SetActiveScene(targetScene); RenderSettings.fog = fog; RenderSettings.fogColor = fogColor; RenderSettings.fogMode = fogMode; RenderSettings.fogDensity = fogDensity; RenderSettings.fogStartDistance = fogStartDistance; RenderSettings.fogEndDistance = fogEndDistance; SceneManager.SetActiveScene(sourceScene); UnityEngine.Debug.Log($"Fog 설정 복사됨 (활성화: {fog})"); } // 원래 활성 씬으로 복원 SceneManager.SetActiveScene(previousActiveScene); UnityEngine.Debug.Log($"라이팅 복사됨: {sourceScene.name} → {targetScene.name}"); } private void RestoreOriginalLighting() { if (!_hasBackup) { EditorUtility.DisplayDialog("오류", "백업된 라이팅 세팅이 없습니다.", "확인"); return; } var activeScene = SceneManager.GetActiveScene(); if (!activeScene.isLoaded) { EditorUtility.DisplayDialog("오류", "활성 씬이 없습니다.", "확인"); return; } // 원래 RenderSettings 복원 _originalLightingSettings.Restore(); _hasBackup = false; EditorSceneManager.MarkSceneDirty(activeScene); UnityEngine.Debug.Log("원래 라이팅 세팅이 복원되었습니다."); EditorUtility.DisplayDialog("완료", "원래 라이팅 세팅이 복원되었습니다.", "확인"); } private void UnloadCurrentBackground() { if (!_loadedBackgroundScene.HasValue) return; try { // 백업이 있으면 Directional Light와 NiloToonOverrider 항상 복원 if (_hasBackup) { // 자동 복원 옵션이 켜져 있으면 RenderSettings도 복원 if (_autoRestoreOnUnload) { _originalLightingSettings.Restore(); UnityEngine.Debug.Log("배경 언로드: 원래 라이팅 세팅이 자동 복원되었습니다."); } else { // RenderSettings는 복원하지 않고 라이팅 컴포넌트만 복원 _originalLightingSettings.RestoreOriginalLighting(); } _hasBackup = false; // MarkSceneDirty는 플레이 모드에서 사용할 수 없음 if (!Application.isPlaying) { var activeScene = SceneManager.GetActiveScene(); if (activeScene.isLoaded) { EditorSceneManager.MarkSceneDirty(activeScene); } } } if (Application.isPlaying) { SceneManager.UnloadSceneAsync(_loadedBackgroundScene.Value); } else { EditorSceneManager.CloseScene(_loadedBackgroundScene.Value, true); } _loadedBackgroundScene = null; _currentSceneName = "없음"; Repaint(); } catch (Exception ex) { UnityEngine.Debug.LogError($"씬 언로드 실패: {ex.Message}"); } } } }