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

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

SDL Timers vs Frame Counting
Why might I want to use SDL timers instead of just counting frames in the main game loop for timing events?
Or Ask your Own Question
Purchase the course to ask your own questions