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

Game-State-Synchronisation: Konsistenz auf dem Server

Wenn in einem Mehrspielerspiel zwei Spieler im selben Raum stehen und der eine den Gegner direkt vor sich sieht, während der andere ihn drei Schritte weiter links sieht, liegt das Problem fast immer in der Schicht der Spiel-State-Synchronisation. Der von Server und Clients geteilte „Weltzustand" driftet mit der Zeit auseinander, und diese Abweichung wird sichtbar. In diesem Artikel erkläre ich, wie wir Konsistenz zwischen Clients aufbauen: das Modell des autoritativen Servers, tick-basierte Updates und Interpolation, praxisnah dargestellt.

Was ist State, und warum muss er synchronisiert werden?

Aus Sicht eines Spiels ist der State die Momentaufnahme der Welt zu einem bestimmten Zeitpunkt: Positionen, Geschwindigkeiten, Lebenspunkte und Inventare der Spieler, das Verhalten von NPCs, offene Türen, fallengelassene Gegenstände. In einem Einzelspielerspiel lebt dieses Bild in einem einzigen Speicherbereich, und keinerlei Inkonsistenz ist möglich. In einem Netzwerkspiel hingegen hält jeder Client seine eigene Kopie des States, und der Server hält seine eigene. Da die Lichtgeschwindigkeit endlich ist, können diese Kopien nie im selben Moment aktualisiert werden; unser Ziel ist nicht, Inkonsistenz zu beseitigen, sondern sie so klein und kurzlebig zu halten, dass sie unbemerkt bleibt.

Synchronisation löst zwei Kernfragen: Wessen Wort gilt als „wahr", und wie füllen wir die Zwischenbilder auf? Das Erste ist eine Frage der Autorität, das Zweite der Interpolation.

Der autoritative Server: eine einzige Quelle der Wahrheit

Das Fundament moderner Mehrspielerarchitektur ist das Modell des autoritativen Servers. Hier ist die einzige Quelle der Wahrheit der Server. Wenn ein Client eine Taste drückt, sagt er nicht „ich bin gerade an Position X"; er sendet eine Eingabe (Input) wie „ich möchte vorwärts gehen". Der Server verarbeitet diese Eingabe in seiner eigenen Simulation, berechnet das Ergebnis und sendet den aktualisierten State an alle Clients zurück.

Der größte Vorteil dieses Ansatzes ist die Cheat-Resistenz. Der Client kann nicht direkt einen State wie „die Lebenspunkte des Gegners sind 0" oder „ich bin in der Wand" schreiben; er teilt nur eine Absicht mit, und der Server entscheidet. Dasselbe Prinzip gilt für MMORPG-Server wie Metin2: Schaden, Drops und Positionsvalidierung erfolgen serverseitig, sonst könnte ein Client, der seinen eigenen Speicher manipuliert, alles zerstören.

  • Client → Server: sendet nur Eingaben/Befehle (Bewegungsrichtung, Angriff, Gegenstand benutzen).
  • Server: führt die Simulation aus, wendet Kollisionen und Regeln an.
  • Server → Clients: sendet die neuen State-Snapshots aus.

Tick-basierte Simulation

Der Server schreitet die Welt nicht kontinuierlich voran, sondern in festen Intervallen. Jeder Schritt heißt Tick. Ein Server mit 20 Ticks/s aktualisiert die Welt 20-mal pro Sekunde (alle 50 ms). Der Grund für einen festen Tick ist Determinismus: Wenn dieselben Eingaben in derselben Reihenfolge mit demselben Zeitschritt verarbeitet werden, fällt das Ergebnis auf jeder Maschine identisch aus.

// Server-Schleife mit festem Zeitschritt (vereinfacht)
const double TICK_RATE = 20.0;            // Ticks pro Sekunde
const double DT = 1.0 / TICK_RATE;        // 0,05 s
double accumulator = 0.0;
double previous = now_seconds();

while (running) {
    double current = now_seconds();
    accumulator += current - previous;
    previous = current;

    while (accumulator >= DT) {
        process_inputs();   // Client-Eingaben anwenden
        step_world(DT);     // Physik + Regeln
        accumulator -= DT;
    }
    broadcast_snapshot();   // State an Clients senden
}

Die Tickrate ist eine Abwägung. Ein hoher Tick (z. B. 60) liefert flüssigeres, präziseres Gameplay, erhöht aber die Kosten für Bandbreite und CPU. Schnelle Shooter verlangen einen hohen Tick; für ein MMO genügen 10–20 Ticks in den meisten Fällen.

Snapshots, Deltas und Bandbreite

Die gesamte Welt bei jedem Tick an jeden Client zu senden, ist Verschwendung. Es gibt zwei gängige Optimierungen:

  • Delta-Kompression: nur die Felder senden, die sich gegenüber dem vorherigen Snapshot geändert haben. Für einen stillstehenden NPC fließen keine Daten.
  • Interessensbereich (Area of Interest): einem Spieler nur die Entitäten in seiner Sicht-/Einflussreichweite senden. Er muss nichts von einem Kampf am anderen Ende der Karte wissen. In MMO-Größenordnung ist das unverzichtbar.

