using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEditor.SceneManagement; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.UIElements; 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) { 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 = 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) { 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 }); 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 }); } 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 }); } } } public void DisableOriginalLighting() { foreach (var backup in originalDirectionalLights) if (backup.light != null) backup.light.enabled = false; 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) Debug.Log($"기존 씬 라이팅 비활성화: Directional Light {lightCount}개, NiloToonOverrider {overriderCount}개"); } public void RestoreOriginalLighting() { 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) Debug.Log($"기존 씬 라이팅 복원: Directional Light {lightCount}개, NiloToonOverrider {overriderCount}개"); } public void Restore() { 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; 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; RestoreOriginalLighting(); } } /// /// 배경 씬 로더 에디터 윈도우 (UI Toolkit) /// 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 BackgroundSceneDatabase _database; private Dictionary> _categorizedScenes; private Dictionary _categoryFoldouts = new Dictionary(); private Dictionary _thumbnailCache = new Dictionary(); private string _searchFilter = ""; private string _selectedCategory = "전체"; private int _viewMode = 0; // 0: 그리드, 1: 리스트 private bool _isLoading; private Scene? _loadedBackgroundScene; private string _currentSceneName = "없음"; // 라이팅 옵션 private bool _copyLighting = true; private bool _copySkybox = true; private bool _copyAmbient = true; private bool _copyFog = true; private bool _autoRestoreOnUnload = true; private OriginalLightingSettings _originalLightingSettings = new OriginalLightingSettings(); private bool _hasBackup = false; // UI 참조 private ToolbarSearchField _searchField; private DropdownField _categoryDropdown; private Button _gridBtn, _listBtn; private ScrollView _scrollView; private Label _statusLabel; private Button _unloadBtn; private Foldout _lightingFoldout; private Toggle _copyLightingToggle, _copySkyboxToggle, _copyAmbientToggle, _copyFogToggle, _autoRestoreToggle; private VisualElement _subToggles; private Label _backupInfoLabel; private Button _copyLightingBtn, _restoreBtn; [MenuItem("Streamingle/Background Scene Loader %#b")] public static void ShowWindow() { var window = GetWindow("배경 씬 로더"); window.minSize = new Vector2(400, 500); window.Show(); } public void CreateGUI() { var root = rootVisualElement; // 공용 USS 로드 var uss = AssetDatabase.LoadAssetAtPath( "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss"); if (uss != null) root.styleSheets.Add(uss); root.AddToClassList("tool-root"); root.style.paddingTop = 0; root.style.paddingBottom = 0; root.style.paddingLeft = 0; root.style.paddingRight = 0; LoadOrCreateDatabase(); BuildUI(root); RegisterEvents(); UpdateCurrentSceneStatus(); RebuildContent(); root.RegisterCallback(OnDetachFromPanel); } private static Button MakeBarButton(string text, System.Action onClick) { var btn = new Button(onClick) { text = text }; btn.style.height = 26; btn.style.paddingLeft = 14; btn.style.paddingRight = 14; btn.style.fontSize = 12; return btn; } private void BuildUI(VisualElement root) { // ── 검색 툴바 (Toolbar 사용 — ToolbarSearchField 정상 렌더링) ── var toolbar = new Toolbar(); toolbar.style.borderBottomWidth = 1; toolbar.style.borderBottomColor = new Color(1f, 1f, 1f, 0.06f); _searchField = new ToolbarSearchField(); _searchField.style.flexGrow = 1; _searchField.RegisterValueChangedCallback(OnSearchChanged); toolbar.Add(_searchField); var refreshBtn = new ToolbarButton(OnRefreshClicked) { text = "새로고침" }; toolbar.Add(refreshBtn); root.Add(toolbar); // ── 컨트롤 바 ── var controlsRow = new VisualElement(); controlsRow.AddToClassList("browser-controls-row"); controlsRow.style.minHeight = 38; var catLabel = new Label("카테고리"); catLabel.style.fontSize = 12; catLabel.style.marginRight = 4; controlsRow.Add(catLabel); _categoryDropdown = new DropdownField(); _categoryDropdown.style.minWidth = 120; _categoryDropdown.style.height = 26; _categoryDropdown.RegisterValueChangedCallback(OnCategoryChanged); controlsRow.Add(_categoryDropdown); controlsRow.Add(new VisualElement { style = { flexGrow = 1 } }); var viewGroup = new VisualElement(); viewGroup.AddToClassList("view-toggle-group"); viewGroup.style.height = 28; _gridBtn = new Button(() => SetViewMode(0)) { text = "그리드" }; _gridBtn.AddToClassList("view-btn"); _gridBtn.style.height = 24; viewGroup.Add(_gridBtn); _listBtn = new Button(() => SetViewMode(1)) { text = "리스트" }; _listBtn.AddToClassList("view-btn"); _listBtn.style.height = 24; viewGroup.Add(_listBtn); controlsRow.Add(viewGroup); root.Add(controlsRow); // ── 스크롤 콘텐츠 ── _scrollView = new ScrollView(ScrollViewMode.Vertical); _scrollView.AddToClassList("browser-scroll"); root.Add(_scrollView); // ── 라이팅 옵션 ── BuildLightingOptions(root); // ── 상태바 ── var statusBar = new VisualElement(); statusBar.AddToClassList("status-bar"); statusBar.style.minHeight = 40; _statusLabel = new Label("현재 배경: 없음"); _statusLabel.AddToClassList("status-label"); _statusLabel.style.fontSize = 12; statusBar.Add(_statusLabel); statusBar.Add(new VisualElement { style = { flexGrow = 1 } }); var buildBtn = MakeBarButton("빌드 세팅", ShowBuildSettingsMenu); statusBar.Add(buildBtn); _unloadBtn = MakeBarButton("배경 언로드", UnloadCurrentBackground); _unloadBtn.AddToClassList("btn-danger"); _unloadBtn.style.height = 26; statusBar.Add(_unloadBtn); root.Add(statusBar); UpdateCategoryDropdown(); UpdateViewModeButtons(); } private void BuildLightingOptions(VisualElement root) { _lightingFoldout = new Foldout { text = "라이팅 복사 옵션", value = false }; _lightingFoldout.AddToClassList("category-foldout"); var container = new VisualElement(); container.AddToClassList("lighting-options"); _copyLightingToggle = new Toggle("라이팅 복사 활성화") { value = _copyLighting }; _copyLightingToggle.RegisterValueChangedCallback(evt => { _copyLighting = evt.newValue; _subToggles.SetEnabled(_copyLighting); }); container.Add(_copyLightingToggle); _subToggles = new VisualElement(); _subToggles.AddToClassList("sub-toggles"); _copySkyboxToggle = new Toggle("Skybox") { value = _copySkybox }; _copySkyboxToggle.RegisterValueChangedCallback(evt => _copySkybox = evt.newValue); _subToggles.Add(_copySkyboxToggle); _copyAmbientToggle = new Toggle("Ambient (환경광)") { value = _copyAmbient }; _copyAmbientToggle.RegisterValueChangedCallback(evt => _copyAmbient = evt.newValue); _subToggles.Add(_copyAmbientToggle); _copyFogToggle = new Toggle("Fog (안개)") { value = _copyFog }; _copyFogToggle.RegisterValueChangedCallback(evt => _copyFog = evt.newValue); _subToggles.Add(_copyFogToggle); container.Add(_subToggles); _autoRestoreToggle = new Toggle("배경 언로드 시 자동 복원") { value = _autoRestoreOnUnload }; _autoRestoreToggle.RegisterValueChangedCallback(evt => _autoRestoreOnUnload = evt.newValue); container.Add(_autoRestoreToggle); _backupInfoLabel = new Label("원래 라이팅 세팅이 백업되었습니다. '원래대로 복원' 버튼으로 복원할 수 있습니다."); _backupInfoLabel.AddToClassList("backup-info"); _backupInfoLabel.style.display = DisplayStyle.None; container.Add(_backupInfoLabel); var btnRow = new VisualElement(); btnRow.AddToClassList("lighting-buttons"); _copyLightingBtn = new Button(CopyLightingFromCurrentBackground) { text = "현재 배경에서 라이팅 복사" }; _copyLightingBtn.AddToClassList("btn-primary"); btnRow.Add(_copyLightingBtn); _restoreBtn = new Button(RestoreOriginalLighting) { text = "원래대로 복원" }; btnRow.Add(_restoreBtn); container.Add(btnRow); _lightingFoldout.Add(container); root.Add(_lightingFoldout); } // ─── 이벤트 관리 ─── private void RegisterEvents() { EditorApplication.playModeStateChanged += OnPlayModeStateChanged; EditorSceneManager.sceneOpened += OnSceneOpened; EditorSceneManager.sceneClosed += OnSceneClosed; } private void OnDetachFromPanel(DetachFromPanelEvent evt) { EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; EditorSceneManager.sceneOpened -= OnSceneOpened; EditorSceneManager.sceneClosed -= OnSceneClosed; ClearThumbnailCache(); } private void OnSceneOpened(Scene scene, OpenSceneMode mode) { UpdateCurrentSceneStatus(); if (!string.IsNullOrEmpty(scene.path) && scene.path.Replace("\\", "/").Contains(BACKGROUND_PATH)) { var sceneInfo = _database?.FindByPath(scene.path); if (sceneInfo == null) RefreshSceneList(); } RebuildContent(); } private void OnSceneClosed(Scene scene) { UpdateCurrentSceneStatus(); RebuildContent(); } private void OnPlayModeStateChanged(PlayModeStateChange state) { if (state == PlayModeStateChange.EnteredEditMode || state == PlayModeStateChange.EnteredPlayMode) { UpdateCurrentSceneStatus(); RebuildContent(); } } private void OnRefreshClicked() { RefreshSceneList(); RefreshAllThumbnails(); UpdateCategoryDropdown(); RebuildContent(); } private void OnSearchChanged(ChangeEvent evt) { _searchFilter = evt.newValue; RebuildContent(); } private void OnCategoryChanged(ChangeEvent evt) { _selectedCategory = evt.newValue ?? "전체"; RebuildContent(); } private void SetViewMode(int mode) { _viewMode = mode; UpdateViewModeButtons(); RebuildContent(); } private void UpdateViewModeButtons() { _gridBtn.EnableInClassList("view-btn--active", _viewMode == 0); _listBtn.EnableInClassList("view-btn--active", _viewMode == 1); } private void UpdateCategoryDropdown() { var categories = new List { "전체" }; if (_categorizedScenes != null) categories.AddRange(_categorizedScenes.Keys.OrderBy(x => x)); _categoryDropdown.choices = categories; if (!categories.Contains(_selectedCategory)) _selectedCategory = "전체"; _categoryDropdown.SetValueWithoutNotify(_selectedCategory); } // ─── 콘텐츠 빌드 ─── private void RebuildContent() { if (_scrollView == null) return; _scrollView.Clear(); if (_database == null || _categorizedScenes == null) { _scrollView.Add(new Label("데이터베이스를 로드하는 중...") { pickingMode = PickingMode.Ignore }); _scrollView.Q