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

491 lines
17 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
{
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<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("谱面文件未找到!");
}
}
// 在 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<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);
}
// 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<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;
}
// Parse main beatmap
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;
}
// Parse extras using BeatmapExtra which contains the requested fields and types
BeatmapExtra extra = null;
try
{
extra = JsonUtility.FromJson<BeatmapExtra>(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;
}
}