aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Game-Server

Game-Loop-Tick-Rate: Den Server-Loop Entwerfen

Im Herzen jedes Spielservers schlägt eine einzige Sache: die Schleife. Die Game-Loop-Tick-Rate bestimmt, wie oft pro Sekunde der Server die Welt aktualisiert — Physik berechnet, Bewegung anwendet und den Zustand an die Clients sendet. Wählt man diese Zahl und die Bauweise der Schleife falsch, erleben Spieler teleportierende Charaktere, inkonsistente Kollisionen und ein Gameplay, das auseinanderfällt, sobald das Tempo steigt. In diesem Artikel erkläre ich, was ein Tick ist, warum man einen festen Zeitschritt braucht und wie man in C++ einen stabilen Server-Loop baut.

Was ist ein Tick, und was bedeutet Tick-Rate?

Ein Tick ist ein einzelner Schritt, um den die Simulation voranschreitet. Bei jedem Tick bewegt der Server den Spielzustand um eine feste Zeitmenge vorwärts: Er liest Eingaben, bewegt Charaktere, löst Kollisionen auf und berechnet das Ergebnis. Die Tick-Rate ist die Anzahl davon pro Sekunde, meist in Hz angegeben. 20 Ticks/s bedeutet, dass die Welt 20-mal pro Sekunde aktualisiert wird (alle 50 ms).

Gängige Werte unterscheiden sich je nach Genre:

  • 10–20 Hz — MMOs und Nicht-Shooter-RPGs, bei denen die Reaktionszeit unkritisch ist.
  • 30 Hz — eine sinnvolle Balance für viele Action- und Survival-Spiele.
  • 60–128 Hz — kompetitive FPS-Titel, bei denen Zielen und Trefferregistrierung hohe Präzision brauchen.

Eine höhere Tick-Rate macht das Spiel reaktionsfreudiger, lässt aber CPU-Last und Bandbreite proportional wachsen. 64 Hz bedeutet doppelt so viel Arbeit — und meist doppelt so viele Pakete — wie 32 Hz. Die richtige Zahl ist die Balance zwischen der Präzision, die dein Spiel verlangt, und der Last, die dein Server tragen kann.

Warum ein fester Zeitschritt?

Eine naive Schleife wird oft so geschrieben: „Miss die verstrichene Zeit, bewege alles um diesen Betrag vorwärts." Das ist ein variabler Zeitschritt, und der macht Ärger. Ändert sich delta in jedem Frame, ist die Physik nicht deterministisch: Dieselbe Eingabe kann zwei verschiedene Ergebnisse liefern, schnelle Objekte können durch Kollisionen hindurchschießen (Tunneling), und Server und Client kommen nie auf dieselbe Zahl.

Ein fester Zeitschritt löst das. Man bewegt die Simulation stets um dasselbe dt vorwärts (zum Beispiel 1/30 Sekunde). Selbst wenn die reale Zeit schneller oder langsamer läuft, schreitet die Logik in jedem Schritt um denselben Betrag voran. Dieser Determinismus ist unverzichtbar für Replays, Cheat-Erkennung und eine server-autoritative Architektur.

Die Logik läuft über einen Akkumulator: Man addiert die verstrichene reale Zeit zu einem Eimer, und solange der Eimer ein dt übersteigt, führt man feste Schritte aus. Hängt die CPU um einen Frame zurück, führt die Schleife mehrere Ticks hintereinander aus, um den Akkumulator zu leeren („Aufholen"), sodass die Simulationszeit nie hinter der realen Zeit zurückbleibt.

Ein Server-Loop mit festem Schritt

Das C++-Beispiel unten zeigt die klassische akkumulatorbasierte Schleife mit einer hochauflösenden Uhr über 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;       // fester Schritt, in Sekunden

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;

        // Ein langsamer Frame darf nicht die spiral of death auslösen
        if (frame > 0.25) frame = 0.25;
        accumulator += frame;

        while (accumulator >= DT) {
            process_input();        // Client-Pakete anwenden
            update_world(DT);       // Physik + Spiellogik, immer dasselbe DT
            accumulator -= DT;
        }

        broadcast_state();          // aktuellen Zustand an Clients senden

        // Bis zum nächsten Tick schlafen, damit die CPU nicht leer dreht
        auto next = previous + duration_cast<clock_t::duration>(duration<double>(DT));
        std::this_thread::sleep_until(next);
    }
}

