using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; using UnityEngine.UI; /// /// 에디터/PC 환경 전용 테스트 보조 스크립트. /// RuntimeInitializeOnLoadMethod로 자동 생성되므로 씬에 직접 추가할 필요 없습니다. /// Quest 빌드 시 자동으로 비활성화됩니다. /// /// 기능: /// 1. TrackedDeviceGraphicRaycaster → GraphicRaycaster 교체 (마우스 클릭 활성화) /// 2. Canvas EventCamera 자동 갱신 (클릭 위치 정확도) /// 3. ESC 키로 씬별 뒤로가기 /// public class DesktopUIMode : MonoBehaviour { #if !UNITY_ANDROID || UNITY_EDITOR // 어떤 씬에서 Play를 눌러도 자동 실행 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] private static void AutoCreate() { if (FindObjectOfType() != null) return; // 이미 있으면 생성 안 함 var go = new GameObject("[DesktopUIMode]"); go.AddComponent(); } // ESC 뒤로가기 씬 매핑 private static readonly System.Collections.Generic.Dictionary BackSceneMap = new System.Collections.Generic.Dictionary { { "SongSelect", "Intro" }, { "SongCreator", "Intro" }, { "MapEditorScene", "SongCreator" }, { "Game", "SongSelect" }, }; private void Awake() { // 중복 방지 (씬에 직접 놓은 경우 AutoCreate와 겹칠 수 있음) if (FindObjectsOfType().Length > 1) { Destroy(gameObject); return; } DontDestroyOnLoad(gameObject); SceneManager.sceneLoaded += OnSceneLoaded; PatchCanvases(); // 현재 씬 즉시 패치 } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { StartCoroutine(PatchAfterFrame()); } private System.Collections.IEnumerator PatchAfterFrame() { yield return null; // Canvas.Awake 이후 실행 보장 PatchCanvases(); } private void Update() { RefreshCanvasCameras(); // 매 프레임 worldCamera 갱신 (카메라 초기화 타이밍 보정) if (Keyboard.current?.escapeKey.wasPressedThisFrame == true) GoBack(); } // ── Canvas 패치 ────────────────────────────────────────── private static void PatchCanvases() { foreach (var canvas in FindObjectsOfType()) { if (canvas.renderMode != RenderMode.WorldSpace) continue; // TrackedDeviceGraphicRaycaster → GraphicRaycaster var tracked = canvas.GetComponent("TrackedDeviceGraphicRaycaster"); if (tracked != null) { DestroyImmediate(tracked); if (canvas.GetComponent() == null) canvas.gameObject.AddComponent(); Debug.Log($"[DesktopUIMode] {canvas.name} Raycaster 교체 완료"); } } RemoveDuplicateAudioListeners(); RefreshCanvasCameras(); } private static void RemoveDuplicateAudioListeners() { var listeners = FindObjectsOfType(); if (listeners.Length <= 1) return; // 첫 번째(DontDestroyOnLoad에 없는 것 우선)만 남기고 나머지 제거 AudioListener keep = null; foreach (var al in listeners) { if (al.gameObject.scene.name != "DontDestroyOnLoad") { keep = al; break; } } if (keep == null) keep = listeners[0]; foreach (var al in listeners) { if (al != keep) { Debug.Log($"[DesktopUIMode] 중복 AudioListener 제거: {al.gameObject.name}"); DestroyImmediate(al); } } } private static void RefreshCanvasCameras() { Camera cam = Camera.main; if (cam == null) { // Camera.main이 없으면 씬에서 직접 찾기 (DontDestroyOnLoad 제외) foreach (var c in FindObjectsOfType()) { if (c.enabled && c.gameObject.scene.name != "DontDestroyOnLoad") { cam = c; break; } } } if (cam == null) cam = FindObjectOfType(); // 최후 수단 if (cam == null) return; foreach (var canvas in FindObjectsOfType()) { if (canvas.renderMode == RenderMode.WorldSpace && canvas.worldCamera != cam) canvas.worldCamera = cam; } } // ── ESC 뒤로가기 ───────────────────────────────────────── private static void GoBack() { string current = SceneManager.GetActiveScene().name; if (BackSceneMap.TryGetValue(current, out string target)) SceneManager.LoadScene(target); } #endif }