using System.IO; using UnityEngine; using UnityEngine.UI; public class BeatmapManager : MonoBehaviour { public Beatmap beatmap; // 当前谱面数据 // 音符生成器引用 public NoteSpawner noteSpawner; // Extra fields from beatmap JSON (stored temporarily) [HideInInspector] public string parsedTitle; [HideInInspector] public string parsedComposer; [HideInInspector] public string parsedIllustrator; [HideInInspector] public string parsedCharter; [HideInInspector] public string parsedBeatmapId; [HideInInspector] public string parsedDifficultyName; [HideInInspector] public string parsedCreatedDate; [HideInInspector] public string parsedLastSavedTime; [HideInInspector] public string parsedMusicFile; [HideInInspector] public string parsedBackgroundFile; [HideInInspector] public int parsedDuration; // as int per request [HideInInspector] public int parsedBpm; // as int per request [HideInInspector] public int parsedDifficulty; // duplicate of beatmap.difficulty [HideInInspector] public int parsedNoteAmount; [HideInInspector] public float globalDelaySeconds = 0f; // 从 JSON 文件加载谱面数据 public void LoadBeatmap(string fileName) { string path = Application.streamingAssetsPath + "/" + fileName; if (File.Exists(path)) { string json = File.ReadAllText(path); Debug.Log("谱面 JSON 已读取: " + path); ProcessJsonAndLoad(json); } else { Debug.LogError("文件不存在:" + path); } } // 新增:从原始 JSON 字符串加载谱面(允许任意磁盘路径先读取后传入) public void LoadBeatmapFromJsonString(string json) { if (string.IsNullOrEmpty(json)) { Debug.LogError("LoadBeatmapFromJsonString: json is null or empty"); return; } ProcessJsonAndLoad(json); } // 新增:解析 JSON 但不启动 NoteSpawner(用于 test mode 的延迟开始) public bool ParseJsonOnly(string json) { if (string.IsNullOrEmpty(json)) { Debug.LogError("ParseJsonOnly: json is empty"); return false; } // Parse main beatmap Beatmap parsed = null; try { parsed = JsonUtility.FromJson(json); } catch (System.Exception ex) { Debug.LogError($"ParseJsonOnly 解析 Beatmap 失败: {ex}"); return false; } if (parsed == null) { Debug.LogError("ParseJsonOnly: 解析 Beatmap 返回 null"); return false; } // Parse extras BeatmapExtra extra = null; try { extra = JsonUtility.FromJson(json); } catch { extra = null; } if (extra != null) { parsedTitle = extra.title; parsedComposer = extra.composer; parsedIllustrator = extra.illustrator; parsedCharter = extra.charter; parsedBeatmapId = extra.beatmapId; parsedDifficultyName = extra.difficultyName; parsedCreatedDate = extra.createdDate; parsedLastSavedTime = extra.lastSavedTime; parsedMusicFile = extra.musicFile; parsedBackgroundFile = extra.backgroundFile; parsedDuration = extra.duration; parsedBpm = extra.bpm; parsedDifficulty = extra.difficulty; parsedNoteAmount = extra.noteAmount; globalDelaySeconds = extra.globalDelaySeconds; if (!string.IsNullOrEmpty(extra.musicFile)) parsed.musicFile = extra.musicFile; if (!string.IsNullOrEmpty(extra.backgroundFile)) parsed.backgroundFile = extra.backgroundFile; } else { parsedTitle = parsed.title; parsedComposer = parsed.composer; parsedIllustrator = parsed.illustrator; parsedCharter = parsed.charter; parsedBeatmapId = parsed.beatmapId; parsedDifficultyName = parsed.difficultyName; parsedCreatedDate = parsed.createdDate; parsedMusicFile = parsed.musicFile; parsedBackgroundFile = parsed.backgroundFile; parsedDuration = Mathf.RoundToInt(parsed.duration); parsedBpm = Mathf.RoundToInt(parsed.bpm); parsedDifficulty = parsed.difficulty; } // NOTE: Do NOT modify parsed.notes here. globalDelaySeconds will be applied to audio playback instead. beatmap = parsed; Debug.Log("ParseJsonOnly: parsed beatmap " + beatmap.title); return true; } // 直接传入已经解析的 Beatmap(兼容旧调用) public void LoadBeatmap(Beatmap loadedBeatmap) { beatmap = loadedBeatmap; Debug.Log("谱面已加载(来自 Beatmap 对象):" + (beatmap != null ? beatmap.title : "null")); // No globalDelaySeconds available in this path. Call NoteSpawner directly. noteSpawner.LoadBeatmap(beatmap); } // 读取并加载当前谱面到音符生成器(备用) public void LoadBeatmapFromFile() { string path = Application.streamingAssetsPath + "/Emilia_demo.json"; if (File.Exists(path)) { string json = File.ReadAllText(path); ProcessJsonAndLoad(json); } else { Debug.LogError("谱面文件未找到!"); } } // 在 Start() 方法中初始化 void Start() { // 如果处于测试模式,则跳过自动加载谱面 if (GameConfig.testMode) { Debug.Log("Test mode active: skipping automatic beatmap load in BeatmapManager.Start"); } else { // 加载默认谱面 LoadBeatmap("Emilia_demo.json"); // 加载并实例化音符 } } // Helper class used to parse extra fields from JSON that Beatmap doesn't define [System.Serializable] private class BeatmapExtra { public string title; public string composer; public string illustrator; public string charter; public string beatmapId; public int duration; // as int per request public int bpm; // as int per request public int difficulty; public string difficultyName; public string createdDate; public string lastSavedTime; public string musicFile; public string backgroundFile; public int noteAmount; public float globalDelaySeconds; } // Parse JSON string to Beatmap and extras, then pass to NoteSpawner private void ProcessJsonAndLoad(string json) { if (string.IsNullOrEmpty(json)) { Debug.LogError("ProcessJsonAndLoad: json is empty"); return; } // Parse main beatmap Beatmap parsed = null; try { parsed = JsonUtility.FromJson(json); } catch (System.Exception ex) { Debug.LogError($"解析 Beatmap 失败: {ex}"); return; } if (parsed == null) { Debug.LogError("解析 Beatmap 返回 null"); return; } // Parse extras using BeatmapExtra which contains the requested fields and types BeatmapExtra extra = null; try { extra = JsonUtility.FromJson(json); } catch { extra = null; } if (extra != null) { // store into manager fields parsedTitle = extra.title; parsedComposer = extra.composer; parsedIllustrator = extra.illustrator; parsedCharter = extra.charter; parsedBeatmapId = extra.beatmapId; parsedDifficultyName = extra.difficultyName; parsedCreatedDate = extra.createdDate; parsedLastSavedTime = extra.lastSavedTime; parsedMusicFile = extra.musicFile; parsedBackgroundFile = extra.backgroundFile; parsedDuration = extra.duration; parsedBpm = extra.bpm; parsedDifficulty = extra.difficulty; parsedNoteAmount = extra.noteAmount; globalDelaySeconds = extra.globalDelaySeconds; // If JSON contains absolute paths for music/background, copy into parsed object for later use if (!string.IsNullOrEmpty(extra.musicFile)) parsed.musicFile = extra.musicFile; if (!string.IsNullOrEmpty(extra.backgroundFile)) parsed.backgroundFile = extra.backgroundFile; Debug.Log($"Beatmap extras parsed: title={parsedTitle}, composer={parsedComposer}, duration={parsedDuration}, bpm={parsedBpm}, difficulty={parsedDifficulty}, noteAmount={parsedNoteAmount}, globalDelaySeconds={globalDelaySeconds}"); } else { // Fallback to populate from parsed Beatmap where possible parsedTitle = parsed.title; parsedComposer = parsed.composer; parsedIllustrator = parsed.illustrator; parsedCharter = parsed.charter; parsedBeatmapId = parsed.beatmapId; parsedDifficultyName = parsed.difficultyName; parsedCreatedDate = parsed.createdDate; parsedMusicFile = parsed.musicFile; parsedBackgroundFile = parsed.backgroundFile; // convert types parsedDuration = Mathf.RoundToInt(parsed.duration); parsedBpm = Mathf.RoundToInt(parsed.bpm); parsedDifficulty = parsed.difficulty; Debug.LogWarning("BeatmapExtra not found in JSON; populated limited fields from Beatmap."); } // NOTE: Do NOT modify parsed.notes here. globalDelaySeconds will be applied to audio playback instead. // assign and hand off to spawner beatmap = parsed; Debug.Log("谱面已加载:" + beatmap.title); noteSpawner.LoadBeatmap(beatmap); } }