Ereignisgesteuerte Kommunikation in einer Game Engine: Ja oder Nein?

62

Ich lese Game Coding Complete und der Autor empfiehlt eine ereignisgesteuerte Kommunikation zwischen Spielobjekten und Modulen.

Grundsätzlich sollten alle lebenden Spielschauspieler über ein internes Ereignismeldesystem mit den Schlüsselmodulen (Physik, KI, Spielelogik, Spieleansicht usw.) kommunizieren. Dies bedeutet, dass Sie einen effizienten Event-Manager entwerfen müssen. Ein schlecht entworfenes System frisst CPU-Zyklen, insbesondere bei mobilen Plattformen.

Ist dies ein bewährter und empfohlener Ansatz? Wie soll ich entscheiden, ob ich es verwenden soll?

Bunkai.Satori
quelle
Hallo James, danke für deine Antwort. Ich mag es, obwohl das Buch die ereignisgesteuerte Kommunikation als ein wirklich komplexes System beschreibt, das offensichtlich eine Menge CPU-Ressourcen verbraucht.
Bunkai.Satori
Schreiben Sie es in eine Antwort und fügen Sie es in einen Link zur allgemeinen Übersicht über ein Ereignismeldesystem ein, das ich als Antwort auf einen Stapelüberlauf gegeben habe. Genießen! Denken Sie daran, dass das, was manche Leute für komplex halten, nicht unbedingt sein muss :)
James
Sie können diese Antwort auch mit c ++ 11 überprüfen
user2826084
libuvhat sich vor kurzem zu einer erfolgreichen Veranstaltungsbibliothek entwickelt. (Es ist in C. Node.js ist der bekannteste Anwendungsfall.)
Anko

Antworten:

46

Dies ist eine Erweiterung meines Kommentars zu einer vollständigen Antwort, wie vorgeschlagen.

Ja , schlicht und einfach. Kommunikation muss stattfinden und es gibt Situationen, in denen "Sind wir schon da?" Polling-Typ ist erforderlich. Wenn die Dinge überprüft werden, ob sie etwas anderes tun sollen, wird im Allgemeinen Zeit verschwendet. Sie könnten sie stattdessen auf Dinge reagieren lassen, die ihnen gesagt werden. Ein klar definierter Kommunikationspfad zwischen Objekten / Systemen / Modulen beschleunigt die parallele Einrichtung erheblich.

Ich habe über Stack Overflow einen allgemeinen Überblick über ein Ereignismeldesystem gegeben . Ich habe es von der Schule an in professionellen Spieletiteln und jetzt in Nicht-Spielprodukten verwendet, die natürlich jedes Mal an den Anwendungsfall angepasst wurden.

BEARBEITEN : Um eine Kommentarfrage zu beantworten, woher Sie wissen, welche Objekte die Nachricht erhalten sollen : Die Objekte selbst sollten Requestüber Ereignisse benachrichtigt werden. Ihr EventMessagingSystem(EMS) benötigt ein Register(int iEventId, IEventMessagingSystem * pOjbect, (EMSCallback)fpMethodToUseForACallback)sowie ein Matching Unregister(einen eindeutigen Eintrag für ein iEventIdaus dem Objektzeiger und Rückruf). Auf diese Weise kann ein Objekt, wenn es etwas über eine Nachricht wissen möchte, Register()mit dem System verbunden werden. Wenn es nicht mehr über die Ereignisse Bescheid wissen muss, kann esUnregister(). Natürlich möchten Sie einen Pool dieser Callback-Registrierungsobjekte und eine effektive Möglichkeit, sie zu Listen hinzuzufügen oder daraus zu entfernen. (Normalerweise habe ich selbstordnende Arrays verwendet. Eine ungewöhnliche Art zu sagen, dass sie ihre eigenen Zuordnungen zwischen einem Poolstapel nicht verwendeter Objekte und Arrays verfolgen, deren Größe bei Bedarf geändert wird.)

