Wie kann man Server-Client-Uhren für präzise Netzwerkspiele wie Quake 3 synchron halten?

15

Ich arbeite an einem 2D-Top-Down-Shooter und gebe mein Bestes, um Konzepte zu kopieren, die in vernetzten Spielen wie Quake 3 verwendet werden.

  • Ich habe einen autorisierenden Server.
  • Der Server sendet Snapshots an Clients.
  • Schnappschüsse enthalten einen Zeitstempel und Entitätspositionen.
  • Objekte werden zwischen Schnappschusspositionen interpoliert, damit die Bewegung reibungslos aussieht.
  • Die Interpolation von Entitäten erfolgt zwangsläufig geringfügig "in der Vergangenheit", sodass wir mehrere Schnappschüsse haben, zwischen denen interpoliert werden kann.

Das Problem, dem ich gegenüberstehe, ist "Uhrzeitsynchronisation".

  • Nehmen wir der Einfachheit halber einmal an, dass es beim Übertragen von Paketen zum und vom Server keine Latenz gibt.
  • Wenn die Serveruhr 60 Sekunden vor der Clientuhr liegt, liegt ein Snapshot-Zeitstempel 60000 ms vor dem lokalen Client-Zeitstempel.
  • Daher werden Entitäts-Snapshots gesammelt und etwa 60 Sekunden lang gespeichert, bevor der Client erkennt, dass eine bestimmte Entität seine Züge ausführt, da es so lange dauert, bis die Client-Uhr aufholt.

Ich habe es geschafft, dies zu überwinden, indem ich bei jedem Empfang eines Snapshots die Differenz zwischen Server- und Client-Uhr berechnet habe.

// For simplicity, don't worry about latency for now...
client_server_clock_delta = snapshot.server_timestamp - client_timestamp;

Wenn ich feststelle, wie weit die Entität in der Interpolation ist, addiere ich einfach die Differenz zur aktuellen Zeit des Kunden. Das Problem dabei ist jedoch, dass es zu Ruckeln kommt, da die Differenz zwischen den beiden Uhren abrupt schwankt, da Schnappschüsse schneller / langsamer als andere ankommen.

Wie kann ich die Uhren genau genug synchronisieren, dass die einzige wahrnehmbare Verzögerung die ist, die für die Interpolation fest codiert ist, und die, die durch gewöhnliche Netzwerklatenz verursacht wird?

Mit anderen Worten, wie kann ich verhindern, dass die Interpolation zu spät oder zu früh startet, wenn die Uhren erheblich desynchronisiert sind, ohne dass es zu Ruckeln kommt?

Bearbeiten: Laut Wikipedia kann NTP verwendet werden, um Uhren über das Internet innerhalb weniger Millisekunden zu synchronisieren. Allerdings scheint das Protokoll kompliziert und vielleicht übertrieben für den Einsatz in Spielen?

Joncom
quelle
wie ist es kompliziert ? Es ist eine Anfrage und Antwort jeweils mit Zeitstempel der Übertragung und der Ankunft, dann ein bisschen Mathe das Delta zu bekommen
Ratsche Freak
@ratchetfreak: Laut ( mine-control.com/zack/timesync/timesync.html ) ist "NTP leider sehr kompliziert und, was noch wichtiger ist, langsam auf das genaue Zeitdelta zu konvergieren. Dies macht NTP für das Netzwerk weniger als ideal Spielablauf, bei dem der Spieler erwartet, dass ein Spiel sofort beginnt ... "
Joncom

Antworten:

9

Nach dem Durchsuchen scheint es keine triviale Aufgabe zu sein, die Uhren von zwei oder mehr Computern zu synchronisieren. Ein Protokoll wie NTP macht einen guten Job, ist aber angeblich langsam und zu komplex, um in Spielen praktisch zu sein. Außerdem wird UDP verwendet, was bei mir nicht funktioniert, da ich mit Web-Sockets arbeite, die UDP nicht unterstützen.

