In het hart van elke gameserver klopt één ding: de loop. De game loop tick rate bepaalt hoe vaak per seconde de server de wereld bijwerkt — fysica berekenen, beweging toepassen en de toestand naar clients sturen. Kies dit getal, en de manier waarop de loop is gebouwd, verkeerd en spelers krijgen teleporterende personages, inconsistente botsingen en gameplay die uiteenvalt zodra het tempo stijgt. In dit artikel leg ik uit wat een tick is, waarom je een vaste tijdstap nodig hebt en hoe je een stabiele serverloop bouwt in C++.
Wat is een tick, en wat betekent tick rate?
Een tick is één stap waarmee de simulatie vooruitgaat. Bij elke tick brengt de server de spelstaat een vaste hoeveelheid tijd vooruit: hij leest invoer, verplaatst personages, lost botsingen op en berekent het resultaat. De tick rate is hoeveel daarvan er per seconde gebeuren, meestal uitgedrukt in Hz. 20 ticks/s betekent dat de wereld 20 keer per seconde wordt bijgewerkt (één keer per 50 ms).
Gangbare waarden verschillen per genre:
- 10–20 Hz — MMO's en niet-shooter-RPG's, waar reactietijd niet kritisch is.
- 30 Hz — een redelijke balans voor veel actie- en survivalgames.
- 60–128 Hz — competitieve FPS-titels, waar richten en hitregistratie hoge precisie vereisen.
Een hogere tick rate maakt de game responsiever, maar laat de CPU-belasting en bandbreedte evenredig groeien. 64 Hz betekent twee keer zoveel werk — en meestal twee keer zoveel pakketten — als 32 Hz. Het juiste getal is de balans tussen de precisie die je game vereist en de belasting die je server aankan.
Waarom een vaste tijdstap?
Een naïeve loop wordt vaak zo geschreven: "meet de verstreken tijd, breng alles met dat bedrag vooruit." Dat is een variabele tijdstap, en dat geeft problemen. Als delta elke frame verandert, is de fysica niet deterministisch: dezelfde invoer kan twee verschillende uitkomsten geven, snelle objecten kunnen door botsingen heen schieten (tunneling), en server en client komen nooit op hetzelfde getal uit.
Een vaste tijdstap lost dit op. Je brengt de simulatie altijd met dezelfde dt vooruit (bijvoorbeeld 1/30 seconde). Ook als de echte tijd sneller of trager loopt, gaat de logica elke stap evenveel vooruit. Dat determinisme is essentieel voor replays, valsspeldetectie en een server-authoritative architectuur.
De logica draait op een accumulator: je telt de verstreken echte tijd op bij een emmer, en zolang de emmer één dt overschrijdt voer je vaste stappen uit. Loopt de CPU een frame achter, dan voert de loop meerdere ticks achter elkaar uit om de accumulator te legen ("inhalen"), zodat de simulatietijd nooit achterblijft op de echte tijd.
Een serverloop met vaste stap
Het C++-voorbeeld hieronder toont de klassieke accumulator-gebaseerde loop, met een hogeresolutieklok via std::chrono:
#include <chrono>
#include <thread>
using clock_t = std::chrono::steady_clock;
using namespace std::chrono;
const int TICK_RATE = 30;
const double DT = 1.0 / TICK_RATE; // vaste stap, in seconden
void run_server() {
auto previous = clock_t::now();
double accumulator = 0.0;
while (server_running) {
auto now = clock_t::now();
double frame = duration<double>(now - previous).count();
previous = now;
// Eén trage frame mag niet de spiral of death veroorzaken
if (frame > 0.25) frame = 0.25;
accumulator += frame;
while (accumulator >= DT) {
process_input(); // clientpakketten toepassen
update_world(DT); // fysica + spellogica, altijd dezelfde DT
accumulator -= DT;
}
broadcast_state(); // huidige toestand naar clients sturen
// Slapen tot de volgende tick zodat de CPU niet leeg draait
auto next = previous + duration_cast<clock_t::duration>(duration<double>(DT));
std::this_thread::sleep_until(next);
}
}
Drie kritische punten hier: DT is altijd constant (niet de variabele verstreken tijd van de klok), de frametijd krijgt een plafond (anders zwelt de accumulator op als de server vastloopt en blokkeert de loop in eindeloze ticks — de "spiral of death"), en sleep_until wacht op de volgende tick zodat de CPU niet 100% leeg draait.
Slaapprecisie en timingdrift
sleep_until gebruiken in plaats van sleep_for is belangrijk. sleep_for(33ms) zegt elke keer "slaap 33 ms", maar de wektijd-latentie stapelt zich elke ronde op en verschuift de ticks langzaam. sleep_until(next) mikt op een absoluut doeltijdstip; wordt één tick laat gewekt, dan wordt de volgende eerder gewekt en blijft het gemiddelde op koers.
De slaapresolutie van het besturingssysteem is niet perfect: een paar honderd microseconden op Linux, en milliseconden op Windows met de standaardtimer. Mik je op een hoge tick rate (64+ Hz), dan is een korte busy-wait vlak voor het doeltijdstip een gangbare techniek om de klok aan te scherpen — maar dat houdt een CPU-kern bezig, dus gebruik het spaarzaam waar het telt.
Tick rate, netwerksnelheid en de clientkant
De simulatiesnelheid van de server en de snelheid waarmee hij toestand over het netwerk stuurt hoeven niet gelijk te zijn. Een server kan op 60 Hz simuleren maar de toestand slechts op 20 Hz uitzenden, wat de bandbreedte verlaagt en de simulatiekwaliteit behoudt. De client vult het gat met interpolatie (afvlakken tussen twee eerdere toestanden) en predictie voor zijn eigen invoer.
Een praktische checklist:
- Kies de simulatiesnelheid op basis van de precisie die je game nodig heeft; denk apart na over de netwerksnelheid.
- Houd de server gezaghebbend: de client stuurt invoer, de server berekent het resultaat.
- Zet een ticknummer in elk toestandspakket zodat de client weet welke stap hij ziet.
- Meet de tickduur; gaat een tick
DToverschrijden, verlaag dan de tick rate of optimaliseer het werk.
Veelgemaakte fouten
- Fysica op een variabele delta: breekt het determinisme; hitregistratie en replays worden onbetrouwbaar.
- Een accumulator zonder plafond: zodra de server vastloopt, blokkeert de spiral of death hem volledig.
- Blokkerende I/O in de tick: een schijf- of synchrone databankoproep legt de hele simulatie stil; verplaats die naar een aparte thread of wachtrij.
- Blind de tick rate verhogen: CPU en bandbreedte groeien lineair; meet eerst het knelpunt.
Veelgestelde vragen
Hoe hoog moet de tick rate zijn?
Dat hangt van het genre af. 10–20 Hz volstaat voor een traag MMO; 30 Hz is een evenwichtig startpunt voor actiegames; competitieve shooters mikken op 60–128 Hz. Bepaal eerst de reactietijd die je game nodig heeft en meet dan of de server het aankan.
Wat is het verschil tussen een vaste en een variabele tijdstap?
Bij een vaste stap gaat de simulatie altijd met dezelfde dt vooruit en is ze deterministisch; bij een variabele stap gebruikt elke frame de echte verstreken tijd, wat de fysica inconsistent en niet-herhaalbaar maakt. Server-authoritative games gebruiken bijna altijd een vaste stap.
Daalt de latentie als ik de tick rate verhoog?
Een beetje: een hogere tick rate verkort de wachttijd tussen het verwerken van invoer door de server. Maar het verandert de netwerk-round-trip (ping) niet. De gevoelde latentie hangt grotendeels af van het netwerk en van het ontwerp van interpolatie/predictie aan de clientkant.
Is je serverloop instabiel, of zoek je de juiste tick rate? Laten we samen de architectuur van je gameserver bekijken en een stabiele, deterministische game loop bouwen — neem contact met me op.