Networking für Echtzeit-Strategiespiele

16

Ich entwickle ein Echtzeit-Strategiespiel für einen Informatikkurs, den ich besuche. Einer der schwierigeren Aspekte scheint die Client-Server-Vernetzung und -Synchronisation zu sein. Ich habe mich mit diesem Thema beschäftigt (einschließlich 1500 Bogenschützen ), aber ich habe mich entschieden, einen Client-Server-Ansatz zu wählen, der sich von anderen Modellen unterscheidet (zum Beispiel über LAN).

Dieses Echtzeit-Strategiespiel bringt einige Probleme mit sich. Zum Glück ist jede Aktion des Spielers deterministisch. Es gibt jedoch Ereignisse, die in festgelegten Intervallen auftreten. Zum Beispiel besteht das Spiel aus Plättchen, und wenn ein Spieler ein Plättchen nimmt, sollte das 'Energieniveau', ein Wert auf diesem Plättchen, jede Sekunde um eins zunehmen, nachdem es genommen wurde. Dies ist eine sehr schnelle Erklärung, die meinen Anwendungsfall rechtfertigen sollte.

Im Moment mache ich Thin Clients, die nur Pakete an den Server senden und auf eine Antwort warten. Es gibt jedoch mehrere Probleme.

Wenn sich Spiele zwischen Spielern zu einem Endspiel entwickeln, gibt es häufig mehr als 50 Ereignisse pro Sekunde (aufgrund der geplanten Ereignisse, die sich häufen, wie bereits erläutert), und Synchronisierungsfehler treten dann auf. Mein größtes Problem ist, dass selbst eine kleine Abweichung des Status zwischen den Kunden unterschiedliche Entscheidungen der Kunden bedeuten kann, die Schneeball in völlig getrennte Spiele zerlegen. Ein weiteres Problem (das im Moment nicht so wichtig ist) ist die Latenz und man muss einige Millisekunden warten, auch Sekunden nachdem sie ihren Zug gemacht haben, um das Ergebnis zu sehen.

Ich frage mich, welche Strategien und Algorithmen ich verwenden könnte, um dies für den Endbenutzer einfacher, schneller und angenehmer zu machen. Dies ist besonders interessant angesichts der hohen Anzahl von Ereignissen pro Sekunde sowie mehrerer Spieler pro Spiel.

TL; DR beim Erstellen eines RTS mit> 50 Ereignissen pro Sekunde, wie synchronisiere ich Clients?

maxov
quelle
Sie können möglicherweise implementieren, was Eve-online tut, und die Zeit "verlangsamen", damit alles ordnungsgemäß verarbeitet werden kann.
Ryan Erb
3
Hier ist ein obligatorischer Link zum Client / Server-Modell von Planetary Annihilation: forrestthewoods.ghost.io/… Dies ist eine Alternative zum Lockstep-Modell, das für sie anscheinend sehr gut funktioniert.
DallonF
Erwägen Sie, die Anzahl der Ereignisse zu reduzieren, indem Sie ein einzelnes Update für alle genommenen Kacheln anstelle von Updates für jedes Kacheln senden oder, wie von Ilmari beantwortet, Nicht-Spieler-Aktionen dezentralisieren.
Lilienthal

Antworten:

13

Ihr Ziel, 50 Ereignisse pro Sekunde in Echtzeit zu synchronisieren, scheint mir nicht realistisch zu sein. Aus diesem Grund wird im Artikel über 1500 Bogenschützen über den Lock-Step-Ansatz gesprochen !

In einem Satz: Die einzige Möglichkeit, zu viele Elemente in zu kurzer Zeit über ein zu langsames Netzwerk zu synchronisieren, besteht darin, NICHT zu viele Elemente in zu kurzer Zeit über ein zu langsames Netzwerk zu synchronisieren, sondern den Status auf allen Clients deterministisch fortzusetzen und nur die zu synchronisieren das Nötigste (Benutzereingabe).

