배경 씬 로더 기능 개선: - BackgroundSceneDatabase 에셋 추가 - 플레이 모드 언로드 시 MarkSceneDirty 오류 수정 - Directional Light 및 NiloToonOverrider 백업/복원 기능 - 빌드 세팅 자동 추가 기능 배경 폴더 구조 정리: - [초금비]방 → [공용]방 이름 변경 - [공용]루프탑 카페 씬 구조 정리 (Day/Night 씬 통합) - 미사용 배경 삭제: [공용]교실, [공용]농구장 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
395 lines
13 KiB
C#
395 lines
13 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
|
|
namespace Streamingle.Background
|
|
{
|
|
/// <summary>
|
|
/// 런타임 배경 씬 로더 UI (IMGUI)
|
|
/// Inspector에서 설정하거나 단축키로 토글 가능
|
|
/// </summary>
|
|
public class BackgroundSceneLoaderUI : MonoBehaviour
|
|
{
|
|
[Header("설정")]
|
|
[SerializeField] private BackgroundSceneDatabase sceneDatabase;
|
|
[SerializeField] private KeyCode toggleKey = KeyCode.F2;
|
|
[SerializeField] private bool showOnStart = false;
|
|
|
|
[Header("UI 설정")]
|
|
[SerializeField] private int windowWidth = 400;
|
|
[SerializeField] private int windowHeight = 500;
|
|
[SerializeField] private int thumbnailSize = 100;
|
|
|
|
private bool _showUI;
|
|
private Vector2 _scrollPosition;
|
|
private string _searchFilter = "";
|
|
private string _selectedCategory = "전체";
|
|
private Dictionary<string, List<BackgroundSceneInfo>> _categorizedScenes;
|
|
private Dictionary<string, bool> _categoryFoldouts = new Dictionary<string, bool>();
|
|
private Dictionary<string, Texture2D> _loadedThumbnails = new Dictionary<string, Texture2D>();
|
|
|
|
private Rect _windowRect;
|
|
private bool _isDragging;
|
|
private Vector2 _dragOffset;
|
|
|
|
private BackgroundSceneLoader _loader;
|
|
private string _statusMessage = "";
|
|
private float _statusTime;
|
|
|
|
private GUIStyle _headerStyle;
|
|
private GUIStyle _buttonStyle;
|
|
private GUIStyle _searchStyle;
|
|
private GUIStyle _categoryStyle;
|
|
private bool _stylesInitialized;
|
|
|
|
private void Start()
|
|
{
|
|
_showUI = showOnStart;
|
|
_windowRect = new Rect(20, 20, windowWidth, windowHeight);
|
|
|
|
if (sceneDatabase != null)
|
|
{
|
|
_categorizedScenes = sceneDatabase.GetScenesByCategory();
|
|
foreach (var category in _categorizedScenes.Keys)
|
|
{
|
|
_categoryFoldouts[category] = true;
|
|
}
|
|
|
|
// 썸네일 로드
|
|
LoadThumbnails();
|
|
}
|
|
|
|
// 로더 초기화
|
|
_loader = BackgroundSceneLoader.Instance;
|
|
_loader.Database = sceneDatabase;
|
|
_loader.OnSceneLoadCompleted += OnSceneLoaded;
|
|
_loader.OnSceneUnloaded += OnSceneUnloaded;
|
|
_loader.OnError += OnError;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (_loader != null)
|
|
{
|
|
_loader.OnSceneLoadCompleted -= OnSceneLoaded;
|
|
_loader.OnSceneUnloaded -= OnSceneUnloaded;
|
|
_loader.OnError -= OnError;
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (Input.GetKeyDown(toggleKey))
|
|
{
|
|
_showUI = !_showUI;
|
|
}
|
|
}
|
|
|
|
private void LoadThumbnails()
|
|
{
|
|
if (sceneDatabase == null) return;
|
|
|
|
foreach (var scene in sceneDatabase.scenes)
|
|
{
|
|
if (string.IsNullOrEmpty(scene.thumbnailPath)) continue;
|
|
if (_loadedThumbnails.ContainsKey(scene.thumbnailPath)) continue;
|
|
|
|
// Resources에서 로드 시도 (런타임에서는 Resources 폴더만 가능)
|
|
// 에디터에서만 AssetDatabase 사용 가능
|
|
#if UNITY_EDITOR
|
|
var texture = UnityEditor.AssetDatabase.LoadAssetAtPath<Texture2D>(scene.thumbnailPath);
|
|
if (texture != null)
|
|
{
|
|
_loadedThumbnails[scene.thumbnailPath] = texture;
|
|
scene.thumbnail = texture;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
private void InitStyles()
|
|
{
|
|
if (_stylesInitialized) return;
|
|
|
|
_headerStyle = new GUIStyle(GUI.skin.box)
|
|
{
|
|
fontSize = 14,
|
|
fontStyle = FontStyle.Bold,
|
|
alignment = TextAnchor.MiddleCenter,
|
|
normal = { textColor = Color.white }
|
|
};
|
|
|
|
_buttonStyle = new GUIStyle(GUI.skin.button)
|
|
{
|
|
fontSize = 12
|
|
};
|
|
|
|
_searchStyle = new GUIStyle(GUI.skin.textField)
|
|
{
|
|
fontSize = 12
|
|
};
|
|
|
|
_categoryStyle = new GUIStyle(GUI.skin.label)
|
|
{
|
|
fontSize = 13,
|
|
fontStyle = FontStyle.Bold
|
|
};
|
|
|
|
_stylesInitialized = true;
|
|
}
|
|
|
|
private void OnGUI()
|
|
{
|
|
if (!_showUI) return;
|
|
|
|
InitStyles();
|
|
|
|
// 드래그 처리
|
|
HandleDrag();
|
|
|
|
// 윈도우 그리기
|
|
_windowRect = GUI.Window(GetInstanceID(), _windowRect, DrawWindow, "");
|
|
}
|
|
|
|
private void HandleDrag()
|
|
{
|
|
Event e = Event.current;
|
|
Rect titleBar = new Rect(_windowRect.x, _windowRect.y, _windowRect.width, 30);
|
|
|
|
if (e.type == EventType.MouseDown && titleBar.Contains(e.mousePosition))
|
|
{
|
|
_isDragging = true;
|
|
_dragOffset = e.mousePosition - new Vector2(_windowRect.x, _windowRect.y);
|
|
e.Use();
|
|
}
|
|
else if (e.type == EventType.MouseUp)
|
|
{
|
|
_isDragging = false;
|
|
}
|
|
else if (e.type == EventType.MouseDrag && _isDragging)
|
|
{
|
|
_windowRect.position = e.mousePosition - _dragOffset;
|
|
e.Use();
|
|
}
|
|
}
|
|
|
|
private void DrawWindow(int windowID)
|
|
{
|
|
// 타이틀 바
|
|
GUI.Box(new Rect(0, 0, _windowRect.width, 30), "배경 씬 로더", _headerStyle);
|
|
|
|
// 닫기 버튼
|
|
if (GUI.Button(new Rect(_windowRect.width - 25, 5, 20, 20), "X"))
|
|
{
|
|
_showUI = false;
|
|
}
|
|
|
|
GUILayout.Space(35);
|
|
|
|
// 현재 씬 상태
|
|
GUILayout.BeginHorizontal();
|
|
string currentSceneName = _loader?.CurrentSceneName ?? "없음";
|
|
GUILayout.Label($"현재: {currentSceneName}", GUILayout.Width(200));
|
|
|
|
bool hasLoadedScene = _loader != null &&
|
|
_loader.CurrentBackgroundScene.HasValue &&
|
|
_loader.CurrentBackgroundScene.Value.IsValid() &&
|
|
_loader.CurrentBackgroundScene.Value.isLoaded;
|
|
|
|
if (hasLoadedScene && GUILayout.Button("언로드", GUILayout.Width(60)))
|
|
{
|
|
_loader.UnloadCurrentScene();
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
// 검색
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Label("검색:", GUILayout.Width(40));
|
|
_searchFilter = GUILayout.TextField(_searchFilter, _searchStyle, GUILayout.Width(150));
|
|
if (GUILayout.Button("X", GUILayout.Width(25)))
|
|
{
|
|
_searchFilter = "";
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
// 카테고리 선택
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Label("카테고리:", GUILayout.Width(60));
|
|
|
|
var categories = new List<string> { "전체" };
|
|
if (_categorizedScenes != null)
|
|
{
|
|
categories.AddRange(_categorizedScenes.Keys.OrderBy(x => x));
|
|
}
|
|
|
|
int currentIndex = categories.IndexOf(_selectedCategory);
|
|
if (currentIndex < 0) currentIndex = 0;
|
|
|
|
// 간단한 드롭다운 대체
|
|
if (GUILayout.Button(_selectedCategory, GUILayout.Width(100)))
|
|
{
|
|
currentIndex = (currentIndex + 1) % categories.Count;
|
|
_selectedCategory = categories[currentIndex];
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
GUILayout.Space(10);
|
|
|
|
// 씬 목록
|
|
_scrollPosition = GUILayout.BeginScrollView(_scrollPosition);
|
|
|
|
if (_categorizedScenes != null)
|
|
{
|
|
var filteredScenes = GetFilteredScenes();
|
|
DrawSceneList(filteredScenes);
|
|
}
|
|
|
|
GUILayout.EndScrollView();
|
|
|
|
// 상태 메시지
|
|
if (Time.time - _statusTime < 3f && !string.IsNullOrEmpty(_statusMessage))
|
|
{
|
|
GUILayout.Label(_statusMessage);
|
|
}
|
|
|
|
// 로딩 표시
|
|
if (_loader != null && _loader.IsLoading)
|
|
{
|
|
GUILayout.Label("로딩 중...");
|
|
}
|
|
}
|
|
|
|
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.ToLower().Contains(_searchFilter.ToLower()) ||
|
|
s.categoryName.ToLower().Contains(_searchFilter.ToLower())
|
|
).ToList();
|
|
}
|
|
|
|
if (scenes.Count > 0)
|
|
{
|
|
result[kvp.Key] = scenes;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private void DrawSceneList(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;
|
|
|
|
// 카테고리 헤더
|
|
GUILayout.BeginHorizontal();
|
|
_categoryFoldouts[category] = GUILayout.Toggle(_categoryFoldouts[category],
|
|
$"{(_categoryFoldouts[category] ? "▼" : "▶")} {category} ({sceneList.Count})",
|
|
_categoryStyle);
|
|
GUILayout.EndHorizontal();
|
|
|
|
if (!_categoryFoldouts[category]) continue;
|
|
|
|
// 씬 목록
|
|
foreach (var sceneInfo in sceneList)
|
|
{
|
|
DrawSceneItem(sceneInfo);
|
|
}
|
|
|
|
GUILayout.Space(5);
|
|
}
|
|
}
|
|
|
|
private void DrawSceneItem(BackgroundSceneInfo sceneInfo)
|
|
{
|
|
string currentName = _loader?.CurrentSceneName ?? "";
|
|
bool isCurrentScene = !string.IsNullOrEmpty(currentName) &&
|
|
currentName != "없음" &&
|
|
currentName == sceneInfo.sceneName;
|
|
|
|
GUILayout.BeginHorizontal(GUI.skin.box);
|
|
|
|
// 썸네일
|
|
if (sceneInfo.thumbnail != null)
|
|
{
|
|
GUILayout.Box(sceneInfo.thumbnail, GUILayout.Width(50), GUILayout.Height(50));
|
|
}
|
|
else
|
|
{
|
|
GUILayout.Box("No\nImg", GUILayout.Width(50), GUILayout.Height(50));
|
|
}
|
|
|
|
// 씬 정보
|
|
GUILayout.BeginVertical();
|
|
GUIStyle nameStyle = isCurrentScene ? new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Bold } : GUI.skin.label;
|
|
GUILayout.Label(sceneInfo.sceneName, nameStyle);
|
|
GUILayout.Label(sceneInfo.categoryName, new GUIStyle(GUI.skin.label) { fontSize = 10 });
|
|
GUILayout.EndVertical();
|
|
|
|
GUILayout.FlexibleSpace();
|
|
|
|
// 로드 버튼
|
|
if (GUILayout.Button(isCurrentScene ? "로드됨" : "로드", _buttonStyle, GUILayout.Width(60), GUILayout.Height(40)))
|
|
{
|
|
if (!isCurrentScene)
|
|
{
|
|
_loader?.LoadScene(sceneInfo);
|
|
}
|
|
}
|
|
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
|
|
private void OnSceneLoaded(BackgroundSceneInfo sceneInfo)
|
|
{
|
|
_statusMessage = $"로드 완료: {sceneInfo.sceneName}";
|
|
_statusTime = Time.time;
|
|
}
|
|
|
|
private void OnSceneUnloaded(string sceneName)
|
|
{
|
|
_statusMessage = $"언로드 완료: {sceneName}";
|
|
_statusTime = Time.time;
|
|
}
|
|
|
|
private void OnError(string error)
|
|
{
|
|
_statusMessage = $"오류: {error}";
|
|
_statusTime = Time.time;
|
|
UnityEngine.Debug.LogError(error);
|
|
}
|
|
|
|
/// <summary>
|
|
/// UI 표시 토글
|
|
/// </summary>
|
|
public void ToggleUI()
|
|
{
|
|
_showUI = !_showUI;
|
|
}
|
|
|
|
/// <summary>
|
|
/// UI 표시 설정
|
|
/// </summary>
|
|
public void SetUIVisible(bool visible)
|
|
{
|
|
_showUI = visible;
|
|
}
|
|
}
|
|
}
|