Ich habe hier jedoch eine Methode gefunden , die relativ einfach zu sein scheint:

Es wird behauptet, dass die Uhren innerhalb von 150 ms (oder besser) voneinander synchronisiert werden.

Ich weiß nicht, ob das für meine Zwecke gut genug ist, aber ich konnte keine genauere Alternative finden.

Hier ist der Algorithmus, den es bereitstellt:

Für Spiele ist eine einfache Taktsynchronisationstechnik erforderlich. Idealerweise sollte es die folgenden Eigenschaften aufweisen: ziemlich genau (150 ms oder besser), schnell konvergierbar, einfach zu implementieren, in der Lage, auf Stream-basierten Protokollen wie TCP ausgeführt zu werden.

Ein einfacher Algorithmus mit diesen Eigenschaften lautet wie folgt:

  1. Der Client stempelt die aktuelle Ortszeit in ein "Zeitanforderungs" -Paket und sendet es an den Server
  2. Nach Eingang beim Server stempelt der Server die Serverzeit und kehrt zurück
  3. Nach Eingang beim Client subtrahiert der Client die aktuelle Zeit von der gesendeten Zeit und teilt sie durch zwei, um die Latenz zu berechnen. Es subtrahiert die aktuelle Zeit von der Serverzeit, um das Client-Server-Zeit-Delta zu bestimmen, und addiert die halbe Latenz, um das richtige Zeitdelta zu erhalten. (Bisher ist diese Algothim sehr ähnlich zu SNTP)
  4. Das erste Ergebnis sollte sofort verwendet werden, um die Uhr zu aktualisieren, da die lokale Uhr mindestens in den richtigen Ballpark (mindestens in die richtige Zeitzone!) Verschoben wird.
  5. Der Client wiederholt die Schritte 1 bis 3 fünfmal oder öfter und hält jedes Mal einige Sekunden an. Anderer Datenverkehr ist möglicherweise in der Zwischenzeit zulässig, sollte jedoch für optimale Ergebnisse minimiert werden
  6. Die Ergebnisse der Paketbestätigungen werden akkumuliert und in der Reihenfolge der niedrigsten Latenz bis zur höchsten Latenz sortiert. Die mittlere Latenz wird bestimmt, indem die Stichprobe der Mitte aus dieser geordneten Liste ausgewählt wird.
  7. Alle Abtastwerte über ungefähr 1 Standardabweichung vom Median werden verworfen und die verbleibenden Abtastwerte werden unter Verwendung eines arithmetischen Mittels gemittelt.

Die einzige Feinfühligkeit dieses Algorithmus besteht darin, dass Pakete über einer Standardabweichung über dem Median verworfen werden. Der Zweck dieses Befehls besteht darin, Pakete zu eliminieren, die von TCP erneut übertragen wurden. Stellen Sie sich zur Veranschaulichung vor, dass eine Stichprobe von fünf Paketen über TCP gesendet wurde und es keine erneute Übertragung gab. In diesem Fall hat das Latenz-Histogramm einen einzelnen Modus (Cluster), der um die mittlere Latenz zentriert ist. Stellen Sie sich nun vor, dass in einem anderen Versuch ein einzelnes Paket der fünf erneut übertragen wird. Die erneute Übertragung führt dazu, dass dieses eine Sample im Latenz-Histogramm weit nach rechts fällt, im Durchschnitt doppelt so weit entfernt wie der Median des primären Modus. Durch einfaches Ausschneiden aller Stichproben, die mehr als eine Standardabweichung vom Median abweichen, können diese Streumodi leicht eliminiert werden, vorausgesetzt, sie machen nicht den Großteil der Statistik aus.

Diese Lösung scheint meine Frage zufriedenstellend zu beantworten, da sie die Uhr synchronisiert und dann stoppt, sodass die Zeit linear fließen kann. Während meine anfängliche Methode die Uhr ständig aktualisiert, springt die Zeit beim Empfang von Schnappschüssen etwas umher.

