Game Timer System
A named timer registry that centralizes per-frame ticking, lets each timer choose scaled or unscaled time, and keeps game session, pause and cutscene timing in one place.
What the system is for
Every game needs timers. Cooldowns, countdowns, session clocks, cutscene delays — they all end up living in different MonoBehaviours, each maintaining its own Update logic and each having to decide independently whether to respect Time.timeScale or not. That scattered bookkeeping becomes a problem the moment you need to pause the game, skip a cutscene or hand elapsed time to an analytics system.
The Timer module in Serenity replaces that scatter with a single ITimerService registry. Systems register named timers, choose a time mode, and call start, stop or reset. The service ticks every registered timer once per frame from a single point, reading delta time through an injected ITimeProvider so the Unity dependency never leaks into your domain.
The Unity problem
The usual pattern is to copy the same countdown or elapsed-timer pattern into every system that needs it. Each copy has its own Time.deltaTime or Time.unscaledDeltaTime call, its own running flag and its own reset logic. When a new requirement arrives — pause should freeze cooldowns but not the session clock, or a cutscene needs real-time progress — every copy needs to change. The bugs introduced by forgetting one of them are difficult to trace because nothing ties the timers together.
Without a shared timer layer, coordinating between Game Session elapsed time, Game Mode pause and a cutscene sequence requires either tight coupling between those systems or another round of duplicated time bookkeeping. Neither option scales.
How Serenity approaches it
Serenity models the timer domain through ITimerService, ITimer and TimerFactory. ITimerService is the registry: it creates timers with AddTimer, retrieves them by name with GetTimer, removes them with RemoveTimer and drives them all with a single UpdateTimers call. ITimer represents one timer: it exposes SetTimer, StartTimer, StopTimer, GetTimeInSeconds and the per-frame Tick entry point. TimerFormatterService converts raw seconds into formatted hour, minute and second strings for display.
Time scaling is controlled per timer through the TimeMode enum. TimeMode.Scaled timers advance with Time.deltaTime and pause automatically when timeScale reaches zero. TimeMode.Unscaled timers advance with real-world delta and continue running through pause. ITimeProvider abstracts that delta away from the domain so the Unity layer can be replaced or mocked in tests.
How it fits into Serenity
The Timer module lives in the Serenity.Timer namespace and follows the foundation's layered structure. The Domain layer owns the TimeMode enum and TimerFormatterService. The Application layer defines ITimer, ITimerService, TimerFactory and ITimerServiceFactory. The Infrastructure layer provides UnityTimer, UnityTimerService, UnityTimerFactory and UnityTimeProvider, which bridge the domain interfaces to Unity's time API. UnityTimerInstaller registers the service through the initialization pipeline.
Timer cooperates with Game Session, which owns the session elapsed-time timer and is therefore the primary consumer of AddTimer at startup. Game Mode signals pause by changing timeScale, which naturally stops all Scaled timers without any Timer-specific pause logic. Sequence and cutscene systems can register Unscaled timers to track real-time progress independently of game speed.
Practical workflow
- Resolve ITimerService through the installer and call AddTimer with a string identifier and a TimeMode to register a new timer.
- Call StartTimer on the returned ITimer to begin advancing it each frame.
- Call UpdateTimers once per frame from your game loop — the service reads delta from ITimeProvider and ticks all registered timers.
- Retrieve any timer by name with GetTimer and read its current value through GetTimeInSeconds or the individual GetHours, GetMinutes and GetSeconds accessors.
- Use TimerFormatterService to format raw seconds into a display string for UI countdown or elapsed-time labels.
- Call StopTimer to pause a specific timer, ResetTimer to return it to its configured duration, and RemoveTimer to deregister it when the owning system shuts down.
What you get
- Named timer registry through ITimerService with AddTimer, GetTimer, RemoveTimer and GetAllTimers
- Per-timer time mode selection through TimeMode.Scaled and TimeMode.Unscaled
- Centralized per-frame ticking via UpdateTimers — individual systems do not implement their own time bookkeeping
- ITimeProvider abstraction that keeps Unity's time API out of the domain layer
- ITimer interface with SetTimer, StartTimer, StopTimer and GetTimeInSeconds
- TimerFormatterService for converting raw seconds into formatted HH:MM:SS display strings
- TimerFactory and ITimerServiceFactory for creating timer instances without Unity coupling
- Integration with Game Session for elapsed time and Game Mode for pause through timeScale
When to use this
- Projects that need multiple named timers — cooldowns, session clocks, countdowns — managed from one place instead of scattered across MonoBehaviours.
- Games where pause must freeze some timers but not others, requiring explicit control over scaled versus unscaled time per timer.
- Codebases where cutscenes or sequences need real-time progress tracking that is independent of game speed.
- Teams that want timer logic testable in plain C# without depending on Unity's time API in the domain layer.
Related systems
Use Serenity when you want every timer in the game registered, named and ticked from one place, with each timer deciding for itself whether pause means stop — and without any of that logic leaking into your domain.
English
Español
Català