BEARBEITEN : Für ein voll funktionsfähiges Spiel mit einer Update-Schleife und einem Event-Messaging-System möchten Sie vielleicht ein altes Schulprojekt von mir ausprobieren . Der oben verlinkte Stapelüberlauf-Beitrag verweist ebenfalls darauf.

James
quelle
Hallo James, da ich mehr über das interne Event-Messaging-System nachdenke, muss es meiner Meinung nach keine zusätzlichen Kosten für die Engine verursachen. Wenn sich beispielsweise 50 Objekte auf dem Bildschirm befinden, von denen jedoch nur 5 ihr Verhalten ändern sollen; Nach dem traditionellen System müssten alle 50 Objekte alle möglichen Aktionen überprüfen, um zu sehen, ob sie etwas tun sollten. Bei Verwendung von Ereignismeldungen werden jedoch nur 5 Nachrichten an diese 5 Objekte mit einer bestimmten Änderung der Aktion gesendet. Das sieht nach einem zeitsparenden Ansatz aus.
Bunkai.Satori
Das in meiner obigen Antwort verknüpfte System funktioniert so, dass sich Objekte nur registrieren, um von den gewünschten Nachrichten zu hören. Wir würden dies zu unserem Vorteil in Videospielen verwenden, in denen sich möglicherweise 100 bis 200 Objekte in einem Level befinden, aber nur Sie Aktivieren Sie diejenigen, mit denen der Spieler direkt interagieren kann, und halten Sie die Anzahl der zuhörenden Dinge auf ungefähr 10. Alles in allem sollte diese Art von System „schlauer“ sein als „Sind wir schon da?“. Polling-System, und sollte den Overhead eines Motors reduzieren, zumindest was die Kommunikation betrifft.
James
Mit dem ereignisgesteuerten System ist eines verbunden, das mich verwirrt: Unter herkömmlichen Systemen, bei denen jedes Objekt einen vordefinierten Satz von Methoden hat (OnUpdate (), OnMove (), OnAI (), OnCollisionCheck () ...), wird jedes Objekt regelmäßig aufgerufen verantwortlich für die Überprüfung und Verwaltung seines Zustands. Bei einem ereignisgesteuerten System muss für jedes Objekt eine Prüfbedingung für das Mastersystem vorliegen, und anschließend müssen Nachrichten an diejenigen gesendet werden, bei denen ein Ereignis festgestellt wurde. Für mich standardisiert dies die möglichen Objektzustände und schränkt die Freiheit ein, ein einzigartiges Objektverhalten zu erzeugen. Ist das wahr?
Bunkai.Satori
Das Observer-Muster ist die typische Alternative zum Polling. en.wikipedia.org/wiki/Observer_pattern
Ricket
1
@ hamlin11 Ich bin froh, dass diese Informationen den Menschen immer noch helfen :) Wenn dies in Bezug auf die Registrierung ziemlich häufig vorkommt, denken Sie daran, dass Sie die Geschwindigkeit optimieren möchten, einen Pool von Registrierungsobjekten haben, aus denen Sie ziehen können usw.
James
22

Meiner Meinung nach solltest du einfach anfangen, dein Spiel zu entwickeln und umzusetzen, womit du dich wohl fühlst. Wenn Sie dies tun, werden Sie feststellen, dass Sie an einigen Stellen MVC verwenden, an anderen jedoch nicht. Ereignisse an einigen Orten, aber nicht an anderen; Bestandteile einiger Orte, aber Vererbung anderer; sauberes Design an einigen Orten und crufty Design an anderen.

Und das ist in Ordnung, denn wenn Sie fertig sind, haben Sie tatsächlich ein Spiel und ein Spiel ist viel cooler als ein Nachrichtenübermittlungssystem.

user744
quelle
2
Hallo Joe, danke für deine Antwort, es gefällt mir. Sie glauben, dass es nicht nötig ist, einen Ansatz künstlich voranzutreiben. Ich sollte Anwendungen so entwerfen, wie ich es für richtig halte, und wenn etwas später nicht funktioniert, werde ich es einfach wiederholen.
Bunkai.Satori
Deshalb gibt es das System. Viele Leute haben genau das getan, was Sie gesagt haben, haben den Albtraum miterlebt und gesagt, dass es einen besseren Weg geben muss. Sie können entweder Ihre Fehler wiederholen oder daraus lernen und sie vielleicht noch weiter vorantreiben.
user441521
16

