Wie kann eine Datenstruktur über ein Netzwerk synchronisiert werden?

12

Kontext

In dem Spiel, an dem ich gerade arbeite (eine Art Point-and-Click-Grafik-Abenteuer), wird so ziemlich alles, was in der Spielwelt passiert, von einem Action-Manager gesteuert, der ein bisschen wie folgt aufgebaut ist:

Bildbeschreibung hier eingeben

Wenn zum Beispiel das Ergebnis der Untersuchung eines Objekts dazu führen soll, dass der Charakter Hallo sagt, ein wenig geht und sich dann hinsetzt, dann spawne ich einfach den folgenden Code:

var actionGroup = actionManager.CreateGroup();
actionGroup.Add(new TalkAction("Guybrush", "Hello there!");
actionGroup.Add(new WalkAction("Guybrush", new Vector2(300, 300));
actionGroup.Add(new SetAnimationAction("Guybrush", "Sit"));

Dadurch wird eine neue Aktionsgruppe erstellt (eine ganze Zeile in der Abbildung oben) und dem Manager hinzugefügt. Alle Gruppen werden parallel ausgeführt, aber die Aktionen in jeder Gruppe sind so verkettet, dass die zweite erst nach Abschluss der ersten beginnt. Wenn die letzte Aktion in einer Gruppe beendet ist, wird die Gruppe zerstört.

Problem

Jetzt muss ich diese Informationen über ein Netzwerk replizieren, damit in einer Mehrspielersitzung alle Spieler dasselbe sehen. Die einzelnen Aktionen zu serialisieren ist nicht das Problem. Aber ich bin ein absoluter Anfänger in Sachen Networking und habe ein paar Fragen. Ich denke, der Einfachheit halber können wir in dieser Diskussion die Action-Manager-Komponente so abstrahieren, dass sie einfach ist:

var actionManager = new List<List<string>>();

Wie soll ich vorgehen, um den Inhalt der obigen Datenstruktur zwischen allen Playern synchron zu halten?

Neben der Kernfrage habe ich auch ein paar andere Bedenken, die damit zusammenhängen (dh alle möglichen Auswirkungen desselben Problems oben):

  • Wenn ich eine Server / Client-Architektur verwende (wobei einer der Spieler sowohl als Server als auch als Client fungiert) und einer der Clients eine Gruppe von Aktionen erzeugt hat, sollte er diese direkt dem Manager hinzufügen oder nur eine Anfrage senden auf den Server, der wiederum jedem Client befiehlt, diese Gruppe hinzuzufügen ?
  • Was ist mit Paketverlusten und dergleichen? Das Spiel ist deterministisch, aber ich denke, dass jede Diskrepanz in der Reihenfolge der Aktionen, die in einem Client ausgeführt werden, zu inkonsistenten Zuständen der Welt führen kann. Wie schütze ich mich vor solchen Problemen ?
  • Was ist, wenn ich zu viele Aktionen auf einmal hinzufüge, führt dies nicht zu Problemen bei der Verbindung? Wie kann man das lindern?
David Gouveia
quelle
5
Obligatorisch " Bitte zuerst lesen" Link gafferongames.com/networking-for-game-programmers, der trotz einigem Code nicht sehr technisch ist, aber wortreich ist und Ihre Optionen ziemlich gut erklärt. Außerdem werden Sie so mit allen anderen gleichgestellt, die diesen Link gelesen haben, und das ist ein glücklicher Ort.
Patrick Hughes
@PatrickHughes Danke für den Link! Ich werde auf jeden Fall alles lesen.
David Gouveia
Es gibt einige Pakete, die so etwas für Sie verwalten. Ich weiß nicht, wie effizient oder schnell es ist, aber NowJS ( nowjs.com ) ist ein interessantes Beispiel. Aus Entwicklersicht haben Sie einfach gemeinsame Datenstrukturen zwischen Client und Server. Ändere eins, die anderen ändern sich.
Tim Holt

Antworten:

9

Wenn ich eine Server / Client-Architektur verwende (wobei einer der Spieler sowohl als Server als auch als Client fungiert) und einer der Clients eine Gruppe von Aktionen erzeugt hat, sollte er diese direkt dem Manager hinzufügen oder nur eine Anfrage senden auf den Server, der wiederum jedem Client befiehlt, diese Gruppe hinzuzufügen?

In diesem Fall haben Sie drei Möglichkeiten, von denen Sie bereits zwei erwähnt haben. Welche Option Sie wählen, hängt von einer Vielzahl von Faktoren ab, die nur Sie selbst beurteilen können:

1: Client erzeugt eine Gruppe von Aktionen, fügt sie direkt hinzu und sendet sie dann an den Server. Der Server akzeptiert es ohne Prüfung

* Der Vorteil dieses Ansatzes besteht darin, dass dem Kunden durch seine eigenen Aktionen eine sofortige Rückmeldung unabhängig von der Netzwerkverzögerung gegeben wird. Der Nachteil ist, dass die Nachrichten des Clients vom Server als Fakt akzeptiert werden (was sie möglicherweise nicht sind) *

2: Der Client sendet eine Anforderung an den Server, der die Anforderung an alle weiterleitet, einschließlich des Clients, von dem die Anforderung stammt.

Der Vorteil dieses Ansatzes besteht darin, dass der Server nun die Kontrolle über alles hat, was im Spiel passiert, wodurch die meisten Probleme mit illegalen Aktivitäten behoben werden weitere Informationen, die der Kunde nicht hatte, als er einen bestimmten Schritt unternahm)

3: Der Client erzeugt eine Gruppe von Aktionen, fügt sie direkt hinzu und sendet sie dann an den Server. Der Server verarbeitet die Anforderung, überprüft die Aktionen selbst, um sicherzustellen, dass sie nicht illegal sind, und sendet die Aktion dann an alle Spieler, einschließlich des Clients.

Dies bringt das Beste aus 1 und 2 auf Kosten etwas höherer Bandbreitenanforderungen. Die Vorteile sind eine sofortige Rückmeldung an den Kunden für seine eigenen Aktionen. Wenn die Aktionen des Kunden illegal sind, ergreift der Server die entsprechenden Maßnahmen (korrigiert den Kunden mit Nachdruck).

Was ist mit Paketverlusten und dergleichen? Das Spiel ist deterministisch, aber ich denke, dass jede Diskrepanz in der Reihenfolge der Aktionen, die in einem Client ausgeführt werden, zu inkonsistenten Zuständen der Welt führen kann. Wie schütze ich mich vor solchen Problemen?

Paketverlust und extreme Verzögerung sind ein schwer zu behebendes Problem . Abhängig von der Art des Spiels (zB Dead-Reckoning) werden unterschiedliche Lösungen (keine davon ist perfekt) eingesetzt, um das Problem zu lösen. Ich gehe davon aus, dass Sie das Spiel Physik nicht synchronisieren müssen.

Eines der ersten Dinge, die Sie tun sollten, ist, alle Ihre Züge mit einem Zeitstempel zu versehen. Ihr System sollte diese dann berücksichtigen und spielen die Bewegungen entsprechend (vielleicht der Server diese Verantwortung und dann verweigern illegale Züge). Nehmen Sie bei der Implementierung dieses Systems eine Latenz von 0 an (lokal testen).

Eine Latenz von 0 ist natürlich keine gute Annahme, auch in lokalen Netzwerken. Welche Zeit schätzen Sie ein? Hier kommt das Thema Zeitsynchronisation ins Spiel. Viele Artikel / Veröffentlichungen im Internet führen Sie zur Lösung dieses Problems.

Was ist, wenn ich zu viele Aktionen auf einmal hinzufüge, führt dies nicht zu Problemen bei der Verbindung? Wie kann man das lindern?

Zuerst das Offensichtliche. Senden Sie niemals Zeichenfolgen oder serialisierte Objekte. Senden Sie Nachrichten nicht so schnell, wie das Spiel selbst aktualisiert wird. Senden Sie sie in Stücken (z. B. 10 Mal pro Sekunde). Es wird Fehler (insbesondere Rundungsfehler) geben, die der Server / Client hin und wieder beheben muss (der Server sendet also jede Sekunde alles [alles = alle Daten, die wichtig sind, um die Dinge in Ihrem Spiel in Ordnung zu halten) Zustand], den der Client dann einfängt und / oder berücksichtigt, um seinen Zustand zu korrigieren).EDIT: Einer meiner Kollegen erwähnte, dass wenn Sie UDP verwenden [was Sie sollten, wenn Sie viele Daten haben], dann gibt es keine Netzwerkbestellung. Das Senden von mehr als 10 Paketen pro Sekunde erhöht die Änderungen der Pakete, die nicht in der richtigen Reihenfolge ankommen. Ich werde sehen, ob ich diese Behauptung zitieren kann. Nicht in Ordnung befindliche Pakete können entweder verworfen oder zur Nachrichten- / Verschiebungswarteschlange Ihres Systems hinzugefügt werden, die Änderungen in der Vergangenheit verarbeiten kann, um den aktuellen Status zu korrigieren

