using System.IO; using UnityEngine; using UnityEngine.UI; using System.Collections.Generic; public class BeatmapManager : MonoBehaviour { public Beatmap beatmap; // 当前谱面数据 // 音符生成器引用 public NoteSpawner noteSpawner; // teamUIController 引用,直接拖拽赋值 public teamUIController uiController; // 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; // New parsed fields [HideInInspector] public bool parsedEnemyListIsEmpty; [HideInInspector] public ColorSegment[] parsedColorSegments; [HideInInspector] public NoteStatistic[] parsedNoteStatistics; [HideInInspector] public TrackStates parsedTrackStates; // Multipliers for difficulty levels [Header("Difficulty Multipliers")] public float ezMultiplier = 1f; public float hdMultiplier = 1.5f; public float inMultiplier = 2f; public float imMultiplier = 3f; // 新增:基础生命值缩放因子(Base HP Unit Scale) // 用于将 Note 数量(例如 709)缩放到 40000 左右的范围。 // 计算公式:NoteAmount * DifficultyMult * BaseScale ≈ TotalHP [Header("HP Calculation Settings")] [Tooltip("Scales the note amount to enemy HP. Example: NoteAmount * MaxMultiplier * BaseScale ≈ TotalHP")] public float baseHpUnitScale = 1f; // Total chart score and per-note score fields public int totalChartScore = 1000000; // Default total score for a chart public int perNoteScore; public int leftoverScore; // 从 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; // New fields parsedEnemyListIsEmpty = extra.enemyList_isEmpty; if (!parsedEnemyListIsEmpty) { parsedColorSegments = extra.colorSegments; } parsedNoteStatistics = extra.noteStatistics; parsedTrackStates = extra.trackStates; } 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; } // Calculate per-note and leftover scores CalculateNoteScores(); // NOTE: Do NOT modify parsed.notes here. globalDelaySeconds will be applied to audio playback instead. beatmap = parsed; Debug.Log("ParseJsonOnly: parsed beatmap " + beatmap.title); // 调用统一的敌人处理逻辑(包含 HP 计算和设置) SetupEnemiesAndHP(); 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"); // 加载并实例化音符 } } // 获取当前难度乘数 private float GetCurrentDifficultyMultiplier() { // 假设 1=EZ, 2=HD, 3=IN, 4=IM if (parsedDifficulty == 1) return ezMultiplier; if (parsedDifficulty == 2) return hdMultiplier; if (parsedDifficulty == 3) return inMultiplier; if (parsedDifficulty == 4) return imMultiplier; return 1f; // 默认值 } // 统一处理敌人 ID 提取、发送给 UI 以及 HP 计算的逻辑 private void SetupEnemiesAndHP() { if (uiController == null) { Debug.LogError("teamUIController not assigned in BeatmapManager Inspector"); return; } if (!parsedEnemyListIsEmpty && parsedColorSegments != null && parsedColorSegments.Length > 0) { // 1. 提取敌人 IDs List enemyIds = new List(); foreach (var segment in parsedColorSegments) { Debug.Log($"Processing colorSegment: enemyID='{segment.enemyID}', percentage={segment.percentage}"); int enemyId; if (!int.TryParse(segment.enemyID, out enemyId)) { Debug.LogError($"Failed to parse enemyID '{segment.enemyID}' to int"); continue; } enemyIds.Add(enemyId); } // 2. 补齐 5 个槽位 while (enemyIds.Count < 5) { enemyIds.Add(0); } // A. 传递 ID 列表给 uiController Debug.Log($"Assigning enemySlotIds to teamUIController: {string.Join(",", enemyIds)}"); uiController.enemySlotIds = enemyIds; // B. 让 UI 控制器加载 SO (此时 SO 的 HP 仍是默认值) uiController.PopulateEnemySOsFromIds(); // C. 计算并应用 HP 逻辑 if (parsedNoteAmount > 0) { float currentMultiplier = GetCurrentDifficultyMultiplier(); // 计算总 HP:音符数 * 难度乘数 * 基础缩放 float totalCalculatedHP = parsedNoteAmount * currentMultiplier * baseHpUnitScale; // 计算活跃敌人数量(非0 ID) int activeEnemyCount = enemyIds.FindAll(id => id != 0).Count; if (activeEnemyCount == 0) activeEnemyCount = 1; // 防止除零 // 计算单个敌人 HP (总血量 / 敌人数量,均匀分配) int individualMaxHP = Mathf.RoundToInt(totalCalculatedHP / activeEnemyCount); Debug.Log($"[BeatmapManager] HP Calc -> Notes: {parsedNoteAmount}, Multiplier: {currentMultiplier}, Total: {totalCalculatedHP:F0}, ActiveEnemies: {activeEnemyCount}, Per Enemy: {individualMaxHP}"); // D. 【关键调用】将计算出的 HP 回填到 teamUIController 中的 SO 中 uiController.ApplyCalculatedEnemyHP(individualMaxHP); } } else { // 如果没有敌人,也要通知 UI 控制器清空列表 Debug.Log("Parsed enemy list is empty, clearing UI."); uiController.enemySlotIds = new List { 0, 0, 0, 0, 0 }; uiController.PopulateEnemySOsFromIds(); } } // 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}"); // New fields parsedEnemyListIsEmpty = extra.enemyList_isEmpty; if (!parsedEnemyListIsEmpty) { parsedColorSegments = extra.colorSegments; } parsedNoteStatistics = extra.noteStatistics; parsedTrackStates = extra.trackStates; } 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."); } // Calculate per-note and leftover scores CalculateNoteScores(); // 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); if (noteSpawner != null) { noteSpawner.LoadBeatmap(beatmap); } else { Debug.LogError("NoteSpawner is null in BeatmapManager."); } // 调用统一的敌人处理逻辑(包含 HP 计算和设置) SetupEnemiesAndHP(); } // Calculate per-note and leftover scores based on parsedNoteAmount private void CalculateNoteScores() { if (parsedNoteAmount > 0) { perNoteScore = totalChartScore / parsedNoteAmount; leftoverScore = totalChartScore % parsedNoteAmount; Debug.Log($"Calculated perNoteScore: {perNoteScore}, leftoverScore: {leftoverScore}"); } else { perNoteScore = 0; leftoverScore = 0; Debug.LogWarning("parsedNoteAmount is zero or less; perNoteScore and leftoverScore set to 0."); } } // 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; // New fields public bool enemyList_isEmpty; public ColorSegment[] colorSegments; public NoteStatistic[] noteStatistics; public TrackStates trackStates; } // New serializable classes for the extra fields [System.Serializable] public class ColorSegment { public string enemyID; public float percentage; } [System.Serializable] public class NoteStatistic { public string colorType; public int count; } [System.Serializable] public class TrackStates { public bool trackFading_active; public TrackState red; public TrackState green; public TrackState yellow; public TrackState purple; public TrackState blue; } [System.Serializable] public class TrackState { public bool isOn; public float fadeTime; } }