Timers in Stateful Cloud Code
Use timers in Stateful Cloud Code modules to schedule delayed function calls.
Read time 3 minutesLast updated a day ago
Use timers to schedule time-based game mechanics in your Stateful Cloud Code modules without managing infrastructure or maintaining persistent connections from game clients. A timer registers a one-off callback that executes a specified Cloud Code function after a defined time span elapses.
Timers are server authoritative and persist within the scope of your module. When a timer elapses, the Cloud Code runtime invokes the target function with the arguments you specified at registration.
Common use cases for timers include the following:
- Turn timers: Enforce time limits for player turns in multiplayer games.
- Cooldowns: Restrict how frequently players can perform certain actions.
- Delayed events: Trigger in-game events after a specified duration.
- Idle mechanics: Process offline progression or resource generation at intervals.
Timer registration
To use timers, addITimerServiceIn this example, callingusing Unity.Services.CloudCode.Core;[StateScope(Scope.MultiplayerSession)]public class RegisterTimerExample{ [CloudCodeFunction("PerformDelayedAction")] public async Task<bool> PerformDelayedAction(IExecutionContext context, ITimerService timerService) { // Register a timer that calls "DelayedAction" after 30 seconds await timerService.Register( TimeSpan.FromSeconds(30), "DelayedAction", new Dictionary<string, object> { { "action", "NOTHING" } } ); return true; } [CloudCodeFunction("DelayedAction")] public async Task DelayedAction(string action) { // Perform your action here }}
PerformDelayedActionDelayedActionPerformDelayedActionactionTimer parameters
TheRegisterParameter | Type | Description |
|---|---|---|
| | The duration to wait before the timer elapses. |
| | The name of the Cloud Code function to call when the timer elapses. |
| | An optional dictionary containing arguments to pass to the target function. |
- Register timers with a delay between 1 second and 24 hours.
- A module can run up to 10 timers at the same time.
Turn timer implementation
You can implement recurring turn timers by having the function called by the timer register its own timer. This pattern is useful for turn-based games where each player has a fixed amount of time to act.In this pattern, each call tousing Unity.Services.CloudCode.Core;[StateScope(Scope.MultiplayerSession)]public class TurnBasedGameExample{ public List<string> _players = new List<string>(); public int _currentPlayerIndex; public bool _gameActive; public string? _timerId; [CloudCodeFunction("StartGame")] public async Task<string> StartGame(ITimerService timerService, List<string> playerIds) { _players = playerIds; _currentPlayerIndex = 0; _gameActive = true; // Start the first turn timer _timerId = await timerService.Register( TimeSpan.FromSeconds(60), "OnTurnTimeout", new Dictionary<string, object> { { "playerId", _players[_currentPlayerIndex] } } ); return $"Game started. {_players[_currentPlayerIndex]}'s turn."; } [CloudCodeFunction("MakeMove")] public async Task<string> MakeMove(IExecutionContext context, ITimerService timerService, string move) { if (_players[_currentPlayerIndex] != context.PlayerId) { return "Not your turn."; } if (_timerId != null) { var timer = await timerService.Fetch(_timerId); timer.Cancel(); } // Process the move here // Advance to the next player and register a new turn timer _currentPlayerIndex = (_currentPlayerIndex + 1) % _players.Count; await timerService.Register( TimeSpan.FromSeconds(60), "OnTurnTimeout", new Dictionary<string, object> { { "playerId", _players[_currentPlayerIndex] } } ); return $"Move '{move}' processed. Next player's turn."; } [CloudCodeFunction("OnTurnTimeout")] public async Task<string> OnTurnTimeout(IExecutionContext context, ITimerService timerService) { if (!_gameActive || _players[_currentPlayerIndex] != context.PlayerId) { return "Timer no longer valid."; } // Handle timeout (skip turn, apply penalty, etc.) var skippedPlayer = _players[_currentPlayerIndex]; // Advance to the next player and register a new turn timer _currentPlayerIndex = (_currentPlayerIndex + 1) % _players.Count; await timerService.Register( TimeSpan.FromSeconds(60), "OnTurnTimeout", new Dictionary<string, object> { { "playerId", _players[_currentPlayerIndex] } } ); return $"Player {skippedPlayer} timed out. Next player's turn."; }}
OnTurnTimeoutMakeMoveActing on timers
Sometimes, you might need to cancel a timer before it elapses, for example, when a player completes their turn before the timeout. You might also need to make checks based on a timer's remaining time. To implement either of these scenarios, fetch the running timer by ID withtimerService.Fetch()[StateScope(Scope.MultiplayerSession)]public class TimerActionExample{ private string _activeTimerId; [CloudCodeFunction("StartAction")] public async Task<string> StartAction(ITimerService timerService) { // Register returns a timer ID _activeTimerId = await timerService.Register( TimeSpan.FromSeconds(30), "DoSomething" // Callback function not included below ); return "Action started. Complete within 30 seconds."; } [CloudCodeFunction("UserCompleteAction")] public async Task<string> UserCompleteAction(ITimerService timerService) { // Cancel the timer since the action completed in time if (!string.IsNullOrEmpty(_activeTimerId)) { var timer = await timerService.Fetch(_activeTimerId); timer.Cancel(); _activeTimerId = null; } return "Action completed successfully."; } [CloudCodeFunction("RemainingTime")] public async Task<TimeSpan> RemainingTime(ITimerService timerService) { // Return how much time is left on the active timer if (string.IsNullOrEmpty(_activeTimerId)) { throw new InvalidOperationException("Cannot return remaining time because no timer is currently active."); } var timer = await timerService.Fetch(_activeTimerId); return timer.RemainingTime(); }}