Frage

Für ein 2D-Spiel, das ich (für Android) mache, verwende ich ein komponentbasiertes System, bei dem ein GameObject mehrere Gamecomponent-Objekte enthält. Gamecomponenten können Dinge wie Eingabekomponenten, Rendering -Komponenten, Kugeln emittierende Komponenten usw. sein. Derzeit haben Gamecomponenten einen Hinweis auf das Objekt, das sie besitzt und es ändern kann, aber das GameObject selbst enthält nur eine Liste von Komponenten, und es ist ihm egal, was die Komponenten so lange sind, wie sie aktualisiert werden können, wenn das Objekt aktualisiert wird.

Manchmal hat eine Komponente einige Informationen, die das GameObject kennen muss. Zum Beispiel registriert sich ein GameObject selbst mit dem Subsystem der Kollisionserkennung, wenn es mit einem anderen Objekt kollidiert wird. Das Subsystem der Kollisionserkennung muss den Begrenzungsbox des Objekts kennen. Ich speichere X und Y im Objekt direkt (da es von mehreren Komponenten verwendet wird), aber Breite und Höhe sind der Rendering -Komponente, die die Bitmap des Objekts enthält, nur bekannt. Ich hätte gerne eine Methode Getboundingbox oder Getwidth im GameObject, das diese Informationen erhält. Oder im Allgemeinen möchte ich einige Informationen von einer Komponente an das Objekt senden. In meinem aktuellen Design weiß das GameObject jedoch nicht, welche spezifischen Komponenten es in der Liste hat.

