成就系统 巨大修改 新的ui 黑白效果 等一堆
This commit is contained in:
@@ -228,18 +228,10 @@ public class HoldNote : BaseNote
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
controller.ConfigureTiming(time, delay, travelTime);
|
||||
|
||||
// If the activation time is already in the past at the moment of Setup,
|
||||
// the segment should have already traveled some distance. In that case
|
||||
// snap its position forward so it appears at the correct location.
|
||||
// Do this only when activation happened before now to avoid snapping
|
||||
// freshly spawned segments that haven't started moving yet.
|
||||
if (controller.ActivationTime < Time.time - 0.0001f)
|
||||
{
|
||||
// Use current transform.position as the spawn origin (NoteSpawner sets this before Setup).
|
||||
CalibratePosition(transform.position, 0f);
|
||||
}
|
||||
// Keep all hold segments moving in absolute time so they stay visually connected.
|
||||
float headActivationTime = time - travelTime;
|
||||
float baseSpawnYOffset = -speed * delay;
|
||||
controller.ConfigureAbsolutePositioning(transform.position, headActivationTime, baseSpawnYOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -273,15 +265,8 @@ public class HoldNote : BaseNote
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[HoldNote] START auto-Miss: {noteColor}");
|
||||
|
||||
// register as missed
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, false);
|
||||
|
||||
InputManager.Instance?.ShowJudgeResult(trackIndex, "Miss");
|
||||
JudgeSoundManager.Instance?.PlayJudgeSound("Miss");
|
||||
teamUIController.Instance?.OnJudgeResult("Miss");
|
||||
|
||||
// Show judgement prefab for auto-miss as well (consistent with other notes)
|
||||
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, "Miss");
|
||||
// Register miss properly via EvaluateHoldEnd
|
||||
EvaluateHoldEnd(Time.time, true);
|
||||
|
||||
isJudged = true;
|
||||
ScheduleReturnToPool(0.2f);
|
||||
@@ -291,6 +276,7 @@ public class HoldNote : BaseNote
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[HoldNote] END auto-Miss: {noteColor}");
|
||||
HandleEnd(true);
|
||||
// HandleEnd/EvaluateHoldEnd will release lock as needed
|
||||
isJudged = true;
|
||||
}
|
||||
}
|
||||
@@ -411,6 +397,23 @@ public class HoldNote : BaseNote
|
||||
return;
|
||||
}
|
||||
|
||||
// NEW: If the key is still being held down when end segment enters judge zone, immediately judge
|
||||
if (Input.GetKey(keyToPress) && JudgeManager.Instance.IsStartJudged(noteID) && !isJudged)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[HoldNote] End entered while key still held: {noteColor}, calculating result immediately");
|
||||
// Record release time as current time (player is still holding)
|
||||
releaseTime = Time.time;
|
||||
hasReleased = true;
|
||||
JudgeManager.Instance?.RegisterNoteReleased(noteID, true);
|
||||
// Evaluate and judge the hold end
|
||||
EvaluateHoldEnd(releaseTime, false);
|
||||
isJudged = true;
|
||||
// Terminate input - mark key as released
|
||||
isHoldActive = false;
|
||||
AnimationController.Global?.StopHoldParticles();
|
||||
return;
|
||||
}
|
||||
|
||||
autoReturnCoroutine = StartCoroutine(DelayedAutoReturnCheck());
|
||||
}
|
||||
}
|
||||
@@ -536,15 +539,19 @@ public class HoldNote : BaseNote
|
||||
if (forceMiss)
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[HoldNote] START forced Miss: {noteColor}");
|
||||
ScoreManager.Instance.countMiss += 1;
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, false);
|
||||
isHoldActive = false;
|
||||
|
||||
// Use EvaluateHoldEnd to centralize miss logic and statistics
|
||||
EvaluateHoldEnd(Time.time, true);
|
||||
|
||||
// Still need to show judge result and update combo for the head segment
|
||||
InputManager.Instance?.ShowJudgeResult(trackIndex, "Miss");
|
||||
JudgeSoundManager.Instance?.PlayJudgeSound("Miss");
|
||||
teamUIController.Instance?.OnJudgeResult("Miss");
|
||||
|
||||
// IMPORTANT: release lock immediately on miss
|
||||
ReleaseTrackJudgeLockSafe();
|
||||
|
||||
// Also show judgement prefab for forced miss on start
|
||||
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, "Miss");
|
||||
// Return soon
|
||||
ScheduleReturnToPool(0.2f);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -561,7 +568,11 @@ public class HoldNote : BaseNote
|
||||
if (offset <= pRange)
|
||||
{
|
||||
result = "Perfect";
|
||||
// Score statistics for Hold notes are now handled exclusively in EvaluateHoldEnd
|
||||
// to ensure they only count as 1 note in the total sum.
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
// RecordOffset removed from here to prevent double counting.
|
||||
// It will be called once in EvaluateHoldEnd.
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, true);
|
||||
isHoldActive = true;
|
||||
PlayHitAnimation();
|
||||
@@ -577,6 +588,7 @@ public class HoldNote : BaseNote
|
||||
{
|
||||
result = "Great";
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
// RecordOffset removed from here to prevent double counting
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, true);
|
||||
isHoldActive = true;
|
||||
PlayHitAnimation();
|
||||
@@ -590,6 +602,7 @@ public class HoldNote : BaseNote
|
||||
{
|
||||
result = "Good";
|
||||
if (noteData != null) noteData.judgeOffsetMs = rawOffsetMs;
|
||||
// RecordOffset removed from here to prevent double counting
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, true);
|
||||
isHoldActive = true;
|
||||
PlayHitAnimation();
|
||||
@@ -602,9 +615,13 @@ public class HoldNote : BaseNote
|
||||
else
|
||||
{
|
||||
result = "Miss";
|
||||
ScoreManager.Instance.countMiss += 1;
|
||||
// For a Miss at the start, we immediately evaluate the end as a Miss too
|
||||
// to ensure ScoreManager records exactly one Miss for this hold note.
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, false);
|
||||
isHoldActive = false;
|
||||
|
||||
EvaluateHoldEnd(Time.time, true);
|
||||
|
||||
try
|
||||
{
|
||||
bool triggered = SkillBuilder.Instance?.NotifyNoteHit(this.trackIndex, result, SkillDefinition.NoteTypeTrigger.Tap, this.noteID) ?? false;
|
||||
@@ -614,11 +631,20 @@ public class HoldNote : BaseNote
|
||||
{
|
||||
Debug.LogError($"[HoldNote] NotifyNoteHit(Start Miss) threw: {ex}");
|
||||
}
|
||||
|
||||
// IMPORTANT: release lock immediately on miss
|
||||
ReleaseTrackJudgeLockSafe();
|
||||
}
|
||||
|
||||
// show judge result and update combo/UI like short notes
|
||||
InputManager.Instance?.ShowJudgeResult(trackIndex, result);
|
||||
teamUIController.Instance?.OnJudgeResult(result);
|
||||
|
||||
// Only call OnJudgeResult on head if it's a hit, so it counts as 1 combo for the whole note.
|
||||
// If it's a Miss, EvaluateHoldEnd(..., true) already handled the OnJudgeResult("Miss").
|
||||
if (result != "Miss")
|
||||
{
|
||||
teamUIController.Instance?.OnJudgeResult(result);
|
||||
}
|
||||
|
||||
// Play judge sound only for START segment
|
||||
if (segment == NoteSegment.Start)
|
||||
@@ -636,6 +662,12 @@ public class HoldNote : BaseNote
|
||||
{
|
||||
StartCoroutine(DelayedReturn());
|
||||
}
|
||||
|
||||
// If start head was judged as Miss, ensure we return soon so it can't keep stale state
|
||||
if (segment == NoteSegment.Start && result == "Miss")
|
||||
{
|
||||
ScheduleReturnToPool(0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator DelayedReturn()
|
||||
@@ -672,7 +704,15 @@ public class HoldNote : BaseNote
|
||||
{
|
||||
result = "Miss";
|
||||
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.LogWarning($"[HoldNote] END judged as Miss: {noteColor} reason={(forceMiss ? "forced Miss" : "start not judged")}");
|
||||
|
||||
// IMPORTANT: release lock on end miss too
|
||||
ReleaseTrackJudgeLockSafe();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -687,21 +727,30 @@ public class HoldNote : BaseNote
|
||||
{
|
||||
result = "Perfect";
|
||||
ScoreManager.Instance.countPerfect += 1;
|
||||
if (trackIndex >= 0 && trackIndex < 5) ScoreManager.Instance.trackPerfectCounts[trackIndex]++;
|
||||
}
|
||||
else if (frac > 0.5f)
|
||||
{
|
||||
result = "Great";
|
||||
ScoreManager.Instance.countGreat += 1;
|
||||
if (trackIndex >= 0 && trackIndex < 5) ScoreManager.Instance.trackGreatCounts[trackIndex]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = "Good";
|
||||
ScoreManager.Instance.countGood += 1;
|
||||
if (trackIndex >= 0 && trackIndex < 5) ScoreManager.Instance.trackGoodCounts[trackIndex]++;
|
||||
}
|
||||
|
||||
if (noteData != null && result != "Miss")
|
||||
{
|
||||
noteData.judgeOffsetMsEnd = (info.scheduledEnd - releaseTime) * 1000f;
|
||||
float offsetEnd = (info.scheduledEnd - releaseTime) * 1000f;
|
||||
noteData.judgeOffsetMsEnd = offsetEnd;
|
||||
|
||||
// Record the head offset (stored during HandleStart) as the representative timing for this note
|
||||
float headOffset = noteData.judgeOffsetMs;
|
||||
if (float.IsNaN(headOffset)) headOffset = 0;
|
||||
ScoreManager.Instance?.RecordOffset(headOffset);
|
||||
}
|
||||
|
||||
// cleanup pool
|
||||
@@ -719,31 +768,45 @@ public class HoldNote : BaseNote
|
||||
{
|
||||
result = "Perfect";
|
||||
ScoreManager.Instance.countPerfect += 1;
|
||||
if (trackIndex >= 0 && trackIndex < 5) ScoreManager.Instance.trackPerfectCounts[trackIndex]++;
|
||||
}
|
||||
else if (frac > 0.5f)
|
||||
{
|
||||
result = "Great";
|
||||
ScoreManager.Instance.countGreat += 1;
|
||||
if (trackIndex >= 0 && trackIndex < 5) ScoreManager.Instance.trackGreatCounts[trackIndex]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = "Good";
|
||||
ScoreManager.Instance.countGood += 1;
|
||||
if (trackIndex >= 0 && trackIndex < 5) ScoreManager.Instance.trackGoodCounts[trackIndex]++;
|
||||
}
|
||||
|
||||
float rawOffsetMsEnd = (scheduledEndTime - actualReleaseTime) * 1000f;
|
||||
if (noteData != null && result != "Miss")
|
||||
{
|
||||
noteData.judgeOffsetMsEnd = rawOffsetMsEnd;
|
||||
|
||||
// Fallback: use head offset if available, otherwise 0
|
||||
float headOffset = noteData.judgeOffsetMs;
|
||||
if (float.IsNaN(headOffset)) headOffset = 0;
|
||||
ScoreManager.Instance?.RecordOffset(headOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[HoldNote] END result for {noteID}: {result} (color={noteColor})");
|
||||
|
||||
// show UI/audio and combo
|
||||
// show UI/audio
|
||||
InputManager.Instance?.ShowJudgeResult(trackIndex, result);
|
||||
teamUIController.Instance?.OnJudgeResult(result);
|
||||
|
||||
// ONLY call OnJudgeResult on end if it's a Miss, to break the combo started at the head.
|
||||
// If it's a hit, we don't call it again to avoid double-counting combo.
|
||||
if (result == "Miss")
|
||||
{
|
||||
teamUIController.Instance?.OnJudgeResult(result);
|
||||
}
|
||||
|
||||
// Ensure END always spawns judgement prefab (including Miss)
|
||||
Animation_GenerateJudgementSituationPrefab.Instance?.SpawnJudgePrefab(noteColor, result);
|
||||
@@ -830,11 +893,6 @@ public class HoldNote : BaseNote
|
||||
|
||||
private void HandleEnd(bool forceMiss = false)
|
||||
{
|
||||
if (!JudgeManager.Instance.TryResolveEnd(noteID))
|
||||
{
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[HoldNote] END try-resolve failed: {noteColor}");
|
||||
return;
|
||||
}
|
||||
// record release time and register release in JudgeManager
|
||||
if (!hasReleased)
|
||||
{
|
||||
@@ -865,12 +923,9 @@ public class HoldNote : BaseNote
|
||||
}
|
||||
|
||||
if (GameConfig.verboseLogs) Debug.Log($"[HoldNote] ReturnToPool called for {noteID} segment={segment} time={Time.time:F3}");
|
||||
|
||||
// Release lock if still held
|
||||
if (TrackKeyManager.Instance != null)
|
||||
{
|
||||
TrackKeyManager.Instance.UnlockTrackForJudge(trackIndex, noteID);
|
||||
}
|
||||
|
||||
// CRITICAL: Always release lock before returning to pool, regardless of state
|
||||
ReleaseTrackJudgeLockSafe();
|
||||
|
||||
gameObject.SetActive(false);
|
||||
|
||||
@@ -884,6 +939,9 @@ public class HoldNote : BaseNote
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
// also release any stale lock from previous lifecycle
|
||||
ReleaseTrackJudgeLockSafe();
|
||||
|
||||
hasReleased = false;
|
||||
segment = NoteSegment.None;
|
||||
keyToPress = KeyCode.None;
|
||||
@@ -910,6 +968,15 @@ public class HoldNote : BaseNote
|
||||
ResetVisualScale();
|
||||
}
|
||||
|
||||
private void ReleaseTrackJudgeLockSafe()
|
||||
{
|
||||
// Defensive: any miss / pooling path should release the track lock.
|
||||
if (TrackKeyManager.Instance != null && !string.IsNullOrEmpty(noteID))
|
||||
{
|
||||
TrackKeyManager.Instance.UnlockTrackForJudge(trackIndex, noteID);
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyVisualScale(float scaleY)
|
||||
{
|
||||
float s = Mathf.Clamp(scaleY, 0.1f, 4f);
|
||||
@@ -942,12 +1009,21 @@ public class HoldNote : BaseNote
|
||||
|
||||
float activation = controller.ActivationTime;
|
||||
float s = controller.CurrentSpeed;
|
||||
float elapsed = Mathf.Max(0f, Time.time - activation);
|
||||
Vector3 expected = spawnPointPosition + Vector3.down * s * elapsed;
|
||||
float elapsed = Time.time - activation;
|
||||
Vector3 expected = spawnPointPosition;
|
||||
|
||||
if (controller.UsesAbsolutePositioning)
|
||||
{
|
||||
expected.y -= (controller.BaseSpawnYOffset + s * elapsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
expected += Vector3.down * s * Mathf.Max(0f, elapsed);
|
||||
}
|
||||
|
||||
if (Vector3.Distance(transform.position, expected) > tolerance)
|
||||
{
|
||||
transform.position = expected;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user