Ich habe in den letzten 3-4 Jahren an einigen Hobbyprojekten gearbeitet. Nur einfache 2D- und 3D-Spiele. Aber in letzter Zeit habe ich ein größeres Projekt gestartet. Also, in den letzten Monaten habe ich versucht, eine Spielobjektklasse zu entwerfen, die die Basis aller meiner Spielobjekte sein kann. Nach langem Testen habe ich mich also an Google gewandt, was mich schnell auf einige GDC-PDFs und PowerPoints aufmerksam machte. Und jetzt versuche ich, komponentenbasierte Spielobjekte zu erfassen.
Ich verstehe, dass die Engine ein Spielobjekt erstellt und dann verschiedene Komponenten anfügt, die Dinge wie Gesundheit, Physik, Vernetzung und alles, was Sie dazu bringen, erledigen. Ich verstehe jedoch nicht, wie Komponente X weiß, ob Y den Status des Objekts geändert hat. Zum Beispiel, woher weiß die PhysicsComponent, ob der Spieler am Leben ist, weil die Gesundheit von der HealthComponent kontrolliert wird. Und wie spielt die HealthComponent die "player-died-animation" ab?
Ich hatte den Eindruck, dass es so etwas war (In der HealthComponent):
if(Health < 0) {
AnimationComponent.PlayAnimation("played-died-animation")
}
Aber woher weiß die HealthComponent, dass an das Spielobjekt, an das sie angehängt ist, eine AnimationComponent angehängt ist? Die einzige Lösung, die ich hier sehe, ist
Prüfen Sie, ob eine AnimationComponent angeschlossen ist oder nicht (entweder im Komponentencode oder auf der Engine-Seite).
Haben Sie Komponenten erfordern andere Komponenten, aber das scheint das gesamte Komponentendesign zu bekämpfen.
Schreiben Sie wie HealthWithAnimationComponent, HealthNoAnimationComponent und so weiter, was wiederum die gesamte Idee des Komponentendesigns zu bekämpfen scheint.
Antworten:
In all Ihren Beispielen gibt es ein schreckliches Problem. Die Integritätskomponente muss über alle Komponententypen informiert sein, die möglicherweise auf das Absterben der Entität reagieren müssen. Daher ist keines Ihrer Szenarien geeignet. Ihre Entität hat eine Integritätskomponente. Es hat eine Animationskomponente. Weder hängen von dem anderen ab, noch wissen sie davon. Sie kommunizieren über ein Nachrichtensystem.
Wenn die Integritätskomponente feststellt, dass die Entität "gestorben" ist, sendet sie eine Nachricht "Ich bin gestorben". Es liegt in der Verantwortung der Animationskomponente, auf diese Nachricht zu antworten, indem sie die entsprechende Animation abspielt.
Die Integritätskomponente sendet die Nachricht nicht direkt an die Animationskomponente. Vielleicht überträgt es es an jede Komponente in dieser Entität, vielleicht an das gesamte System; Vielleicht muss die Animationskomponente dem Nachrichtensystem mitteilen, dass es an Nachrichten interessiert ist, bei denen ich gestorben bin. Es gibt viele Möglichkeiten, das Nachrichtensystem zu implementieren. Wie auch immer Sie es implementieren, der Punkt ist, dass die Integritätskomponente und die Animationskomponente niemals wissen oder sich darum kümmern müssen, ob die andere vorhanden ist, und dass das Hinzufügen neuer Komponenten niemals das Ändern vorhandener Komponenten erfordert, um ihnen entsprechende Nachrichten zu senden.
quelle
Artemis behebt das Problem , indem keine Verarbeitung innerhalb von Komponenten durchgeführt wird. Komponenten enthalten nur die Daten, die sie benötigen. Systeme lesen mehrere Komponententypen und führen die erforderliche Verarbeitung durch.
In Ihrem Fall haben Sie möglicherweise ein RenderSystem, das die HealthComponent (und andere) einliest und die Warteschlangen mit den entsprechenden Animationen abspielt. Durch die Trennung von Daten von Funktionen wird es einfacher, Abhängigkeiten ordnungsgemäß zu verwalten.
quelle
In Ihrem Code können Sie nach Möglichkeiten suchen (ich habe sie verwendet, möglicherweise gibt es auch andere Möglichkeiten), um festzustellen, ob sich der Status des Objekts geändert hat:
Dazu habe ich verwendet, 1. HasComponent-Funktion von GameObject, oder 2. Wenn Sie eine Komponente anhängen, können Sie Abhängigkeiten in einer Konstruktionsfunktion überprüfen, oder 3. Wenn ich sicher bin, dass das Objekt diese Komponente hat, verwende ich sie einfach.
In einigen Artikeln habe ich gelesen, dass in Ideal Systemkomponenten nicht voneinander abhängen, aber im wirklichen Leben ist es nicht so.
Es ist eine schlechte Idee, solche Komponenten zu schreiben. In meiner App habe ich die Health-Komponente am unabhängigsten erstellt. Jetzt denke ich über ein Observer-Muster nach, das Abonnenten über ein bestimmtes Ereignis benachrichtigt (z. B. "Treffer", "Heilung" usw.). Daher muss AnimationComponent selbst entscheiden, wann die Animation abgespielt werden soll.
Aber als ich einen Artikel über CBES gelesen habe, hat mich das beeindruckt, und ich bin jetzt sehr glücklich, wenn ich CBES nutze und neue Möglichkeiten dafür entdecke.
quelle
Es ist wie Michael, Patrick Hughes und Blecki sagt. Die Lösung, um zu vermeiden, dass das Problem einfach verschoben wird, besteht darin, die Ideologie aufzugeben, die das Problem überhaupt verursacht.
Es ist weniger OOD und eher funktionale Programmierung. Als ich anfing, mit Component-Based Design zu experimentieren, entdeckte ich dieses Problem auf der ganzen Linie. Ich googelte weiter und fand "Functive Reactive Programming" die Lösung.
Jetzt sind meine Komponenten nichts anderes als eine Sammlung von Variablen und Feldern, die den aktuellen Status beschreiben. Dann habe ich eine Reihe von "System" -Klassen, die alle für sie relevanten Komponenten aktualisieren. Der reaktive Teil wird erreicht, indem die Systeme in einer genau definierten Reihenfolge ausgeführt werden. Auf diese Weise wird sichergestellt, dass jedes System, das als nächstes für die Verarbeitung und Aktualisierung vorgesehen ist, sowie alle Komponenten und Entitäten, die es lesen und aktualisieren möchte, stets mit aktuellen Daten arbeiten.
In gewisser Weise könnte man jedoch immer noch behaupten, dass das Problem erneut aufgetreten ist. Was ist, wenn es nicht einfach ist, in welcher Reihenfolge Ihre Systeme ausgeführt werden müssen? Was ist, wenn es zyklische Beziehungen gibt und es nur eine Frage der Zeit ist, bis Sie auf ein Durcheinander von if-else- und switch-Anweisungen starren? Es ist eine implizite Form der Nachrichtenübermittlung, nicht wahr? Auf den ersten Blick halte ich es für ein kleines Risiko. Normalerweise werden die Dinge in der richtigen Reihenfolge verarbeitet. So etwas wie: Spielereingabe -> Elementpositionen -> Kollisionserkennung -> Spielelogik -> Rendern -> Neu starten. In diesem Fall verfügen Sie über jeweils ein System, versehen jedes System mit einer update () -Methode und führen sie anschließend in Ihrem Gameloop nacheinander aus.
quelle