成就系统 巨大修改 新的ui 黑白效果 等一堆

This commit is contained in:
FloatGaming
2026-01-23 18:43:23 +08:00
parent f3ece026ca
commit 0e47864569
256 changed files with 134131 additions and 31192 deletions
+122 -46
View File
@@ -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;
}
}
}
}