Eine bessere Frage ist, welche Alternativen gibt es? Wie können Sie diese Systeme in einem solch komplexen System mit richtig unterteilten Modulen für Physik, KI usw. orchestrieren?

Message Passing scheint die "beste" Lösung für dieses Problem zu sein. Ich kann mir momentan keine Alternativen vorstellen. In der Praxis gibt es jedoch zahlreiche Beispiele für die Weitergabe von Nachrichten. Tatsächlich verwenden Betriebssysteme die Nachrichtenübermittlung für einige ihrer Funktionen. Aus Wikipedia :

Nachrichten werden auch häufig im gleichen Sinne wie Interprozesskommunikation verwendet. Die andere übliche Technik sind Streams oder Pipes, bei denen Daten stattdessen als Folge elementarer Datenelemente gesendet werden (die übergeordnete Version einer virtuellen Verbindung).

(Interprozesskommunikation ist die Kommunikation zwischen Prozessen (dh die Ausführung von Programminstanzen) innerhalb der Betriebssystemumgebung.)

Wenn es also gut genug für ein Betriebssystem ist, ist es wahrscheinlich gut genug für ein Spiel, oder? Es gibt auch andere Vorteile, aber ich lasse den Wikipedia-Artikel über das Versenden von Nachrichten das Erklären übernehmen.

Ich habe auch eine Frage zu Stack Overflow gestellt: "Datenstrukturen für die Nachrichtenübergabe in einem Programm?" , die Sie vielleicht überlesen möchten.

Ricket
quelle
Hallo Ricket, danke für deine Antwort. Sie haben gefragt, wie die Spielobjekte auf andere Weise miteinander kommunizieren können. Was mir einfällt, sind direkte Methodenaufrufe. Es ist richtig, dass es Ihnen nicht so viel Flexibilität gibt, aber auf der anderen Seite vermeidet es das Generieren von Ereignismeldungen, das Weitergeben, das Lesen usw. Wir sprechen immer noch über mobile Plattformen, nicht über High-End-Spiele. Das Betriebssystem verwendet häufig das interne Nachrichtensystem. Es ist jedoch nicht für den Echtzeitbetrieb ausgelegt, bei dem selbst kleine Verzögerungen zu Störungen führen.
Bunkai.Satori
Lassen Sie mich diese Frage noch eine Weile offen halten. Ich würde gerne mehr Meinungen sammeln, bevor ich es schließe.
Bunkai.Satori
Direkte Methodenaufrufe führen im Allgemeinen zu einer Kopplung der Klassen, die sich gegenseitig aufrufen. Dies ist nicht gut für ein System, das in Komponenten unterteilt ist, die austauschbar sein sollen. Es gibt einige Muster, die direkte Methodenaufrufe mit geringerer Kohäsion ermöglichen (z. B. dient der "Controller" -Teil von MVC im Wesentlichen diesem Zweck, um die Abfragen des Modells in der Ansicht zu erleichtern, sofern Sie sie nicht koppeln). Im Allgemeinen ist jedoch die Nachrichtenübergabe der einzige Weg Ein System, bei dem keine Kopplung zwischen Subsystemen besteht (oder zumindest der einzige Weg, den ich kenne).
Ricket
Ah, jetzt haben wir angefangen, Muster zu diskutieren. Ich habe etwas über diesen Programmieransatz gehört. Jetzt beginne ich zu verstehen, was Muster sind. Ganz anders sieht es beim Anwendungsdesign aus. Sie können Objekte steuern, während Sie den gleichen Code verwenden, um jedes Objekt zu überprüfen. Nachdem Sie das Objekt überprüft haben, setzen Sie seine Attribute entsprechend.
Bunkai.Satori
1
Die Frage "Datenstrukturen ..." hat eine ERSTAUNLICHE Antwort. Ich würde sagen, Ihre ausgewählte Antwort und der dazugehörige Nebula3-Artikel sind die beste Beschreibung der Game-Engine-Architektur ihrer Größe, die ich je gesehen habe.
deft_code
15

