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

560 lines
21 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;
// 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");
}
// 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.");
}
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.");
}
}
[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; }
}