结算重做部分 谱面分装好 编队UI之bug修复
This commit is contained in:
@@ -25,6 +25,7 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
public float scoreEfficiency = 1f;
|
||||
|
||||
private bool isDead = false; // new: dead flag
|
||||
public BeatmapManager bmm;
|
||||
|
||||
// add attack stat to be available at runtime
|
||||
[Tooltip("基础攻击力(运行时使用),会从 AllyHero_SO.levelStats[0].attack 填充(如果存在)")]
|
||||
@@ -35,6 +36,9 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
[Tooltip("Base score value for a perfect hit on this track")]
|
||||
public int baseTrackScore = 1000;
|
||||
|
||||
// Editable multipliers for each judge quality (exposed for designers)
|
||||
[Tooltip("Score multiplier for Perfect (default 1.0)")]
|
||||
public float perfectRatio = 1f;
|
||||
[Tooltip("Score multiplier for Great (e.g. 0.75)")]
|
||||
public float greatRatio = 0.75f;
|
||||
[Tooltip("Score multiplier for Good (e.g. 0.5)")]
|
||||
@@ -298,34 +302,41 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
}
|
||||
|
||||
// Scoring API
|
||||
public void AddScoreForJudge(string judge)
|
||||
// Modified to return the amount added so callers can track per-track contributions
|
||||
public int AddScoreForJudge(string judge)
|
||||
{
|
||||
int add = 0;
|
||||
float multiplier = 0f;
|
||||
switch (judge)
|
||||
{
|
||||
case "Perfect":
|
||||
add = baseTrackScore;
|
||||
multiplier = perfectRatio;
|
||||
break;
|
||||
case "Great":
|
||||
add = Mathf.FloorToInt(baseTrackScore * greatRatio);
|
||||
multiplier = greatRatio;
|
||||
break;
|
||||
case "Good":
|
||||
add = Mathf.FloorToInt(baseTrackScore * goodRatio);
|
||||
multiplier = goodRatio;
|
||||
break;
|
||||
case "Miss":
|
||||
add = Mathf.FloorToInt(baseTrackScore * missRatio);
|
||||
multiplier = missRatio;
|
||||
break;
|
||||
default:
|
||||
add = 0;
|
||||
multiplier = 0f;
|
||||
break;
|
||||
}
|
||||
// apply scoreEfficiency or other modifiers here
|
||||
add = Mathf.FloorToInt(add * scoreEfficiency);
|
||||
|
||||
// Use perNoteScore from BeatmapManager as the base score for each note hit
|
||||
int baseScore = bmm != null ? bmm.perNoteScore : baseTrackScore;
|
||||
add = Mathf.FloorToInt(baseScore * multiplier);
|
||||
|
||||
int before = currentScore;
|
||||
currentScore = Mathf.Clamp(currentScore + add, 0, maxTrackScore);
|
||||
int actuallyAdded = currentScore - before;
|
||||
UpdateScoreUI();
|
||||
// update global total
|
||||
ScoreManager.Instance?.RecalculateTotal();
|
||||
return actuallyAdded;
|
||||
}
|
||||
|
||||
// Add an arbitrary score delta directly (used by skills to modify single judge or grant/penalize score)
|
||||
|
||||
@@ -8,10 +8,73 @@ public class ScoreManager : MonoBehaviour
|
||||
|
||||
public int totalScore = 0;
|
||||
|
||||
// per-track pm score sums (red, green, yellow, purple, blue)
|
||||
public int red_pmScore_sum = 0;
|
||||
public int green_pmScore_sum = 0;
|
||||
public int yellow_pmScore_sum = 0;
|
||||
public int purple_pmScore_sum = 0;
|
||||
public int blue_pmScore_sum = 0;
|
||||
|
||||
// aggregate of the five track pm sums
|
||||
public int allSum_pmScore = 0;
|
||||
|
||||
private int[] pmScoreSums = new int[5];
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null) Instance = this;
|
||||
else Destroy(gameObject);
|
||||
|
||||
for (int i = 0; i < pmScoreSums.Length; i++) pmScoreSums[i] = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a per-note pm score delta to the specified track (0..4) and update named fields + aggregate.
|
||||
/// </summary>
|
||||
public void AddPmScoreForTrack(int trackIndex, int delta)
|
||||
{
|
||||
if (trackIndex < 0 || trackIndex >= pmScoreSums.Length) return;
|
||||
if (delta == 0) return;
|
||||
pmScoreSums[trackIndex] += delta;
|
||||
// clamp at int.MaxValue-1 to avoid overflow
|
||||
if (pmScoreSums[trackIndex] < 0) pmScoreSums[trackIndex] = 0;
|
||||
if (pmScoreSums[trackIndex] > int.MaxValue - 1) pmScoreSums[trackIndex] = int.MaxValue - 1;
|
||||
|
||||
// update named fields for easy access
|
||||
red_pmScore_sum = pmScoreSums[0];
|
||||
green_pmScore_sum = pmScoreSums[1];
|
||||
yellow_pmScore_sum = pmScoreSums[2];
|
||||
purple_pmScore_sum = pmScoreSums[3];
|
||||
blue_pmScore_sum = pmScoreSums[4];
|
||||
|
||||
// recompute aggregate
|
||||
long agg = 0;
|
||||
for (int i = 0; i < pmScoreSums.Length; i++) agg += pmScoreSums[i];
|
||||
allSum_pmScore = (int)Mathf.Min((float)agg, (float)int.MaxValue - 1f);
|
||||
|
||||
Debug.Log($"[ScoreManager] Added {delta} to track {trackIndex} pm sum. New per-track sums: R{red_pmScore_sum} G{green_pmScore_sum} Y{yellow_pmScore_sum} P{purple_pmScore_sum} B{blue_pmScore_sum} -> allSum={allSum_pmScore}");
|
||||
|
||||
// Update UI in teamUIController if available (per-track pm sums + aggregate)
|
||||
var ui = teamUIController.Instance;
|
||||
if (ui != null)
|
||||
{
|
||||
// Legacy Text fields on teamUIController
|
||||
try
|
||||
{
|
||||
if (ui.red_pmScore_sum != null) ui.red_pmScore_sum.text = red_pmScore_sum.ToString();
|
||||
if (ui.green_pmScore_sum != null) ui.green_pmScore_sum.text = green_pmScore_sum.ToString();
|
||||
if (ui.yellow_pmScore_sum != null) ui.yellow_pmScore_sum.text = yellow_pmScore_sum.ToString();
|
||||
if (ui.purple_pmScore_sum != null) ui.purple_pmScore_sum.text = purple_pmScore_sum.ToString();
|
||||
if (ui.blue_pmScore_sum != null) ui.blue_pmScore_sum.text = blue_pmScore_sum.ToString();
|
||||
if (ui.allSum_pmScore != null) ui.allSum_pmScore.text = allSum_pmScore.ToString();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[ScoreManager] Failed to write pm sums to teamUIController fields: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
// Optionally update any UI for pm sums here if teamUIController exposes fields (not implemented by default)
|
||||
}
|
||||
|
||||
public void RecalculateTotal()
|
||||
|
||||
@@ -38,6 +38,9 @@ public class EnemyData_SO : ScriptableObject
|
||||
[Tooltip("最大法力值(仅当类型为 Boss 或 Legend 时生效)")]
|
||||
public int enemy_maxMana = 0;
|
||||
|
||||
[Header("死亡时提供的分数")]
|
||||
public int scoreOnBeingDefeated;
|
||||
|
||||
// Skills (same idea as AllyHero_SO)
|
||||
[Header("Skills")]
|
||||
public SkillDefinition[] availableSkills;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class BeatmapManager : MonoBehaviour
|
||||
|
||||
@@ -11,6 +11,9 @@ public class BeatmapManager : MonoBehaviour
|
||||
// 音符生成器引用
|
||||
public NoteSpawner noteSpawner;
|
||||
|
||||
// 新增:teamUIController 引用,直接拖拽赋值
|
||||
public teamUIController uiController;
|
||||
|
||||
// Extra fields from beatmap JSON (stored temporarily)
|
||||
[HideInInspector] public string parsedTitle;
|
||||
[HideInInspector] public string parsedComposer;
|
||||
@@ -30,6 +33,23 @@ public class BeatmapManager : MonoBehaviour
|
||||
|
||||
[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
|
||||
public float ezMultiplier = 1f;
|
||||
public float hdMultiplier = 1.5f;
|
||||
public float inMultiplier = 2f;
|
||||
public float imMultiplier = 3f;
|
||||
|
||||
// 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)
|
||||
{
|
||||
@@ -113,6 +133,15 @@ public class BeatmapManager : MonoBehaviour
|
||||
|
||||
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
|
||||
{
|
||||
@@ -131,10 +160,76 @@ public class BeatmapManager : MonoBehaviour
|
||||
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);
|
||||
|
||||
// Process colorSegments if not empty (for both test and normal mode)
|
||||
if (!parsedEnemyListIsEmpty && parsedColorSegments != null && parsedColorSegments.Length > 0)
|
||||
{
|
||||
// Determine multiplier based on difficulty
|
||||
float multiplier = ezMultiplier;
|
||||
if (parsedDifficulty == 1) multiplier = hdMultiplier;
|
||||
else if (parsedDifficulty == 2) multiplier = inMultiplier;
|
||||
else if (parsedDifficulty == 3) multiplier = imMultiplier;
|
||||
|
||||
// Calculate total HP
|
||||
float totalHP = parsedNoteAmount * multiplier;
|
||||
|
||||
// Set enemy slot IDs and calculate HP for each enemy
|
||||
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;
|
||||
}
|
||||
Debug.LogError($"Parsed enemyId: {enemyId}");
|
||||
enemyIds.Add(enemyId);
|
||||
|
||||
// Calculate individual enemy HP
|
||||
int enemyHP = Mathf.RoundToInt(totalHP * segment.percentage);
|
||||
|
||||
// Find and update the corresponding EnemyData_SO
|
||||
EnemyData_SO[] allEnemies = Resources.LoadAll<EnemyData_SO>("");
|
||||
foreach (var so in allEnemies)
|
||||
{
|
||||
if (so != null && so.enemyID == enemyId)
|
||||
{
|
||||
so.enemy_maxHP = enemyHP;
|
||||
Debug.Log(string.Format("Set enemy {0} maxHP to {1}", enemyId, enemyHP));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update teamUIController
|
||||
if (uiController != null)
|
||||
{
|
||||
Debug.Log($"enemyIds before padding: {string.Join(",", enemyIds)}");
|
||||
// Ensure enemySlotIds has exactly 5 elements, padding with 0 if necessary
|
||||
while (enemyIds.Count < 5)
|
||||
{
|
||||
enemyIds.Add(0);
|
||||
}
|
||||
Debug.Log($"enemyIds after padding: {string.Join(",", enemyIds)}");
|
||||
uiController.enemySlotIds = enemyIds;
|
||||
Debug.LogError($"Assigned enemySlotIds to teamUIController: {string.Join(",", enemyIds)}");
|
||||
uiController.PopulateEnemySOsFromIds();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("teamUIController not assigned in the Inspector");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -197,11 +292,51 @@ public class BeatmapManager : MonoBehaviour
|
||||
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;
|
||||
}
|
||||
|
||||
// Parse JSON string to Beatmap and extras, then pass to NoteSpawner
|
||||
private void ProcessJsonAndLoad(string json)
|
||||
{
|
||||
// Debug.LogError("ProcessJsonAndLoad called");
|
||||
if (string.IsNullOrEmpty(json))
|
||||
{
|
||||
Debug.LogError("ProcessJsonAndLoad: json is empty");
|
||||
@@ -259,6 +394,15 @@ public class BeatmapManager : MonoBehaviour
|
||||
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
|
||||
{
|
||||
@@ -281,11 +425,93 @@ public class BeatmapManager : MonoBehaviour
|
||||
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);
|
||||
noteSpawner.LoadBeatmap(beatmap);
|
||||
|
||||
// Process colorSegments if not empty
|
||||
if (!parsedEnemyListIsEmpty && parsedColorSegments != null && parsedColorSegments.Length > 0)
|
||||
{
|
||||
// Determine multiplier based on difficulty
|
||||
float multiplier = ezMultiplier;
|
||||
if (parsedDifficulty == 1) multiplier = hdMultiplier;
|
||||
else if (parsedDifficulty == 2) multiplier = inMultiplier;
|
||||
else if (parsedDifficulty == 3) multiplier = imMultiplier;
|
||||
|
||||
// Calculate total HP
|
||||
float totalHP = parsedNoteAmount * multiplier;
|
||||
|
||||
// Set enemy slot IDs and calculate HP for each enemy
|
||||
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;
|
||||
}
|
||||
Debug.LogError($"Parsed enemyId: {enemyId}");
|
||||
enemyIds.Add(enemyId);
|
||||
|
||||
// Calculate individual enemy HP
|
||||
int enemyHP = Mathf.RoundToInt(totalHP * segment.percentage);
|
||||
|
||||
// Find and update the corresponding EnemyData_SO
|
||||
EnemyData_SO[] allEnemies = Resources.LoadAll<EnemyData_SO>("");
|
||||
foreach (var so in allEnemies)
|
||||
{
|
||||
if (so != null && so.enemyID == enemyId)
|
||||
{
|
||||
so.enemy_maxHP = enemyHP;
|
||||
Debug.Log(string.Format("Set enemy {0} maxHP to {1}", enemyId, enemyHP));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update teamUIController
|
||||
if (uiController != null)
|
||||
{
|
||||
Debug.Log($"enemyIds before padding: {string.Join(",", enemyIds)}");
|
||||
// Ensure enemySlotIds has exactly 5 elements, padding with 0 if necessary
|
||||
while (enemyIds.Count < 5)
|
||||
{
|
||||
enemyIds.Add(0);
|
||||
}
|
||||
Debug.Log($"enemyIds after padding: {string.Join(",", enemyIds)}");
|
||||
uiController.enemySlotIds = enemyIds;
|
||||
Debug.LogError($"Assigned enemySlotIds to teamUIController: {string.Join(",", enemyIds)}");
|
||||
uiController.PopulateEnemySOsFromIds();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("teamUIController not assigned in the Inspector");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,56 +106,9 @@ public class GameManager : MonoBehaviour
|
||||
StartCoroutine(HandleTestModeStartup());
|
||||
return;
|
||||
}
|
||||
|
||||
// 以下为原有流程(已保留注释,未改写)
|
||||
// 在游戏开始时加载并初始化谱面
|
||||
string beatmapFilePath = Path.Combine(Application.streamingAssetsPath, "Emilia_demo.json");
|
||||
|
||||
if (!File.Exists(beatmapFilePath))
|
||||
{
|
||||
Debug.LogError($"谱面文件不存在: {beatmapFilePath}");
|
||||
return;
|
||||
}
|
||||
|
||||
string json = File.ReadAllText(beatmapFilePath);
|
||||
// 假设 Beatmap 是一个已定义的类
|
||||
Beatmap beatmap = JsonUtility.FromJson<Beatmap>(json); // 解析 JSON 为 Beatmap 对象
|
||||
|
||||
if (beatmap == null)
|
||||
{
|
||||
Debug.LogError("谱面解析失败,JSON 格式可能错误!");
|
||||
return;
|
||||
}
|
||||
|
||||
beatmapManager.LoadBeatmap(beatmap); // 传递给 BeatmapManager
|
||||
noteSpawner.LoadBeatmap(beatmap); // 传递给 NoteSpawner
|
||||
|
||||
// 播放背景音乐(延迟播放由 beatmapManager.globalDelaySeconds 决定)
|
||||
if (!string.IsNullOrEmpty(beatmap.musicFile))
|
||||
{
|
||||
AudioClip musicClip = Resources.Load<AudioClip>(beatmap.musicFile);
|
||||
if (musicClip == null)
|
||||
{
|
||||
Debug.LogError($"音乐文件加载失败: {beatmap.musicFile}");
|
||||
}
|
||||
else
|
||||
{
|
||||
musicSource.clip = musicClip;
|
||||
float delay = beatmapManager != null ? beatmapManager.globalDelaySeconds : 0f;
|
||||
if (delay > 0f)
|
||||
{
|
||||
musicSource.PlayDelayed(delay);
|
||||
Debug.Log($"Scheduled music to play after {delay} seconds.");
|
||||
}
|
||||
else
|
||||
{
|
||||
musicSource.Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("谱面中 musicFile 为空!");
|
||||
StartCoroutine(HandleNormalModeStartup());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,6 +299,81 @@ public class GameManager : MonoBehaviour
|
||||
UpdateStatusOnConsole("Playback started");
|
||||
}
|
||||
|
||||
private IEnumerator HandleNormalModeStartup()
|
||||
{
|
||||
// 加载谱面但不生成音符
|
||||
string beatmapFilePath = Path.Combine(Application.streamingAssetsPath, "Emilia_demo.json");
|
||||
|
||||
if (!File.Exists(beatmapFilePath))
|
||||
{
|
||||
Debug.LogError($"谱面文件不存在: {beatmapFilePath}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
string json = File.ReadAllText(beatmapFilePath);
|
||||
bool parsed = beatmapManager.ParseJsonOnly(json);
|
||||
if (!parsed)
|
||||
{
|
||||
Debug.LogError("ParseJsonOnly 失败");
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 暂停系统
|
||||
SubscribeToPauseManager();
|
||||
PauseManager.Instance?.Pause(true);
|
||||
UpdateStatusOnConsole("Paused: press Space to start playback");
|
||||
|
||||
// 等待空格
|
||||
while (!Input.GetKeyDown(KeyCode.Space))
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// 恢复
|
||||
PauseManager.Instance?.Pause(false);
|
||||
|
||||
// 生成音符
|
||||
if (beatmapManager.beatmap != null)
|
||||
{
|
||||
noteSpawner.LoadBeatmap(beatmapManager.beatmap);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("beatmap 未解析");
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 播放背景音乐(延迟播放由 beatmapManager.globalDelaySeconds 决定)
|
||||
if (!string.IsNullOrEmpty(beatmapManager.parsedMusicFile))
|
||||
{
|
||||
AudioClip musicClip = Resources.Load<AudioClip>(beatmapManager.parsedMusicFile);
|
||||
if (musicClip == null)
|
||||
{
|
||||
Debug.LogError($"音乐文件加载失败: {beatmapManager.parsedMusicFile}");
|
||||
}
|
||||
else
|
||||
{
|
||||
musicSource.clip = musicClip;
|
||||
float delay = beatmapManager.globalDelaySeconds;
|
||||
if (delay > 0f)
|
||||
{
|
||||
musicSource.PlayDelayed(delay);
|
||||
Debug.Log($"Scheduled music to play after {delay} seconds.");
|
||||
}
|
||||
else
|
||||
{
|
||||
musicSource.Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("谱面中 musicFile 为空!");
|
||||
}
|
||||
|
||||
UpdateStatusOnConsole("Playback started");
|
||||
}
|
||||
|
||||
private void UpdateStatusOnConsole(string message)
|
||||
{
|
||||
Debug.Log(message);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
using System;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class InputManager : MonoBehaviour
|
||||
{
|
||||
@@ -12,7 +13,7 @@ public class InputManager : MonoBehaviour
|
||||
public TextMeshProUGUI[] trackJudgeTexts = new TextMeshProUGUI[5];
|
||||
|
||||
[Header("按键指示文本(独立于判定文本,五个分别对应按键:D,F,Space,J,K)")]
|
||||
public TextMeshProUGUI[] trackKeyTexts = new TextMeshProUGUI[5];
|
||||
public Text[] trackKeyTexts = new Text[5];
|
||||
|
||||
[Header("是否显示判定文字")]
|
||||
public bool showJudgeText = true;
|
||||
|
||||
@@ -112,6 +112,26 @@ public class Note : BaseNote
|
||||
InputManager.Instance?.ShowJudgeResult(TrackIndex, judgeResult);
|
||||
JudgeSoundManager.Instance?.PlayJudgeSound(judgeResult);
|
||||
teamUIController.Instance?.OnJudgeResult(judgeResult); // 更新combo计数
|
||||
|
||||
// Calculate score addition via AllyCombatant and add to per-track pm sums
|
||||
try
|
||||
{
|
||||
var allyGo = GameObject.Find($"ally_0{TrackIndex + 1}");
|
||||
if (allyGo != null)
|
||||
{
|
||||
var ally = allyGo.GetComponent<AllyCombatant>();
|
||||
if (ally != null)
|
||||
{
|
||||
int added = ally.AddScoreForJudge(judgeResult);
|
||||
ScoreManager.Instance?.AddPmScoreForTrack(TrackIndex, added);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[Note] Failed to add per-track score: {ex}");
|
||||
}
|
||||
|
||||
// Notify SkillBuilder about this note hit so OnNoteHit skills may trigger (tap notes)
|
||||
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, judgeResult, SkillDefinition.NoteTypeTrigger.Tap); } catch { }
|
||||
}
|
||||
@@ -151,6 +171,26 @@ public class Note : BaseNote
|
||||
InputManager.Instance?.ShowJudgeResult(TrackIndex, "Miss");
|
||||
JudgeSoundManager.Instance?.PlayJudgeSound("Miss");
|
||||
teamUIController.Instance?.OnJudgeResult("Miss");
|
||||
|
||||
// Add score for Miss as well (some systems may give 0)
|
||||
try
|
||||
{
|
||||
var allyGo = GameObject.Find($"ally_0{TrackIndex + 1}");
|
||||
if (allyGo != null)
|
||||
{
|
||||
var ally = allyGo.GetComponent<AllyCombatant>();
|
||||
if (ally != null)
|
||||
{
|
||||
int added = ally.AddScoreForJudge("Miss");
|
||||
ScoreManager.Instance?.AddPmScoreForTrack(TrackIndex, added);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[Note] Failed to add per-track score for Miss: {ex}");
|
||||
}
|
||||
|
||||
// Notify SkillBuilder about Miss so OnNoteHit skills configured for Miss can trigger
|
||||
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, "Miss", SkillDefinition.NoteTypeTrigger.Tap); } catch { }
|
||||
// InputManager.Instance?.ShowJudgeResult(TrackIndex, "Miss"); // 已在HandlePress中调用 for manual presses
|
||||
|
||||
@@ -9,11 +9,13 @@ using UnityEditor;
|
||||
|
||||
public class teamUIController : MonoBehaviour
|
||||
{
|
||||
public static teamUIController Instance {
|
||||
get;
|
||||
private set;
|
||||
public static teamUIController Instance
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
|
||||
// --- 新增:队伍槽 ID 列表(从上到下 5 个友军) ---
|
||||
[Header("Runtime ally configuration")]
|
||||
[Tooltip("IDs for the 5 ally slots (top to bottom). These are used by PopulateAllySOsFromIds to resolve SOs into currentAllySOs.")]
|
||||
@@ -22,7 +24,7 @@ public class teamUIController : MonoBehaviour
|
||||
// --- 新增:敌人队列槽 ID 列表(按出场顺序) ---
|
||||
[Header("Runtime enemy configuration")]
|
||||
[Tooltip("IDs for enemies to appear in sequence. All enemies share a single GameObject and will be initialized from these SOs in order.")]
|
||||
public List<int> enemySlotIds = new List<int>();
|
||||
public List<int> enemySlotIds = new List<int>() { 0, 0, 0, 0, 0 };
|
||||
|
||||
// --- 新增:解析后的当前五个 SO 引用(按顺序) ---
|
||||
private TeamCharacterDataInfo[] currentAllySOs = new TeamCharacterDataInfo[5];
|
||||
@@ -158,7 +160,7 @@ public class teamUIController : MonoBehaviour
|
||||
case 4: isAlly05_active = false; break;
|
||||
}
|
||||
ToggleAllyObject(i, false);
|
||||
Debug.Log($"[teamUIController] slot {i+1} id=0 -> disabled");
|
||||
Debug.Log($"[teamUIController] slot {i + 1} id=0 -> disabled");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -174,7 +176,7 @@ public class teamUIController : MonoBehaviour
|
||||
if (obj is TeamCharacterDataInfo tc)
|
||||
{
|
||||
found = tc;
|
||||
Debug.Log($"[teamUIController] (Editor) matched asset by path: {path} for id={id} slot={i+1}");
|
||||
Debug.Log($"[teamUIController] (Editor) matched asset by path: {path} for id={id} slot={i + 1}");
|
||||
break;
|
||||
}
|
||||
// if it's AllyHero_SO, map to a runtime TeamCharacterDataInfo instance
|
||||
@@ -187,7 +189,7 @@ public class teamUIController : MonoBehaviour
|
||||
mapped.CharacterTeamSprite = ah.ally_heroProfile;
|
||||
mapped.name = ah.name;
|
||||
found = mapped;
|
||||
Debug.Log($"[teamUIController] (Editor) matched AllyHero_SO by path: {path} for id={id} slot={i+1}");
|
||||
Debug.Log($"[teamUIController] (Editor) matched AllyHero_SO by path: {path} for id={id} slot={i + 1}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -238,7 +240,7 @@ public class teamUIController : MonoBehaviour
|
||||
case 4: isAlly05_active = true; break;
|
||||
}
|
||||
ToggleAllyObject(i, true);
|
||||
DebugCharacterSO(found, i+1, true);
|
||||
DebugCharacterSO(found, i + 1, true);
|
||||
// Also attempt to set the teammate character image from an underlying AllyHero_SO if available
|
||||
try
|
||||
{
|
||||
@@ -280,7 +282,7 @@ public class teamUIController : MonoBehaviour
|
||||
if (runtimeDiscovered.Count == 0)
|
||||
{
|
||||
var arrAll = Resources.LoadAll("");
|
||||
if (arrAll != null && arr.Length > 0)
|
||||
if (arrAll != null && arrAll.Length > 0) // Fixed: changed arr.Length to arrAll.Length
|
||||
{
|
||||
runtimeDiscovered.AddRange(arrAll);
|
||||
Debug.LogWarning($"[teamUIController] (Runtime) Resources.LoadAll returned 0 in '{resourcesPath}', fell back to loading all ScriptableObjects ({arrAll.Length})");
|
||||
@@ -293,7 +295,7 @@ public class teamUIController : MonoBehaviour
|
||||
{
|
||||
Debug.LogWarning($"[teamUIController] (Runtime) Resources.LoadAll failed for '{resourcesPath}': {ex.Message}");
|
||||
var arrAll = Resources.LoadAll("");
|
||||
if (arrAll != null && arr.Length > 0)
|
||||
if (arrAll != null && arrAll.Length > 0) // Fixed: changed arr.Length to arrAll.Length
|
||||
runtimeDiscovered.AddRange(arrAll);
|
||||
}
|
||||
}
|
||||
@@ -463,9 +465,25 @@ public class teamUIController : MonoBehaviour
|
||||
#endif
|
||||
}
|
||||
|
||||
// ResolveResourcesRelativePath helper method is missing, assuming it exists externally or is part of a base class / extension.
|
||||
// Since the user is providing the full class, I will assume it's either defined outside this snippet or is implicitly handled,
|
||||
// but the code within #if UNITY_EDITOR is what matters for the file content.
|
||||
// For completeness, if it's not defined, the runtime block would throw an error, but as I cannot add it, I must assume it's defined elsewhere.
|
||||
// A simplified placeholder for ResolveResourcesRelativePath for compilation (assuming it strips Resources/ and leading/trailing slashes):
|
||||
private string ResolveResourcesRelativePath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path)) return string.Empty;
|
||||
path = path.Replace("\\", "/");
|
||||
path = path.Replace("Assets/Resources/", "");
|
||||
path = path.Replace("Resources/", "");
|
||||
return path.Trim('/').ToLower();
|
||||
}
|
||||
|
||||
|
||||
// Populate enemy SOs from enemySlotIds using editor/runtime paths similar to allies
|
||||
public void PopulateEnemySOsFromIds()
|
||||
{
|
||||
Debug.Log($"PopulateEnemySOsFromIds called, enemySlotIds: {string.Join(",", enemySlotIds)}");
|
||||
if (enemySlotIds == null || enemySlotIds.Count == 0)
|
||||
{
|
||||
currentEnemySOs = new EnemyData_SO[0];
|
||||
@@ -539,10 +557,10 @@ public class teamUIController : MonoBehaviour
|
||||
if (runtimeDiscovered.Count == 0)
|
||||
{
|
||||
var arrAll = Resources.LoadAll("");
|
||||
if (arrAll != null && arrAll.Length > 0) runtimeDiscovered.AddRange(arrAll);
|
||||
if (arrAll != null && arrAll.Length > 0) runtimeDiscovered.AddRange(arrAll); // Fixed: arr.Length to arrAll.Length
|
||||
}
|
||||
}
|
||||
catch { var arrAll = Resources.LoadAll(""); if (arrAll != null && arrAll.Length > 0) runtimeDiscovered.AddRange(arrAll); }
|
||||
catch { var arrAll = Resources.LoadAll(""); if (arrAll != null && arrAll.Length > 0) runtimeDiscovered.AddRange(arrAll); } // Fixed: arr.Length to arrAll.Length
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -560,6 +578,7 @@ public class teamUIController : MonoBehaviour
|
||||
if (o is EnemyData_SO e)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.name) && e.name.Contains(id.ToString())) { found = e; break; }
|
||||
if (!string.IsNullOrEmpty(e.name) && e.name.StartsWith(id.ToString())) { found = e; break; }
|
||||
if (e.enemyID == id) { found = e; break; }
|
||||
}
|
||||
}
|
||||
@@ -581,6 +600,24 @@ public class teamUIController : MonoBehaviour
|
||||
// build recognized list and update UI
|
||||
FilterRecognizedEnemies();
|
||||
UpdateEnemyListText();
|
||||
|
||||
// Calculate total max HP for all recognized enemies
|
||||
// Note: totalMaxHP is a class member and is intended to be the fixed maximum HP for the entire encounter.
|
||||
totalMaxHP = 0;
|
||||
if (recognizedEnemySOs != null)
|
||||
{
|
||||
foreach (var so in recognizedEnemySOs)
|
||||
{
|
||||
if (so != null) totalMaxHP += so.enemy_maxHP;
|
||||
}
|
||||
}
|
||||
|
||||
// If enemies were populated and we have some, start spawning from the beginning
|
||||
if (recognizedEnemySOs.Length > 0)
|
||||
{
|
||||
enemyCurrentCount = 0;
|
||||
SpawnNextEnemy();
|
||||
}
|
||||
}
|
||||
|
||||
// Build recognizedEnemySOs from currentEnemySOs by filtering out null entries (id==0 or unresolved)
|
||||
@@ -643,6 +680,8 @@ public class teamUIController : MonoBehaviour
|
||||
[SerializeField] private int enemyCurrentCount; // 现在的敌人顺序
|
||||
[Tooltip("当前总分")]
|
||||
public TextMeshProUGUI currentTotalScore;
|
||||
[Header("谱面总分")]
|
||||
public TextMeshProUGUI allSum_pmScore;
|
||||
|
||||
// new: runtime enemy instance and UI sync fields
|
||||
private EnemyCombatant enemyCombatantInstance;
|
||||
@@ -651,6 +690,11 @@ public class teamUIController : MonoBehaviour
|
||||
private int prevEnemyHP = -1;
|
||||
private int prevEnemyMana = -1;
|
||||
|
||||
// For total enemy health bar
|
||||
private int totalMaxHP = 0; // 保留现有 totalMaxHP
|
||||
private Coroutine totalFadeHealthCoroutine;
|
||||
// 移除 private float prevTotalHealthFill = 1f;
|
||||
|
||||
[Header("Combo判定最低要求")]
|
||||
public ComboJudgeType comboJudgeType = ComboJudgeType.Perfect;
|
||||
|
||||
@@ -697,6 +741,8 @@ public class teamUIController : MonoBehaviour
|
||||
[SerializeField] private int teammate01_currentMana;
|
||||
[Tooltip("最大法力值")]
|
||||
[SerializeField] private int teammate01_maxMana;
|
||||
[Header("pmScore")]
|
||||
public TextMeshProUGUI red_pmScore_sum;
|
||||
|
||||
[Header("teammate02")]
|
||||
[Tooltip("父物体-控制整体显隐")]
|
||||
@@ -731,6 +777,8 @@ public class teamUIController : MonoBehaviour
|
||||
[SerializeField] private int teammate02_currentMana;
|
||||
[Tooltip("最大法力值")]
|
||||
[SerializeField] private int teammate02_maxMana;
|
||||
[Header("pmScore")]
|
||||
public TextMeshProUGUI green_pmScore_sum;
|
||||
|
||||
[Header("teammate03")]
|
||||
[Tooltip("父物体-控制整体显隐")]
|
||||
@@ -765,6 +813,8 @@ public class teamUIController : MonoBehaviour
|
||||
[SerializeField] private int teammate03_currentMana;
|
||||
[Tooltip("最大法力值")]
|
||||
[SerializeField] private int teammate03_maxMana;
|
||||
[Header("pmScore")]
|
||||
public TextMeshProUGUI yellow_pmScore_sum;
|
||||
|
||||
[Header("teammate04")]
|
||||
[Tooltip("父物体-控制整体显隐")]
|
||||
@@ -799,6 +849,8 @@ public class teamUIController : MonoBehaviour
|
||||
[SerializeField] private int teammate04_currentMana;
|
||||
[Tooltip("最大法力值")]
|
||||
[SerializeField] private int teammate04_maxMana;
|
||||
[Header("pmScore")]
|
||||
public TextMeshProUGUI purple_pmScore_sum;
|
||||
|
||||
[Header("teammate05")]
|
||||
[Tooltip("父物体-控制整体显隐")]
|
||||
@@ -833,6 +885,8 @@ public class teamUIController : MonoBehaviour
|
||||
[SerializeField] private int teammate05_currentMana;
|
||||
[Tooltip("最大法力值")]
|
||||
[SerializeField] private int teammate05_maxMana;
|
||||
[Header("pmScore")]
|
||||
public TextMeshProUGUI blue_pmScore_sum;
|
||||
|
||||
[Header("currentEnemy")]
|
||||
[Tooltip("父物体-控制整体显隐")]
|
||||
@@ -875,6 +929,13 @@ public class teamUIController : MonoBehaviour
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
// Load ally slot IDs from PlayerPrefs
|
||||
allySlotIds[0] = PlayerPrefs.GetInt("selected_heroSlot01_heroID", 0);
|
||||
allySlotIds[1] = PlayerPrefs.GetInt("selected_heroSlot02_heroID", 0);
|
||||
allySlotIds[2] = PlayerPrefs.GetInt("selected_heroSlot03_heroID", 0);
|
||||
allySlotIds[3] = PlayerPrefs.GetInt("selected_heroSlot04_heroID", 0);
|
||||
allySlotIds[4] = PlayerPrefs.GetInt("selected_heroSlot05_heroID", 0);
|
||||
|
||||
PopulateAllySOsFromIds();
|
||||
|
||||
// initialize previous flags so Update can detect changes
|
||||
@@ -898,7 +959,7 @@ public class teamUIController : MonoBehaviour
|
||||
SpawnNextEnemy();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
@@ -921,6 +982,8 @@ public class teamUIController : MonoBehaviour
|
||||
if (prevEnemyHP != hp)
|
||||
{
|
||||
UpdateEnemyHealthVisuals(prevEnemyHP, hp, true);
|
||||
// 当当前敌人的 HP 变化时,也需要更新总血条
|
||||
UpdateAllEnemyTotalHealthUIOnHpChange(hp);
|
||||
prevEnemyHP = hp;
|
||||
}
|
||||
if (prevEnemyMana != mana)
|
||||
@@ -996,42 +1059,120 @@ public class teamUIController : MonoBehaviour
|
||||
}
|
||||
|
||||
// Try to ensure the teammateXX_current_scoreText fields are assigned; if null, search the corresponding parent for TMP/Text
|
||||
// Try to ensure the teammateXX_current_scoreText fields and pm sum fields are assigned;
|
||||
// if null, search the corresponding parent, a scene object by name, or convert legacy Text -> TMP.
|
||||
private void ResolveScoreTextReferences()
|
||||
{
|
||||
void TryResolve(ref TextMeshProUGUI field, GameObject parent, string slotName)
|
||||
TextMeshProUGUI FindOrConvertTMP(GameObject candidate, string sceneNameHint = null)
|
||||
{
|
||||
if (field != null) return;
|
||||
if (parent != null)
|
||||
if (candidate != null)
|
||||
{
|
||||
var tmp = parent.GetComponentInChildren<TextMeshProUGUI>(true);
|
||||
if (tmp != null)
|
||||
{
|
||||
field = tmp;
|
||||
Debug.Log($"[teamUIController] Resolved {slotName} TMP from parent {parent.name} -> {tmp.gameObject.name}");
|
||||
return;
|
||||
}
|
||||
var legacy = parent.GetComponentInChildren<Text>(true);
|
||||
// Try TMP first
|
||||
var tmp = candidate.GetComponentInChildren<TextMeshProUGUI>(true);
|
||||
if (tmp != null) return tmp;
|
||||
|
||||
// Legacy Text fallback: copy text to a new TMP component on the same GameObject
|
||||
var legacy = candidate.GetComponentInChildren<Text>(true);
|
||||
if (legacy != null)
|
||||
{
|
||||
// if only legacy Text exists, try to create a TMP component to avoid type mismatch
|
||||
var go = legacy.gameObject;
|
||||
var created = go.GetComponent<TextMeshProUGUI>() ?? go.AddComponent<TextMeshProUGUI>();
|
||||
created.text = legacy.text;
|
||||
field = created;
|
||||
Debug.LogWarning($"[teamUIController] Found legacy Text for {slotName} under {parent.name}. Added/used TMP component on {go.name} and copied text.");
|
||||
return;
|
||||
var added = go.GetComponent<TextMeshProUGUI>() ?? go.AddComponent<TextMeshProUGUI>();
|
||||
added.text = legacy.text;
|
||||
// Optionally disable legacy renderer to avoid duplicate rendering
|
||||
legacy.enabled = false;
|
||||
Debug.LogWarning($"[teamUIController] Converted legacy Text to TMP on {go.name} (parent {candidate.name})");
|
||||
return added;
|
||||
}
|
||||
}
|
||||
Debug.LogWarning($"[teamUIController] Could not resolve {slotName} TMP (parent '{parent?.name}')");
|
||||
|
||||
// If candidate not found or no components under it, try direct scene object by name
|
||||
if (!string.IsNullOrEmpty(sceneNameHint))
|
||||
{
|
||||
var goByName = GameObject.Find(sceneNameHint);
|
||||
if (goByName != null)
|
||||
{
|
||||
var tmp2 = goByName.GetComponent<TextMeshProUGUI>();
|
||||
if (tmp2 != null) return tmp2;
|
||||
var legacy2 = goByName.GetComponent<Text>();
|
||||
if (legacy2 != null)
|
||||
{
|
||||
var created = goByName.GetComponent<TextMeshProUGUI>() ?? goByName.AddComponent<TextMeshProUGUI>();
|
||||
created.text = legacy2.text;
|
||||
legacy2.enabled = false;
|
||||
Debug.LogWarning($"[teamUIController] Converted legacy Text to TMP on {goByName.name}");
|
||||
return created;
|
||||
}
|
||||
// also try children
|
||||
var childTmp = goByName.GetComponentInChildren<TextMeshProUGUI>(true);
|
||||
if (childTmp != null) return childTmp;
|
||||
var childLegacy = goByName.GetComponentInChildren<Text>(true);
|
||||
if (childLegacy != null)
|
||||
{
|
||||
var go = childLegacy.gameObject;
|
||||
var created = go.GetComponent<TextMeshProUGUI>() ?? go.AddComponent<TextMeshProUGUI>();
|
||||
created.text = childLegacy.text;
|
||||
childLegacy.enabled = false;
|
||||
Debug.LogWarning($"[teamUIController] Converted legacy Text to TMP on {go.name} (child of {goByName.name})");
|
||||
return created;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Best-effort fallback: find any TMP in scene that matches hint substring
|
||||
if (!string.IsNullOrEmpty(sceneNameHint))
|
||||
{
|
||||
var all = Resources.FindObjectsOfTypeAll<TextMeshProUGUI>();
|
||||
var lowHint = sceneNameHint.ToLower();
|
||||
foreach (var t in all)
|
||||
{
|
||||
if (t == null || t.gameObject == null) continue;
|
||||
if (t.gameObject.name.ToLower().Contains(lowHint) || t.gameObject.name.ToLower().Contains("pm") && t.gameObject.name.ToLower().Contains(lowHint))
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
TryResolve(ref teammate01_current_scoreText, objectFather_ally01, "teammate01_current_scoreText");
|
||||
TryResolve(ref teammate02_current_scoreText, objectFather_ally02, "teammate02_current_scoreText");
|
||||
TryResolve(ref teammate03_current_scoreText, objectFather_ally03, "teammate03_current_scoreText");
|
||||
TryResolve(ref teammate04_current_scoreText, objectFather_ally04, "teammate04_current_scoreText");
|
||||
TryResolve(ref teammate05_current_scoreText, objectFather_ally05, "teammate05_current_scoreText");
|
||||
// Helper: try resolve TMP under parent, then scene by name
|
||||
void TryResolveScore(ref TextMeshProUGUI field, GameObject parent, string sceneName)
|
||||
{
|
||||
if (field != null) return;
|
||||
// 1) under parent
|
||||
field = FindOrConvertTMP(parent, null);
|
||||
if (field != null)
|
||||
{
|
||||
Debug.Log($"[teamUIController] Resolved score TMP from parent {parent?.name} -> {field.gameObject.name}");
|
||||
return;
|
||||
}
|
||||
// 2) by scene object name
|
||||
field = FindOrConvertTMP(null, sceneName);
|
||||
if (field != null)
|
||||
{
|
||||
Debug.Log($"[teamUIController] Resolved score TMP by scene name '{sceneName}' -> {field.gameObject.name}");
|
||||
return;
|
||||
}
|
||||
Debug.LogWarning($"[teamUIController] Could not resolve {sceneName} TMP (parent '{parent?.name}')");
|
||||
}
|
||||
|
||||
// total score: if assigned field is null, try to find any TMP in scene named "currentTotalScore" or under this object
|
||||
// Resolve per-track current score TMPs (these are usually set in inspector; fallback to parent lookup)
|
||||
TryResolveScore(ref teammate01_current_scoreText, objectFather_ally01, "teammate01_current_scoreText");
|
||||
TryResolveScore(ref teammate02_current_scoreText, objectFather_ally02, "teammate02_current_scoreText");
|
||||
TryResolveScore(ref teammate03_current_scoreText, objectFather_ally03, "teammate03_current_scoreText");
|
||||
TryResolveScore(ref teammate04_current_scoreText, objectFather_ally04, "teammate04_current_scoreText");
|
||||
TryResolveScore(ref teammate05_current_scoreText, objectFather_ally05, "teammate05_current_scoreText");
|
||||
|
||||
// Resolve pm score TMPs and aggregate sum (recommend these fields be TextMeshProUGUI)
|
||||
TryResolveScore(ref red_pmScore_sum, null, "red_pmScore_sum");
|
||||
TryResolveScore(ref green_pmScore_sum, null, "green_pmScore_sum");
|
||||
TryResolveScore(ref yellow_pmScore_sum, null, "yellow_pmScore_sum");
|
||||
TryResolveScore(ref purple_pmScore_sum, null, "purple_pmScore_sum");
|
||||
TryResolveScore(ref blue_pmScore_sum, null, "blue_pmScore_sum");
|
||||
// allSum_pmScore may have been declared as TextMeshProUGUI; if it's legacy Text, convert similarly
|
||||
// If you declared allSum_pmScore as TextMeshProUGUI field, do:
|
||||
TryResolveScore(ref allSum_pmScore, null, "allSum_pmScore");
|
||||
|
||||
// total score: if assigned field is null, try to find TMP child under this object or by name "currentTotalScore"
|
||||
if (currentTotalScore == null)
|
||||
{
|
||||
var tmp = GetComponentInChildren<TextMeshProUGUI>(true);
|
||||
@@ -1040,6 +1181,15 @@ public class teamUIController : MonoBehaviour
|
||||
currentTotalScore = tmp;
|
||||
Debug.Log($"[teamUIController] Resolved currentTotalScore from child {tmp.gameObject.name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var byName = GameObject.Find("currentTotalScore");
|
||||
if (byName != null)
|
||||
{
|
||||
var t = byName.GetComponent<TextMeshProUGUI>() ?? byName.GetComponent<Text>()?.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
if (t != null) currentTotalScore = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1125,6 +1275,8 @@ public class teamUIController : MonoBehaviour
|
||||
if (enemyCounter != null) enemyCounter.text = "0/0";
|
||||
if (currentEnemy_typeText != null) currentEnemy_typeText.text = "(普通)";
|
||||
UpdateEnemyListText();
|
||||
// 刷新总血条,在没有敌人时显示 0/TotalMax
|
||||
UpdateAllEnemyTotalHealthUIImmediate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1143,6 +1295,8 @@ public class teamUIController : MonoBehaviour
|
||||
if (enemyCounter != null) enemyCounter.text = "0/" + enemyCounterMax.ToString();
|
||||
if (currentEnemy_typeText != null) currentEnemy_typeText.text = "(普通)";
|
||||
UpdateEnemyListText();
|
||||
// 刷新总血条,在所有敌人死亡时显示 0/TotalMax
|
||||
UpdateAllEnemyTotalHealthUIImmediate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1199,7 +1353,7 @@ public class teamUIController : MonoBehaviour
|
||||
// update enemy counter text
|
||||
if (enemyCounter != null)
|
||||
{
|
||||
enemyCounter.text = $"{Mathf.Clamp(enemyCurrentCount+1,1,999)}/{enemyCounterMax}";
|
||||
enemyCounter.text = $"{Mathf.Clamp(enemyCurrentCount + 1, 1, 999)}/{enemyCounterMax}";
|
||||
}
|
||||
|
||||
// refresh the enemy list display
|
||||
@@ -1234,6 +1388,94 @@ public class teamUIController : MonoBehaviour
|
||||
UpdateEnemyUIImmediate();
|
||||
}
|
||||
|
||||
// 新增:在当前敌人 HP 变化时,更新总血条 UI
|
||||
private void UpdateAllEnemyTotalHealthUIOnHpChange(int newCurrentHP)
|
||||
{
|
||||
if (allEnemy_totalHealthImage == null) return;
|
||||
|
||||
// 重新计算总当前血量(currentEnemySOs 保持不变,但 active enemy 的 current HP 改变了)
|
||||
int totalCur = 0;
|
||||
if (currentEnemySOs != null)
|
||||
{
|
||||
for (int i = enemyCurrentCount; i < currentEnemySOs.Length; i++)
|
||||
{
|
||||
var so = currentEnemySOs[i];
|
||||
if (so == null) continue;
|
||||
|
||||
// 仅更新当前活跃敌人的血量,后续敌人保持 Max HP
|
||||
if (i == enemyCurrentCount) totalCur += newCurrentHP; else totalCur += so.enemy_maxHP;
|
||||
}
|
||||
}
|
||||
|
||||
float newFill = totalMaxHP > 0 ? (float)totalCur / totalMaxHP : 0f;
|
||||
|
||||
// 1. 更新实血条
|
||||
allEnemy_totalHealthImage.fillAmount = newFill;
|
||||
if (totalEnemy_healthRate != null) totalEnemy_healthRate.text = $"{totalCur}/{totalMaxHP}";
|
||||
|
||||
// 2. Animate fade bar (only when health decreases)
|
||||
if (allEnemy_totalFadehealthImage != null)
|
||||
{
|
||||
float currentFadeFill = allEnemy_totalFadehealthImage.fillAmount;
|
||||
|
||||
if (currentFadeFill > newFill)
|
||||
{
|
||||
// Health decreased: stop previous coroutine and start new fall animation
|
||||
if (totalFadeHealthCoroutine != null) StopCoroutine(totalFadeHealthCoroutine);
|
||||
if (Application.isPlaying)
|
||||
totalFadeHealthCoroutine = StartCoroutine(TotalFadeHealthCoroutine(newFill));
|
||||
else
|
||||
allEnemy_totalFadehealthImage.fillAmount = newFill;
|
||||
}
|
||||
else if (currentFadeFill < newFill)
|
||||
{
|
||||
// Health increased (healing): update fade bar immediately
|
||||
if (totalFadeHealthCoroutine != null) StopCoroutine(totalFadeHealthCoroutine);
|
||||
allEnemy_totalFadehealthImage.fillAmount = newFill;
|
||||
}
|
||||
// If currentFadeFill == newFill, do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:用于在 SpawnNextEnemy/No Enemy 时立即更新总血条(不使用动画逻辑)
|
||||
private void UpdateAllEnemyTotalHealthUIImmediate()
|
||||
{
|
||||
if (allEnemy_totalHealthImage == null) return;
|
||||
|
||||
int totalCur = 0;
|
||||
if (currentEnemySOs != null)
|
||||
{
|
||||
for (int i = enemyCurrentCount; i < currentEnemySOs.Length; i++)
|
||||
{
|
||||
var so = currentEnemySOs[i];
|
||||
if (so == null) continue;
|
||||
|
||||
// 仅更新当前活跃敌人的血量,后续敌人保持 Max HP
|
||||
if (i == enemyCurrentCount)
|
||||
{
|
||||
// 使用 Combatant 实例的当前 HP
|
||||
if (enemyCombatantInstance != null) totalCur += enemyCombatantInstance.currentHP;
|
||||
else totalCur += so.enemy_maxHP; // Fallback
|
||||
}
|
||||
else
|
||||
{
|
||||
totalCur += so.enemy_maxHP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float newFill = totalMaxHP > 0 ? (float)totalCur / totalMaxHP : 0f;
|
||||
|
||||
// 立即更新实血条和虚血条
|
||||
allEnemy_totalHealthImage.fillAmount = newFill;
|
||||
if (allEnemy_totalFadehealthImage != null) allEnemy_totalFadehealthImage.fillAmount = newFill;
|
||||
if (totalEnemy_healthRate != null) totalEnemy_healthRate.text = $"{totalCur}/{totalMaxHP}";
|
||||
|
||||
// 停止可能正在运行的虚血条动画
|
||||
if (totalFadeHealthCoroutine != null) StopCoroutine(totalFadeHealthCoroutine);
|
||||
}
|
||||
|
||||
|
||||
private void UpdateEnemyUIImmediate()
|
||||
{
|
||||
if (enemyCombatantInstance == null) return;
|
||||
@@ -1253,27 +1495,8 @@ public class teamUIController : MonoBehaviour
|
||||
if (currentEnemy_manaRate != null)
|
||||
currentEnemy_manaRate.text = enemyCombatantInstance.maxMana > 0 ? $"{enemyCombatantInstance.currentMana}/{enemyCombatantInstance.maxMana}" : "0/0";
|
||||
|
||||
if (allEnemy_totalHealthImage != null)
|
||||
{
|
||||
// optional: aggregate total health across remaining enemies
|
||||
int totalCur = 0, totalMax = 0;
|
||||
if (currentEnemySOs != null)
|
||||
{
|
||||
for (int i = enemyCurrentCount; i < currentEnemySOs.Length; i++)
|
||||
{
|
||||
var so = currentEnemySOs[i];
|
||||
if (so == null) continue;
|
||||
totalMax += Mathf.Max(1, so.enemy_maxHP);
|
||||
// use max for not-yet-spawned enemies
|
||||
if (i == enemyCurrentCount) totalCur += enemyCombatantInstance.currentHP; else totalCur += so.enemy_maxHP;
|
||||
}
|
||||
}
|
||||
if (totalMax > 0)
|
||||
{
|
||||
allEnemy_totalHealthImage.fillAmount = (float)totalCur / totalMax;
|
||||
if (totalEnemy_healthRate != null) totalEnemy_healthRate.text = $"{totalCur}/{totalMax}";
|
||||
}
|
||||
}
|
||||
// Aggregate total health logic (modified to call the helper method for animation control)
|
||||
UpdateAllEnemyTotalHealthUIImmediate();
|
||||
}
|
||||
|
||||
private void UpdateEnemyHealthVisuals(int oldHP, int newHP, bool animateFade)
|
||||
@@ -1377,4 +1600,19 @@ public class teamUIController : MonoBehaviour
|
||||
c.a = 0f;
|
||||
currentEnemy_hurtRedImage.color = c;
|
||||
}
|
||||
|
||||
private IEnumerator TotalFadeHealthCoroutine(float targetFill)
|
||||
{
|
||||
if (allEnemy_totalFadehealthImage == null) yield break;
|
||||
float start = allEnemy_totalFadehealthImage.fillAmount;
|
||||
float duration = 0.6f;
|
||||
float t = 0f;
|
||||
while (t < duration)
|
||||
{
|
||||
t += Time.deltaTime;
|
||||
allEnemy_totalFadehealthImage.fillAmount = Mathf.Lerp(start, targetFill, t / duration);
|
||||
yield return null;
|
||||
}
|
||||
allEnemy_totalFadehealthImage.fillAmount = targetFill;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user