Nachrichten funktionieren im Allgemeinen gut, wenn:

  1. Es ist der Sache, die die Nachricht sendet, egal, ob sie empfangen wird.
  2. Der Absender muss keine sofortige Antwort vom Empfänger erhalten.
  3. Es können mehrere Empfänger vorhanden sein, die einen einzelnen Absender abhören.
  4. Nachrichten werden selten oder unvorhersehbar gesendet. (Mit anderen Worten, wenn jedes Objekt in jedem einzelnen Frame eine "Aktualisierungs" -Nachricht erhalten muss, sind Nachrichten für Sie nicht besonders wichtig.)

Je mehr Ihre Anforderungen mit dieser Liste übereinstimmen, desto besser sind die Nachrichten. Im Allgemeinen sind sie ziemlich gut. Sie verschwenden keine CPU-Zyklen, nur um nichts anderes zu tun als Abfragen oder andere globalere Lösungen. Sie können fantastisch Teile einer Codebasis entkoppeln, was immer hilfreich ist.

herrlich
quelle
4

Gute Antworten bisher von James und Ricket, ich antworte nur, um einen Hinweis zur Vorsicht hinzuzufügen. Nachrichtenübermittlung / ereignisgesteuerte Kommunikation ist sicherlich ein wichtiges Werkzeug im Arsenal des Entwicklers, kann aber leicht überbeansprucht werden. Wenn Sie einen Hammer haben, sieht alles aus wie ein Nagel und so weiter.

100% ig sollten Sie über den Datenfluss rund um Ihren Titel nachdenken und sich darüber im Klaren sein, wie Informationen von einem Subsystem zu einem anderen übertragen werden. In einigen Fällen ist das Weiterleiten von Nachrichten eindeutig am besten. in anderen Fällen kann es zweckmäßiger sein, wenn ein Subsystem über eine Liste gemeinsam genutzter Objekte arbeitet, sodass auch andere Subsysteme auf derselben gemeinsam genutzten Liste arbeiten können. und viele weitere Methoden.

Sie sollten jederzeit wissen, welche Subsysteme auf welche Daten zugreifen müssen und wann sie darauf zugreifen müssen. Dies wirkt sich auf Ihre Fähigkeit zur Parallelisierung und Optimierung aus und hilft Ihnen, die schleichenderen Probleme zu vermeiden, wenn verschiedene Teile Ihres Motors zu eng miteinander verwoben sind. Sie müssen darüber nachdenken, unnötiges Kopieren von Daten zu minimieren und wie sich Ihr Datenlayout auf die Cache-Nutzung auswirkt. All dies führt Sie zur jeweils besten Lösung.

Abgesehen davon hatte so ziemlich jedes Spiel, an dem ich jemals gearbeitet habe, ein solides asynchrones Benachrichtigungssystem für Nachrichtenübermittlung und Ereignisse. Es ermöglicht Ihnen, prägnanten, effizienten und wartbaren Code zu schreiben, selbst angesichts komplexer und sich schnell ändernder Designanforderungen.

MrCranky
quelle
+1 für gute Zusatzinformationen. Hallo MrCranky, danke. Laut Ihrer Aussage lohnt es sich, das Event-Messaging-System auch für mobile Spiele in Betracht zu ziehen. Die Planung sollte jedoch nicht übersehen werden, und es sollte sehr gut überlegt werden, wo das Messagingsystem verwendet wird.
Bunkai.Satori
4

Nun, ich weiß, dass dieser Beitrag ziemlich alt ist, aber ich konnte nicht widerstehen.

Ich habe vor kurzem eine Game-Engine gebaut. Es verwendet 3D-Party-Bibliotheken für Rendering und Physik, aber ich habe den Kernteil geschrieben, der die Entitäten und die Spiellogik definiert und verarbeitet.

