- BackgroundSceneLoaderWindow: OnGUI → CreateGUI (Toolbar + ToolbarSearchField) - PropBrowserWindow: OnGUI → CreateGUI (Toolbar + ToolbarSearchField) - StreamingleCommon.uss: 브라우저 공통 스타일 추가 (그리드/리스트/뷰토글/액션바/상태바) - excludeFromWeb 상태 새로고침 시 보존 수정 - 삭제된 배경 리소스 정리 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1432 lines
57 KiB
C#
1432 lines
57 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// 원래 라이팅 세팅을 저장하는 클래스
|
|
/// </summary>
|
|
[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<LightBackup> originalLights = new List<LightBackup>();
|
|
public List<VolumeBackup> originalVolumes = new List<VolumeBackup>();
|
|
public List<DirectionalLightBackup> originalDirectionalLights = new List<DirectionalLightBackup>();
|
|
public List<ComponentBackup> originalNiloToonOverriders = new List<ComponentBackup>();
|
|
|
|
[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<Light>(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<Volume>(true);
|
|
foreach (var volume in volumes)
|
|
{
|
|
originalVolumes.Add(new VolumeBackup { volume = volume, wasEnabled = volume.enabled });
|
|
}
|
|
|
|
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 });
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 배경 씬 로더 에디터 윈도우 (UI Toolkit)
|
|
/// </summary>
|
|
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<string, List<BackgroundSceneInfo>> _categorizedScenes;
|
|
private Dictionary<string, bool> _categoryFoldouts = new Dictionary<string, bool>();
|
|
private Dictionary<string, Texture2D> _thumbnailCache = new Dictionary<string, Texture2D>();
|
|
|
|
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<BackgroundSceneLoaderWindow>("배경 씬 로더");
|
|
window.minSize = new Vector2(400, 500);
|
|
window.Show();
|
|
}
|
|
|
|
public void CreateGUI()
|
|
{
|
|
var root = rootVisualElement;
|
|
|
|
// 공용 USS 로드
|
|
var uss = AssetDatabase.LoadAssetAtPath<StyleSheet>(
|
|
"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<DetachFromPanelEvent>(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<string> evt)
|
|
{
|
|
_searchFilter = evt.newValue;
|
|
RebuildContent();
|
|
}
|
|
|
|
private void OnCategoryChanged(ChangeEvent<string> 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<string> { "전체" };
|
|
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<Label>().AddToClassList("browser-empty");
|
|
return;
|
|
}
|
|
|
|
if (_database.scenes.Count == 0)
|
|
{
|
|
_scrollView.Add(new Label("배경 씬이 없습니다.\n새로고침 버튼을 눌러 씬을 검색하세요.") { pickingMode = PickingMode.Ignore });
|
|
_scrollView.Q<Label>().AddToClassList("browser-empty");
|
|
return;
|
|
}
|
|
|
|
var filteredScenes = GetFilteredScenes();
|
|
|
|
if (_viewMode == 0)
|
|
BuildGridView(filteredScenes);
|
|
else
|
|
BuildListView(filteredScenes);
|
|
|
|
UpdateStatusBar();
|
|
}
|
|
|
|
private Dictionary<string, List<BackgroundSceneInfo>> GetFilteredScenes()
|
|
{
|
|
var result = new Dictionary<string, List<BackgroundSceneInfo>>();
|
|
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 BuildGridView(Dictionary<string, List<BackgroundSceneInfo>> 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;
|
|
|
|
var foldout = new Foldout { text = $"{category} ({sceneList.Count})", value = _categoryFoldouts[category] };
|
|
foldout.AddToClassList("category-foldout");
|
|
foldout.RegisterValueChangedCallback(evt => _categoryFoldouts[category] = evt.newValue);
|
|
|
|
var gridContainer = new VisualElement();
|
|
gridContainer.AddToClassList("grid-container");
|
|
|
|
foreach (var sceneInfo in sceneList)
|
|
{
|
|
var item = CreateGridItem(sceneInfo);
|
|
gridContainer.Add(item);
|
|
}
|
|
|
|
foldout.Add(gridContainer);
|
|
_scrollView.Add(foldout);
|
|
}
|
|
}
|
|
|
|
private VisualElement CreateGridItem(BackgroundSceneInfo sceneInfo)
|
|
{
|
|
var item = new VisualElement();
|
|
item.AddToClassList("grid-item");
|
|
|
|
bool isCurrentScene = _currentSceneName == sceneInfo.sceneName;
|
|
item.EnableInClassList("grid-item--active", isCurrentScene);
|
|
|
|
// 썸네일
|
|
var thumbContainer = new VisualElement();
|
|
thumbContainer.AddToClassList("grid-thumbnail");
|
|
|
|
var thumbnail = GetThumbnail(sceneInfo);
|
|
if (thumbnail != null)
|
|
{
|
|
thumbContainer.style.backgroundImage = new StyleBackground(thumbnail);
|
|
}
|
|
else
|
|
{
|
|
var noThumb = new Label("No\nThumbnail");
|
|
noThumb.AddToClassList("no-thumbnail");
|
|
thumbContainer.Add(noThumb);
|
|
}
|
|
|
|
// 웹 제외 배지
|
|
if (sceneInfo.excludeFromWeb)
|
|
{
|
|
var badge = new Label("X");
|
|
badge.AddToClassList("exclude-badge");
|
|
thumbContainer.Add(badge);
|
|
}
|
|
|
|
item.Add(thumbContainer);
|
|
|
|
// 라벨
|
|
var label = new Label(sceneInfo.sceneName);
|
|
label.AddToClassList("grid-label");
|
|
item.Add(label);
|
|
|
|
// 더블클릭 → 로드
|
|
item.RegisterCallback<ClickEvent>(evt =>
|
|
{
|
|
if (evt.clickCount == 2)
|
|
{
|
|
LoadScene(sceneInfo);
|
|
evt.StopPropagation();
|
|
}
|
|
});
|
|
|
|
// 우클릭 → 컨텍스트 메뉴
|
|
item.AddManipulator(new ContextualMenuManipulator(menuEvt =>
|
|
{
|
|
menuEvt.menu.AppendAction("로드 (Additive)", _ => LoadScene(sceneInfo, LoadSceneMode.Additive));
|
|
menuEvt.menu.AppendAction("로드 (Single)", _ => LoadScene(sceneInfo, LoadSceneMode.Single));
|
|
menuEvt.menu.AppendSeparator();
|
|
menuEvt.menu.AppendAction("프로젝트에서 선택", _ => SelectInProject(sceneInfo));
|
|
menuEvt.menu.AppendAction("폴더 열기", _ => RevealInFinder(sceneInfo));
|
|
menuEvt.menu.AppendSeparator();
|
|
menuEvt.menu.AppendAction("썸네일 생성", _ => CreateThumbnail(sceneInfo));
|
|
menuEvt.menu.AppendSeparator();
|
|
menuEvt.menu.AppendAction("웹사이트 업로드 제외",
|
|
_ => ToggleExcludeFromWeb(sceneInfo),
|
|
sceneInfo.excludeFromWeb ? DropdownMenuAction.Status.Checked : DropdownMenuAction.Status.Normal);
|
|
}));
|
|
|
|
return item;
|
|
}
|
|
|
|
private void BuildListView(Dictionary<string, List<BackgroundSceneInfo>> 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;
|
|
|
|
var foldout = new Foldout { text = $"{category} ({sceneList.Count})", value = _categoryFoldouts[category] };
|
|
foldout.AddToClassList("category-foldout");
|
|
foldout.RegisterValueChangedCallback(evt => _categoryFoldouts[category] = evt.newValue);
|
|
|
|
foreach (var sceneInfo in sceneList)
|
|
{
|
|
var row = CreateListRow(sceneInfo);
|
|
foldout.Add(row);
|
|
}
|
|
|
|
_scrollView.Add(foldout);
|
|
}
|
|
}
|
|
|
|
private VisualElement CreateListRow(BackgroundSceneInfo sceneInfo)
|
|
{
|
|
bool isCurrentScene = _currentSceneName == sceneInfo.sceneName;
|
|
|
|
var row = new VisualElement();
|
|
row.AddToClassList("list-row");
|
|
row.EnableInClassList("list-row--active", isCurrentScene);
|
|
|
|
// 썸네일
|
|
var thumb = new VisualElement();
|
|
thumb.AddToClassList("list-thumbnail");
|
|
var thumbnail = GetThumbnail(sceneInfo);
|
|
if (thumbnail != null)
|
|
thumb.style.backgroundImage = new StyleBackground(thumbnail);
|
|
row.Add(thumb);
|
|
|
|
// 정보
|
|
var info = new VisualElement();
|
|
info.AddToClassList("list-row-info");
|
|
|
|
var nameRow = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center } };
|
|
var nameLabel = new Label(sceneInfo.sceneName);
|
|
nameLabel.AddToClassList("list-row-name");
|
|
nameLabel.EnableInClassList("list-row-name--active", isCurrentScene);
|
|
nameRow.Add(nameLabel);
|
|
|
|
if (sceneInfo.excludeFromWeb)
|
|
{
|
|
var excludeLabel = new Label("[제외]");
|
|
excludeLabel.style.color = new Color(0.9f, 0.3f, 0.3f);
|
|
excludeLabel.style.fontSize = 10;
|
|
excludeLabel.style.marginLeft = 6;
|
|
nameRow.Add(excludeLabel);
|
|
}
|
|
info.Add(nameRow);
|
|
|
|
var detailLabel = new Label(sceneInfo.categoryName);
|
|
detailLabel.AddToClassList("list-row-detail");
|
|
info.Add(detailLabel);
|
|
|
|
row.Add(info);
|
|
|
|
// 버튼
|
|
var buttons = new VisualElement();
|
|
buttons.AddToClassList("list-row-buttons");
|
|
|
|
var loadBtn = new Button(() => LoadScene(sceneInfo))
|
|
{
|
|
text = isCurrentScene ? "로드됨" : "로드"
|
|
};
|
|
if (isCurrentScene) loadBtn.AddToClassList("btn-loaded");
|
|
buttons.Add(loadBtn);
|
|
|
|
var menuBtn = new Button() { text = "..." };
|
|
buttons.Add(menuBtn);
|
|
menuBtn.AddManipulator(new ContextualMenuManipulator(menuEvt =>
|
|
{
|
|
menuEvt.menu.AppendAction("로드 (Additive)", _ => LoadScene(sceneInfo, LoadSceneMode.Additive));
|
|
menuEvt.menu.AppendAction("로드 (Single)", _ => LoadScene(sceneInfo, LoadSceneMode.Single));
|
|
menuEvt.menu.AppendSeparator();
|
|
menuEvt.menu.AppendAction("프로젝트에서 선택", _ => SelectInProject(sceneInfo));
|
|
menuEvt.menu.AppendAction("폴더 열기", _ => RevealInFinder(sceneInfo));
|
|
menuEvt.menu.AppendSeparator();
|
|
menuEvt.menu.AppendAction("썸네일 생성", _ => CreateThumbnail(sceneInfo));
|
|
menuEvt.menu.AppendSeparator();
|
|
menuEvt.menu.AppendAction("웹사이트 업로드 제외",
|
|
_ => ToggleExcludeFromWeb(sceneInfo),
|
|
sceneInfo.excludeFromWeb ? DropdownMenuAction.Status.Checked : DropdownMenuAction.Status.Normal);
|
|
}));
|
|
|
|
row.Add(buttons);
|
|
|
|
// 더블클릭 → 로드
|
|
row.RegisterCallback<ClickEvent>(evt =>
|
|
{
|
|
if (evt.clickCount == 2)
|
|
{
|
|
LoadScene(sceneInfo);
|
|
evt.StopPropagation();
|
|
}
|
|
});
|
|
|
|
return row;
|
|
}
|
|
|
|
private void UpdateStatusBar()
|
|
{
|
|
if (_statusLabel != null)
|
|
_statusLabel.text = $"현재 배경: {_currentSceneName}";
|
|
if (_unloadBtn != null)
|
|
_unloadBtn.SetEnabled(_loadedBackgroundScene.HasValue && !_isLoading);
|
|
if (_backupInfoLabel != null)
|
|
_backupInfoLabel.style.display = _hasBackup ? DisplayStyle.Flex : DisplayStyle.None;
|
|
if (_restoreBtn != null)
|
|
_restoreBtn.SetEnabled(_hasBackup);
|
|
}
|
|
|
|
// ─── 데이터베이스 / 씬 관리 (기존 로직 100% 유지) ───
|
|
|
|
private void LoadOrCreateDatabase()
|
|
{
|
|
_database = AssetDatabase.LoadAssetAtPath<BackgroundSceneDatabase>(DATABASE_PATH);
|
|
if (_database == null)
|
|
{
|
|
string dirPath = Path.GetDirectoryName(DATABASE_PATH);
|
|
if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);
|
|
_database = CreateInstance<BackgroundSceneDatabase>();
|
|
AssetDatabase.CreateAsset(_database, DATABASE_PATH);
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
RefreshSceneList();
|
|
}
|
|
|
|
private string FindSceneFolderWithCorrectCase(string parentFolder)
|
|
{
|
|
var subDirs = Directory.GetDirectories(parentFolder);
|
|
foreach (var dir in subDirs)
|
|
{
|
|
string dirName = Path.GetFileName(dir);
|
|
if (string.Equals(dirName, "Scene", StringComparison.OrdinalIgnoreCase))
|
|
return dir;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void RefreshSceneList()
|
|
{
|
|
if (_database == null) return;
|
|
|
|
// excludeFromWeb 상태 보존
|
|
var excludeMap = new Dictionary<string, bool>();
|
|
foreach (var scene in _database.scenes)
|
|
if (scene.excludeFromWeb)
|
|
excludeMap[scene.scenePath] = true;
|
|
|
|
_database.scenes.Clear();
|
|
|
|
if (!Directory.Exists(BACKGROUND_PATH))
|
|
{
|
|
Debug.LogWarning($"배경 폴더가 존재하지 않습니다: {BACKGROUND_PATH}");
|
|
return;
|
|
}
|
|
|
|
var backgroundFolders = Directory.GetDirectories(BACKGROUND_PATH);
|
|
foreach (var folderPath in backgroundFolders)
|
|
{
|
|
string folderName = Path.GetFileName(folderPath);
|
|
string sceneFolderPath = FindSceneFolderWithCorrectCase(folderPath) ?? folderPath;
|
|
|
|
var sceneFiles = Directory.GetFiles(sceneFolderPath, "*.unity", SearchOption.AllDirectories);
|
|
foreach (var sceneFile in sceneFiles)
|
|
{
|
|
if (sceneFile.Contains("SkyBox")) continue;
|
|
|
|
string assetPath = sceneFile.Replace("\\", "/");
|
|
string sceneName = Path.GetFileNameWithoutExtension(sceneFile);
|
|
string sceneDir = Path.GetDirectoryName(sceneFile);
|
|
string thumbnailPath = FindThumbnail(sceneDir, sceneName);
|
|
|
|
var info = new BackgroundSceneInfo
|
|
{
|
|
sceneName = sceneName,
|
|
scenePath = assetPath,
|
|
categoryName = folderName,
|
|
thumbnailPath = thumbnailPath
|
|
};
|
|
// 보존된 excludeFromWeb 상태 복원
|
|
if (excludeMap.ContainsKey(assetPath))
|
|
info.excludeFromWeb = true;
|
|
_database.scenes.Add(info);
|
|
}
|
|
}
|
|
|
|
EditorUtility.SetDirty(_database);
|
|
AssetDatabase.SaveAssets();
|
|
_categorizedScenes = _database.GetScenesByCategory();
|
|
|
|
foreach (var category in _categorizedScenes.Keys)
|
|
if (!_categoryFoldouts.ContainsKey(category))
|
|
_categoryFoldouts[category] = true;
|
|
|
|
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<Texture2D>(sceneInfo.thumbnailPath);
|
|
if (texture != null) _thumbnailCache[sceneInfo.thumbnailPath] = texture;
|
|
return texture;
|
|
}
|
|
|
|
private void ClearThumbnailCache() => _thumbnailCache.Clear();
|
|
|
|
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<Texture2D>(thumbnailPath);
|
|
if (texture != null) _thumbnailCache[thumbnailPath] = texture;
|
|
|
|
RebuildContent();
|
|
}
|
|
|
|
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) EditorUtility.SetDirty(importer);
|
|
AssetDatabase.ImportAsset(scene.thumbnailPath, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
|
|
}
|
|
}
|
|
}
|
|
finally { AssetDatabase.StopAssetEditing(); }
|
|
}
|
|
|
|
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
|
|
Debug.Log("모든 썸네일이 새로고침되었습니다.");
|
|
}
|
|
|
|
// ─── 씬 로드/언로드 (기존 로직 100% 유지) ───
|
|
|
|
private void ToggleExcludeFromWeb(BackgroundSceneInfo sceneInfo)
|
|
{
|
|
sceneInfo.excludeFromWeb = !sceneInfo.excludeFromWeb;
|
|
EditorUtility.SetDirty(_database);
|
|
AssetDatabase.SaveAssets();
|
|
Debug.Log($"[BackgroundSceneLoader] '{sceneInfo.sceneName}' 웹 업로드 {(sceneInfo.excludeFromWeb ? "제외됨" : "포함됨")}");
|
|
RebuildContent();
|
|
}
|
|
|
|
private void LoadScene(BackgroundSceneInfo sceneInfo, LoadSceneMode mode = LoadSceneMode.Additive)
|
|
{
|
|
if (_isLoading)
|
|
{
|
|
Debug.LogWarning("이미 씬을 로드 중입니다.");
|
|
return;
|
|
}
|
|
|
|
if (!Application.isPlaying && EditorSceneManager.GetActiveScene().isDirty)
|
|
{
|
|
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return;
|
|
}
|
|
|
|
try
|
|
{
|
|
_isLoading = true;
|
|
if (Application.isPlaying)
|
|
LoadSceneRuntime(sceneInfo, mode);
|
|
else
|
|
LoadSceneEditor(sceneInfo, mode);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"씬 로드 실패: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
_isLoading = false;
|
|
UpdateCurrentSceneStatus();
|
|
RebuildContent();
|
|
}
|
|
}
|
|
|
|
private void LoadSceneEditor(BackgroundSceneInfo sceneInfo, LoadSceneMode mode)
|
|
{
|
|
if (IsSceneAlreadyLoaded(sceneInfo.scenePath))
|
|
{
|
|
Debug.Log($"[BackgroundSceneLoader] 이미 로드된 씬입니다: {sceneInfo.sceneName}");
|
|
var existingScene = SceneManager.GetSceneByPath(sceneInfo.scenePath);
|
|
_loadedBackgroundScene = existingScene;
|
|
_currentSceneName = sceneInfo.sceneName;
|
|
return;
|
|
}
|
|
|
|
if (mode == LoadSceneMode.Additive) UnloadAllBackgroundScenes();
|
|
|
|
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;
|
|
Debug.Log("원래 라이팅 세팅이 백업되었습니다.");
|
|
}
|
|
_originalLightingSettings.DisableOriginalLighting();
|
|
CopyLightingElements(scene, activeScene);
|
|
Debug.Log($"라이팅 자동 복사됨: {sceneInfo.sceneName}");
|
|
}
|
|
}
|
|
}
|
|
|
|
_currentSceneName = sceneInfo.sceneName;
|
|
Debug.Log($"배경 씬 로드됨: {sceneInfo.sceneName}");
|
|
}
|
|
|
|
private void LoadSceneRuntime(BackgroundSceneInfo sceneInfo, LoadSceneMode mode)
|
|
{
|
|
if (IsSceneAlreadyLoaded(sceneInfo.scenePath))
|
|
{
|
|
Debug.Log($"[BackgroundSceneLoader] [PlayMode] 이미 로드된 씬입니다: {sceneInfo.sceneName}");
|
|
var existingScene = SceneManager.GetSceneByPath(sceneInfo.scenePath);
|
|
_loadedBackgroundScene = existingScene;
|
|
_currentSceneName = sceneInfo.sceneName;
|
|
return;
|
|
}
|
|
|
|
if (mode == LoadSceneMode.Additive) UnloadAllBackgroundScenes();
|
|
|
|
if (mode == LoadSceneMode.Additive && _copyLighting)
|
|
{
|
|
var activeScene = SceneManager.GetActiveScene();
|
|
if (activeScene.isLoaded)
|
|
{
|
|
if (!_hasBackup)
|
|
{
|
|
_originalLightingSettings.Capture(activeScene);
|
|
_hasBackup = true;
|
|
Debug.Log("[PlayMode] 원래 라이팅 세팅이 백업되었습니다.");
|
|
}
|
|
_originalLightingSettings.DisableOriginalLighting();
|
|
}
|
|
}
|
|
|
|
bool sceneInBuild = false;
|
|
for (int i = 0; i < SceneManager.sceneCountInBuildSettings; i++)
|
|
{
|
|
if (SceneUtility.GetScenePathByBuildIndex(i) == sceneInfo.scenePath)
|
|
{
|
|
sceneInBuild = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (sceneInBuild)
|
|
SceneManager.LoadSceneAsync(sceneInfo.scenePath, mode);
|
|
else
|
|
{
|
|
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);
|
|
Debug.Log($"[PlayMode] 라이팅 자동 복사됨: {sceneInfo.sceneName}");
|
|
}
|
|
}
|
|
Debug.Log($"[PlayMode] 배경 씬 로드됨: {sceneInfo.sceneName}");
|
|
}
|
|
RebuildContent();
|
|
}
|
|
|
|
private void UpdateCurrentSceneStatus()
|
|
{
|
|
if (_database == null) return;
|
|
|
|
var loadedBackgroundScenes = new List<Scene>();
|
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
|
{
|
|
var scene = SceneManager.GetSceneAt(i);
|
|
if (!scene.isLoaded) continue;
|
|
if (_database.FindByPath(scene.path) != null)
|
|
loadedBackgroundScenes.Add(scene);
|
|
}
|
|
|
|
if (loadedBackgroundScenes.Count > 1)
|
|
{
|
|
Debug.LogWarning($"[BackgroundSceneLoader] 중복 배경 씬 감지: {loadedBackgroundScenes.Count}개. 첫 번째만 유지합니다.");
|
|
for (int i = 1; i < loadedBackgroundScenes.Count; i++)
|
|
{
|
|
var duplicateScene = loadedBackgroundScenes[i];
|
|
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<Scene>();
|
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
|
{
|
|
var scene = SceneManager.GetSceneAt(i);
|
|
if (!scene.isLoaded) continue;
|
|
if (_database.FindByPath(scene.path) != null)
|
|
scenesToUnload.Add(scene);
|
|
}
|
|
foreach (var scene in scenesToUnload)
|
|
{
|
|
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<SceneAsset>(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)
|
|
{
|
|
EditorUtility.DisplayDialog("썸네일 생성",
|
|
"씬을 로드한 후 Game 뷰에서 원하는 앵글을 설정하고\n" +
|
|
"Streamingle > Capture Thumbnail 메뉴를 사용하세요.",
|
|
"확인");
|
|
}
|
|
|
|
// ─── 빌드 세팅 ───
|
|
|
|
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(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)
|
|
{
|
|
if (!currentScenes.Any(s => s.path == sceneInfo.scenePath))
|
|
{
|
|
currentScenes.Add(new EditorBuildSettingsScene(sceneInfo.scenePath, true));
|
|
addedCount++;
|
|
}
|
|
}
|
|
EditorBuildSettings.scenes = currentScenes.ToArray();
|
|
EditorUtility.DisplayDialog("완료",
|
|
$"빌드 세팅에 {addedCount}개의 배경 씬이 추가되었습니다.\n(총 {_database.scenes.Count}개 중 {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)
|
|
{
|
|
if (!currentScenes.Any(s => s.path == sceneInfo.scenePath))
|
|
{
|
|
currentScenes.Add(new EditorBuildSettingsScene(sceneInfo.scenePath, true));
|
|
addedCount++;
|
|
}
|
|
}
|
|
if (addedCount == 0)
|
|
{
|
|
EditorUtility.DisplayDialog("완료", "모든 배경 씬이 이미 빌드 세팅에 있습니다.", "확인");
|
|
return;
|
|
}
|
|
EditorBuildSettings.scenes = currentScenes.ToArray();
|
|
EditorUtility.DisplayDialog("완료", $"빌드 세팅에 {addedCount}개의 배경 씬이 추가되었습니다.", "확인");
|
|
}
|
|
|
|
private void RemoveAllScenesFromBuildSettings()
|
|
{
|
|
if (_database == null || _database.scenes.Count == 0)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "제거할 배경 씬이 없습니다.", "확인");
|
|
return;
|
|
}
|
|
if (!EditorUtility.DisplayDialog("확인",
|
|
"빌드 세팅에서 모든 배경 씬을 제거하시겠습니까?\n이 작업은 런타임에서 배경 씬 로드를 불가능하게 만듭니다.",
|
|
"제거", "취소")) return;
|
|
|
|
var backgroundPaths = new HashSet<string>(_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}개의 배경 씬이 제거되었습니다.", "확인");
|
|
}
|
|
|
|
// ─── 라이팅 복사/복원 (기존 로직 100% 유지) ───
|
|
|
|
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;
|
|
Debug.Log("원래 라이팅 세팅이 백업되었습니다.");
|
|
}
|
|
CopyLightingElements(_loadedBackgroundScene.Value, activeScene);
|
|
UpdateStatusBar();
|
|
EditorUtility.DisplayDialog("완료", "라이팅 세팅이 복사되었습니다.\n'원래대로 복원' 버튼으로 원래 세팅을 복원할 수 있습니다.", "확인");
|
|
}
|
|
|
|
private void CopyLightingElements(Scene sourceScene, Scene targetScene)
|
|
{
|
|
var previousActiveScene = SceneManager.GetActiveScene();
|
|
SceneManager.SetActiveScene(sourceScene);
|
|
|
|
if (_copySkybox)
|
|
{
|
|
if (RenderSettings.skybox != null)
|
|
{
|
|
var sourceSkybox = RenderSettings.skybox;
|
|
SceneManager.SetActiveScene(targetScene);
|
|
RenderSettings.skybox = sourceSkybox;
|
|
SceneManager.SetActiveScene(sourceScene);
|
|
Debug.Log($"Skybox 복사됨: {sourceSkybox.name}");
|
|
}
|
|
else
|
|
{
|
|
var sourceRoots = sourceScene.GetRootGameObjects();
|
|
foreach (var root in sourceRoots)
|
|
{
|
|
var cameras = root.GetComponentsInChildren<Camera>(true);
|
|
foreach (var cam in cameras)
|
|
{
|
|
var skyboxComp = cam.GetComponent<Skybox>();
|
|
if (skyboxComp != null && skyboxComp.material != null)
|
|
{
|
|
SceneManager.SetActiveScene(targetScene);
|
|
RenderSettings.skybox = skyboxComp.material;
|
|
SceneManager.SetActiveScene(sourceScene);
|
|
Debug.Log($"Skybox 복사됨 (카메라): {skyboxComp.material.name}");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_copyAmbient)
|
|
{
|
|
var am = RenderSettings.ambientMode;
|
|
var asc = RenderSettings.ambientSkyColor;
|
|
var aec = RenderSettings.ambientEquatorColor;
|
|
var agc = RenderSettings.ambientGroundColor;
|
|
var al = RenderSettings.ambientLight;
|
|
var ai = RenderSettings.ambientIntensity;
|
|
var ssc = RenderSettings.subtractiveShadowColor;
|
|
SceneManager.SetActiveScene(targetScene);
|
|
RenderSettings.ambientMode = am;
|
|
RenderSettings.ambientSkyColor = asc;
|
|
RenderSettings.ambientEquatorColor = aec;
|
|
RenderSettings.ambientGroundColor = agc;
|
|
RenderSettings.ambientLight = al;
|
|
RenderSettings.ambientIntensity = ai;
|
|
RenderSettings.subtractiveShadowColor = ssc;
|
|
SceneManager.SetActiveScene(sourceScene);
|
|
Debug.Log("Ambient 설정 복사됨");
|
|
}
|
|
|
|
if (_copyFog)
|
|
{
|
|
var f = RenderSettings.fog;
|
|
var fc = RenderSettings.fogColor;
|
|
var fm = RenderSettings.fogMode;
|
|
var fd = RenderSettings.fogDensity;
|
|
var fsd = RenderSettings.fogStartDistance;
|
|
var fed = RenderSettings.fogEndDistance;
|
|
SceneManager.SetActiveScene(targetScene);
|
|
RenderSettings.fog = f;
|
|
RenderSettings.fogColor = fc;
|
|
RenderSettings.fogMode = fm;
|
|
RenderSettings.fogDensity = fd;
|
|
RenderSettings.fogStartDistance = fsd;
|
|
RenderSettings.fogEndDistance = fed;
|
|
SceneManager.SetActiveScene(sourceScene);
|
|
Debug.Log($"Fog 설정 복사됨 (활성화: {f})");
|
|
}
|
|
|
|
SceneManager.SetActiveScene(previousActiveScene);
|
|
Debug.Log($"라이팅 복사됨: {sourceScene.name} → {targetScene.name}");
|
|
}
|
|
|
|
private void RestoreOriginalLighting()
|
|
{
|
|
if (!_hasBackup)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "백업된 라이팅 세팅이 없습니다.", "확인");
|
|
return;
|
|
}
|
|
var activeScene = SceneManager.GetActiveScene();
|
|
if (!activeScene.isLoaded)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "활성 씬이 없습니다.", "확인");
|
|
return;
|
|
}
|
|
_originalLightingSettings.Restore();
|
|
_hasBackup = false;
|
|
EditorSceneManager.MarkSceneDirty(activeScene);
|
|
UpdateStatusBar();
|
|
Debug.Log("원래 라이팅 세팅이 복원되었습니다.");
|
|
EditorUtility.DisplayDialog("완료", "원래 라이팅 세팅이 복원되었습니다.", "확인");
|
|
}
|
|
|
|
private void UnloadCurrentBackground()
|
|
{
|
|
if (!_loadedBackgroundScene.HasValue) return;
|
|
try
|
|
{
|
|
if (_hasBackup)
|
|
{
|
|
if (_autoRestoreOnUnload)
|
|
{
|
|
_originalLightingSettings.Restore();
|
|
Debug.Log("배경 언로드: 원래 라이팅 세팅이 자동 복원되었습니다.");
|
|
}
|
|
else
|
|
{
|
|
_originalLightingSettings.RestoreOriginalLighting();
|
|
}
|
|
_hasBackup = false;
|
|
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 = "없음";
|
|
RebuildContent();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"씬 언로드 실패: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
}
|