Syncing Timers in Multiplayer
What's the best way to handle timer-based events in a multiplayer game where timing needs to be synchronized?
Synchronizing timers in multiplayer games requires careful consideration of network latency and state management. Here's a pattern that helps maintain consistency across clients while being resistant to network issues.
Basic Approach
Instead of letting each client manage its own timers independently, we synchronize based on a shared start time:
#include <SDL.h>
#include <iostream>
class NetworkSpawner {
public:
struct SpawnConfig {
Uint32 StartTime;
Uint32 Interval;
int WaveSize;
};
void StartSpawning(
const SpawnConfig& Config) {
MyConfig = Config;
IsSpawning = true;
LastSpawnTime = Config.StartTime;
std::cout << "Starting wave at time "
<< Config.StartTime << "ms\n";
}
void Update() {
if (!IsSpawning) { return; }
Uint32 CurrentTime{SDL_GetTicks()};
Uint32 TimeSinceStart{
CurrentTime - MyConfig.StartTime};
Uint32 ExpectedSpawns{
TimeSinceStart / MyConfig.Interval};
// Spawn any missing enemies to catch up
while (SpawnCount < ExpectedSpawns &&
SpawnCount < MyConfig.WaveSize) {
SpawnEnemy(CurrentTime);
SpawnCount++;
}
// Check if wave is complete
if (SpawnCount >= MyConfig.WaveSize) {
IsSpawning = false;
}
}
private:
void SpawnEnemy(Uint32 Time) {
std::cout << "Spawning enemy at time "
<< Time << "ms\n";
LastSpawnTime = Time;
}
SpawnConfig MyConfig;
bool IsSpawning{false};
Uint32 LastSpawnTime{0};
Uint32 SpawnCount{0};
};
// Simulate network message received from server
void HandleServerMessage(
NetworkSpawner& Spawner) {
NetworkSpawner::SpawnConfig Config;
Config.StartTime = SDL_GetTicks();
Config.Interval = 1000;
Config.WaveSize = 3;
Spawner.StartSpawning(Config);
}
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_TIMER);
NetworkSpawner Spawner;
HandleServerMessage(Spawner);
// Simulate game loop
for (int i = 0; i < 4; ++i) {
Spawner.Update();
SDL_Delay(1000);
}
SDL_Quit();
return 0;
}
Starting wave at time 0ms
Spawning enemy at time 1008ms
Spawning enemy at time 2008ms
Spawning enemy at time 3009ms
Key Concepts
Server Authority: The server sends:
- Wave start times
- Spawn intervals
- Number of enemies to spawn
Time Synchronization: Clients convert server time to local time:
Uint32 ServerToLocalTime(Uint32 ServerTime) {
return ServerTime + EstimatedTimeOffset;
}
Catch-up Mechanism: If a client falls behind (lag spike, etc), it can catch up by spawning multiple enemies in one frame, maintaining consistency with other clients.
State Verification: Periodically verify game state with the server:
void VerifyState(const ServerState& State) {
if (SpawnCount != State.ExpectedSpawns) {
// Reconcile difference
SpawnCount = State.ExpectedSpawns;
}
}
Remember to:
- Account for network latency in timing calculations
- Handle disconnections gracefully
- Consider interpolation for smooth visual presentation
- Implement proper error handling for desyncs
This approach provides a good balance between responsiveness and consistency while being resistant to network issues.
SDL2 Timers and Callbacks
Learn how to use callbacks with SDL_AddTimer()
to provide functions that are executed on time-based intervals