Sie sollten über ein Nachrichtensystem verfügen, das auf etwas basiert, das nur sehr wenig Speicherplatz beansprucht. Wenn Sie etwas senden müssen, das nur zwei Werte haben kann, dann senden Sie nur ein Bit. Wenn Sie wissen, dass Sie nur 256 Züge haben können, senden Sie ein nicht signiertes Zeichen. Es gibt verschiedene Bit-Twiddling-Tricks, um Nachrichten so eng wie möglich zu packen.

Ihr Nachrichtensystem wird höchstwahrscheinlich viele Aufzählungen verwenden, um Ihnen das einfache Extrahieren von Zügen aus einer eingehenden Nachricht zu ermöglichen.

Ich bin sicher, Sie wissen bereits, dass Sie Probleme, die mit hoher Latenz und Paketverlust verbunden sind, nicht beheben können, aber Sie können die Auswirkungen weniger deutlich machen. Viel Glück!

BEARBEITEN: Patrick Hughes 'Kommentar oben mit dem Link macht diese Antwort unvollständig und bestenfalls spezifisch für die Frage von OP. Ich würde empfehlen, stattdessen die verknüpften Themen zu lesen

Samaursa
quelle
Vielen Dank für die ausführliche Antwort, die so ziemlich alle meine Bedenken abdeckt. Nur eine Frage zu dem Teil, den Sie sagen so every second, the server sends everything ... which the client then snaps to. Kann ich einfach eine große Menge solcher Informationen gleichzeitig senden? Was ist eine vernünftige Maximalgröße?
David Gouveia
Ein angemessenes Maximum ist eine Zahl, die keine Verzögerung einführt:) ... Ich weiß, dass Sie nicht nach dieser Antwort gesucht haben, aber ich möchte keine Zahl aufschreiben, da ich mit Ihrem Spiel nicht vertraut bin.
Samaursa
Es gibt auch keine feste Regel, dass Sie einen großen Teil senden sollten. Ich habe dies nur als Beispiel angeführt.
Samaursa
@ Samaursa - Ich würde dem Kommentar "Wenn Sie UDP verwenden [was Sie sollten, wenn Sie viele Daten haben]" nicht zustimmen. Da TCP für die Netzwerkprogrammierung neu ist, erledigt es die meisten schwierigen Aufgaben für Sie, und das auf Kosten einer wesentlich langsameren. In ihrem Fall würde ich TCP verwenden, bis es ein Engpass wurde. Zu diesem Zeitpunkt hätten sie ein besseres Verständnis dafür, wie ihre App das Netzwerk nutzt, sodass die Implementierung des UDP-Materials einfacher wäre.
Chris
@ Chris: Ich werde nicht zustimmen :) ... die Entscheidung zwischen TCP und UDP ist wie (imo) die Entscheidung zwischen Python und C ++ für die Entwicklung. Theoretisch mag es in Ordnung klingen, aber in der Praxis wird es schwierig sein, einfach umzuschalten (wieder, das ist imo).
Samaursa