Der Motor folgt sicherlich einem traditionellen Ansatz. Es gibt eine Hauptaktualisierungsschleife, die die Aktualisierungsfunktion für alle Entitäten aufruft. Kollisionen werden direkt per Rückruf an die Entitäten gemeldet. Die Kommunikation zwischen Entitäten erfolgt über intelligente Zeiger, die zwischen Entitäten ausgetauscht werden.

Es gibt ein primitives Nachrichtensystem, das nur eine kleine Gruppe von Entitäten zu Engine-Nachrichten verarbeitet. Es ist vorzuziehen, diese Nachrichten am Ende einer Spielinteraktion zu verarbeiten (Beispiel: Erstellung oder Zerstörung einer Entität), da sie mit der Aktualisierungsliste in Konflikt geraten können. Am Ende jeder Spielrunde wird eine kleine Liste von Nachrichten verbraucht.

Trotz des primitiven Nachrichtensystems würde ich sagen, dass das System größtenteils auf "Aktualisierungsschleifen" basiert.

Gut. Nachdem ich dieses System benutzt habe, denke ich, dass es sehr einfach, schnell und gut organisiert ist. Die Spiellogik ist sichtbar und in sich geschlossen, nicht dynamisch wie eine Nachricht. Ich würde es wirklich nicht ereignisgesteuert machen, da meiner Meinung nach Ereignissysteme die Spielelogik unnötig komplizieren und den Spielcode sehr schwer zu verstehen und zu debuggen machen.

Aber ich denke auch, dass ein reines "Update Loop-basiertes" System wie das meine auch einige Probleme hat.

In einigen Momenten befindet sich beispielsweise eine Entität möglicherweise in einem "Nichtstun" -Zustand, wartet darauf, dass sich der Spieler nähert, oder etwas anderes. In den meisten Fällen verbrennt die Entität die Prozessorzeit für nichts und es ist besser, die Entität auszuschalten und sie einzuschalten, wenn ein bestimmtes Ereignis eintritt.

Also werde ich in meiner nächsten Spiel-Engine einen anderen Ansatz verfolgen. Die Entitäten registrieren sich für Motoroperationen wie Aktualisieren, Zeichnen, Kollisionserkennung und so weiter. Jedes dieser Ereignisse verfügt über separate Listen von Entitätsschnittstellen für die tatsächlichen Entitäten.

Artur Correa
quelle
Sie werden feststellen, dass Entitäten zu Monster-Code-Basen werden, die mit jeder Art von Komplexität im Spiel nur schwer zu verwalten sind. Am Ende werden Sie sie zusammenkoppeln und das Hinzufügen oder Ändern von Funktionen wird zum Erliegen kommen. Indem Sie Ihre Features in kleine Komponenten aufteilen, können Sie Features einfacher verwalten und hinzufügen. Dann stellt sich die Frage, wie diese Komponenten kommunizieren. Die Antwort sind Ereignisse von einer Komponente, die Funktionen einer anderen auslösen, während primitive Daten in den Argumenten weitergegeben werden.
user441521
3

Ja. Es ist eine sehr effiziente Möglichkeit für Spielsysteme, miteinander zu kommunizieren. Ereignisse helfen Ihnen, viele Systeme zu entkoppeln und es sogar zu ermöglichen, Dinge separat zu kompilieren, ohne dass Sie die Existenz der anderen kennen. Dies bedeutet, dass Ihre Klassen einfacher prototypisiert werden können und die Kompilierungszeiten schneller sind. Noch wichtiger ist, dass Sie am Ende ein Flat-Code-Design anstelle eines Abhängigkeits-Chaos haben.

Ein weiterer großer Vorteil von Ereignissen besteht darin, dass sie problemlos über ein Netzwerk oder einen anderen Textkanal gestreamt werden können. Sie können sie für die spätere Wiedergabe aufnehmen. Die Möglichkeiten sind endlos.