Man sollte außerdem nur die für das Netzwerk relevanten Felder serialisieren (Position, Rotation, Animationszustand, Lebenspunkte) statt aller. Die Position in einen begrenzten Bereich zu quantisieren (z. B. 16 Bit), statt sie in voller Präzision zu senden, verkleinert die Pakete erheblich.

Latenz verbergen: Interpolation und Vorhersage

Selbst wenn Snapshots nur 20-mal pro Sekunde eintreffen, will der Spieler auf einem 144-Hz-Display flüssige Bewegung sehen. Es gibt zwei Techniken, um die Lücken zu füllen.

Interpolation (für entfernte Spieler): Der Client tastet die Position sanft zwischen den letzten beiden empfangenen Snapshots ab. Dazu rendert er bewusst einen kleinen Puffer (z. B. 100 ms) hinterher, damit er stets zwei echte Datenpunkte hat, zwischen denen er überblenden kann.

// Lineare Interpolation zwischen zwei Snapshots
Vec2 lerp(const Vec2& a, const Vec2& b, float t) {
    return { a.x + (b.x - a.x) * t,
             a.y + (b.y - a.y) * t };
}
// t: Verhältnis des Render-Moments zwischen den beiden Snapshot-Zeiten (0..1)

Client-seitige Vorhersage (für deinen eigenen Spieler): Die eigene Spielfigur auf die Serverantwort warten zu lassen, erzeugt lästigen Input-Lag. Stattdessen wendet der Client die Eingabe lokal in demselben Moment an, in dem er sie an den Server sendet. Wenn der offizielle State vom Server eintrifft und die Vorhersage korrekt war, gibt es keinen Unterschied; war sie falsch, korrigiert die Reconciliation dies: Der Client spult auf den vom Server bestätigten State zurück und spielt die noch nicht bestätigten Eingaben erneut ab. Bei einer Abweichung sieht man einen kleinen „Teleport"; um ihn zu mildern, wird die Korrektur über einige Frames überblendet.

Quellen von Inkonsistenz und praktische Tipps

  • Paketverlust: Über UDP können Snapshots verloren gehen. Verwende einen zuverlässigen Kanal/Bestätigungsmechanismus für kritische Ereignisse (Tod, Gegenstandsaufnahme); versuche nicht, kontinuierliche Daten wie die Position zuverlässig zu senden, da der nächste Snapshot ohnehin frische Daten bringt.
  • Zeitversatz (Clock Skew): Client- und Serveruhren driften. Versieh Snapshots mit einer Server-Ticknummer/einem Zeitstempel und interpoliere gegen diese Zeit statt gegen die Wanduhr.
  • Float-Nichtdeterminismus: Gleitkommaergebnisse können je nach Compiler/Plattform leicht variieren. Wenn du vollständigen Determinismus brauchst, ziehe Festkomma-Arithmetik in Betracht; die meisten autoritativen Server benötigen sie nicht, da die Quelle der Wahrheit einzig ist.
  • Testen: Injiziere während der Entwicklung künstliche Latenz und Paketverluste (mit tc netem unter Linux). Ein System, das sich bei 200 ms Latenz gut anfühlt, ist bei 20 ms makellos.

Häufige Fragen

Sollte ich TCP oder UDP verwenden?

Für schnelle, positionslastige Spiele wird in der Regel UDP bevorzugt, weil TCPs Garantie der geordneten Zustellung neue Pakete warten lässt, während ein altes verlorenes Paket erneut übertragen wird (Head-of-Line-Blocking), was die Latenz aufbläht. Für rundenbasierte oder latenztolerante Spiele ist TCP mehr als ausreichend. Viele Engines bauen ihre eigene Zuverlässigkeitsschicht auf UDP auf.

Wie hoch sollte ich die Tickrate einstellen?

Das hängt vom Gameplay ab. In Shootern, in denen die Reaktionszeit entscheidend ist, sind 30–64 Ticks üblich; in MMOs und Strategiespielen reichen 10–20 Ticks und sind besser skalierbar. Entscheide, indem du zuerst das Spielgefühl misst und dann die Serverkosten.

Erzeugt client-seitige Vorhersage ein Cheat-Risiko?

Nein, denn die Vorhersage ist nur eine visuelle Schätzung; die endgültige Entscheidung fällt immer auf dem autoritativen Server. Steht die lokale Vorhersage des Clients im Widerspruch zum Server, gilt der State des Servers und der Client wird korrigiert.

Baust du einen Mehrspielerserver? Lass uns gemeinsam die autoritative Architektur, das Tick-Design und die Strategien zum Verbergen von Latenz planen, abgestimmt auf das Genre deines Projekts. Kontaktiere mich, und lass uns über deinen Bedarf sprechen.

Bu kategorideki tüm yazılar →

Devamı için