SimulationClock.cs 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. namespace MazeGen
  4. {
  5. /// <summary>
  6. /// The single source of truth for "is the maze simulation running right now".
  7. ///
  8. /// Everything that advances maze state (agent decision timers, fight muster countdowns,
  9. /// the level's elapsed-time clock) must tick on <see cref="DeltaTime"/> rather than
  10. /// <see cref="Time.deltaTime"/>, so that one pause freezes the whole simulation coherently.
  11. ///
  12. /// Deliberately does NOT touch <see cref="Time.timeScale"/>: real Unity time has to keep
  13. /// flowing while the maze is frozen, because that's what drives the watched-fight playback
  14. /// animation and its coroutines.
  15. ///
  16. /// Pausing is ref-counted by reason string (<see cref="PushHold"/>/<see cref="PopHold"/>) so
  17. /// several independent things - three fights breaking out at once, plus an arena playback -
  18. /// can each hold the simulation without stomping on each other's resume. The user's own
  19. /// pause button is separate (<see cref="UserPaused"/>) and always wins.
  20. /// </summary>
  21. public static class SimulationClock
  22. {
  23. private static readonly HashSet<string> _holds = new HashSet<string>();
  24. private static bool _userPaused;
  25. private static float _timeScale = 1f;
  26. /// <summary>The observer's manual pause. Independent of, and additive with, any holds.</summary>
  27. public static bool UserPaused
  28. {
  29. get => _userPaused;
  30. set => _userPaused = value;
  31. }
  32. /// <summary>Fast-forward / slow-motion multiplier for maze simulation time.</summary>
  33. public static float TimeScale
  34. {
  35. get => _timeScale;
  36. set => _timeScale = Mathf.Clamp(value, 0.1f, 8f);
  37. }
  38. public static bool IsPaused => _userPaused || _holds.Count > 0;
  39. /// <summary>Human-readable list of what's currently holding the simulation (null if nothing is).</summary>
  40. public static string HoldSummary => _holds.Count == 0 ? null : string.Join(", ", _holds);
  41. /// <summary>Scaled simulation delta for this frame - zero while paused.</summary>
  42. public static float DeltaTime => IsPaused ? 0f : Time.deltaTime * _timeScale;
  43. public static void PushHold(string reason) => _holds.Add(reason ?? "hold");
  44. public static void PopHold(string reason) => _holds.Remove(reason ?? "hold");
  45. public static bool HasHold(string reason) => _holds.Contains(reason ?? "hold");
  46. /// <summary>
  47. /// Clears all state. Called from MazeSceneBuilder.Awake because this is static state and
  48. /// the editor can be configured to skip domain reload between Play sessions, which would
  49. /// otherwise leave a stale pause hold from the previous run holding the new one frozen.
  50. /// </summary>
  51. public static void Reset()
  52. {
  53. _holds.Clear();
  54. _userPaused = false;
  55. _timeScale = 1f;
  56. }
  57. }
  58. }