Lennart Rolland
quelle
6

Jede Aktion des Spielers ist deterministisch. Es gibt jedoch Ereignisse, die in festgelegten Intervallen stattfinden

Ich denke, es gibt dein Problem; Ihr Spiel sollte nur eine Zeitachse haben (für Dinge, die das Gameplay beeinflussen). Sie sagen, dass bestimmte Dinge mit einer Geschwindigkeit von X pro Sekunde wachsen ; Finden Sie heraus, wie viele Spielschritte in einer Sekunde sind, und rechnen Sie dies in eine Rate von X pro Y Spielschritten um . Dann bleibt alles deterministisch, auch wenn sich das Spiel verlangsamt.

Das Spiel unabhängig in Echtzeit laufen zu lassen, hat andere Vorteile:

  • Sie können ein Benchmarking durchführen, indem Sie es so schnell wie möglich ausführen
  • Sie können Fehler beheben, indem Sie das Spiel verlangsamen, um flüchtige Ereignisse zu sehen, und wie bereits erwähnt
  • Das Spiel bleibt deterministisch, was für den Mehrspielermodus sehr wichtig ist.

Sie haben auch erwähnt, dass Sie bei mehr als 50 Ereignissen oder Verzögerungen von bis zu Sekunden auf Probleme stoßen. Dies ist viel kleiner als das in 1500 Bogenschützen beschriebene Szenario. Sehen Sie also nach, ob Sie Ihr Spiel profilieren und herausfinden können, wo die Verlangsamung liegt.

congusbongus
quelle
1
+1: Frame-basiert ist die richtige Wahl, nicht zeitbasiert. Natürlich können Sie versuchen, N Bilder pro Sekunde zu speichern. Ein kleines Problem ist besser als eine vollständige Desynchronisierung.
PatrickB
@PatrickB: Ich sehe, dass viele Spiele eine "simulierte" Zeit verwenden, die nicht an die Videoframes gebunden ist. World of Warcraft aktualisiert nur Dinge wie Mana alle 100 ms, und die Zwergenfestung verwendet standardmäßig 10 Ticks pro Videorahmen.
Mooing Duck
@Mooing Duck: Mein Kommentar war spezifisch für RTSs. Wenn kleine Fehler später toleriert und korrigiert werden können (z. B. MMORPGs, FPSs), ist die Verwendung kontinuierlicher Werte nicht nur in Ordnung, sondern auch kritisch. Deterministische Simulationen, die über mehrere Maschinen hinweg synchronisiert werden müssen? Halte dich an Frames.
PatrickB
4

Um das Problem mit geplanten Ereignissen zu lösen, senden Sie die Ereignisse zunächst nicht, wenn sie eintreten , sondern wenn sie ursprünglich geplant sind. Das heißt, anstatt jede Sekunde eine Meldung "Inkrementiere die Energie der Kacheln ( x , y )" zu senden, sende einfach eine einzelne Meldung "Inkrementiere die Energie der Kacheln ( x , y ) einmal pro Sekunde, bis sie voll sind, oder bis unterbrochen ". Jeder Client ist dann dafür verantwortlich, die Aktualisierungen lokal zu planen.

Tatsächlich können Sie dieses Prinzip weiterentwickeln und nur Spieleraktionen übertragen : Alles andere kann von jedem Client (und dem Server nach Bedarf) lokal berechnet werden.

(Natürlich sollten Sie wahrscheinlich auch gelegentlich Prüfsummen des Spielstatus übertragen, um eine versehentliche Desynchronisierung zu erkennen, und in diesem Fall einen Mechanismus zum erneuten Synchronisieren von Clients verwenden, z. B. indem Sie alle Spieldaten von der autorisierenden Kopie des Servers erneut an die Clients senden Dies sollte jedoch hoffentlich ein seltenes Ereignis sein, das nur beim Testen oder bei seltenen Fehlfunktionen auftritt.)


