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