Joncom
quelle
Wie hat das dann für Sie geklappt? Ich bin jetzt in der gleichen Situation. Ich verwende ein Server-Framework, das nur TCP unterstützt. Daher kann ich kein NTP verwenden, das UDP-Datagramme sendet. Es fällt mir schwer, Zeitsynchronisationsalgorithmen zu finden, die behaupten, eine zuverlässige Zeitsynchronisation über TCP durchzuführen. Die Synchronisation innerhalb einer Sekunde würde jedoch für meine Bedürfnisse ausreichen.
Dynamokaj
@ Dynamokaj Funktioniert ziemlich gut.
Joncom
Cool. Ist es möglich, dass Sie die Implementierung teilen könnten?
Dynamokaj
@dynamokaj Scheint, ich kann eine solche Implementierung in keinem Projekt finden, das mir gerade einfällt. Alternativ funktioniert für mich Folgendes: 1) Verwenden Sie sofort die Latenz, die Sie aus einer Ping-Anforderung / Antwort berechnen, und dann 2) für alle zukünftigen Antworten, die schrittweise und nicht sofort auf den neuen Wert angewendet werden. Dies hat einen "Durchschnitts" -Effekt, der für meine Zwecke sehr genau war.
Joncom
Kein Problem. Ich verwende meinen Back-End-Dienst in Google App Engine und daher in der Googles-Infrastruktur, in der Server mit Google NTP Server synchronisiert werden: time.google.com ( developers.google.com/time ) Ich verwende daher den folgenden NTP-Client für meinen Xamarin Mobile-Client um den Offset zwischen Client und Server zu erhalten. components.xamarin.com/view/rebex-time - Vielen Dank, dass Sie sich die Zeit genommen haben, um zu antworten.
Dynamokaj
1

Grundsätzlich kann man nicht die ganze Welt reparieren und irgendwann muss man die Grenze ziehen.

Wenn der Server und alle Clients die gleiche Bildrate verwenden, müssen sie nur beim Herstellen einer Verbindung und gelegentlich danach, insbesondere nach einem Latenzereignis, synchronisieren. Die Latenz beeinflusst weder den Zeitfluss noch die Fähigkeit des PCs, ihn zu messen. In vielen Fällen müssen Sie also hochrechnen, anstatt zu interpolieren. Dies erzeugt ebenso unerwünschte Effekte, aber es ist wieder das, was es ist, und Sie müssen das am wenigsten verfügbare Übel auswählen.

Bedenken Sie, dass in vielen populären MMOs nacheilende Spieler visuell offensichtlich sind. Wenn Sie sehen, wie sie direkt in eine Wand rennen, wird dies von Ihrem Kunden extrapoliert. Wenn Ihr Kunde die neuen Daten erhält, hat der Spieler (auf seinem Kunden) möglicherweise eine beträchtliche Entfernung zurückgelegt und wird an einen neuen Ort teleportieren (das von Ihnen erwähnte "Ruckeln"?). Dies passiert sogar in großen Spielen von bekannten Marken.

Technisch ist dies ein Problem mit der Netzwerkinfrastruktur des Spielers, nicht mit Ihrem Spiel. Der Punkt, an dem es von einem zum anderen geht, ist genau die Linie, die Sie zeichnen müssen. Ihr Code sollte auf 3 verschiedenen Computern ungefähr dieselbe Zeitspanne aufzeichnen. Wenn Sie kein Update erhalten, sollte sich dies nicht auf die Framerate von Update () auswirken. Wenn überhaupt, sollte es schneller sein, da wahrscheinlich weniger zu aktualisieren ist.

"Wenn Sie schlechtes Internet haben, können Sie dieses Spiel nicht wettbewerbsfähig spielen."
Das ist kein Reinfall oder Fehler.

Jon
quelle