Um die Clients synchron zu halten, stellen Sie sicher, dass Ihr Spiel deterministisch ist. Andere Antworten haben diesbezüglich bereits gute Ratschläge gegeben, lassen Sie mich jedoch eine kurze Zusammenfassung dessen geben, was zu tun ist:

  • Machen Sie Ihr Spiel intern rundenbasiert, wobei jede Runde oder jedes "Häkchen" etwa 1/50 Sekunden dauert. (Tatsächlich könnten Sie wahrscheinlich mit 1/10 Sekunden oder länger davonkommen.) Alle Spieleraktionen, die während einer einzelnen Runde stattfinden, sollten als simultan behandelt werden. Alle Nachrichten, zumindest vom Server an die Clients, sollten mit der Turn-Nummer versehen werden, damit jeder Client weiß, an welcher Turn jedes Ereignis stattfindet.

    Da in Ihrem Spiel eine Client-Server-Architektur verwendet wird, kann der Server als endgültiger Schiedsrichter für die Ereignisse in jeder Runde fungieren, was einige Dinge vereinfacht. Beachten Sie jedoch, dass dies bedeutet, dass Clients auch ihre eigenen Aktionen vom Server bestätigen müssen: Wenn ein Client die Nachricht "Ich verschiebe Einheit X eine Kachel nach links" sendet und die Antwort des Servers nichts über das Verschieben von Einheit X aussagt, sagt der Client nichts Es muss davon ausgegangen werden, dass dies nicht der Fall ist, und möglicherweise alle vorausschauenden Bewegungsanimationen abgebrochen werden, die bereits abgespielt wurden.

  • Definieren Sie eine konsistente Reihenfolge für "gleichzeitige" Ereignisse, die in derselben Runde auftreten, damit jeder Client sie in derselben Reihenfolge ausführt. Diese Reihenfolge kann beliebig sein, solange sie deterministisch und für alle Clients (und den Server) gleich ist.

    Zum Beispiel können Sie zuerst alle Ressourcen erhöhen (was auf einmal geschehen kann, wenn das Ressourcenwachstum in einem Plättchen das in einem anderen nicht stören kann), dann die Einheiten jedes Spielers in einer vorgegebenen Reihenfolge verschieben und dann die NPC-Einheiten verschieben. Um fair zu den Spielern zu sein, möchten Sie möglicherweise die Reihenfolge der Einheitenbewegungen zwischen den Zügen variieren, damit jeder Spieler gleich oft als Erster geht. Dies ist in Ordnung, solange es deterministisch erfolgt (z. B. basierend auf der Turn-Nummer).

  • Wenn Sie Gleitkomma-Mathematik verwenden, stellen Sie sicher, dass Sie sie im strengen IEEE-Modus verwenden. Dies kann die Arbeit etwas verlangsamen, ist jedoch ein geringer Preis für die Konsistenz zwischen den Kunden. Stellen Sie außerdem sicher, dass während der Kommunikation keine versehentlichen Rundungen auftreten (z. B. wenn ein Client einen gerundeten Wert an den Server überträgt, den ungerundeten Wert jedoch intern verwendet). Wie oben erwähnt, ist ein Protokoll zum Erkennen und Wiederherstellen nach einer Desynchronisation ebenfalls eine gute Idee, nur für den Fall.

Ilmari Karonen
quelle
1
Synchronisieren Sie außerdem den RNG, um ihn zu starten, und rufen Sie den synchronisierten RNG nur dann ab, wenn Sie vom Server dazu aufgefordert werden. Starcraft1 hatte lange Zeit einen Fehler, bei dem der RNG-Startwert während der Wiederholungen nicht gespeichert wurde, sodass die Wiederholungen langsam von den tatsächlichen Spielen abweichen.
Mooing Duck
1
@MooingDuck: Guter Punkt. In der Tat würde ich vorschlagen, den aktuellen RNG-Startwert bei jeder Runde zu übertragen, damit die RNG-Desynchronisation sofort erkannt wird. Wenn Ihr UI-Code eine Zufälligkeit benötigt, ziehen Sie ihn nicht aus derselben RNG-Instanz, die für die Spielelogik verwendet wurde.
Ilmari Karonen
3

