Files
bansonic_beta_main/Assets/scripts/gamePlay_gameplay/TrackKeyManager.cs
T
2026-01-15 02:46:42 +08:00

177 lines
5.8 KiB
C#

using System.Collections.Generic;
using UnityEngine;
public class TrackKeyManager : MonoBehaviour
{
public static TrackKeyManager Instance { get; private set; }
// store note instance IDs (as strings) in queue order per track
private Dictionary<int, Queue<string>> trackKeyMappings = new Dictionary<int, Queue<string>>();
// 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>>();
// transient per-frame consumption: ensures one physical press only affects one note per track per frame
private Dictionary<int, int> trackConsumedFrame = new Dictionary<int, int>();
// track last frame we checked for focus loss
private int lastCheckedFrame = -1;
private bool lastFocusedState = true;
private void Awake()
{
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(gameObject);
}
}
private void Update()
{
// Clear per-frame consumption dictionary when focus is regained or at frame boundaries
int currentFrame = Time.frameCount;
bool isFocused = Application.isFocused;
// If we've moved to a new frame or focus state changed, clear consumption tracking
if (lastCheckedFrame != currentFrame || (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");
}
}
lastCheckedFrame = currentFrame;
lastFocusedState = isFocused;
}
/// <summary>
/// 当音符进入判定区域时,添加音符 id 到队列
/// </summary>
public void RegisterKey(int trackIndex, string noteInstanceId)
{
if (!trackKeyMappings.ContainsKey(trackIndex))
trackKeyMappings[trackIndex] = new Queue<string>();
trackKeyMappings[trackIndex].Enqueue(noteInstanceId);
}
/// <summary>
/// 当音符离开判定区域时,移除音符 id
/// </summary>
public void UnregisterKey(int trackIndex, string noteInstanceId)
{
if (!trackKeyMappings.ContainsKey(trackIndex) || trackKeyMappings[trackIndex].Count == 0)
return;
// If the head matches, dequeue. Otherwise try to remove from queue by rebuilding it.
if (trackKeyMappings[trackIndex].Peek() == noteInstanceId)
{
trackKeyMappings[trackIndex].Dequeue();
return;
}
// Otherwise remove matching id if present (preserve order of others)
var q = trackKeyMappings[trackIndex];
var temp = new Queue<string>();
while (q.Count > 0)
{
var v = q.Dequeue();
if (v != noteInstanceId) temp.Enqueue(v);
}
trackKeyMappings[trackIndex] = temp;
}
/// <summary>
/// 获取轨道当前排在队首的音符实例 id(队列头)
/// </summary>
public string GetCurrentNoteId(int trackIndex)
{
if (trackKeyMappings.ContainsKey(trackIndex) && trackKeyMappings[trackIndex].Count > 0)
{
return trackKeyMappings[trackIndex].Peek();
}
return null; // 无音符
}
/// <summary>
/// Try to register a note as being judged on a track. Returns true if successful (no other note is currently being judged).
/// This prevents multiple notes on the same track from being judged by a single key press.
/// </summary>
public bool TryLockTrackForJudge(int trackIndex, string noteId)
{
if (trackIndex < 0) return false;
if (!trackNotesBeingJudged.ContainsKey(trackIndex))
{
trackNotesBeingJudged[trackIndex] = new HashSet<string>();
}
// If there are already notes being judged on this track, reject
if (trackNotesBeingJudged[trackIndex].Count > 0)
{
return false;
}
// Lock this note
trackNotesBeingJudged[trackIndex].Add(noteId);
return true;
}
/// <summary>
/// Release the lock for a note on a track. Call this after judgment is complete.
/// </summary>
public void UnlockTrackForJudge(int trackIndex, string noteId)
{
if (trackIndex < 0) return;
if (trackNotesBeingJudged.ContainsKey(trackIndex))
{
trackNotesBeingJudged[trackIndex].Remove(noteId);
}
}
/// <summary>
/// Try to mark this track as consumed for the current frame. Returns true if this call
/// successfully consumes the track (no other note on the same track has consumed this frame).
/// </summary>
public bool TryConsumeTrackForFrame(int trackIndex)
{
if (trackIndex < 0) return false;
int currentFrame = Time.frameCount;
// If the recorded frame is old or invalid, allow consumption
if (!trackConsumedFrame.ContainsKey(trackIndex))
{
trackConsumedFrame[trackIndex] = currentFrame;
return true;
}
// If we're in a different frame, reset and allow
int recordedFrame = trackConsumedFrame[trackIndex];
if (recordedFrame != currentFrame)
{
trackConsumedFrame[trackIndex] = currentFrame;
return true;
}
// Same frame, already consumed
return false;
}
/// <summary>
/// Force reset the consumption state (useful for testing or specific scenarios)
/// </summary>
public void ResetConsumptionState()
{
trackConsumedFrame.Clear();
}
}