Ich hatte ja bereits im Tutorial zum World-Server-Login gezeigt, dass man bei einer aktiven ENet-Verbindung über get_statistic(ENetPacketPeer.PEER_ROUND_TRIP_TIME)
, die sogenannte Paketumlaufzeit bzw. Round-Trip-Time (RTT) abfragen kann. Ich hatte damals Werte im Bereich 17ms erhalten und mich zwar gewundert, es dann aber bei folgendem Erklärungsversuch belassen:
Man darf sich über die relativ hohen Werte ~15-20ms für die RTT bei lokalen Tests nicht wundern. Das klingt zwar im ersten Moment hoch, da die Verbindung ja lokal ist, aber man muss bedenken, dass die RTT nicht nur Hin- und Rückweg beinhaltet, was ja lokal entfällt, sondern auch die Verarbeitungszeit auf dem Server beinhaltet.
Mein damaliger Kommentar ist nicht falsch, aber sehr vage gehalten. Das lag daran, dass ich mir zu diesem Zeitpunkt nicht ganz erklären konnte, warum die Verarbeitungszeit so hoch ist, schließlich gab es zum damaligen Projektstand keinerlei Interaktion auf dem World-Server. Ich habe diese Information erst einmal für mich abgelegt und dachte, ich werde es bei Gelegenheit vielleicht herausfinden. Einige Wochen später bin ich bei der Recherche zum Replikationsintervall vom MultiplayerSynchronizer
auf folgende Beschreibung gestoßen:
Time interval between synchronizations. When set to
0.0
(the default), synchronizations happen every network process frame.
Und ich habe mich gefragt, was genau dieser “network process frame” ist. Da ich in der Dokumentation nichts weiter dazu finden konnte, habe ich irgendwann im networking-Kanal des Godot Contributors Chat nachgefragt und hatte wenig später eine Antwort von fales dazu:
process frame is the func _process(delta), so it’s the engine FPS.
Da hat es bei mir dann Klick gemacht 💡. Alle ankommenden Netzwerknachrichten werden also einmal pro Frame aus dem Netzwerkpuffer abgeholt und anschließend verarbeitet. Standardmäßig arbeite ich mit einer Bildwiederholfrequenz von 60Hz, weil meine Grafikkarte so im Idle unter 10W verbraucht. Die Folge davon ist, dass der Server ebenfalls mit ca. 60 FPS läuft. Somit bearbeitet er 60 mal pro Sekunde die Netzwerknachrichten, was in einer Verarbeitungszeit von 1s ÷ 60 ≈ 0,017s ≙ 17ms resultiert und damit genau der ermittelten RTT entspricht.
Damit hatte ich also endlich meine Erklärung für diese “hohen” Latenzen, obwohl gar kein Netzwerk involviert war. Dieses Wissen wird sicherlich auch in Zukunft sehr hilfreich sein. Doch wenn man vorhat, diese Information an die Spieler weiterzugeben, sollte man sie darüber in Kenntniss setzen, dass es sich dabei um die RTT handelt und nicht nur um den “Ping”. Diese Korrelation kann uns ebenfalls bei der Entwicklung unterstützen, denn wir können gezielt unterschiedliche Latenzen simulieren und so ausprobieren, wie sich unsere Spiele mit welcher Latenz “anfühlen”. Bislang dachte ich, dies nur mit Hilfe von Proxies erreichen zu können.
Einstellen kann man die Max FPS über das Property application/run/max_fps
. Daher kann man es entweder über den Code oder aber in den Projekteinstellungen (Anwendung -> Ausführen -> Max. FPS) setzen. Beim Weg über die Projekteinstellungen nicht vergessen, die “Erweiterten Einstellungen” zu aktivieren.
Und zum Schluss noch ein kurzes Video, wie sich das Beispielprojekt bei 6 Server-FPS, also mit einer RTT von 160, “anfühlt”:
Natürlich ist es ruckelig, schließlich müssen wir bedenken, dass wir uns bislang noch nicht mit Client-Side-Prediction beschäftigt haben. Aber achtet man genau darauf, kann man erkennen, dass davon bereits eine einfache Version integriert ist. Besonders wenn man die Bewegung der Spielfigur mit den Projektilen vergleicht, die noch keinerlei Client-Side-Prediction haben. Die Projektile sollen dann schon mal als kleiner Teaser für ein nächstes Tutorial dienen 😉.