Sie sollten Ihre Spielelogik völlig unabhängig von Echtzeit machen und im Wesentlichen rundenbasiert machen. Auf diese Weise wissen Sie genau, auf welche Weise "Kachelnergie verändert wird". In Ihrem Fall beträgt jede Runde nur 1/50 Sekunde.

Auf diese Weise müssen Sie sich nur um die Eingaben der Spieler kümmern, alles andere wird von der Spielelogik verwaltet und ist auf allen Clients völlig identisch. Selbst wenn das Spiel aufgrund von Net Lag oder einer komplizierten Berechnung für einen Moment ins Stocken gerät, laufen die Ereignisse dennoch für alle synchron.

Kromster sagt Unterstützung Monica
quelle
1

Zuallererst müssen Sie verstehen, dass PC float / double math NICHT deterministisch ist, es sei denn, Sie geben an, dass IEEE-754 für Ihre Berechnung verwendet werden soll (dies ist langsam).

Dann ist dies, wie ich es implementieren würde: Der Client stellt eine Verbindung zum Server her und synchronisiert die Zeit (achten Sie auf die Ping-Latenz!).

Jedes Mal, wenn ein Client eine Aktion ausführt, enthält er einen Zeitstempel / eine Wendung und es ist Sache des Servers, einen schlechten Zeitstempel / eine schlechte Wendung abzulehnen. Dann sendet der Server die Aktion an die Clients zurück, und jedes Mal, wenn eine Runde "geschlossen" ist (der Server akzeptiert keine so alten Runden / Zeitstempel), sendet der Server die Aktion an die Clients und beendet sie.

Kunden haben 2 "Welt": Eine ist mit der Endrunde synchronisiert, die andere wird ab der Endrunde berechnet, wobei die in der Warteschlange angekommene Aktion bis zur aktuellen Clientrunde / Zeitmarke addiert wird.

Da der Server eine etwas veraltete Aktion akzeptiert, fügt der Client möglicherweise seine eigene Aktion direkt in die Warteschlange ein, sodass die Roundtrip-Zeit über das Netzwerk zumindest für Ihre eigene Aktion ausgeblendet wird.

Das Letzte ist, mehr Aktionen in die Warteschlange zu stellen, damit Sie das MTU-Paket füllen können und weniger Protokollaufwand verursachen. Eine gute Idee ist, dies auf dem Server zu tun, sodass jedes End-Turn-Ereignis eine Aktion in der Warteschlange enthält.

Ich benutze diesen Algorithmus für ein Echtzeit-Shooter-Spiel und er funktioniert einwandfrei (mit und ohne Client-Chaching, aber mit einem Server-Ping von nur 20/50 ms). Außerdem sendet jeder X-End-Turn-Server ein spezielles "All" "client map" -Paket zum Korrigieren von Driftwerten.

Lesto
quelle
Gleitkomma-Mathematik-Probleme können im Allgemeinen als solche vermieden werden. In einem RTS können Sie die Simulation und Bewegung normalerweise einfach mit Ganzzahl- / Festkommawerten ausführen und Gleitkommawerte nur für die Anzeigeebene verwenden, die das Spielverhalten nicht beeinflusst.
Peteris
Mit Integer ist es schwierig, horizontale Kacheln zu erstellen, es sei denn, es handelt sich um eine achteckige Tafel. Es gibt keine Hardwarebeschleunigung für den Festpunkt, daher kann sie langsamer sein als der Schwebeflug. Ieee754
Lesto