using System.IO; using UnityEngine; using UnityEngine.UI; using System.Collections.Generic; public class BeatmapManager : MonoBehaviour { // static holder to accept SongData passed from previous scene before this manager exists public static SongData pendingSongData = null; public static int pendingDifficulty = -1; /// /// Called by previous scene to hand over a SongData SO and difficulty to this manager after scene load. /// This stores the pair in static fields so BeatmapManager.Start can pick them up. /// public static void SetPendingSong(SongData song, int difficulty) { pendingSongData = song; pendingDifficulty = difficulty; Debug.LogWarning($"BeatmapManager.SetPendingSong called: song={(song==null?"NULL":song.songName)}, difficulty={difficulty}"); } [Header("Assigned SongData (set at runtime from previous scene or via Inspector)")] public SongData assignedSongData; // visible in Inspector so you can see the SO passed from previous scene [Header("Assigned difficulty (for inspector visibility)")] public int assignedDifficulty = -1; /// /// Public API to accept a SongData at runtime (can be called by other scripts after scene load) /// Also sets inspector-exposed fields so you can visually confirm in Editor during play. /// public void AcceptSongData(SongData song, int difficulty) { assignedSongData = song; assignedDifficulty = difficulty; Debug.LogWarning($"BeatmapManager.AcceptSongData: accepted song={(song==null?"NULL":song.songName)}, difficulty={difficulty}"); } public Beatmap beatmap; // ��ǰ�������� // �������������� public NoteSpawner noteSpawner; // teamUIController ���ã�ֱ����ק��ֵ public teamUIController uiController; public Image bgSpriteImage; // ����ͼƬ���� public GameObject bgSpriteObject; public Image startCanvas_image; // 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; [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(); ApplyTrackScoreCaps(beatmap); ApplyTrackScoreCaps(beatmap); 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); ApplyTrackScoreCaps(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("�����ļ�δ�ҵ���"); } } // �������� TextAsset ���������� parseOnly �����Ƿ�ֻ�ǽ�����ֱ�Ӽ��� public bool LoadBeatmapFromTextAsset(TextAsset chartAsset, bool parseOnly = false) { if (chartAsset == null) { Debug.LogWarning("LoadBeatmapFromTextAsset: chartAsset is null"); return false; } if (string.IsNullOrEmpty(chartAsset.text)) { Debug.LogWarning("LoadBeatmapFromTextAsset: chartAsset.text is null or empty"); return false; } if (parseOnly) { return ParseJsonOnly(chartAsset.text); } else { ProcessJsonAndLoad(chartAsset.text); return true; } } // �������� SongData �� chart TextAsset �������� public bool LoadBeatmapFromSongData(SongData song, int difficulty, bool parseOnly = false) { if (song == null) { Debug.LogWarning("LoadBeatmapFromSongData: song is null"); return false; } TextAsset ta = song.GetChartFile(difficulty); if (ta == null) { Debug.LogWarning($"LoadBeatmapFromSongData: chart TextAsset for difficulty {difficulty} is null on song {song.songName}"); return false; } Debug.LogWarning($"LoadBeatmapFromSongData: loading chart for song {song.songName}, difficulty {difficulty}, parseOnly={parseOnly}, chartSize={(ta.text != null ? ta.text.Length : 0)}"); return LoadBeatmapFromTextAsset(ta, parseOnly); } // �� Start() �����г�ʼ�� void Start() { // ����������һ���������ݹ����� SongData��ͨ�� BeatmapManager.pendingSongData�� if (pendingSongData != null) { Debug.LogWarning($"BeatmapManager.Start: pendingSongData detected: {pendingSongData.songName}, difficulty={pendingDifficulty}"); // �� pending ��ʾ�� Inspector �ֶΣ����ڼ�� assignedSongData = pendingSongData; assignedDifficulty = pendingDifficulty; // ���Խ�����ֻ���������������������� bool ok = LoadBeatmapFromSongData(assignedSongData, assignedDifficulty, true); if (!ok) Debug.LogWarning("BeatmapManager.Start: LoadBeatmapFromSongData failed"); else { Debug.LogWarning("BeatmapManager.Start: chart parsed from SongData, attempting to assign audio and pause system"); // try to assign audio to GameManager.musicSource var gm = FindAnyObjectByType(); if (gm != null && gm.musicSource != null) { if (assignedSongData != null && assignedSongData.audioFile != null) { gm.musicSource.clip = assignedSongData.audioFile; gm.musicSource.loop = false; Debug.LogWarning("Assigned SongData.audioFile to GameManager.musicSource.clip (from SO)"); } else if (!string.IsNullOrEmpty(parsedMusicFile)) { Debug.LogWarning($"Attempting Resources.Load for audio: {parsedMusicFile}"); var ac = Resources.Load(parsedMusicFile); if (ac != null) { gm.musicSource.clip = ac; gm.musicSource.loop = false; Debug.LogWarning("Assigned audio via Resources.Load(parsedMusicFile)"); } else { Debug.LogWarning($"Resources.Load failed for '{parsedMusicFile}'"); } } else { Debug.LogWarning("No audio available on SongData and parsedMusicFile empty"); } } else { Debug.LogWarning("GameManager or its musicSource not found; audio not assigned"); } // Assign fullscreen image from SongData to bgSprite if available if (assignedSongData != null && assignedSongData.fullscreen_songPicture != null) { // Prefer setting a Scene GameObject's SpriteRenderer if provided if (bgSpriteObject != null) { var sr = bgSpriteObject.GetComponentInChildren(); if (sr != null) { sr.sprite = assignedSongData.fullscreen_songPicture; // ensure fully visible sr.color = new Color(sr.color.r, sr.color.g, sr.color.b, 1f); if (startCanvas_image != null) { startCanvas_image.sprite = assignedSongData.fullscreen_songPicture; } sr = null; Debug.LogWarning($"Assigned SongData.fullscreen_songPicture to SpriteRenderer on bgSpriteObject for song {assignedSongData.songName}"); } else { Debug.LogWarning("bgSpriteObject has no SpriteRenderer in children; cannot assign fullscreen sprite"); } } else if (bgSpriteImage != null) { bgSpriteImage.sprite = assignedSongData.fullscreen_songPicture; Debug.LogWarning($"Assigned SongData.fullscreen_songPicture to bgSpriteImage for song {assignedSongData.songName}"); } else { Debug.LogWarning("No bg target (bgSpriteObject or bgSpriteImage) assigned; fullscreen sprite not applied"); } } // Pause the system to show pause overlay (PauseManager will enable overlayRoot) var pauseMgr = PauseManager.Instance ?? FindAnyObjectByType(); if (pauseMgr != null) { pauseMgr.Pause(true); Debug.LogWarning("System paused after loading chart and audio to allow player to start"); } else { Debug.LogWarning("PauseManager not found in scene; cannot pause system automatically"); } } // clear pending so it doesn't run again pendingSongData = null; pendingDifficulty = -1; } // ������ڲ���ģʽ���������Զ��������� if (GameConfig.testMode) { Debug.Log("Test mode active: skipping automatic beatmap load in BeatmapManager.Start"); } else { // ԭ��Ĭ�ϼ��صĵ���������ע�ͣ���Ϊ������Ҫʱ�ֶ��ָ� // LoadBeatmap("Emilia_demo.json"); // ���ز�ʵ�������� Debug.LogWarning("Default automatic demo beatmap load is commented out to prioritize SO-provided charts."); } } // ��ȡ��ǰ�Ѷȳ��� private float GetCurrentDifficultyMultiplier() { 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) { 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); } while (enemyIds.Count < 5) enemyIds.Add(0); Debug.Log($"Assigning enemySlotIds to teamUIController: {string.Join(",", enemyIds)}"); uiController.enemySlotIds = enemyIds; uiController.PopulateEnemySOsFromIds(); if (parsedNoteAmount > 0) { float currentMultiplier = GetCurrentDifficultyMultiplier(); float totalCalculatedHP = parsedNoteAmount * currentMultiplier * baseHpUnitScale; int activeEnemyCount = enemyIds.FindAll(id => id != 0).Count; if (activeEnemyCount == 0) activeEnemyCount = 1; int individualMaxHP = Mathf.RoundToInt(totalCalculatedHP / activeEnemyCount); Debug.Log($"[BeatmapManager] HP Calc -> Notes: {parsedNoteAmount}, Multiplier: {currentMultiplier}, Total: {totalCalculatedHP:F0}, ActiveEnemies: {activeEnemyCount}, Per Enemy: {individualMaxHP}"); uiController.ApplyCalculatedEnemyHP(individualMaxHP); } } else { 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; } 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; } 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; 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; Debug.LogWarning("BeatmapExtra not found in JSON; populated limited fields from Beatmap."); } // Ensure amount and color statistics are populated even if missing from JSON ValidateAndPopulateStatistics(parsed); CalculateNoteScores(); beatmap = parsed; Debug.Log("�����Ѽ��أ�" + beatmap.title); if (noteSpawner != null) noteSpawner.LoadBeatmap(beatmap); else Debug.LogError("NoteSpawner is null in BeatmapManager."); SetupEnemiesAndHP(); } 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."); } } private void ValidateAndPopulateStatistics(Beatmap parsed) { if (parsed == null || parsed.notes == null) return; // Ensure note amount is correct if (parsedNoteAmount <= 0) { parsedNoteAmount = parsed.notes.Length; if (JudgeManager.IsDebugEnabled) Debug.Log($"[BeatmapManager] Calculated missing noteAmount: {parsedNoteAmount}"); } // Ensure note statistics (counts by color) are populated if (parsedNoteStatistics == null || parsedNoteStatistics.Length == 0) { var counts = new System.Collections.Generic.Dictionary(); foreach (var note in parsed.notes) { if (string.IsNullOrEmpty(note.color)) continue; string c = note.color.ToLower(); if (!counts.ContainsKey(c)) counts[c] = 0; counts[c]++; } parsedNoteStatistics = new NoteStatistic[counts.Count]; int i = 0; foreach (var kvp in counts) { parsedNoteStatistics[i++] = new NoteStatistic { colorType = kvp.Key, count = kvp.Value }; } if (JudgeManager.IsDebugEnabled) Debug.Log($"[BeatmapManager] Calculated missing noteStatistics for {counts.Count} colors"); } } // Compute per-track note counts and apply max score caps to allies private void ApplyTrackScoreCaps(Beatmap parsed) { if (parsed == null || parsed.notes == null) return; const int trackCount = 5; var counts = new int[trackCount]; for (int i = 0; i < parsed.notes.Length; i++) { var n = parsed.notes[i]; int idx = n != null ? n.trackIndex : -1; if (idx < 0 || idx >= trackCount) continue; counts[idx]++; } int per = perNoteScore; if (per <= 0 && parsedNoteAmount > 0) per = totalChartScore / parsedNoteAmount; var ui = teamUIController.Instance; for (int i = 0; i < trackCount; i++) { int maxScore = Mathf.Max(0, per * counts[i]); var ally = ResolveAllyCombatant(i, ui); if (ally != null) { ally.SetMaxTrackScore(maxScore, true); continue; } // Fallback: update UI text directly if combatant not found string value = $"0/{maxScore}"; if (ui != null) { switch (i) { case 0: if (ui.teammate01_current_scoreText != null) ui.teammate01_current_scoreText.text = value; break; case 1: if (ui.teammate02_current_scoreText != null) ui.teammate02_current_scoreText.text = value; break; case 2: if (ui.teammate03_current_scoreText != null) ui.teammate03_current_scoreText.text = value; break; case 3: if (ui.teammate04_current_scoreText != null) ui.teammate04_current_scoreText.text = value; break; case 4: if (ui.teammate05_current_scoreText != null) ui.teammate05_current_scoreText.text = value; break; } } } } private AllyCombatant ResolveAllyCombatant(int slotIndex, teamUIController ui) { GameObject go = null; if (ui != null) go = ui.GetAllyObjectBySlot(slotIndex); if (go == null) { var byName = GameObject.Find($"ally_0{slotIndex + 1}"); if (byName != null) go = byName; } if (go == null) return null; var ally = go.GetComponent(); if (ally != null) return ally; return go.GetComponentInChildren(true); } [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; public bool enemyList_isEmpty; public ColorSegment[] colorSegments; public NoteStatistic[] noteStatistics; public TrackStates trackStates; } [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; } }