修复短音符延迟

This commit is contained in:
FloatGaming
2026-01-15 02:46:42 +08:00
parent 271a576289
commit f3ece026ca
10 changed files with 666 additions and 105 deletions
+135 -15
View File
@@ -7,14 +7,18 @@ public class Note : BaseNote
private AnimationController anim;
private NoteController controller;
private bool isJudged = false;
private bool hasLock = false; // track whether we hold the track lock
private NoteData noteData;
[Header("ж")]
public NoteJudgeConfig judgeConfig; // жãԤʱֵ
[Header("判定配置")]
public NoteJudgeConfig judgeConfig; // 判定窗口配置,包含判定时间范围
// Cache allies per track to avoid GameObject.Find on every judge.
private static AllyCombatant[] allyCache;
// Timeout handling - force miss if note isn't judged by this time
private float missDeadlineTime = -1f;
private static AllyCombatant GetAllyForTrackCached(int trackIndex)
{
@@ -44,8 +48,13 @@ public class Note : BaseNote
Speed = speed;
this.hitTime = hitTime;
isJudged = false;
hasLock = false;
this.judgeConfig = judgeConfig;
this.noteData = data;
// Set miss deadline: hitTime + missRange (the latest time to judge before auto-miss)
float missRange = (judgeConfig?.missRange ?? 0.5f);
this.missDeadlineTime = hitTime + missRange;
if (controller != null)
{
@@ -54,10 +63,10 @@ public class Note : BaseNote
InputManager.OnKeyPressed += HandlePress;
// жü
// 判定配置检查
if (judgeConfig == null)
{
if (GameConfig.verboseLogs) Debug.LogError($"NoteJudgeConfig is null! жδtrack={trackIndex}");
if (GameConfig.verboseLogs) Debug.LogError($"NoteJudgeConfig is null! 判定配置未赋值!track={trackIndex}");
}
else
{
@@ -68,17 +77,77 @@ public class Note : BaseNote
private void OnDestroy()
{
InputManager.OnKeyPressed -= HandlePress;
// Release lock if we still hold it
if (hasLock)
{
TrackKeyManager.Instance?.UnlockTrackForJudge(TrackIndex, gameObject.GetInstanceID().ToString());
hasLock = false;
}
}
private void Update()
{
// Force miss if we've passed the deadline without being judged
if (!isJudged && Time.time > missDeadlineTime && gameObject.activeSelf)
{
if (GameConfig.verboseLogs) Debug.Log($"[Note] {noteColor} on track {TrackIndex} exceeded miss deadline at time {Time.time:F3}, forcing Miss");
JudgeMiss();
}
}
private void HandlePress(KeyCode key)
{
// ֻжȷж
if (isJudged || key != keyToPress || (controller != null && !controller.IsInJudgeZone()))
// only proceed for matching key and not already judged
if (isJudged || key != keyToPress)
return;
float timeDifference = Mathf.Abs(Time.time - hitTime);
// ƫֵǰֵ
float rawOffsetMs = (hitTime - Time.time) * 1000f;
string myId = gameObject.GetInstanceID().ToString();
// Ensure only one note per track consumes this physical press per frame
if (TrackKeyManager.Instance != null && !TrackKeyManager.Instance.TryConsumeTrackForFrame(TrackIndex))
{
if (GameConfig.verboseLogs) Debug.Log($"[Note] Press ignored due to frame consumption on track {TrackIndex}");
return;
}
// Ensure we are the front-most note on this track. If not, ignore this press so later press can hit the next note.
var headId = TrackKeyManager.Instance?.GetCurrentNoteId(TrackIndex);
if (headId != null && headId != myId)
{
if (GameConfig.verboseLogs) Debug.Log($"[Note] Press ignored because another note is ahead on track {TrackIndex}: head={headId} me={myId}");
return;
}
// Try to acquire lock for this note (prevents multiple notes on same track being judged at once)
if (TrackKeyManager.Instance != null && !TrackKeyManager.Instance.TryLockTrackForJudge(TrackIndex, myId))
return;
hasLock = true;
float pressTime = Time.time;
float maxWindow = (judgeConfig?.missRange ?? 0.5f);
// If there is a controller and the note is not inside judge zone, only allow judgment
// if the press time is within the allowed window. Otherwise ignore the press.
if (controller != null && !controller.IsInJudgeZone())
{
if (Mathf.Abs(pressTime - hitTime) > maxWindow)
{
// outside allowed window and not in judge zone -> release lock and ignore the press
if (hasLock)
{
TrackKeyManager.Instance?.UnlockTrackForJudge(TrackIndex, myId);
hasLock = false;
}
if (GameConfig.verboseLogs) Debug.Log($"[Note] {noteColor} press rejected: outside timing window and not in judge zone");
return;
}
// otherwise within window: fall through to normal judging
}
float timeDifference = Mathf.Abs(pressTime - hitTime);
// raw offset ms: positive = note was early (hitTime > pressTime)
float rawOffsetMs = (hitTime - pressTime) * 1000f;
string judgeResult = null;
if (judgeConfig != null)
@@ -106,7 +175,7 @@ public class Note : BaseNote
}
else
{
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress}: Miss");
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress}: Miss (timeout)");
judgeResult = "Miss";
}
}
@@ -125,7 +194,7 @@ public class Note : BaseNote
}
else
{
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress}: Miss");
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress}: Miss (timeout)");
judgeResult = "Miss";
}
}
@@ -135,6 +204,9 @@ public class Note : BaseNote
InputManager.Instance?.ShowJudgeResult(TrackIndex, judgeResult);
JudgeSoundManager.Instance?.PlayJudgeSound(judgeResult);
teamUIController.Instance?.OnJudgeResult(judgeResult);
// Spawn judgment prefab - ensure noteColor is passed correctly
if (GameConfig.verboseLogs) Debug.Log($"[Note] Spawning judge prefab: color={noteColor}, result={judgeResult}");
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, judgeResult);
try
@@ -174,12 +246,26 @@ public class Note : BaseNote
controller.StopMovement();
controller.PlayHitEffect();
}
// Remove from per-track queue immediately so following notes become head
string myId = gameObject.GetInstanceID().ToString();
TrackKeyManager.Instance?.UnregisterKey(TrackIndex, myId);
// Release lock if we held it
if (hasLock)
{
TrackKeyManager.Instance?.UnlockTrackForJudge(TrackIndex, myId);
hasLock = false;
}
ReturnToPool();
if (anim != null)
{
anim.PlayDestroyAnimation(noteColor);
}
// notify global judge manager that this short note has been finally judged (hit)
JudgeManager.Instance?.NotifyNoteJudged();
}
public void JudgeMiss()
@@ -194,6 +280,9 @@ public class Note : BaseNote
InputManager.Instance?.ShowJudgeResult(TrackIndex, "Miss");
JudgeSoundManager.Instance?.PlayJudgeSound("Miss");
teamUIController.Instance?.OnJudgeResult("Miss");
// Spawn judgment prefab for Miss - ensure noteColor is passed correctly
if (GameConfig.verboseLogs) Debug.Log($"[Note.JudgeMiss] Spawning miss judge prefab: color={noteColor}");
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, "Miss");
try
@@ -213,16 +302,38 @@ public class Note : BaseNote
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, "Miss", SkillDefinition.NoteTypeTrigger.Tap); } catch { }
// notify global judge manager that this note has been finally judged (miss)
JudgeManager.Instance?.NotifyNoteJudged();
// Remove from per-track queue and release lock immediately so next note can be judged
string myId = gameObject.GetInstanceID().ToString();
TrackKeyManager.Instance?.UnregisterKey(TrackIndex, myId);
if (hasLock)
{
TrackKeyManager.Instance?.UnlockTrackForJudge(TrackIndex, myId);
hasLock = false;
}
ReturnToPool();
}
private void ReturnToPool()
{
InputManager.OnKeyPressed -= HandlePress;
// Release lock if we still hold it
if (hasLock)
{
TrackKeyManager.Instance?.UnlockTrackForJudge(TrackIndex, gameObject.GetInstanceID().ToString());
hasLock = false;
}
if (controller != null)
{
controller.ResetState();
}
// Ensure we are removed from per-track queue to avoid stale entries
TrackKeyManager.Instance?.UnregisterKey(TrackIndex, gameObject.GetInstanceID().ToString());
gameObject.SetActive(false);
NotePool.Instance.ReturnNote(gameObject, noteColor);
}
@@ -238,15 +349,24 @@ public class Note : BaseNote
}
/// <summary>
/// Controller ֪ͨ Note Ƿж
/// From Controller notify Note whether it's inside judge zone
/// </summary>
public void SetJudgeZone(bool inZone)
{
// leaving judge zone without being judged -> Miss
// leaving judge zone
if (!inZone)
{
// JudgeMiss already handles UI/sound and prevents double execution.
JudgeMiss();
// Only process if we haven't been judged yet
if (!isJudged)
{
// IMPORTANT: Don't call JudgeMiss directly here because SetJudgeZone is called
// from OnTriggerExit2D, which is in a physics callback.
// Calling ReturnToPool (which deactivates the object) inside a physics callback
// causes "GameObject is already being activated or deactivated" errors.
// Instead, mark for miss and let Update handle it next frame.
if (GameConfig.verboseLogs) Debug.Log($"[Note.SetJudgeZone] Note {noteColor} left judge zone on track {TrackIndex}, will force Miss next frame");
missDeadlineTime = Time.time; // Force deadline to now so Update will handle it
}
}
}
}