Ich kann mir verschiedene Möglichkeiten vorstellen, dieses Problem zu lösen:

  1. Anstatt eine vollständig generische Liste von Komponenten zu haben, kann ich das GameObject für einige der wichtigen Komponenten ein spezifisches Feld haben lassen. Beispielsweise kann eine Mitgliedsvariable bezeichnet werden, die als RenderingComponent bezeichnet wird. Wann immer ich die Breite des Objekts bekommen muss, das ich gerade benutze renderingComponent.getWidth(). Diese Lösung ermöglicht immer noch eine generische Liste von Komponenten, behandelt jedoch einige von ihnen anders, und ich fürchte, ich habe am Ende mehrere außergewöhnliche Felder, da weitere Komponenten abgefragt werden müssen. Einige Objekte haben nicht einmal Rendering -Komponenten.

  2. Haben Sie die erforderlichen Informationen als Mitglieder des GameObject, lassen Sie die Komponenten jedoch aktualisieren. Ein Objekt hat also eine Breite und eine Höhe, die standardmäßig 0 oder -1 beträgt. Eine Rendering -Komponente kann sie jedoch auf die richtigen Werte in seiner Aktualisierungsschleife einstellen. Dies fühlt sich wie ein Hack an und ich könnte viele Dinge aus Gründen der Bequemlichkeit in den GameObject -Kurs drücken, auch wenn nicht alle Objekte sie brauchen.

  3. Lassen Sie Komponenten eine Schnittstelle implementieren, die angibt, für welche Art von Informationen sie abfragt werden können. Beispielsweise würde eine Rendering -Komponente die Hassize -Schnittstelle implementieren, die Methoden wie Getwidth und Geteight enthält. Wenn das GameObject die Breite benötigt, werden seine Komponenten überprüft, ob sie die Hassize -Schnittstelle implementieren (mithilfe der instanceof Schlüsselwort in Java oder is in C#). Dies scheint eine allgemeinere Lösung zu sein, ein Nachteil ist, dass die Suche nach der Komponente einige Zeit dauern kann (aber die meisten Objekte haben nur 3 oder 4 Komponenten).

In dieser Frage geht es nicht um ein bestimmtes Problem. Es kommt oft in meinem Design und ich habe mich gefragt, wie es der beste Weg ist, damit umzugehen. Die Leistung ist etwas wichtig, da dies ein Spiel ist, aber die Anzahl der Komponenten pro Objekt ist im Allgemeinen gering (das Maximum beträgt 8).

Die Kurzversion

Wie können Sie in einem Komponenten -basierten System für ein Spiel Informationen am besten an das Objekt weitergeben und gleichzeitig das Design generisch halten?

War es hilfreich?

Lösung

Wir erhalten Variationen dieser Frage drei- oder viermal pro Woche auf gadev.net (wobei das GameObject in der Regel als „Entität“ bezeichnet wird) und bisher gibt es keinen Konsens über den besten Ansatz. Es hat sich jedoch gezeigt, dass verschiedene Ansätze praktikabel sind, daher würde ich mir darüber keine Sorgen machen.

In der Regel betrachten die Probleme jedoch die Kommunikation zwischen Komponenten. Selten machen sich die Leute Sorgen, Informationen von einer Komponente an die Entität zu erhalten - wenn ein Unternehmen weiß, auf welche Informationen sie benötigt, weiß es vermutlich genau, auf welche Art von Komponenten es zugreifen muss und auf welche Eigenschaft oder Methode sie auf diese Komponente aufrufen muss, um sie zu erhalten die Daten. Wenn Sie eher reaktiv als aktiv sein müssen, registrieren Sie Rückrufe oder haben Sie ein Beobachtermuster mit den Komponenten eingerichtet, damit die Entität etwas in der Komponente geändert hat, und den Wert an diesem Punkt lesen.

Vollständige generische Komponenten sind weitgehend nutzlos: Sie müssen eine bekannte Schnittstelle bereitstellen, sonst gibt es nur wenige Punkte. Andernfalls können Sie genauso gut nur eine große assoziative Reihe von unguteten Werten haben und damit fertig sein. In Java, Python, C#und anderen Sprachen auf leicht hoher Ebene als C ++ können Sie Reflection verwenden, um Ihnen eine genauere Möglichkeit zu geben, bestimmte Unterklassen zu verwenden, ohne Informationen und Schnittstelleninformationen in die Komponenten selbst zu kodieren.

Wie für die Kommunikation:

Einige Leute machen Annahmen, dass eine Entität immer einen bekannten Satz von Komponententypen enthält (wobei jede Instanz eine von mehreren möglichen Unterklassen ist) und kann daher nur einen direkten Verweis auf die andere Komponente holen und über seine öffentliche Schnittstelle lesen/schreiben.

Einige Leute verwenden Publish/Abonnieren, Signale/Slots usw., um beliebige Verbindungen zwischen Komponenten zu erstellen. Dies scheint ein bisschen flexibler zu sein, aber letztendlich brauchen Sie immer noch etwas mit dem Wissen über diese implizite Abhängigkeiten. (Und wenn dies zur Kompilierungszeit bekannt ist, warum nicht einfach den vorherigen Ansatz verwenden?)

Oder Sie können alle gemeinsam genutzten Daten in die Entität selbst einfügen und diesen als gemeinsam genutzten Kommunikationsbereich verwenden (schwach im Zusammenhang mit der Tafelsystem in AI) dass jede der Komponenten lesen und schreiben kann. Dies erfordert normalerweise eine gewisse Robustheit angesichts bestimmter Eigenschaften, die nicht existieren, wenn Sie es erwartet haben. Es eignet sich auch nicht zur Parallelität, obwohl ich bezweifle, dass dies eine massive Sorge um ein kleines eingebettetes System ist ...?

Schließlich haben einige Leute Systeme, in denen die Entität überhaupt nicht existiert. Die Komponenten leben in ihren Subsystemen und der einzige Begriff einer Entität ist ein ID -Wert in bestimmten Komponenten - wenn eine Rendering -Komponente (innerhalb des Rendering -Systems) und eine Player -Komponente (innerhalb des Spielers System) dieselbe ID haben, können Sie annehmen Ersteres verarbeitet die Zeichnung des letzteren. Es gibt jedoch kein einzelnes Objekt, das eine dieser Komponenten aggregiert.

Andere Tipps

Wie andere gesagt haben, gibt es hier keine immer richtige Antwort. Verschiedene Spiele werden sich für unterschiedliche Lösungen eignen. Wenn Sie ein großes komplexes Spiel mit vielen verschiedenen Arten von Einheiten erstellen, kann eine entkoppeltere generische Architektur mit abstrakten Nachrichten zwischen Komponenten die Mühe für die Wartbarkeit wert sein, die Sie erhalten. Für ein einfacheres Spiel mit ähnlichen Einheiten kann es am sinnvollsten sein, all diesen Zustand in GameObject zu bringen.

Für Ihr spezifisches Szenario, in dem Sie den Begrenzungsbox irgendwo speichern müssen und nur die Kollisionskomponente darum kümmert, würde ich:

  1. Speichern Sie es in der Kollisionskomponente selbst.
  2. Lassen Sie den Kollisionserkennungscode direkt mit den Komponenten funktionieren.

Anstatt die Kollisionsmotor durch eine Sammlung von GameObjects zu iterieren, um die Interaktion zu beheben, haben Sie sie direkt durch eine Sammlung von Collisioncomponenten iteriert. Sobald eine Kollision aufgetreten ist, liegt es in der Komponente, dies auf das übergeordnete GameObject zu bringen.

Dies gibt Ihnen ein paar Vorteile:

  1. Lässt den kollisionsspezifischen Zustand aus dem GameObject heraus.
  2. Erhöht Sie von der Iteration über GameObjects, die keine Kollisionskomponenten haben. (Wenn Sie viele nicht interaktive Objekte wie visuelle Effekte und Dekoration haben, kann dies eine anständige Anzahl von Zyklen sparen.)
  3. Erhöht Sie von Verbrennungszyklen zwischen dem Objekt und seiner Komponente. Wenn Sie durch die Objekte iterieren, dann tun Sie dies getCollisionComponent() Bei jedem einzelnen kann dieser Zeigerverfolgung einen Cache-Fehlschlag verursachen. Für jeden Rahmen für jedes Objekt kann viel CPU verbrennen.

Wenn Sie interessiert sind, habe ich mehr über dieses Muster hier, Obwohl es so aussieht, als ob Sie bereits das meiste verstehen, was in diesem Kapitel ist.

Benutze ein "Eventbus". (Beachten Sie, dass Sie den Code wahrscheinlich nicht so verwenden können, aber er sollte Ihnen die Grundidee geben).

Erstellen Sie im Grunde eine zentrale Ressource, in der sich jedes Objekt als Zuhörer registrieren kann und sagen "Wenn X passiert, möchte ich es wissen". Wenn im Spiel etwas passiert, kann das verantwortliche Objekt einfach ein Ereignis X an den Event -Bus senden und alle interessanten Parteien werden es bemerken.

Bearbeiten] für eine detailliertere Diskussion siehe Nachrichtenübergang (Dank an snk_kid für den Hinweis darauf).

Ein Ansatz besteht darin, einen Komponentenbehälter zu initialisieren. Jede Komponente kann einen Dienst anbieten und benötigt auch Dienste aus anderen Komponenten. Abhängig von Ihrer Programmiersprache und -umgebung müssen Sie eine Methode zur Bereitstellung dieser Informationen entwickeln.

In seiner einfachsten Form haben Sie Einzelverbindungen zwischen Komponenten, aber Sie benötigen auch Eins-zu-viele-Verbindungen. ZB der CollectionDetector wird eine Liste von Komponenten haben, die implementiert werden IBoundingBox.

Während der Initialisierung verdrahtet der Container Verbindungen zwischen den Komponenten, und während der Laufzeit werden keine zusätzlichen Kosten stattfinden.

Dies liegt in Ihrer Nähe von Ihnen Lösung 3), erwarten Sie, dass die Verbindungen zwischen Komponenten nur einmal verkabelt werden und bei jeder Iteration der Spielschleife nicht überprüft werden.

Das Managed Extensibility Framework für .NET ist eine schöne Lösung für dieses Problem. Mir ist klar, dass Sie beabsichtigen, sich auf Android zu entwickeln, aber Sie können sich trotzdem von diesem Rahmen inspirieren lassen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top