客户端rsa,冗余清理,bug修复,安卓build问题

This commit is contained in:
FloatGaming
2026-07-14 01:43:49 +08:00
parent fd22501f71
commit f8985d91d2
682 changed files with 4595 additions and 11216 deletions
@@ -38,8 +38,6 @@ public class AnimationController : MonoBehaviour
private bool holdActive = false;
private string currentHoldColor;
private bool isHolding = false;
private WaitForSeconds cachedHoldWait;
private float cachedHoldWaitSeconds = -1f;
private static bool loggedMissingHitParticleSource;
// Track active particle instances for immediate cleanup
@@ -203,7 +201,7 @@ public class AnimationController : MonoBehaviour
private IEnumerator RecycleFxRoutine(GameObject instance, int rentToken, float delay)
{
yield return new WaitForSeconds(delay);
yield return GameplayClock.WaitForSeconds(delay);
if (instance == null)
yield break;
@@ -316,12 +314,7 @@ public class AnimationController : MonoBehaviour
while (holdActive)
{
float waitSeconds = Mathf.Max(0.01f, holdParticleInterval);
if (cachedHoldWait == null || !Mathf.Approximately(cachedHoldWaitSeconds, waitSeconds))
{
cachedHoldWaitSeconds = waitSeconds;
cachedHoldWait = new WaitForSeconds(waitSeconds);
}
yield return cachedHoldWait;
yield return GameplayClock.WaitForSeconds(waitSeconds);
if (!holdActive) break;
PlayDestroyAnimation(color);
}
+10 -14
View File
@@ -218,6 +218,12 @@ public class GameManager : MonoBehaviour
private Coroutine playbackDelayCoroutine;
private void BeginGameplayClock()
{
GameplayClock.Reset();
GameplayClock.StartChart(0f);
}
// Public helper to reliably unmute and start music playback, respecting delay.
public void PlayMusicWithDelay(float delaySeconds)
{
@@ -275,7 +281,7 @@ public class GameManager : MonoBehaviour
{
float clampedStartTime = ClampAudioStartTime(delaySeconds < 0f ? -delaySeconds : 0f);
musicSource.time = clampedStartTime;
musicSource.Play();
musicSource.Play();
PlaybackStarted = true;
}
catch { }
@@ -1287,6 +1293,7 @@ public class GameManager : MonoBehaviour
// Resume using PauseManager
PauseManager.Instance?.Pause(false);
BeginGameplayClock();
// Start spawning using the parsed beatmap
if (beatmapManager.beatmap != null)
@@ -1346,6 +1353,7 @@ public class GameManager : MonoBehaviour
// Resume
PauseManager.Instance?.Pause(false);
BeginGameplayClock();
// Start spawning using the existing parsed beatmap
if (beatmapManager.beatmap != null)
@@ -1380,13 +1388,6 @@ public class GameManager : MonoBehaviour
float delay = beatmapManager.globalDelaySeconds;
PlayMusicWithDelay(delay);
// ensure overlay shows and pause logic runs after loading the clip
var pm2 = PauseManager.Instance ?? SceneObjectLookupCache.FindAny<PauseManager>();
if (pm2 != null)
{
pm2.Pause(true);
Debug.Log("PauseManager.Pause(true) invoked after assigning musicClip in normal startup");
}
}
}
}
@@ -1444,6 +1445,7 @@ public class GameManager : MonoBehaviour
// Resume
PauseManager.Instance?.Pause(false);
BeginGameplayClock();
// Start spawning
if (beatmapManager.beatmap != null)
@@ -1473,12 +1475,6 @@ public class GameManager : MonoBehaviour
float delay = beatmapManager.globalDelaySeconds;
PlayMusicWithDelay(delay);
var pm3 = PauseManager.Instance ?? SceneObjectLookupCache.FindAny<PauseManager>();
if (pm3 != null)
{
pm3.Pause(true);
Debug.Log("PauseManager.Pause(true) invoked after assigning parsedMusicFile clip");
}
}
}
else
@@ -0,0 +1,116 @@
using UnityEngine;
public static class GameplayClock
{
private static bool initialized;
private static bool paused;
private static double chartStartDspTime;
private static double pauseStartDspTime;
private static double accumulatedPauseSeconds;
private static float chartStartSongTime;
public static bool IsInitialized => initialized;
public static bool IsPaused => paused;
public static float ChartStartSongTime => chartStartSongTime;
public static double ChartStartDspTime => chartStartDspTime;
public static void Reset()
{
initialized = false;
paused = false;
chartStartDspTime = 0d;
pauseStartDspTime = 0d;
accumulatedPauseSeconds = 0d;
chartStartSongTime = 0f;
}
public static void StartChart(float initialSongTimeSeconds = 0f)
{
initialized = true;
paused = false;
chartStartDspTime = AudioSettings.dspTime;
pauseStartDspTime = 0d;
accumulatedPauseSeconds = 0d;
chartStartSongTime = Mathf.Max(0f, initialSongTimeSeconds);
}
public static void Pause()
{
if (!initialized || paused)
{
return;
}
paused = true;
pauseStartDspTime = AudioSettings.dspTime;
}
public static void Resume()
{
if (!initialized || !paused)
{
return;
}
accumulatedPauseSeconds += AudioSettings.dspTime - pauseStartDspTime;
pauseStartDspTime = 0d;
paused = false;
}
public static float NowSongTime
{
get
{
if (!initialized)
{
return 0f;
}
double currentDsp = paused ? pauseStartDspTime : AudioSettings.dspTime;
double elapsed = currentDsp - chartStartDspTime - accumulatedPauseSeconds;
return chartStartSongTime + Mathf.Max(0f, (float)elapsed);
}
}
public static float SongTimeFromDsp(double dspTime)
{
if (!initialized)
{
return chartStartSongTime;
}
double effectivePause = accumulatedPauseSeconds;
if (paused && dspTime > pauseStartDspTime)
{
dspTime = pauseStartDspTime;
}
double elapsed = dspTime - chartStartDspTime - effectivePause;
return chartStartSongTime + Mathf.Max(0f, (float)elapsed);
}
public static float ToAbsoluteChartTime(float songTimelineTimeSeconds)
{
return chartStartSongTime + songTimelineTimeSeconds;
}
public static System.Collections.IEnumerator WaitForSeconds(float seconds)
{
if (seconds <= 0f)
{
yield break;
}
if (!initialized)
{
yield return new UnityEngine.WaitForSeconds(seconds);
yield break;
}
float targetTime = NowSongTime + seconds;
while (NowSongTime < targetTime)
{
yield return null;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 47a06e6eb65d9734f8cf63eb1cb59207
+21 -16
View File
@@ -312,6 +312,11 @@ public class HoldNote : BaseNote
}
}
private void OnDisable()
{
prevTrackHeld = false;
}
private void OnDestroy()
{
if (controller != null)
@@ -435,7 +440,7 @@ public class HoldNote : BaseNote
}
// Removed: CalibratePosition for delay==0f was causing Start segments to snap to incorrect positions
// if they started moving immediately after Setup (due to activationTime <= Time.time).
// if they started moving immediately after Setup (due to activationTime <= gameplay clock).
// Start segments should begin at spawnPoint and move from there naturally.
}
@@ -446,7 +451,7 @@ public class HoldNote : BaseNote
var jm = cachedJudgeManager;
var tkm = cachedTrackKeyManager;
bool debugEnabled = JudgeManager.IsDebugEnabled;
float now = Time.time;
float now = GameplayClock.NowSongTime;
// Sample held state and derive key-down/up edges at the very top, BEFORE any
// early return. Unity's Input.GetKeyDown/Up are global per-frame edges that
@@ -740,7 +745,7 @@ public class HoldNote : BaseNote
{
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] End entered while key still held: {noteColor}, forcing Perfect");
// Record release time as current time (player is still holding)
releaseTime = Time.time;
releaseTime = GameplayClock.NowSongTime;
hasReleased = true;
JudgeManager.Instance?.RegisterNoteReleased(noteID, true);
// Evaluate and judge the hold end - force Perfect since we reached the end while holding
@@ -818,12 +823,11 @@ public class HoldNote : BaseNote
if (segment == NoteSegment.End)
{
float timeToWait = Mathf.Max(0f, scheduledEndTime - Time.time);
float timeToWait = Mathf.Max(0f, scheduledEndTime - GameplayClock.NowSongTime);
if (timeToWait > 0f)
{
// use scaled time here so pause affects this wait
float target = Time.time + timeToWait;
while (Time.time < target)
float target = GameplayClock.NowSongTime + timeToWait;
while (GameplayClock.NowSongTime < target)
{
yield return null;
}
@@ -858,7 +862,7 @@ public class HoldNote : BaseNote
private IEnumerator DelayedReturnToPool(float delay)
{
yield return new WaitForSeconds(delay);
yield return GameplayClock.WaitForSeconds(delay);
if (gameObject.activeSelf)
{
ReturnToPool();
@@ -883,7 +887,7 @@ public class HoldNote : BaseNote
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] START forced Miss: {noteColor}");
// Use EvaluateHoldEnd to centralize miss logic and statistics
EvaluateHoldEnd(Time.time, true);
EvaluateHoldEnd(GameplayClock.NowSongTime, true);
// Still need to show judge result and update combo for the head segment
InputManager.Instance?.ShowJudgeResult(trackIndex, "Miss");
@@ -897,7 +901,7 @@ public class HoldNote : BaseNote
return;
}
float pressTime = Time.time;
float pressTime = GameplayClock.NowSongTime;
float rawOffsetMs = (hitTime - pressTime) * 1000f;
float offset = Mathf.Abs(pressTime - hitTime);
string result;
@@ -962,7 +966,7 @@ public class HoldNote : BaseNote
JudgeManager.Instance.RegisterStartJudged(noteID, false);
isHoldActive = false;
EvaluateHoldEnd(Time.time, true);
EvaluateHoldEnd(GameplayClock.NowSongTime, true);
try
{
@@ -1035,10 +1039,10 @@ public class HoldNote : BaseNote
private IEnumerator DelayedReturn()
{
yield return new WaitForSeconds(0.05f);
yield return GameplayClock.WaitForSeconds(0.05f);
if (gameObject.activeSelf)
{
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] DelayedReturn: returning {noteID} to pool at time={Time.time:F3}");
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] DelayedReturn: returning {noteID} to pool at time={GameplayClock.NowSongTime:F3}");
ReturnToPool();
}
}
@@ -1331,7 +1335,7 @@ public class HoldNote : BaseNote
}
else
{
releaseTime = Time.time;
releaseTime = GameplayClock.NowSongTime;
}
hasReleased = true;
JudgeManager.Instance?.RegisterNoteReleased(noteID, true);
@@ -1353,7 +1357,7 @@ public class HoldNote : BaseNote
return;
}
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] ReturnToPool called for {noteID} segment={segment} time={Time.time:F3}");
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] ReturnToPool called for {noteID} segment={segment} time={GameplayClock.NowSongTime:F3}");
// CRITICAL: Always release lock before returning to pool, regardless of state
ReleaseTrackJudgeLockSafe();
@@ -1389,6 +1393,7 @@ public class HoldNote : BaseNote
isJudged = false;
isHoldActive = false;
hasBeenHeldFromStart = false;
prevTrackHeld = false;
holdStartTime = -1f;
@@ -1463,7 +1468,7 @@ public class HoldNote : BaseNote
float activation = controller.ActivationTime;
float s = controller.CurrentSpeed;
float elapsed = Time.time - activation;
float elapsed = GameplayClock.NowSongTime - activation;
Vector3 expected = spawnPointPosition;
if (controller.UsesAbsolutePositioning)
@@ -20,12 +20,12 @@ public class HoldNoteController : MonoBehaviour
if (useAbsolutePositioning)
{
if (!isMoving) return;
ApplyAbsolutePosition(Time.time);
ApplyAbsolutePosition(GameplayClock.NowSongTime);
return;
}
// Start moving after activation time in legacy mode
if (!isMoving && Time.time >= activationTime)
if (!isMoving && GameplayClock.NowSongTime >= activationTime)
{
isMoving = true;
}
@@ -53,7 +53,7 @@ public class HoldNoteController : MonoBehaviour
/// </summary>
public void SetSegmentDelay(float segmentDelay)
{
activationTime = Time.time + segmentDelay;
activationTime = GameplayClock.NowSongTime + segmentDelay;
isMoving = false;
useAbsolutePositioning = false;
visualOffset = 0f;
@@ -79,7 +79,7 @@ public class HoldNoteController : MonoBehaviour
isMoving = true;
// Snap immediately to the expected position to avoid a 1-frame pop.
ApplyAbsolutePosition(Time.time);
ApplyAbsolutePosition(GameplayClock.NowSongTime);
}
public void StopMovement()
@@ -207,7 +207,7 @@ public class JudgeManager : MonoBehaviour
return false;
}
s.startResolved = true;
if (IsDebugEnabled) Debug.Log($"[JudgeManager] TryResolveStart: start resolved for {noteID} at time={Time.time:F3}");
if (IsDebugEnabled) Debug.Log($"[JudgeManager] TryResolveStart: start resolved for {noteID} at time={GameplayClock.NowSongTime:F3}");
return true;
}
@@ -220,7 +220,7 @@ public class JudgeManager : MonoBehaviour
return false;
}
s.endResolved = true;
if (IsDebugEnabled) Debug.Log($"[JudgeManager] TryResolveEnd: end resolved for {noteID} at time={Time.time:F3}");
if (IsDebugEnabled) Debug.Log($"[JudgeManager] TryResolveEnd: end resolved for {noteID} at time={GameplayClock.NowSongTime:F3}");
return true;
}
@@ -233,7 +233,7 @@ public class JudgeManager : MonoBehaviour
return false;
}
s.skillTriggered = true;
if (IsDebugEnabled) Debug.Log($"[JudgeManager] TryTriggerSkill: skill triggered for {noteID} at time={Time.time:F3}");
if (IsDebugEnabled) Debug.Log($"[JudgeManager] TryTriggerSkill: skill triggered for {noteID} at time={GameplayClock.NowSongTime:F3}");
return true;
}
@@ -262,7 +262,7 @@ public class JudgeManager : MonoBehaviour
public void RegisterStartJudged(string noteID, bool state)
{
startJudgedNotes[noteID] = state;
if (IsDebugEnabled) Debug.Log($"[JudgeManager] RegisterStartJudged: {noteID} = {state} at time={Time.time:F3}");
if (IsDebugEnabled) Debug.Log($"[JudgeManager] RegisterStartJudged: {noteID} = {state} at time={GameplayClock.NowSongTime:F3}");
}
public bool IsStartJudged(string noteID)
@@ -288,7 +288,7 @@ public class JudgeManager : MonoBehaviour
if (!judgeQueues[key].Contains(note))
judgeQueues[key].Enqueue(note);
if (IsDebugEnabled) Debug.Log($"[JudgeManager] RegisterNote: key={key} note={note.name} time={Time.time:F3} queueSize={judgeQueues[key].Count}");
if (IsDebugEnabled) Debug.Log($"[JudgeManager] RegisterNote: key={key} note={note.name} time={GameplayClock.NowSongTime:F3} queueSize={judgeQueues[key].Count}");
}
public void UnregisterNote(KeyCode key, Note note)
@@ -315,7 +315,7 @@ public class JudgeManager : MonoBehaviour
Note note = judgeQueues[key].Dequeue();
if (note != null && !note.IsJudged())
{
if (IsDebugEnabled) Debug.Log($"[JudgeManager] JudgeEarliestNote: judging {note.name} for key={key} at time={Time.time:F3}");
if (IsDebugEnabled) Debug.Log($"[JudgeManager] JudgeEarliestNote: judging {note.name} for key={key} at time={GameplayClock.NowSongTime:F3}");
note.Judge();
}
}
@@ -1,100 +1,109 @@
using UnityEngine;
public class Notes : MonoBehaviour
{
public int trackIndex; // Documentation text normalized.
public float hitTime; // Documentation text normalized.
private bool canBeJudged = false; // Documentation text normalized.
private bool isHit = false; // Documentation text normalized.
private void Update()
{
// Documentation text normalized.
if (canBeJudged && !isHit && Time.timeSinceLevelLoad > hitTime + 0.3f)
{
JudgeMiss();
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("JudgmentLine"))
{
canBeJudged = true;
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.CompareTag("JudgmentLine"))
{
canBeJudged = false;
}
}
public void JudgeNote()
{
if (!canBeJudged || isHit) return; // Documentation text normalized.
float currentTime = Time.timeSinceLevelLoad;
float timeDifference = Mathf.Abs(currentTime - hitTime);
if (timeDifference <= 0.05f)
{
JudgePerfect();
}
else if (timeDifference <= 0.1f)
{
JudgeGreat();
}
else if (timeDifference <= 0.2f)
{
JudgeGood();
}
else if (timeDifference <= 0.3f)
{
JudgeMiss();
}
}
private void Recycle()
{
// Documentation text normalized.
if (NotePool.Instance != null)
{
NotePool.Instance.ReturnNote(gameObject, "red"); // Documentation text normalized.
}
else
{
Destroy(gameObject);
}
}
private void JudgePerfect()
{
Debug.Log($"Track {trackIndex}: Perfect!");
isHit = true;
Recycle();
}
private void JudgeGreat()
{
Debug.Log($"Track {trackIndex}: Great!");
isHit = true;
Recycle();
}
private void JudgeGood()
{
Debug.Log($"Track {trackIndex}: Good!");
isHit = true;
Recycle();
}
private void JudgeMiss()
{
Debug.Log($"Track {trackIndex}: Miss!");
isHit = true;
Recycle();
}
}
using UnityEngine;
[AddComponentMenu("")]
[System.Obsolete("Legacy prototype note script. Do not use in gameplay.", false)]
public class Notes : MonoBehaviour
{
public int trackIndex;
public float hitTime;
private bool canBeJudged = false;
private bool isHit = false;
private void OnEnable()
{
enabled = false;
Debug.LogWarning("[Notes] Legacy prototype script was enabled. It has been disabled automatically and is not part of the current gameplay pipeline.", this);
}
private void Update()
{
if (canBeJudged && !isHit && Time.timeSinceLevelLoad > hitTime + 0.3f)
{
JudgeMiss();
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("JudgmentLine"))
{
canBeJudged = true;
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.CompareTag("JudgmentLine"))
{
canBeJudged = false;
}
}
public void JudgeNote()
{
if (!canBeJudged || isHit)
{
return;
}
float currentTime = Time.timeSinceLevelLoad;
float timeDifference = Mathf.Abs(currentTime - hitTime);
if (timeDifference <= 0.05f)
{
JudgePerfect();
}
else if (timeDifference <= 0.1f)
{
JudgeGreat();
}
else if (timeDifference <= 0.2f)
{
JudgeGood();
}
else if (timeDifference <= 0.3f)
{
JudgeMiss();
}
}
private void Recycle()
{
if (NotePool.Instance != null)
{
NotePool.Instance.ReturnNote(gameObject, "red");
}
else
{
Destroy(gameObject);
}
}
private void JudgePerfect()
{
Debug.Log($"Track {trackIndex}: Perfect!");
isHit = true;
Recycle();
}
private void JudgeGreat()
{
Debug.Log($"Track {trackIndex}: Great!");
isHit = true;
Recycle();
}
private void JudgeGood()
{
Debug.Log($"Track {trackIndex}: Good!");
isHit = true;
Recycle();
}
private void JudgeMiss()
{
Debug.Log($"Track {trackIndex}: Miss!");
isHit = true;
Recycle();
}
}
+6 -6
View File
@@ -218,7 +218,7 @@ public class Note : BaseNote
if (!isJudged && GameConfig.autoPlayEnabled && gameObject.activeSelf)
{
// Only judge once the note reaches its scheduled hit time.
if (Time.time >= hitTime)
if (GameplayClock.NowSongTime >= hitTime)
{
TryAutoJudgePerfect();
}
@@ -226,9 +226,9 @@ public class Note : BaseNote
}
// Force miss if we've passed the deadline without being judged
if (!isJudged && Time.time > missDeadlineTime && gameObject.activeSelf)
if (!isJudged && GameplayClock.NowSongTime > missDeadlineTime && gameObject.activeSelf)
{
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] {noteColor} on track {TrackIndex} exceeded miss deadline at time {Time.time:F3}, forcing Miss");
if (JudgeManager.IsDebugEnabled) Debug.Log($"[Note] {noteColor} on track {TrackIndex} exceeded miss deadline at time {GameplayClock.NowSongTime:F3}, forcing Miss");
JudgeMiss();
}
}
@@ -330,7 +330,7 @@ public class Note : BaseNote
hasLock = true;
float pressTime = Time.time;
float pressTime = GameplayClock.NowSongTime;
float maxWindow = (judgeConfig?.missRange ?? 0.5f);
// If there is a controller and the note is not inside judge zone, only allow judgment
@@ -562,7 +562,7 @@ public class Note : BaseNote
}
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, "Miss", SkillDefinition.NoteTypeTrigger.Tap); } catch { }
LogTapJudge("Miss", 0f, false, false, Time.time);
LogTapJudge("Miss", 0f, false, false, GameplayClock.NowSongTime);
// notify global judge manager that this note has been finally judged (miss)
JudgeManager.Instance?.NotifyNoteJudged();
@@ -628,7 +628,7 @@ public class Note : BaseNote
// 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
missDeadlineTime = GameplayClock.NowSongTime; // Force deadline to now so Update will handle it
}
}
}
@@ -27,7 +27,7 @@ public class NoteController : MonoBehaviour
if (isMoving && useAbsolutePositioning)
{
// Use absolute positioning based on elapsed time since activation
float elapsedSinceActivation = Time.time - activationTime;
float elapsedSinceActivation = GameplayClock.NowSongTime - activationTime;
float travelDistance = speed * Mathf.Max(0f, elapsedSinceActivation);
// Position = spawnPoint + initial offset + downward travel
@@ -65,7 +65,7 @@ public class NoteController : MonoBehaviour
activationTime = hitTime - travelTime;
baseSpawnYOffset = initialYOffset;
useAbsolutePositioning = true;
transform.position = GetExpectedPosition(Time.time);
transform.position = GetExpectedPosition(GameplayClock.NowSongTime);
if (JudgeManager.IsDebugEnabled)
Debug.Log($"[NoteController] Configured: spawnPos={spawnPosition}, activationTime={activationTime:F3}, initialYOffset={initialYOffset:F4}");
+13 -18
View File
@@ -89,7 +89,7 @@ public class NoteSpawner : MonoBehaviour
public NoteJudgeConfig judgeConfig; // Documentation text normalized.
private Beatmap beatmap;
private float startTime; // Documentation text normalized.
private float startTime; // Absolute chart time anchor.
private float bpm = 120f;
private bool isSpawning = false;
@@ -247,7 +247,7 @@ public class NoteSpawner : MonoBehaviour
JudgeManager.Instance?.SetTotalNotes(total);
bpm = beatmap.bpm;
startTime = Time.time;
startTime = GameplayClock.ChartStartSongTime;
if (JudgeManager.IsDebugEnabled) Debug.Log($"歌曲开始时间: {startTime}");
// keep reference so we can stop spawning when doing immediate settlement
@@ -305,14 +305,12 @@ public class NoteSpawner : MonoBehaviour
float travelTime = GetLaneTravelTimeSeconds(laneTravelTimes, note.trackIndex);
float spawnTime = note.time - travelTime;
float delay = spawnTime - (Time.time - startTime) + spawnOffset;
float chartSpawnTime = startTime + spawnTime + spawnOffset;
float delay = chartSpawnTime - GameplayClock.NowSongTime;
if (delay > 0)
{
// Use scaled-time wait so spawning is paused while Time.timeScale==0 (PauseManager pause)
float target = Time.time + delay;
// Wait using frames so this loop respects Time.timeScale (Time.time won't advance when paused)
while (Time.time < target)
while (GameplayClock.NowSongTime < chartSpawnTime)
{
yield return null;
}
@@ -390,8 +388,7 @@ public class NoteSpawner : MonoBehaviour
Transform spawnPoint = spawnPoints[noteData.trackIndex];
// Calculate hit time (realtime when note should be judged)
float rawHit = startTime + noteData.time + globalHitDelay;
float realtimeHit = Mathf.Max(0f, rawHit);
float chartHitTime = Mathf.Max(0f, startTime + noteData.time + globalHitDelay);
Vector3 initialPosition = spawnPoint.position;
note.transform.position = initialPosition;
@@ -403,12 +400,12 @@ public class NoteSpawner : MonoBehaviour
if (noteScript != null)
{
// Setup note script with timing parameters
noteScript.Setup(key, noteData.trackIndex, noteSpeed, realtimeHit, noteData.color, judgeConfig, noteData, isSync);
noteScript.Setup(key, noteData.trackIndex, noteSpeed, chartHitTime, noteData.color, judgeConfig, noteData, isSync);
// Configure controller for absolute positioning (replaces relative Translate)
if (noteController != null)
{
noteController.ConfigureAbsolutePositioning(initialPosition, realtimeHit, travelTime, 0f);
noteController.ConfigureAbsolutePositioning(initialPosition, chartHitTime, travelTime, 0f);
}
EnqueueNoteCalibration(noteController);
@@ -447,15 +444,13 @@ public class NoteSpawner : MonoBehaviour
Transform spawnPoint = spawnPoints[noteData.trackIndex];
// Documentation text normalized.
float rawScheduledEnd = startTime + (noteData.time + noteData.length) + globalHitDelay;
float scheduledEndTime = Mathf.Max(0f, rawScheduledEnd); // clamp to non-negative
float scheduledEndTime = Mathf.Max(0f, startTime + (noteData.time + noteData.length) + globalHitDelay);
// Documentation text normalized.
int holdNoteId = ++holdNoteIdCounter;
// base realtime for hits
float rawBase = startTime + noteData.time + globalHitDelay;
float baseHit = Mathf.Max(0f, rawBase);
float baseHit = Mathf.Max(0f, startTime + noteData.time + globalHitDelay);
GameObject startObj = notePool.GetStartNote(noteData.color);
if (startObj == null)
@@ -759,7 +754,7 @@ public class NoteSpawner : MonoBehaviour
if (now >= job.nextTime)
{
Vector3 expected = job.controller.GetExpectedPosition(Time.time);
Vector3 expected = job.controller.GetExpectedPosition(GameplayClock.NowSongTime);
float distanceDeviation = Vector3.Distance(job.obj.transform.position, expected);
if (distanceDeviation > calibrateTolerance)
{
@@ -847,10 +842,10 @@ public class NoteSpawner : MonoBehaviour
if (JudgeManager.IsDebugEnabled)
{
Debug.Log($"[NoteSpawner] Settlement scheduled at chartEnd+{postChartDelay:F1}s " +
$"(chartEnd={chartEndTime:F3}, startTime={startTime:F3}, now={Time.time:F3})");
$"(chartEnd={chartEndTime:F3}, startTime={startTime:F3}, now={GameplayClock.NowSongTime:F3})");
}
while (Time.time < settlementTime)
while (GameplayClock.NowSongTime < settlementTime)
yield return null;
ForceClearInputState(null);
@@ -370,7 +370,7 @@ public class TrackJudgeHitEffectController : MonoBehaviour
if (useUnscaledTime)
yield return new WaitForSecondsRealtime(delay);
else
yield return new WaitForSeconds(delay);
yield return GameplayClock.WaitForSeconds(delay);
if (instance == null)
yield break;
@@ -205,7 +205,7 @@ public class effectEventController : MonoBehaviour
}
float speed = Mathf.Max(0f, cameraDriftSpeed);
float time = Time.time * speed;
float time = GameplayClock.NowSongTime * speed;
float offsetX = (Mathf.PerlinNoise(cameraDriftSeedX, time) - 0.5f) * 2f * Mathf.Abs(cameraDriftRange.x);
float offsetY = (Mathf.PerlinNoise(cameraDriftSeedY, time) - 0.5f) * 2f * Mathf.Abs(cameraDriftRange.y);
@@ -11,11 +11,7 @@ using UnityEngine;
using Bansonic;
using UnityEngine.SceneManagement;
#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
#define ARENA_ROOM_DISABLE_STEAMWORKS
#endif
#if !ARENA_ROOM_DISABLE_STEAMWORKS
#if UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX
using Steamworks;
#endif
@@ -2205,7 +2201,7 @@ namespace GameServer.Client
return network.SteamDisplayName ?? string.Empty;
}
#if ARENA_ROOM_DISABLE_STEAMWORKS
#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
return string.Empty;
#else
if (SteamManager.Initialized && ulong.TryParse(senderSteamId, out ulong parsedSteamId))
@@ -1,6 +1,4 @@
using System;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using GameServer.Client;
@@ -115,8 +113,9 @@ public class GameServerBridge : MonoBehaviour
}
// ── HMAC 签名 ──
// 优先用握手下发的"每会话签名密钥";无会话密钥时回退到内置静态密钥(兼容旧服务端)。
string payload = $"{songId}|{difficulty}|{totalScore}|{chartScore}|{idolScore}";
string hmac = ComputeHmacSha256(payload, HMAC_SECRET);
string hmac = GameServer.Client.GameServerSession.ComputeScoreHmac(payload, HMAC_SECRET);
// ── 上传日志(清晰可见) ──
Debug.Log("╔══════════════════════════════════════════════════════╗");
@@ -169,17 +168,6 @@ public class GameServerBridge : MonoBehaviour
}
}
private static string ComputeHmacSha256(string payload, string secret)
{
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))
{
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
var sb = new StringBuilder(hash.Length * 2);
foreach (byte b in hash) sb.Append(b.ToString("x2"));
return sb.ToString();
}
}
private static string ConvertDifficulty(int i) => i switch { 0 => "ez", 1 => "hd", 2 => "in", 3 => "im", _ => "unknown" };
private static string GetGrade(long s) => s >= 960000 ? "SSS" : s >= 920000 ? "SS" : s >= 880000 ? "S" : s >= 820000 ? "A" : s >= 720000 ? "B" : s >= 600000 ? "C" : s >= 400000 ? "D" : "F";
}
@@ -0,0 +1,261 @@
using System;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
namespace GameServer.Client
{
/// <summary>
/// 客户端会话状态:保存握手返回的会话令牌与"每会话签名密钥",
/// 并用内置的服务端 RSA 公钥验证会话密钥确实来自真实服务端(明文 HTTP 下防中间人伪造)。
///
/// 设计要点:
/// - session_token 用于后续请求的 Authorization: Bearer,服务端据此绑定 steam_id,杜绝伪造他人成绩。
/// - session_key 取代内置静态 HMAC 密钥。静态密钥内置于二进制、可被逆向提取;
/// 会话密钥每次握手随机、仅本会话有效,即使泄露也无法复用。
/// - server_signature 用内置公钥验签。公钥泄露无害(只能验签、不能签名)。
/// </summary>
public static class GameServerSession
{
// 服务端 RSA 公钥(SubjectPublicKeyInfo / PEM)。与服务器 keys/server_public.pem 对应。
// 公钥内置于客户端无安全风险:只能验签、不能签名。
// 留空时跳过验签(灰度期兼容:仍可用会话密钥,只是不做来源校验)。
private const string ServerPublicKeyPem = @"-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0lQb5hAQO7CfTTl2EScC
+YFt11TMEUO7n4aCwBEthWnXj8WM6M7yxbUrojmM3JcqdmaQtC59VTYIralhCtbV
cW6ACoi25vmArzc8QcOwjUPX3ZoorVp9jrVU0eIDo2qqii2eC/OKCw7bPOoIVG5A
ZqfI1CtmkMSDQD9lg0MDpDWWdGJfwGXAznhsERl9K2F42ZPAGU+qFEz4rTg4T+SX
oXT5PrFs0rlCjCUqoYo8s+doiFqxZGCUalk9k2h+e+uNSyVfwEopcZOcWghKlRpi
5VVkFpmDROIXOsVVcTeXWmwR94v6dqPztsyqkjP/S2K5GHtN7ilYFe0w0xAR84DL
ywIDAQAB
-----END PUBLIC KEY-----";
private static string _sessionToken = string.Empty;
private static string _sessionKey = string.Empty;
private static string _expiresAtUtc = string.Empty;
public static string SessionToken => _sessionToken;
public static string SessionKey => _sessionKey;
/// <summary>是否持有可用(未过期)的会话令牌。</summary>
public static bool HasValidToken
{
get
{
if (string.IsNullOrWhiteSpace(_sessionToken))
{
return false;
}
if (string.IsNullOrWhiteSpace(_expiresAtUtc))
{
return true;
}
if (DateTime.TryParse(
_expiresAtUtc,
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.AdjustToUniversal | System.Globalization.DateTimeStyles.AssumeUniversal,
out DateTime expires))
{
// 提前 60 秒判定过期,避免边界请求被服务端拒绝。
return DateTime.UtcNow < expires.AddSeconds(-60);
}
return true;
}
}
public static void Clear()
{
_sessionToken = string.Empty;
_sessionKey = string.Empty;
_expiresAtUtc = string.Empty;
}
/// <summary>
/// 应用握手响应。验签失败时不保存会话密钥(回退到 legacy 静态密钥路径)。
/// 返回是否成功建立"经过验签的"会话。
/// </summary>
public static bool ApplyHandshake(HandshakeResponse resp)
{
if (resp == null || !resp.success)
{
return false;
}
// 无令牌(旧服务端):什么都不做,维持 legacy 行为。
if (string.IsNullOrWhiteSpace(resp.session_token))
{
return false;
}
// 若服务端提供了签名且客户端内置了公钥,则必须验签通过才采用会话密钥。
bool signatureRequired = !string.IsNullOrWhiteSpace(ServerPublicKeyPem)
&& !string.IsNullOrWhiteSpace(resp.server_signature);
if (signatureRequired)
{
string payload = $"{resp.steam_id}|{resp.session_token}|{resp.session_key}|{resp.expires_at}";
if (!VerifyServerSignature(payload, resp.server_signature))
{
Debug.LogWarning("[GameServerSession] 服务端签名验证失败,拒绝采用该会话密钥。");
Clear();
return false;
}
}
_sessionToken = resp.session_token ?? string.Empty;
_sessionKey = resp.session_key ?? string.Empty;
_expiresAtUtc = resp.expires_at ?? string.Empty;
return true;
}
/// <summary>
/// 计算成绩 HMAC:优先用会话密钥;无会话密钥时用传入的 legacy 静态密钥回退。
/// </summary>
public static string ComputeScoreHmac(string payload, string legacyStaticSecret)
{
string secret = !string.IsNullOrWhiteSpace(_sessionKey) ? _sessionKey : legacyStaticSecret;
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))
{
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
var sb = new StringBuilder(hash.Length * 2);
foreach (byte b in hash) sb.Append(b.ToString("x2"));
return sb.ToString();
}
}
private static bool VerifyServerSignature(string payload, string signatureB64)
{
try
{
byte[] signature = Convert.FromBase64String(signatureB64);
RSAParameters parameters = ParsePublicKeyPem(ServerPublicKeyPem);
using (var rsa = RSA.Create())
{
// Unity 的 .NET Standard 2.0 / Mono 运行时没有 ImportFromPem
// 因此手动把 SubjectPublicKeyInfo(PEM) 解析为 RSAParameters 再导入。
rsa.ImportParameters(parameters);
return rsa.VerifyData(
Encoding.UTF8.GetBytes(payload),
signature,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
}
}
catch (Exception ex)
{
Debug.LogWarning($"[GameServerSession] 验签异常: {ex.Message}");
return false;
}
}
// ── 最小 ASN.1/DER 解析:SubjectPublicKeyInfo(PEM) -> RSAParameters ──
// 结构: SEQUENCE { SEQUENCE { OID rsaEncryption, NULL }, BIT STRING { SEQUENCE { INTEGER modulus, INTEGER exponent } } }
private static RSAParameters ParsePublicKeyPem(string pem)
{
string base64 = ExtractPemBody(pem);
byte[] der = Convert.FromBase64String(base64);
int index = 0;
ReadSequence(der, ref index); // 外层 SEQUENCE
SkipAlgorithmIdentifier(der, ref index); // 跳过 AlgorithmIdentifier SEQUENCE
// BIT STRING
ExpectTag(der, ref index, 0x03);
int bitStringLength = ReadLength(der, ref index);
if (bitStringLength < 1 || der[index] != 0x00)
{
throw new FormatException("Unexpected BIT STRING padding in public key.");
}
index += 1; // 跳过 BIT STRING 的未使用位计数(0x00)
ReadSequence(der, ref index); // RSAPublicKey SEQUENCE
byte[] modulus = ReadIntegerUnsigned(der, ref index);
byte[] exponent = ReadIntegerUnsigned(der, ref index);
return new RSAParameters { Modulus = modulus, Exponent = exponent };
}
private static string ExtractPemBody(string pem)
{
var sb = new StringBuilder(pem.Length);
using (var reader = new System.IO.StringReader(pem))
{
string line;
while ((line = reader.ReadLine()) != null)
{
string trimmed = line.Trim();
if (trimmed.Length == 0 || trimmed.StartsWith("-----"))
{
continue;
}
sb.Append(trimmed);
}
}
return sb.ToString();
}
private static void ExpectTag(byte[] data, ref int index, byte tag)
{
if (index >= data.Length || data[index] != tag)
{
throw new FormatException($"Expected ASN.1 tag 0x{tag:X2} at offset {index}.");
}
index += 1;
}
private static void ReadSequence(byte[] data, ref int index)
{
ExpectTag(data, ref index, 0x30);
ReadLength(data, ref index);
}
private static void SkipAlgorithmIdentifier(byte[] data, ref int index)
{
ExpectTag(data, ref index, 0x30);
int length = ReadLength(data, ref index);
index += length; // 整段 AlgorithmIdentifier 内容不需要
}
private static int ReadLength(byte[] data, ref int index)
{
int first = data[index];
index += 1;
if ((first & 0x80) == 0)
{
return first; // 短格式
}
int byteCount = first & 0x7F;
if (byteCount == 0 || byteCount > 4)
{
throw new FormatException("Unsupported ASN.1 length encoding.");
}
int length = 0;
for (int i = 0; i < byteCount; i++)
{
length = (length << 8) | data[index];
index += 1;
}
return length;
}
private static byte[] ReadIntegerUnsigned(byte[] data, ref int index)
{
ExpectTag(data, ref index, 0x02);
int length = ReadLength(data, ref index);
int start = index;
index += length;
// 去掉 DER 正整数为避免歧义而添加的前导 0x00 符号字节
while (length > 1 && data[start] == 0x00)
{
start += 1;
length -= 1;
}
byte[] result = new byte[length];
Array.Copy(data, start, result, 0, length);
return result;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 79cbd031dc2447d4789e769442806868
@@ -49,6 +49,15 @@ namespace GameServer.Client
[JsonProperty("steam_id")] public string steam_id;
[JsonProperty("error_code")] public string error_code;
[JsonProperty("message")] public string message;
// 会话令牌:后续请求以 Authorization: Bearer 携带,服务端据此绑定 steam_id。
[JsonProperty("session_token")] public string session_token;
// 每会话一次性签名密钥:客户端用它对成绩做 HMAC,取代内置静态密钥。
[JsonProperty("session_key")] public string session_key;
// 会话过期时间(UTC ISO8601)。
[JsonProperty("expires_at")] public string expires_at;
// 服务端 RSA 签名(base64),客户端用内置公钥验签以确认会话密钥来自真实服务端。
[JsonProperty("server_signature")] public string server_signature;
[JsonProperty("sign_alg")] public string sign_alg;
}
[Serializable]
@@ -9,11 +9,7 @@ using UnityEngine;
using UnityEngine.Networking;
using Bansonic;
#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
#define NETWORK_DISABLE_STEAMWORKS
#endif
#if !NETWORK_DISABLE_STEAMWORKS
#if UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX
using Steamworks;
#endif
@@ -32,7 +28,7 @@ public class NetworkManager : MonoBehaviour
public const string SteamAvatarUrlPrefix = "steam-avatar://";
[Header("Server")]
[SerializeField] private string serverUrl = "http://47.112.187.172:8080";
[SerializeField] private string serverUrl = "https://game.bansonic.top";
[Header("Auth")]
public string steamId = "";
@@ -820,6 +816,13 @@ public class NetworkManager : MonoBehaviour
SetState(ConnectionState.Handshaking);
HandshakeResponse resp = await PostJson<HandshakeResponse>(BuildApiUrl("/api/handshake"), req, token);
// 存储会话令牌与每会话签名密钥(先用内置 RSA 公钥验签,确认来自真实服务端)。
if (resp != null && resp.success)
{
GameServerSession.ApplyHandshake(resp);
}
OnHandshakeResult?.Invoke(resp);
if (resp != null && resp.success)
@@ -1350,7 +1353,7 @@ public class NetworkManager : MonoBehaviour
{
currentSteamId = string.Empty;
currentDisplayName = string.Empty;
#if NETWORK_DISABLE_STEAMWORKS
#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
if (logWarnings)
{
Debug.LogWarning("[NetworkManager] Steamworks is unavailable on this platform. Using current serialized network identity.");
@@ -1396,7 +1399,7 @@ public class NetworkManager : MonoBehaviour
private static bool TryReadSteamPersona(string targetSteamId, out string displayName)
{
displayName = string.Empty;
#if NETWORK_DISABLE_STEAMWORKS
#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
return false;
#else
if (string.IsNullOrWhiteSpace(targetSteamId) || !SteamManager.Initialized || !ulong.TryParse(targetSteamId, out ulong parsedSteamId))
@@ -1426,7 +1429,7 @@ public class NetworkManager : MonoBehaviour
private async Task<byte[]> GetLocalSteamAvatarPngAsync()
{
#if NETWORK_DISABLE_STEAMWORKS
#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
return null;
#else
if (!SteamManager.Initialized)
@@ -1532,6 +1535,20 @@ public class NetworkManager : MonoBehaviour
}
}
private static void ApplyAuthHeader(UnityWebRequest request)
{
if (request == null)
{
return;
}
string sessionToken = GameServerSession.SessionToken;
if (!string.IsNullOrWhiteSpace(sessionToken))
{
request.SetRequestHeader("Authorization", "Bearer " + sessionToken);
}
}
private async Task<T> PostJson<T>(string url, object payload, CancellationToken token)
{
if (OnlineModeSettings.IsLocalOnlyMode)
@@ -1546,6 +1563,7 @@ public class NetworkManager : MonoBehaviour
request.uploadHandler = new UploadHandlerRaw(body);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
ApplyAuthHeader(request);
request.timeout = 15;
if (VerboseLogs) Debug.Log($"[NetworkManager] HTTP POST {url}");
@@ -1582,6 +1600,7 @@ public class NetworkManager : MonoBehaviour
request.uploadHandler = new UploadHandlerRaw(body);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
ApplyAuthHeader(request);
request.timeout = 15;
if (VerboseLogs) Debug.Log($"[NetworkManager] HTTP PUT {url}");
@@ -1613,6 +1632,7 @@ public class NetworkManager : MonoBehaviour
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
ApplyAuthHeader(request);
request.timeout = 15;
if (VerboseLogs) Debug.Log($"[NetworkManager] HTTP GET {url}");
await SendRequestAsync(request.SendWebRequest(), token);
@@ -46,7 +46,7 @@ public class budeff_appr : MonoBehaviour
private IEnumerator Animate()
{
float delay = Mathf.Max(0f, floatDelay);
if (delay > 0f) yield return new WaitForSeconds(delay);
if (delay > 0f) yield return GameplayClock.WaitForSeconds(delay);
float duration = Mathf.Max(0.0001f, fadeDuration);
bool useAnchored = _rectTransform != null;
@@ -7,6 +7,8 @@ using UnityEngine.UI;
[DefaultExecutionOrder(-1000)]
public class iBudeffPrefabController : MonoBehaviour
{
private static float GameplayNow => Application.isPlaying ? GameplayClock.NowSongTime : Time.realtimeSinceStartup;
public static iBudeffPrefabController Instance { get; private set; }
[Header("budeff Icons Prefab")]
@@ -129,7 +131,7 @@ public class iBudeffPrefabController : MonoBehaviour
if (ally == null || buff == null) return;
if (!Application.isPlaying) return;
if (!IsTrackableBuff(buff)) return;
RegisterEntry(ally, GetGroupForBuff(buff), buff.buffId, 0f, Time.time);
RegisterEntry(ally, GetGroupForBuff(buff), buff.buffId, 0f, GameplayNow);
}
public void NotifyBuffRemoved(AllyCombatant ally, Buff buff)
@@ -170,7 +172,7 @@ public class iBudeffPrefabController : MonoBehaviour
if (!Application.isPlaying) return;
if (!IsTrackableEnemyBuff(buff)) return;
Debug.LogWarning($"[iBudeffPrefabController] Enemy buff applied: enemy={enemy.name} id={enemy.GetInstanceID()} buffId={buff.buffId} desc={buff.description} atkMult={buff.attackMultiplier} healMult={buff.healReceivedMultiplier} scoreMult={buff.scoreMultiplier}");
RegisterEnemyEntry(enemy, GetGroupForEnemyBuff(buff), buff.buffId, 0f, Time.time);
RegisterEnemyEntry(enemy, GetGroupForEnemyBuff(buff), buff.buffId, 0f, GameplayNow);
}
public void NotifyEnemyBuffRemoved(EnemyCombatant enemy, Buff buff)
@@ -188,7 +190,7 @@ public class iBudeffPrefabController : MonoBehaviour
var group = GetGroupForIconType(type);
string id = Guid.NewGuid().ToString();
Debug.LogWarning($"[iBudeffPrefabController] Enemy timed effect: enemy={enemy.name} id={enemy.GetInstanceID()} type={type} value={value} duration={duration} group={group}");
RegisterEnemyEntry(enemy, group, id, value, Time.time);
RegisterEnemyEntry(enemy, group, id, value, GameplayNow);
return id;
}
@@ -206,7 +208,7 @@ public class iBudeffPrefabController : MonoBehaviour
var group = GetGroupForIconType(type);
string id = Guid.NewGuid().ToString();
RegisterEntry(ally, group, id, value, Time.time, duration);
RegisterEntry(ally, group, id, value, GameplayNow, duration);
return id;
}
@@ -1291,7 +1293,7 @@ public class iBudeffPrefabController : MonoBehaviour
{
if (go == null || cg == null) yield break;
cg.alpha = (i % 2 == 0) ? 1f : 0.08f;
yield return new WaitForSeconds(step);
yield return GameplayClock.WaitForSeconds(step);
}
if (go != null && cg != null) cg.alpha = 1f;
@@ -1310,7 +1312,7 @@ public class iBudeffPrefabController : MonoBehaviour
{
if (go == null || cg == null) yield break;
cg.alpha = (i % 2 == 0) ? 0.08f : 1f;
yield return new WaitForSeconds(step);
yield return GameplayClock.WaitForSeconds(step);
}
if (go != null && cg != null) cg.alpha = 0f;
@@ -229,7 +229,7 @@ public class trackFractureController : MonoBehaviour
// float1: hold before the dissolve begins. Fragments have already started drifting during this time.
if (fractureFadeDelay > 0f)
{
yield return new WaitForSeconds(fractureFadeDelay);
yield return GameplayClock.WaitForSeconds(fractureFadeDelay);
}
// float2: drive _Fade from the configured start value to the configured end value over this duration.