using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.Networking; using UnityEngine.SceneManagement; public class Spawner : MonoBehaviour { [Header("오디오 및 파일 설정")] public AudioSource audioSource; [Header("노트 프리팹 & 위치")] public GameObject[] cubePrefabs; public Transform[] spawnPoints; [Header("타이밍 설정")] public float noteSpeed = 2.0f; public float distanceToHit = 10.0f; [Header("씬 설정")] public string songSelectSceneName = "SongSelect"; [Header("뒤로가기 입력")] [Tooltip("Quest B/Y 버튼을 뒤로가기로 쓸 InputAction (선택)")] [SerializeField] private InputActionReference backAction; private List mapNotes = new List(); private int nextNoteIndex = 0; private float travelTime; private bool isReady = false; // ── Unity ──────────────────────────────────────────────── void Awake() { // PlayOnAwake가 OnEnable에서 발동하기 전에 차단 if (audioSource != null) { audioSource.playOnAwake = false; audioSource.clip = null; } } void Start() { travelTime = distanceToHit / noteSpeed; if (GameSession.SelectedSong == null) { Debug.LogWarning("[Spawner] 선택된 곡 없음 → 곡 선택 화면으로 이동"); SceneManager.LoadScene(songSelectSceneName); return; } StartCoroutine(InitGame()); } void Update() { // 뒤로가기: Quest B/Y 버튼 (PC ESC는 DesktopUIMode가 처리) bool goBack = backAction != null && backAction.action.WasPressedThisFrame(); if (goBack) { SceneManager.LoadScene(songSelectSceneName); return; } if (!isReady || audioSource == null || !audioSource.isPlaying) return; float currentTime = audioSource.time; while (nextNoteIndex < mapNotes.Count && currentTime + travelTime >= mapNotes[nextNoteIndex].time) { SpawnNote(mapNotes[nextNoteIndex]); nextNoteIndex++; } } // ── 초기화 ─────────────────────────────────────────────── private IEnumerator InitGame() { SongInfo song = GameSession.SelectedSong; string difficulty = GameSession.SelectedDifficulty; // ── 기본 연결 확인 ─────────────────────────────────────── if (audioSource == null) { Debug.LogError("[Spawner] AudioSource 미연결 — Inspector에서 AudioSource 연결 필요"); yield break; } if (song == null) { Debug.LogError("[Spawner] GameSession.SelectedSong == null — SongSelect에서 곡을 선택하고 Play를 눌러야 합니다"); yield break; } if (string.IsNullOrEmpty(difficulty)) { Debug.LogError("[Spawner] GameSession.SelectedDifficulty 비어있음"); yield break; } Debug.Log($"[Spawner] 시작: song={song.title}({song.id}), diff={difficulty}"); // ── 맵 JSON 로드 ───────────────────────────────────────── string mapPath = GetMapPath(song, difficulty); Debug.Log($"[Spawner] 맵 경로: {mapPath}"); if (string.IsNullOrEmpty(mapPath)) { Debug.LogError($"[Spawner] songs.json에 '{difficulty}' mapFile 없음 — NAS 재업로드 필요"); yield break; } if (!File.Exists(mapPath)) { Debug.LogError($"[Spawner] 맵 파일 없음: {mapPath}\n→ SongSelect에서 다운로드하세요"); yield break; } LoadMapJson(mapPath); // ── 오디오 로드 ────────────────────────────────────────── audioSource.Stop(); audioSource.clip = null; string audioPath = GetAudioPath(song.id); Debug.Log($"[Spawner] 오디오 경로: {audioPath}"); if (!File.Exists(audioPath)) { Debug.LogError($"[Spawner] 오디오 파일 없음: {audioPath}\n→ SongSelect에서 다운로드하세요"); yield break; } yield return LoadAudioClip(audioPath); if (audioSource.clip == null) { Debug.LogError("[Spawner] 오디오 클립 로드 실패 (파일 손상 또는 형식 오류)"); yield break; } SongLibrary.Instance?.TouchSong(song.id); isReady = true; audioSource.Play(); Debug.Log($"[Spawner] 재생 시작: {song.title} / {difficulty} / 노트:{mapNotes.Count}개"); } private void LoadMapJson(string path) { string json = File.ReadAllText(path); MapData data = JsonUtility.FromJson(json); mapNotes = data?.target ?? new List(); mapNotes.Sort((a, b) => a.time.CompareTo(b.time)); Debug.Log($"[Spawner] 노트 로드: {mapNotes.Count}개"); } private IEnumerator LoadAudioClip(string path) { string uri = "file://" + path; using var req = UnityWebRequestMultimedia.GetAudioClip(uri, AudioType.MPEG); yield return req.SendWebRequest(); if (req.result != UnityWebRequest.Result.Success) { Debug.LogError($"[Spawner] 오디오 로드 실패: {req.error}"); yield break; } audioSource.clip = DownloadHandlerAudioClip.GetContent(req); } // ── 노트 스폰 ──────────────────────────────────────────── private void SpawnNote(NoteData data) { if (data.colorType >= cubePrefabs.Length || data.position >= spawnPoints.Length) return; GameObject obj = Instantiate( cubePrefabs[data.colorType], spawnPoints[data.position].position, spawnPoints[data.position].rotation); obj.transform.Rotate(transform.forward, 90 * Random.Range(0, 4)); } // ── 경로 헬퍼 ──────────────────────────────────────────── private static string CacheRoot => Path.Combine(Application.temporaryCachePath, "beatsaber"); private static string GetAudioPath(string songId) => Path.Combine(CacheRoot, songId, $"{songId}.mp3"); private static string GetMapPath(SongInfo song, string difficulty) { DifficultyInfo info = song.difficulties.Get(difficulty); if (info == null) return string.Empty; return Path.Combine(CacheRoot, song.id, Path.GetFileName(info.mapFile)); } }