Ein weiterer Vorteil: Es können mehrere Subsysteme auf dasselbe Ereignis warten. Zum Beispiel können alle Ihre Remote-Ansichten (Spieler) automatisch das Ereignis zum Erstellen von Entitäten abonnieren und Entitäten auf jedem Client erstellen, ohne dass Sie dies tun müssen. Stellen Sie sich vor, wie viel mehr Arbeit wäre, wenn Sie keine Ereignisse verwenden würden: Sie müssten Update()irgendwo Anrufe tätigen oder möglicherweise view->CreateEntityvon der Spielelogik aus aufrufen (wo Kenntnisse über die Ansicht und die benötigten Informationen nicht dazugehören). Es ist schwer, dieses Problem ohne Ereignisse zu lösen.

Mit Ereignissen erhalten Sie eine elegante und entkoppelte Lösung, die eine unbegrenzte Anzahl von Objekten unbegrenzter Art unterstützt, die alle einfach Ereignisse abonnieren und ihre Sache tun können, wenn etwas in Ihrem Spiel passiert. Deshalb sind Events großartig.

Weitere Details und eine Implementierung hier .

user2826084
quelle
Tolle Punkte, wenn auch einseitig. Ich bin der Allgemeinheit gegenüber immer misstrauisch: Gibt es Anti- Use-Fälle für Ereignisse?
Anko
1
Anti-Use-Punkte: Wenn Sie unbedingt direkt reagieren müssen. Wann immer das, was Sie bei einem Ereignis tun möchten, direkt und im selben Thread ausgeführt wird. Wenn es absolut keine externe Verzögerung gibt, kann dies die Beendigung des Anrufs verzögern. Wenn Sie nur lineare Abhängigkeiten haben. Wenn Sie selten mehrere Dinge gleichzeitig tun. Wenn Sie sich node.js ansehen, sind alle io-Aufrufe ereignisbasiert. Node.js ist ein Ort, an dem Ereignisse zu 100% korrekt implementiert wurden.
user2826084
Das Ereignissystem muss nicht asynchron sein. Sie können Coroutinen verwenden, um Asynchronität zu simulieren, während Sie bei Bedarf synchronisiert werden.
user441521
2

Nach dem, was ich gesehen habe, scheint es nicht sehr verbreitet zu sein, eine Engine zu haben, die vollständig auf Messaging basiert. Natürlich gibt es Subsysteme, die sich sehr gut für ein solches Nachrichtensystem eignen, wie zum Beispiel Netzwerke, GUI und möglicherweise andere. Im Allgemeinen kann ich mir jedoch einige Probleme vorstellen:

  • Game Engines verarbeiten große Datenmengen.
  • Spiele müssen schnell sein (mindestens 20-30 FPS).
  • Oft ist es notwendig zu wissen, ob etwas getan wurde oder wann es getan wird.

Mit dem gängigen Spielentwickler-Ansatz "Es muss möglichst effizient sein" sind solche Nachrichtensysteme also nicht so verbreitet.

Ich bin jedoch damit einverstanden, dass Sie einfach weitermachen und es versuchen sollten. Mit einem solchen System sind sicherlich viele Vorteile verbunden, und Rechenleistung ist heutzutage kostengünstig verfügbar.

Mrbinary
quelle
Ich weiß nicht, ob ich damit einverstanden bin. Die Alternative ist Umfragen, was verschwenderisch ist. Nur auf Ereignisse zu reagieren, ist am effizientesten. Ja, es wird immer noch einige Abstimmungen geben, die Ereignisse auslösen, aber es gibt eine ganze Reihe von Dingen, die auch ereignisorientiert sind.
user441521
Dem stimme ich auch nicht zu. Die Anzahl der von den Spiel-Engines behandelten Daten ist weitgehend irrelevant, da die Ereignisnachrichten für die Kommunikation zwischen Subsystemen ausgelegt sind. Spielobjekte sollten nicht direkt mit anderen Spielobjekten kommunizieren (benutzerdefinierte Ereignishandler in Objekten können jedoch eine Ausnahme darstellen). Aufgrund dieser relativ geringen Anzahl von Subsystemen und ihrer "interessanten" Bereiche sind die Leistungskosten eines gut konzipierten Messaging-Backbones in einer Multithread-Engine vernachlässigbar.
Ian Young