user 7a5d1dbe8f Refactor: 배경 씬 로더 시스템 개선 및 폴더 구조 정리
배경 씬 로더 기능 개선:
- 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>
2026-01-07 00:00:35 +09:00

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;
}
}
}