Drei kritische Punkte hier: DT ist immer konstant (nicht die variable verstrichene Zeit aus der Uhr), die Frame-Zeit wird gedeckelt (sonst schwillt der Akkumulator an, wenn der Server stockt, und die Schleife blockiert in endlosen Ticks — die „spiral of death"), und sleep_until wartet auf den nächsten Tick, damit die CPU nicht zu 100 % leer dreht.

Schlafgenauigkeit und Timing-Drift

sleep_until statt sleep_for zu verwenden ist wichtig. sleep_for(33ms) sagt jedes Mal „schlafe 33 ms", aber die Aufweck-Latenz summiert sich in jeder Runde und verschiebt die Ticks langsam. sleep_until(next) zielt auf einen absoluten Zeitpunkt; wacht ein Tick zu spät auf, wacht der nächste früher auf und hält den Durchschnitt auf Kurs.

Die Schlafauflösung des Betriebssystems ist nicht perfekt: ein paar hundert Mikrosekunden unter Linux und Millisekunden unter Windows mit dem Standard-Timer. Zielst du auf eine hohe Tick-Rate (64+ Hz), ist ein kurzer Busy-Wait kurz vor dem Zielzeitpunkt eine gängige Technik, um die Uhr zu schärfen — er hält jedoch einen CPU-Kern beschäftigt, also setze ihn sparsam dort ein, wo es zählt.

Tick-Rate, Netzwerk-Senderate und die Client-Seite

Die Simulationsrate des Servers und seine Rate, mit der er den Zustand übers Netzwerk sendet, müssen nicht gleich sein. Ein Server kann mit 60 Hz simulieren, den Zustand aber nur mit 20 Hz aussenden, was die Bandbreite senkt und die Simulationsqualität erhält. Der Client füllt die Lücke mit Interpolation (Glättung zwischen zwei vergangenen Zuständen) und Prädiktion für die eigene Eingabe.

Eine praktische Checkliste:

  • Wähle die Simulationsrate nach der Präzision, die dein Spiel braucht; denke die Netzwerkrate getrennt.
  • Halte den Server autoritativ: Der Client sendet Eingaben, der Server berechnet das Ergebnis.
  • Setze eine Tick-Nummer in jedes Zustandspaket, damit der Client weiß, welchen Schritt er sieht.
  • Miss die Tick-Dauer; beginnt ein Tick DT zu überschreiten, senke die Tick-Rate oder optimiere die Arbeit.

Häufige Fehler

  • Physik auf variablem Delta: bricht den Determinismus; Trefferregistrierung und Replays werden unzuverlässig.
  • Akkumulator ohne Deckel: stockt der Server einmal, sperrt ihn die spiral of death vollständig.
  • Blockierende E/A im Tick: ein Festplatten- oder synchroner Datenbankaufruf legt die ganze Simulation lahm; verlagere sie in einen separaten Thread oder eine Queue.
  • Die Tick-Rate blind erhöhen: CPU und Bandbreite wachsen linear; miss zuerst den Engpass.

Häufige Fragen

Wie hoch sollte die Tick-Rate sein?

Das hängt vom Genre ab. 10–20 Hz reichen für ein langsames MMO; 30 Hz ist ein ausgewogener Startpunkt für Action-Spiele; kompetitive Shooter zielen auf 60–128 Hz. Lege zuerst die Reaktionszeit fest, die dein Spiel braucht, und miss dann, ob der Server sie tragen kann.

Was ist der Unterschied zwischen festem und variablem Zeitschritt?

Beim festen Schritt schreitet die Simulation stets um dasselbe dt voran und ist deterministisch; beim variablen Schritt nutzt jeder Frame die reale verstrichene Zeit, was die Physik inkonsistent und nicht wiederholbar macht. Server-autoritative Spiele verwenden fast immer einen festen Schritt.

Sinkt die Latenz, wenn ich die Tick-Rate erhöhe?

Ein wenig: Eine höhere Tick-Rate verkürzt die Wartezeit, bis der Server Eingaben verarbeitet. Sie ändert aber nicht die Netzwerk-Umlaufzeit (Ping). Die gefühlte Latenz hängt vor allem vom Netzwerk und vom Design der client-seitigen Interpolation/Prädiktion ab.

Ist dein Server-Loop instabil, oder suchst du die richtige Tick-Rate? Lass uns gemeinsam die Architektur deines Spielservers prüfen und einen stabilen, deterministischen Game-Loop bauen — kontaktiere mich.

Bu kategorideki tüm yazılar →

Devamı için