备份,准备做双端
This commit is contained in:
@@ -18,6 +18,9 @@ public class ScoreManager : MonoBehaviour
|
||||
private readonly float[] _idolscoreLastTargetY = new float[5];
|
||||
private readonly float[] _idolscorePulseAmplitude = new float[5];
|
||||
private readonly float[] _idolscorePulseSeed = new float[5];
|
||||
// True once a key has fully converged to its target (no wobble, displayY == targetY).
|
||||
// While settled we skip the per-frame noise + localScale write; cleared when target changes.
|
||||
private readonly bool[] _idolscoreSettled = new bool[5];
|
||||
|
||||
// per-track pm score sums (red, green, yellow, purple, blue)
|
||||
public int red_pmScore_sum = 0;
|
||||
@@ -69,6 +72,12 @@ public class ScoreManager : MonoBehaviour
|
||||
int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue
|
||||
};
|
||||
|
||||
// 性能:RecalculateTotal 里 5 个分槽 "cur/max" 上次写入的字符串及其目标 Text 对象。
|
||||
// 仅当"数值(cur/max)变化"或"目标对象变化(换局/换场景导致 UI 重建)"时才写 .text,
|
||||
// 避免每判定对 5 个槽位无条件赋值触发 TMP 网格重建。写入逻辑与原先完全一致,只是跳过重复写。
|
||||
private readonly string[] _lastSlotScoreText = new string[5];
|
||||
private readonly UnityEngine.Object[] _lastSlotScoreTarget = new UnityEngine.Object[5];
|
||||
|
||||
// 仅当数值变化时写 TMP.text(避免相同字符串触发无谓网格重建)。
|
||||
private void SetSumTextIfChanged(TextMeshProUGUI field, int value, int cacheIndex)
|
||||
{
|
||||
@@ -105,13 +114,21 @@ public class ScoreManager : MonoBehaviour
|
||||
public int countLate = 0;
|
||||
public float totalOffsetMs = 0f;
|
||||
public int offsetCount = 0;
|
||||
// Raw per-hit samples (ms) for median/suggestion. Miss (0) not included.
|
||||
private readonly System.Collections.Generic.List<float> offsetSamples =
|
||||
new System.Collections.Generic.List<float>();
|
||||
|
||||
/// <summary>
|
||||
/// 只读访问判定偏移样本列表(供直方图等外部组件读取)。
|
||||
/// </summary>
|
||||
public System.Collections.Generic.IReadOnlyList<float> OffsetSamples => offsetSamples;
|
||||
|
||||
public void ResetStatistics()
|
||||
{
|
||||
countPerfect = countGreat = countGood = countMiss = 0;
|
||||
perfectClearBonusPm = 0;
|
||||
perfectClearBonusApplied = false;
|
||||
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
trackPerfectCounts[i] = 0;
|
||||
@@ -123,11 +140,17 @@ public class ScoreManager : MonoBehaviour
|
||||
countEarly = countLate = 0;
|
||||
totalOffsetMs = 0f;
|
||||
offsetCount = 0;
|
||||
offsetSamples.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records a timing offset in milliseconds.
|
||||
/// Positive = Early, Negative = Late.
|
||||
/// Fired whenever a timing offset is recorded (Positive = Early, Negative = Late).
|
||||
/// Display-only hook (e.g. realtime offset bar); does not affect scoring/judgement.
|
||||
/// </summary>
|
||||
public static event System.Action<float> OnOffsetRecorded;
|
||||
|
||||
/// <summary>
|
||||
/// Records a timing offset in milliseconds. Positive = Early, Negative = Late.
|
||||
/// </summary>
|
||||
public void RecordOffset(float offsetMs)
|
||||
{
|
||||
@@ -135,9 +158,26 @@ public class ScoreManager : MonoBehaviour
|
||||
|
||||
totalOffsetMs += offsetMs;
|
||||
offsetCount++;
|
||||
offsetSamples.Add(offsetMs);
|
||||
|
||||
if (offsetMs > 0) countEarly++;
|
||||
else if (offsetMs < 0) countLate++;
|
||||
|
||||
OnOffsetRecorded?.Invoke(offsetMs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns median offset (ms) across all recorded hits. More robust than mean for
|
||||
/// calibration suggestions — single outlier taps don't skew it.
|
||||
/// </summary>
|
||||
public float GetMedianOffsetMs()
|
||||
{
|
||||
int n = offsetSamples.Count;
|
||||
if (n == 0) return 0f;
|
||||
var sorted = new System.Collections.Generic.List<float>(offsetSamples);
|
||||
sorted.Sort();
|
||||
int mid = n / 2;
|
||||
return (n % 2 == 1) ? sorted[mid] : 0.5f * (sorted[mid - 1] + sorted[mid]);
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
@@ -200,6 +240,10 @@ public class ScoreManager : MonoBehaviour
|
||||
Transform t = _idolscoreKeys[i];
|
||||
if (t == null) continue;
|
||||
|
||||
// Settled: display converged to target and wobble decayed. Nothing changes frame to
|
||||
// frame, so skip the noise sample and localScale write until the target moves again.
|
||||
if (_idolscoreSettled[i]) continue;
|
||||
|
||||
Vector3 baseScale = _idolscoreKeyBaseScaleCached[i] ? _idolscoreKeyBaseScales[i] : t.localScale;
|
||||
float targetY = Mathf.Max(0f, _idolscoreTargetY[i]);
|
||||
|
||||
@@ -213,6 +257,16 @@ public class ScoreManager : MonoBehaviour
|
||||
float y = Mathf.Max(0f, _idolscoreDisplayY[i] + noise * _idolscorePulseAmplitude[i]);
|
||||
|
||||
t.localScale = new Vector3(baseScale.x, y, baseScale.z);
|
||||
|
||||
// Mark settled once the smoothed value has essentially reached the target and the
|
||||
// wobble amplitude has decayed to zero, so the next frame can early-out.
|
||||
if (Mathf.Abs(_idolscoreDisplayY[i] - targetY) < 0.0005f && _idolscorePulseAmplitude[i] < 0.0005f)
|
||||
{
|
||||
_idolscoreDisplayY[i] = targetY;
|
||||
_idolscorePulseAmplitude[i] = 0f;
|
||||
t.localScale = new Vector3(baseScale.x, targetY, baseScale.z);
|
||||
_idolscoreSettled[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,6 +436,8 @@ public class ScoreManager : MonoBehaviour
|
||||
{
|
||||
_idolscoreLastTargetY[i] = targetY;
|
||||
_idolscorePulseAmplitude[i] = UnityEngine.Random.Range(0.08f, 0.24f);
|
||||
// Target moved (and/or wobble re-seeded): resume per-frame animation.
|
||||
_idolscoreSettled[i] = false;
|
||||
}
|
||||
|
||||
if (targetY <= 0.001f)
|
||||
@@ -638,13 +694,37 @@ public class ScoreManager : MonoBehaviour
|
||||
var ui = teamUIController.Instance;
|
||||
|
||||
// helper lambda to set text on TMP or legacy Text under a parent fallback
|
||||
void SetScoreText(TextMeshProUGUI assignedTmp, GameObject parent, int cur, int max, string slotName)
|
||||
void SetScoreText(TextMeshProUGUI assignedTmp, GameObject parent, int cur, int max, string slotName, int slotIdx)
|
||||
{
|
||||
string value = cur + "/" + max;
|
||||
|
||||
// 【性能】值变门控:仅当 (目标 Text 对象, value) 与该槽位上次写入不同才赋值 .text。
|
||||
// 目标对象或数值任一变化都会写(换局/换场景使 UI 重建时目标对象变,必写一次),
|
||||
// 因此可见文本与原先逐次无条件赋值完全一致,只是跳过"同对象同值"的重复写。
|
||||
// 用纯判断函数(非闭包 lambda)避免额外 GC:返回 true 表示需要写并已更新缓存。
|
||||
bool ShouldWriteSlot(UnityEngine.Object target)
|
||||
{
|
||||
if (slotIdx >= 0 && slotIdx < 5 &&
|
||||
ReferenceEquals(_lastSlotScoreTarget[slotIdx], target) &&
|
||||
_lastSlotScoreText[slotIdx] == value)
|
||||
{
|
||||
return false; // 同对象同值,已是该文本,跳过写入(等效)。
|
||||
}
|
||||
if (slotIdx >= 0 && slotIdx < 5)
|
||||
{
|
||||
_lastSlotScoreTarget[slotIdx] = target;
|
||||
_lastSlotScoreText[slotIdx] = value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (assignedTmp != null)
|
||||
{
|
||||
assignedTmp.text = value;
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[ScoreManager] Wrote to assigned TMP for {slotName}: '{value}' -> {assignedTmp.gameObject.name}");
|
||||
if (ShouldWriteSlot(assignedTmp))
|
||||
{
|
||||
assignedTmp.text = value;
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[ScoreManager] Wrote to assigned TMP for {slotName}: '{value}' -> {assignedTmp.gameObject.name}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (parent != null)
|
||||
@@ -661,15 +741,21 @@ public class ScoreManager : MonoBehaviour
|
||||
var tmp = slotTmpFallback[idx];
|
||||
if (tmp != null)
|
||||
{
|
||||
tmp.text = value;
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[ScoreManager] Wrote to TMP in parent {parent.name} for {slotName}: '{value}' -> {tmp.gameObject.name}");
|
||||
if (ShouldWriteSlot(tmp))
|
||||
{
|
||||
tmp.text = value;
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[ScoreManager] Wrote to TMP in parent {parent.name} for {slotName}: '{value}' -> {tmp.gameObject.name}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
var legacy = slotLegacyFallback[idx];
|
||||
if (legacy != null)
|
||||
{
|
||||
legacy.text = value;
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[ScoreManager] Wrote to legacy Text in parent {parent.name} for {slotName}: '{value}' -> {legacy.gameObject.name}");
|
||||
if (ShouldWriteSlot(legacy))
|
||||
{
|
||||
legacy.text = value;
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[ScoreManager] Wrote to legacy Text in parent {parent.name} for {slotName}: '{value}' -> {legacy.gameObject.name}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -678,15 +764,21 @@ public class ScoreManager : MonoBehaviour
|
||||
var tmp = parent.GetComponentInChildren<TextMeshProUGUI>(true);
|
||||
if (tmp != null)
|
||||
{
|
||||
tmp.text = value;
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[ScoreManager] Wrote to TMP in parent {parent.name} for {slotName}: '{value}' -> {tmp.gameObject.name}");
|
||||
if (ShouldWriteSlot(tmp))
|
||||
{
|
||||
tmp.text = value;
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[ScoreManager] Wrote to TMP in parent {parent.name} for {slotName}: '{value}' -> {tmp.gameObject.name}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
var legacy = parent.GetComponentInChildren<Text>(true);
|
||||
if (legacy != null)
|
||||
{
|
||||
legacy.text = value;
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[ScoreManager] Wrote to legacy Text in parent {parent.name} for {slotName}: '{value}' -> {legacy.gameObject.name}");
|
||||
if (ShouldWriteSlot(legacy))
|
||||
{
|
||||
legacy.text = value;
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[ScoreManager] Wrote to legacy Text in parent {parent.name} for {slotName}: '{value}' -> {legacy.gameObject.name}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -695,11 +787,11 @@ public class ScoreManager : MonoBehaviour
|
||||
}
|
||||
|
||||
// Update each slot: use the explicit TMP fields if set; otherwise try parent object fields
|
||||
SetScoreText(ui.teammate01_current_scoreText, ui.objectFather_ally01, tmpCurrents[0], tmpMaxes[0], "teammate01");
|
||||
SetScoreText(ui.teammate02_current_scoreText, ui.objectFather_ally02, tmpCurrents[1], tmpMaxes[1], "teammate02");
|
||||
SetScoreText(ui.teammate03_current_scoreText, ui.objectFather_ally03, tmpCurrents[2], tmpMaxes[2], "teammate03");
|
||||
SetScoreText(ui.teammate04_current_scoreText, ui.objectFather_ally04, tmpCurrents[3], tmpMaxes[3], "teammate04");
|
||||
SetScoreText(ui.teammate05_current_scoreText, ui.objectFather_ally05, tmpCurrents[4], tmpMaxes[4], "teammate05");
|
||||
SetScoreText(ui.teammate01_current_scoreText, ui.objectFather_ally01, tmpCurrents[0], tmpMaxes[0], "teammate01", 0);
|
||||
SetScoreText(ui.teammate02_current_scoreText, ui.objectFather_ally02, tmpCurrents[1], tmpMaxes[1], "teammate02", 1);
|
||||
SetScoreText(ui.teammate03_current_scoreText, ui.objectFather_ally03, tmpCurrents[2], tmpMaxes[2], "teammate03", 2);
|
||||
SetScoreText(ui.teammate04_current_scoreText, ui.objectFather_ally04, tmpCurrents[3], tmpMaxes[3], "teammate04", 3);
|
||||
SetScoreText(ui.teammate05_current_scoreText, ui.objectFather_ally05, tmpCurrents[4], tmpMaxes[4], "teammate05", 4);
|
||||
|
||||
// update total score (try TMP first, then legacy Text)
|
||||
if (ui.currentTotalScore != null)
|
||||
|
||||
Reference in New Issue
Block a user