技能主要更新,修复卡顿并加入动画,以及各种其他更新。
This commit is contained in:
@@ -1,385 +1,390 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class Note : BaseNote
|
||||
{
|
||||
private KeyCode keyToPress;
|
||||
private string noteColor;
|
||||
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; // 判定窗口配置,包含判定时间范围
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (trackIndex < 0) return null;
|
||||
if (allyCache == null || allyCache.Length < 10) allyCache = new AllyCombatant[10];
|
||||
if (allyCache[trackIndex] != null) return allyCache[trackIndex];
|
||||
|
||||
var allyGo = GameObject.Find($"ally_0{trackIndex + 1}");
|
||||
if (allyGo != null)
|
||||
{
|
||||
allyCache[trackIndex] = allyGo.GetComponent<AllyCombatant>();
|
||||
}
|
||||
return allyCache[trackIndex];
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
anim = GetComponent<AnimationController>();
|
||||
controller = GetComponent<NoteController>();
|
||||
}
|
||||
|
||||
public void Setup(KeyCode key, int trackIndex, float speed, float hitTime, string color, NoteJudgeConfig judgeConfig, NoteData data)
|
||||
{
|
||||
keyToPress = key;
|
||||
noteColor = color;
|
||||
TrackIndex = trackIndex;
|
||||
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)
|
||||
{
|
||||
controller.SetSpeed(speed);
|
||||
}
|
||||
|
||||
InputManager.OnKeyPressed += HandlePress;
|
||||
|
||||
// 判定配置检查
|
||||
if (judgeConfig == null)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.LogError($"NoteJudgeConfig is null! 判定配置未赋值!track={trackIndex}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"NoteJudgeConfig: perfect={judgeConfig.perfectRange}, great={judgeConfig.greatRange}, good={judgeConfig.goodRange}, miss={judgeConfig.missRange}");
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// only proceed for matching key and not already judged
|
||||
if (isJudged || key != keyToPress)
|
||||
return;
|
||||
|
||||
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
|
||||
ReleaseTrackLock(myId);
|
||||
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)
|
||||
{
|
||||
if (timeDifference <= judgeConfig.perfectRange)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress}: Perfect");
|
||||
judgeResult = "Perfect";
|
||||
ScoreManager.Instance.countPerfect += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackPerfectCounts[TrackIndex]++;
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
ScoreManager.Instance.RecordOffset(rawOffsetMs);
|
||||
}
|
||||
else if (timeDifference <= judgeConfig.greatRange)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress}: Great");
|
||||
judgeResult = "Great";
|
||||
ScoreManager.Instance.countGreat += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackGreatCounts[TrackIndex]++;
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
ScoreManager.Instance.RecordOffset(rawOffsetMs);
|
||||
}
|
||||
else if (timeDifference <= judgeConfig.goodRange)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress}: Good");
|
||||
judgeResult = "Good";
|
||||
ScoreManager.Instance.countGood += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackGoodCounts[TrackIndex]++;
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
ScoreManager.Instance.RecordOffset(rawOffsetMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
JudgeMiss();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// fallback timing
|
||||
if (timeDifference <= 0.08f)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress}: Perfect");
|
||||
judgeResult = "Perfect";
|
||||
ScoreManager.Instance.countPerfect += 1;
|
||||
// Add missing offset record for fallback path too if possible
|
||||
ScoreManager.Instance.RecordOffset(0);
|
||||
}
|
||||
else if (timeDifference <= 0.15f)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress}: Great");
|
||||
judgeResult = "Great";
|
||||
ScoreManager.Instance.countGreat += 1;
|
||||
ScoreManager.Instance.RecordOffset(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
JudgeMiss();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(judgeResult))
|
||||
{
|
||||
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
|
||||
{
|
||||
var ally = GetAllyForTrackCached(TrackIndex);
|
||||
if (ally != null)
|
||||
{
|
||||
int added = ally.AddScoreForJudge(judgeResult);
|
||||
float efficiency = ally.scoreEfficiency;
|
||||
ScoreManager.Instance?.AddPmScoreForTrack(TrackIndex, added, efficiency);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[Note] Failed to add per-track score: {ex}");
|
||||
}
|
||||
|
||||
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, judgeResult, SkillDefinition.NoteTypeTrigger.Tap); } catch { }
|
||||
}
|
||||
|
||||
Judge();
|
||||
}
|
||||
|
||||
public bool IsJudged()
|
||||
{
|
||||
return isJudged;
|
||||
}
|
||||
|
||||
public void Judge()
|
||||
{
|
||||
if (isJudged)
|
||||
return;
|
||||
isJudged = true;
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
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()
|
||||
{
|
||||
// idempotent: this can be called from multiple paths
|
||||
if (isJudged) return;
|
||||
|
||||
isJudged = true;
|
||||
ScoreManager.Instance.countMiss += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackMissCounts[TrackIndex]++;
|
||||
|
||||
// Record a 0 offset for Miss to ensure total Offset Count matches note count
|
||||
ScoreManager.Instance?.RecordOffset(0);
|
||||
|
||||
if (GameConfig.verboseLogs) Debug.Log($"{keyToPress} Miss");
|
||||
|
||||
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
|
||||
{
|
||||
var ally = GetAllyForTrackCached(TrackIndex);
|
||||
if (ally != null)
|
||||
{
|
||||
int added = ally.AddScoreForJudge("Miss");
|
||||
float efficiency = ally.scoreEfficiency;
|
||||
ScoreManager.Instance?.AddPmScoreForTrack(TrackIndex, added, efficiency);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[Note] Failed to add per-track score for Miss: {ex}");
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
ReturnToPool();
|
||||
}
|
||||
|
||||
private void ReleaseTrackLock(string myId)
|
||||
{
|
||||
if (hasLock)
|
||||
{
|
||||
TrackKeyManager.Instance?.UnlockTrackForJudge(TrackIndex, myId);
|
||||
hasLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReturnToPool()
|
||||
{
|
||||
InputManager.OnKeyPressed -= HandlePress;
|
||||
|
||||
// CRITICAL: Always release lock and unregister before deactivating
|
||||
string myId = gameObject.GetInstanceID().ToString();
|
||||
|
||||
// Release lock if we still hold it
|
||||
ReleaseTrackLock(myId);
|
||||
|
||||
// Remove from per-track queue to avoid stale entries
|
||||
TrackKeyManager.Instance?.UnregisterKey(TrackIndex, myId);
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
controller.ResetState();
|
||||
}
|
||||
|
||||
gameObject.SetActive(false);
|
||||
NotePool.Instance.ReturnNote(gameObject, noteColor);
|
||||
}
|
||||
|
||||
public int GetTrackIndex()
|
||||
{
|
||||
return TrackIndex;
|
||||
}
|
||||
|
||||
public KeyCode GetKey()
|
||||
{
|
||||
return keyToPress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// From Controller notify Note whether it's inside judge zone
|
||||
/// </summary>
|
||||
public void SetJudgeZone(bool inZone)
|
||||
{
|
||||
// leaving judge zone
|
||||
if (!inZone)
|
||||
{
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using UnityEngine;
|
||||
|
||||
public class Note : BaseNote
|
||||
{
|
||||
private KeyCode keyToPress;
|
||||
private string noteColor;
|
||||
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; // 判定窗口配置,包含判定时间范围
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (trackIndex < 0) return null;
|
||||
if (allyCache == null || allyCache.Length < 10) allyCache = new AllyCombatant[10];
|
||||
if (allyCache[trackIndex] != null) return allyCache[trackIndex];
|
||||
|
||||
var allyGo = GameObject.Find($"ally_0{trackIndex + 1}");
|
||||
if (allyGo != null)
|
||||
{
|
||||
allyCache[trackIndex] = allyGo.GetComponent<AllyCombatant>();
|
||||
}
|
||||
return allyCache[trackIndex];
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
anim = GetComponent<AnimationController>();
|
||||
controller = GetComponent<NoteController>();
|
||||
}
|
||||
|
||||
public void Setup(KeyCode key, int trackIndex, float speed, float hitTime, string color, NoteJudgeConfig judgeConfig, NoteData data)
|
||||
{
|
||||
keyToPress = key;
|
||||
noteColor = color;
|
||||
TrackIndex = trackIndex;
|
||||
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)
|
||||
{
|
||||
controller.SetSpeed(speed);
|
||||
}
|
||||
|
||||
InputManager.OnKeyPressed += HandlePress;
|
||||
|
||||
// 判定配置检查
|
||||
if (judgeConfig == null)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.LogError($"NoteJudgeConfig is null! 判定配置未赋值!track={trackIndex}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"NoteJudgeConfig: perfect={judgeConfig.perfectRange}, great={judgeConfig.greatRange}, good={judgeConfig.goodRange}, miss={judgeConfig.missRange}");
|
||||
}
|
||||
}
|
||||
|
||||
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 (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] {noteColor} on track {TrackIndex} exceeded miss deadline at time {Time.time:F3}, forcing Miss");
|
||||
JudgeMiss();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePress(KeyCode key)
|
||||
{
|
||||
// only proceed for matching key and not already judged
|
||||
if (isJudged || key != keyToPress)
|
||||
return;
|
||||
|
||||
string myId = gameObject.GetInstanceID().ToString();
|
||||
|
||||
// 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
|
||||
ReleaseTrackLock(myId);
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] {noteColor} press rejected: outside timing window and not in judge zone");
|
||||
return;
|
||||
}
|
||||
// otherwise within window: fall through to normal judging
|
||||
}
|
||||
|
||||
// Prefer the closest candidate in the judge zone for this press
|
||||
if (TrackKeyManager.Instance != null)
|
||||
{
|
||||
if (!TrackKeyManager.Instance.IsBestCandidate(TrackIndex, myId, pressTime, maxWindow))
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] Press ignored because another note is a better candidate on track {TrackIndex}");
|
||||
ReleaseTrackLock(myId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure only one note per track consumes this physical press per frame,
|
||||
// but only after we've confirmed this note is the best candidate.
|
||||
if (!TrackKeyManager.Instance.TryConsumeTrackForFrame(TrackIndex))
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] Press ignored due to frame consumption on track {TrackIndex}");
|
||||
ReleaseTrackLock(myId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (timeDifference <= judgeConfig.perfectRange)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"{keyToPress}: Perfect");
|
||||
judgeResult = "Perfect";
|
||||
ScoreManager.Instance.countPerfect += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackPerfectCounts[TrackIndex]++;
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
ScoreManager.Instance.RecordOffset(rawOffsetMs);
|
||||
}
|
||||
else if (timeDifference <= judgeConfig.greatRange)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"{keyToPress}: Great");
|
||||
judgeResult = "Great";
|
||||
ScoreManager.Instance.countGreat += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackGreatCounts[TrackIndex]++;
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
ScoreManager.Instance.RecordOffset(rawOffsetMs);
|
||||
}
|
||||
else if (timeDifference <= judgeConfig.goodRange)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"{keyToPress}: Good");
|
||||
judgeResult = "Good";
|
||||
ScoreManager.Instance.countGood += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackGoodCounts[TrackIndex]++;
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
ScoreManager.Instance.RecordOffset(rawOffsetMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
JudgeMiss();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// fallback timing
|
||||
if (timeDifference <= 0.08f)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"{keyToPress}: Perfect");
|
||||
judgeResult = "Perfect";
|
||||
ScoreManager.Instance.countPerfect += 1;
|
||||
// Add missing offset record for fallback path too if possible
|
||||
ScoreManager.Instance.RecordOffset(0);
|
||||
}
|
||||
else if (timeDifference <= 0.15f)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"{keyToPress}: Great");
|
||||
judgeResult = "Great";
|
||||
ScoreManager.Instance.countGreat += 1;
|
||||
ScoreManager.Instance.RecordOffset(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
JudgeMiss();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(judgeResult))
|
||||
{
|
||||
InputManager.Instance?.ShowJudgeResult(TrackIndex, judgeResult);
|
||||
JudgeSoundManager.Instance?.PlayJudgeSound(judgeResult);
|
||||
teamUIController.Instance?.OnJudgeResult(judgeResult);
|
||||
|
||||
// Spawn judgment prefab - ensure noteColor is passed correctly
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] Spawning judge prefab: color={noteColor}, result={judgeResult}");
|
||||
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, judgeResult);
|
||||
|
||||
try
|
||||
{
|
||||
var ally = GetAllyForTrackCached(TrackIndex);
|
||||
if (ally != null)
|
||||
{
|
||||
int added = ally.AddScoreForJudge(judgeResult);
|
||||
float efficiency = ally.scoreEfficiency;
|
||||
ScoreManager.Instance?.AddPmScoreForTrack(TrackIndex, added, efficiency);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.LogWarning($"[Note] Failed to add per-track score: {ex}");
|
||||
}
|
||||
|
||||
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, judgeResult, SkillDefinition.NoteTypeTrigger.Tap); } catch { }
|
||||
}
|
||||
|
||||
Judge();
|
||||
}
|
||||
|
||||
public bool IsJudged()
|
||||
{
|
||||
return isJudged;
|
||||
}
|
||||
|
||||
public void Judge()
|
||||
{
|
||||
if (isJudged)
|
||||
return;
|
||||
isJudged = true;
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
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()
|
||||
{
|
||||
// idempotent: this can be called from multiple paths
|
||||
if (isJudged) return;
|
||||
|
||||
isJudged = true;
|
||||
ScoreManager.Instance.countMiss += 1;
|
||||
if (TrackIndex >= 0 && TrackIndex < 5) ScoreManager.Instance.trackMissCounts[TrackIndex]++;
|
||||
|
||||
// Record a 0 offset for Miss to ensure total Offset Count matches note count
|
||||
ScoreManager.Instance?.RecordOffset(0);
|
||||
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"{keyToPress} Miss");
|
||||
|
||||
InputManager.Instance?.ShowJudgeResult(TrackIndex, "Miss");
|
||||
JudgeSoundManager.Instance?.PlayJudgeSound("Miss");
|
||||
teamUIController.Instance?.OnJudgeResult("Miss");
|
||||
|
||||
// Spawn judgment prefab for Miss - ensure noteColor is passed correctly
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note.JudgeMiss] Spawning miss judge prefab: color={noteColor}");
|
||||
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, "Miss");
|
||||
|
||||
try
|
||||
{
|
||||
var ally = GetAllyForTrackCached(TrackIndex);
|
||||
if (ally != null)
|
||||
{
|
||||
int added = ally.AddScoreForJudge("Miss");
|
||||
float efficiency = ally.scoreEfficiency;
|
||||
ScoreManager.Instance?.AddPmScoreForTrack(TrackIndex, added, efficiency);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.LogWarning($"[Note] Failed to add per-track score for Miss: {ex}");
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
ReturnToPool();
|
||||
}
|
||||
|
||||
private void ReleaseTrackLock(string myId)
|
||||
{
|
||||
if (hasLock)
|
||||
{
|
||||
TrackKeyManager.Instance?.UnlockTrackForJudge(TrackIndex, myId);
|
||||
hasLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReturnToPool()
|
||||
{
|
||||
InputManager.OnKeyPressed -= HandlePress;
|
||||
|
||||
// CRITICAL: Always release lock and unregister before deactivating
|
||||
string myId = gameObject.GetInstanceID().ToString();
|
||||
|
||||
// Release lock if we still hold it
|
||||
ReleaseTrackLock(myId);
|
||||
|
||||
// Remove from per-track queue to avoid stale entries
|
||||
TrackKeyManager.Instance?.UnregisterKey(TrackIndex, myId);
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
controller.ResetState();
|
||||
}
|
||||
|
||||
gameObject.SetActive(false);
|
||||
NotePool.Instance.ReturnNote(gameObject, noteColor);
|
||||
}
|
||||
|
||||
public int GetTrackIndex()
|
||||
{
|
||||
return TrackIndex;
|
||||
}
|
||||
|
||||
public KeyCode GetKey()
|
||||
{
|
||||
return keyToPress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// From Controller notify Note whether it's inside judge zone
|
||||
/// </summary>
|
||||
public void SetJudgeZone(bool inZone)
|
||||
{
|
||||
// leaving judge zone
|
||||
if (!inZone)
|
||||
{
|
||||
// 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 (JudgeManager.IsDebugEnabled) 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user