技能主要更新,修复卡顿并加入动画,以及各种其他更新。
This commit is contained in:
@@ -11,6 +11,11 @@ public class TrackKeyManager : MonoBehaviour
|
||||
private Dictionary<int, Queue<(string noteId, string noteType)>> trackKeyMappings =
|
||||
new Dictionary<int, Queue<(string, string)>>();
|
||||
|
||||
// Active notes currently inside judge zone per track
|
||||
private Dictionary<int, List<string>> trackActiveNotes = new Dictionary<int, List<string>>();
|
||||
// Hit times for notes (used to pick best candidate when multiple notes overlap)
|
||||
private readonly Dictionary<string, float> noteHitTimes = new Dictionary<string, float>();
|
||||
|
||||
// Track notes currently being judged to prevent double-judging the same note
|
||||
// Format: trackIndex -> HashSet of note IDs being judged
|
||||
private Dictionary<int, HashSet<string>> trackNotesBeingJudged = new Dictionary<int, HashSet<string>>();
|
||||
@@ -38,6 +43,12 @@ public class TrackKeyManager : MonoBehaviour
|
||||
public int pruneBatchSize = 5;
|
||||
|
||||
private Coroutine cleanupCoroutine;
|
||||
private readonly WaitForSecondsRealtime cleanupInterval = new WaitForSecondsRealtime(1f);
|
||||
private readonly List<int> trackKeysBuffer = new List<int>(16);
|
||||
private readonly List<int> trackLockKeysBuffer = new List<int>(16);
|
||||
private readonly List<string> noteIdsToRemoveBuffer = new List<string>(32);
|
||||
private readonly HashSet<int> allTracksBuffer = new HashSet<int>();
|
||||
private readonly HashSet<string> allQueuedNoteIdsBuffer = new HashSet<string>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -80,25 +91,40 @@ public class TrackKeyManager : MonoBehaviour
|
||||
while (true)
|
||||
{
|
||||
// run once per second (unscaled so it runs during pause)
|
||||
yield return new WaitForSecondsRealtime(1f);
|
||||
yield return cleanupInterval;
|
||||
|
||||
float now = Time.unscaledTime;
|
||||
|
||||
// 1) Prune head entries for all tracks (defensive)
|
||||
var trackKeys = new List<int>(trackKeyMappings.Keys);
|
||||
foreach (var ti in trackKeys)
|
||||
trackKeysBuffer.Clear();
|
||||
foreach (var ti in trackKeyMappings.Keys) trackKeysBuffer.Add(ti);
|
||||
for (int i = 0; i < trackKeysBuffer.Count; i++)
|
||||
{
|
||||
PruneStaleHeads(ti);
|
||||
PruneStaleHeads(trackKeysBuffer[i]);
|
||||
}
|
||||
|
||||
allQueuedNoteIdsBuffer.Clear();
|
||||
foreach (var qpair in trackKeyMappings)
|
||||
{
|
||||
var q = qpair.Value;
|
||||
if (q == null || q.Count == 0) continue;
|
||||
foreach (var item in q)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.noteId))
|
||||
allQueuedNoteIdsBuffer.Add(item.noteId);
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Inspect locks and release any that reference ids that are expired or not present in any queue
|
||||
var trackLockKeys = new List<int>(trackNotesBeingJudged.Keys);
|
||||
foreach (var t in trackLockKeys)
|
||||
trackLockKeysBuffer.Clear();
|
||||
foreach (var t in trackNotesBeingJudged.Keys) trackLockKeysBuffer.Add(t);
|
||||
for (int i = 0; i < trackLockKeysBuffer.Count; i++)
|
||||
{
|
||||
int t = trackLockKeysBuffer[i];
|
||||
var locks = trackNotesBeingJudged[t];
|
||||
if (locks == null || locks.Count == 0) continue;
|
||||
|
||||
var toRemove = new List<string>();
|
||||
noteIdsToRemoveBuffer.Clear();
|
||||
|
||||
foreach (var noteId in locks)
|
||||
{
|
||||
@@ -108,35 +134,23 @@ public class TrackKeyManager : MonoBehaviour
|
||||
// If expiry missing or expired, consider the id stale and remove lock
|
||||
if (!hasExpiry || expired)
|
||||
{
|
||||
toRemove.Add(noteId);
|
||||
noteIdsToRemoveBuffer.Add(noteId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Also if the queues do not contain this id anywhere, it is likely stale
|
||||
bool foundInQueues = false;
|
||||
foreach (var qpair in trackKeyMappings)
|
||||
if (!allQueuedNoteIdsBuffer.Contains(noteId))
|
||||
{
|
||||
foreach (var item in qpair.Value)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.noteId) && item.noteId == noteId)
|
||||
{
|
||||
foundInQueues = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundInQueues) break;
|
||||
}
|
||||
if (!foundInQueues)
|
||||
{
|
||||
toRemove.Add(noteId);
|
||||
noteIdsToRemoveBuffer.Add(noteId);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var id in toRemove)
|
||||
for (int r = 0; r < noteIdsToRemoveBuffer.Count; r++)
|
||||
{
|
||||
var id = noteIdsToRemoveBuffer[r];
|
||||
locks.Remove(id);
|
||||
noteIdExpiry.Remove(id);
|
||||
if (GameConfig.verboseLogs) Debug.LogWarning($"[TrackKeyManager] Cleanup: removed stale lock id={id} on track {t}");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.LogWarning($"[TrackKeyManager] Cleanup: removed stale lock id={id} on track {t}");
|
||||
}
|
||||
|
||||
// If locks become empty, remove dictionary entry to keep structure clean
|
||||
@@ -147,12 +161,12 @@ public class TrackKeyManager : MonoBehaviour
|
||||
}
|
||||
|
||||
// 3) Defensive: if a track has no queued ids but has consumed-frame or locks, clear them
|
||||
var allTracks = new HashSet<int>();
|
||||
foreach (var k in trackKeyMappings.Keys) allTracks.Add(k);
|
||||
foreach (var k in trackNotesBeingJudged.Keys) allTracks.Add(k);
|
||||
foreach (var k in trackConsumedFrame.Keys) allTracks.Add(k);
|
||||
allTracksBuffer.Clear();
|
||||
foreach (var k in trackKeyMappings.Keys) allTracksBuffer.Add(k);
|
||||
foreach (var k in trackNotesBeingJudged.Keys) allTracksBuffer.Add(k);
|
||||
foreach (var k in trackConsumedFrame.Keys) allTracksBuffer.Add(k);
|
||||
|
||||
foreach (var track in allTracks)
|
||||
foreach (var track in allTracksBuffer)
|
||||
{
|
||||
bool hasQueue = trackKeyMappings.ContainsKey(track) && trackKeyMappings[track].Count > 0;
|
||||
bool hasLocks = trackNotesBeingJudged.ContainsKey(track) && trackNotesBeingJudged[track].Count > 0;
|
||||
@@ -160,7 +174,7 @@ public class TrackKeyManager : MonoBehaviour
|
||||
{
|
||||
// clear locks for this track
|
||||
trackNotesBeingJudged.Remove(track);
|
||||
if (GameConfig.verboseLogs) Debug.LogWarning($"[TrackKeyManager] Cleanup: cleared locks for empty track {track}");
|
||||
if (JudgeManager.IsDebugEnabled) Debug.LogWarning($"[TrackKeyManager] Cleanup: cleared locks for empty track {track}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,14 +187,18 @@ public class TrackKeyManager : MonoBehaviour
|
||||
bool isFocused = Application.isFocused;
|
||||
|
||||
// If we've moved to a new frame or focus state changed, clear consumption tracking
|
||||
if (lastCheckedFrame != currentFrame || (isFocused && !lastFocusedState))
|
||||
if (lastCheckedFrame != currentFrame)
|
||||
{
|
||||
// Reset per-frame tracking for the new frame
|
||||
// In high-performance scenarios, we might avoid Clear() if the dict is small,
|
||||
// but for safety with multiple keys, we keep it.
|
||||
}
|
||||
|
||||
if (isFocused && !lastFocusedState)
|
||||
{
|
||||
// On focus regain, clear the consumed frame dictionary to allow input again
|
||||
if (isFocused && !lastFocusedState)
|
||||
{
|
||||
trackConsumedFrame.Clear();
|
||||
if (GameConfig.verboseLogs) Debug.Log("[TrackKeyManager] Focus regained, cleared track consumption");
|
||||
}
|
||||
trackConsumedFrame.Clear();
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log("[TrackKeyManager] Focus regained, cleared track consumption");
|
||||
}
|
||||
|
||||
lastCheckedFrame = currentFrame;
|
||||
@@ -214,8 +232,11 @@ public class TrackKeyManager : MonoBehaviour
|
||||
{
|
||||
q.Dequeue();
|
||||
noteIdExpiry.Remove(head.noteId);
|
||||
noteHitTimes.Remove(head.noteId);
|
||||
if (trackActiveNotes.ContainsKey(trackIndex))
|
||||
trackActiveNotes[trackIndex].Remove(head.noteId);
|
||||
pruned++;
|
||||
if (GameConfig.verboseLogs)
|
||||
if (JudgeManager.IsDebugEnabled)
|
||||
Debug.LogWarning($"[TrackKeyManager] Pruned stale head on track {trackIndex}: id={head.noteId} type={head.noteType} now={now:F2} expiry={expiry:F2}");
|
||||
continue;
|
||||
}
|
||||
@@ -226,16 +247,24 @@ public class TrackKeyManager : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当音符进入判定区域时,添加音符 id 到队列(包含note类型信息)
|
||||
/// noteType: "tap" 或 "hold"
|
||||
/// �����������ж�����ʱ���������� id �����У�����note������Ϣ��
|
||||
/// noteType: "tap" �� "hold"
|
||||
/// </summary>
|
||||
public void RegisterKey(int trackIndex, string noteInstanceId, string noteType = "tap")
|
||||
public void RegisterKey(int trackIndex, string noteInstanceId, string noteType = "tap", float hitTime = float.NaN)
|
||||
{
|
||||
if (!trackKeyMappings.ContainsKey(trackIndex))
|
||||
trackKeyMappings[trackIndex] = new Queue<(string, string)>();
|
||||
|
||||
trackKeyMappings[trackIndex].Enqueue((noteInstanceId, noteType));
|
||||
|
||||
if (!trackActiveNotes.ContainsKey(trackIndex))
|
||||
trackActiveNotes[trackIndex] = new List<string>();
|
||||
if (!string.IsNullOrEmpty(noteInstanceId) && !trackActiveNotes[trackIndex].Contains(noteInstanceId))
|
||||
trackActiveNotes[trackIndex].Add(noteInstanceId);
|
||||
|
||||
if (!string.IsNullOrEmpty(noteInstanceId) && !float.IsNaN(hitTime))
|
||||
noteHitTimes[noteInstanceId] = hitTime;
|
||||
|
||||
// record/refresh TTL so the id can't block forever if exit/unregister is missed
|
||||
if (!string.IsNullOrEmpty(noteInstanceId))
|
||||
{
|
||||
@@ -244,14 +273,20 @@ public class TrackKeyManager : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当音符离开判定区域时,移除音符 id
|
||||
/// �������뿪�ж�����ʱ���Ƴ����� id
|
||||
/// </summary>
|
||||
public void UnregisterKey(int trackIndex, string noteInstanceId)
|
||||
{
|
||||
if (!trackKeyMappings.ContainsKey(trackIndex) || trackKeyMappings[trackIndex].Count == 0)
|
||||
{
|
||||
// still clear TTL record
|
||||
if (!string.IsNullOrEmpty(noteInstanceId)) noteIdExpiry.Remove(noteInstanceId);
|
||||
if (!string.IsNullOrEmpty(noteInstanceId))
|
||||
{
|
||||
noteIdExpiry.Remove(noteInstanceId);
|
||||
noteHitTimes.Remove(noteInstanceId);
|
||||
}
|
||||
if (trackActiveNotes.ContainsKey(trackIndex))
|
||||
trackActiveNotes[trackIndex].Remove(noteInstanceId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -259,7 +294,13 @@ public class TrackKeyManager : MonoBehaviour
|
||||
if (trackKeyMappings[trackIndex].Count > 0 && trackKeyMappings[trackIndex].Peek().noteId == noteInstanceId)
|
||||
{
|
||||
trackKeyMappings[trackIndex].Dequeue();
|
||||
if (!string.IsNullOrEmpty(noteInstanceId)) noteIdExpiry.Remove(noteInstanceId);
|
||||
if (!string.IsNullOrEmpty(noteInstanceId))
|
||||
{
|
||||
noteIdExpiry.Remove(noteInstanceId);
|
||||
noteHitTimes.Remove(noteInstanceId);
|
||||
}
|
||||
if (trackActiveNotes.ContainsKey(trackIndex))
|
||||
trackActiveNotes[trackIndex].Remove(noteInstanceId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -276,11 +317,17 @@ public class TrackKeyManager : MonoBehaviour
|
||||
}
|
||||
trackKeyMappings[trackIndex] = temp;
|
||||
|
||||
if (!string.IsNullOrEmpty(noteInstanceId)) noteIdExpiry.Remove(noteInstanceId);
|
||||
if (!string.IsNullOrEmpty(noteInstanceId))
|
||||
{
|
||||
noteIdExpiry.Remove(noteInstanceId);
|
||||
noteHitTimes.Remove(noteInstanceId);
|
||||
}
|
||||
if (trackActiveNotes.ContainsKey(trackIndex))
|
||||
trackActiveNotes[trackIndex].Remove(noteInstanceId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取轨道当前排在队首的音符实例 id(队列头)
|
||||
/// ��ȡ�����ǰ���ڶ�������ʵ�� id������ͷ��
|
||||
/// </summary>
|
||||
public string GetCurrentNoteId(int trackIndex)
|
||||
{
|
||||
@@ -290,11 +337,11 @@ public class TrackKeyManager : MonoBehaviour
|
||||
{
|
||||
return trackKeyMappings[trackIndex].Peek().noteId;
|
||||
}
|
||||
return null; // 无音符
|
||||
return null; // ������
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前队头note的类型("tap" 或 "hold")
|
||||
/// ��ȡ��ǰ��ͷnote�����ͣ�"tap" �� "hold"��
|
||||
/// </summary>
|
||||
public string GetCurrentNoteType(int trackIndex)
|
||||
{
|
||||
@@ -385,6 +432,44 @@ public class TrackKeyManager : MonoBehaviour
|
||||
{
|
||||
trackConsumedFrame.Clear();
|
||||
}
|
||||
public bool IsBestCandidate(int trackIndex, string noteId, float pressTime, float maxWindow)
|
||||
{
|
||||
if (trackIndex < 0 || string.IsNullOrEmpty(noteId)) return true;
|
||||
if (!trackActiveNotes.TryGetValue(trackIndex, out var list) || list == null || list.Count == 0)
|
||||
return true;
|
||||
|
||||
string bestId = null;
|
||||
float bestDelta = float.MaxValue;
|
||||
float bestHit = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
var id = list[i];
|
||||
if (string.IsNullOrEmpty(id)) continue;
|
||||
if (!noteHitTimes.TryGetValue(id, out var ht)) continue;
|
||||
float delta = Mathf.Abs(pressTime - ht);
|
||||
if (delta > maxWindow) continue;
|
||||
|
||||
if (delta < bestDelta - 0.0001f)
|
||||
{
|
||||
bestDelta = delta;
|
||||
bestId = id;
|
||||
bestHit = ht;
|
||||
}
|
||||
else if (Mathf.Abs(delta - bestDelta) <= 0.0001f)
|
||||
{
|
||||
// tie-breaker: earlier hit time wins
|
||||
if (ht < bestHit)
|
||||
{
|
||||
bestId = id;
|
||||
bestHit = ht;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestId == null) return true;
|
||||
return bestId == noteId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force clear all track locks and per-frame consumption state.
|
||||
@@ -395,6 +480,16 @@ public class TrackKeyManager : MonoBehaviour
|
||||
trackNotesBeingJudged.Clear();
|
||||
trackConsumedFrame.Clear();
|
||||
noteIdExpiry.Clear();
|
||||
if (GameConfig.verboseLogs) Debug.Log("[TrackKeyManager] ClearAllLocks called: cleared locks and consumption state");
|
||||
noteHitTimes.Clear();
|
||||
trackActiveNotes.Clear();
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log("[TrackKeyManager] ClearAllLocks called: cleared locks and consumption state");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user