Files
bansonic_beta_main/Assets/scripts/gamePlay_gameplay/BeatmapManager.cs
T

636 lines
26 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
/// <summary>
/// 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.
/// </summary>
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;
/// <summary>
/// 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.
/// </summary>
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<Beatmap>(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<BeatmapExtra>(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("ļδҵ");
}
}
// 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 = FindObjectOfType<GameManager>();
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<AudioClip>(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<SpriteRenderer>();
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 ?? FindObjectOfType<PauseManager>();
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<int> enemyIds = new List<int>();
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<int> { 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<Beatmap>(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<BeatmapExtra>(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 (GameConfig.verboseLogs) 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<string, int>();
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 (GameConfig.verboseLogs) Debug.Log($"[BeatmapManager] Calculated missing noteStatistics for {counts.Count} colors");
}
}
[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; }
}