"The Game Object" - und komponentenbasiertes Design

25

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

  1. Prüfen Sie, ob eine AnimationComponent angeschlossen ist oder nicht (entweder im Komponentencode oder auf der Engine-Seite).

  2. Haben Sie Komponenten erfordern andere Komponenten, aber das scheint das gesamte Komponentendesign zu bekämpfen.

  3. Schreiben Sie wie HealthWithAnimationComponent, HealthNoAnimationComponent und so weiter, was wiederum die gesamte Idee des Komponentendesigns zu bekämpfen scheint.

hayer
quelle
1
Liebe die Frage. Ich hätte noch vor ein paar Monaten danach fragen sollen, bin aber nie dazu gekommen. Ein weiteres Problem, mit dem ich konfrontiert bin, ist, wenn ein Spielobjekt mehrere Instanzen derselben Komponente aufweist (zum Beispiel mehrere Animationen). Es wäre großartig, wenn die Antworten dies berühren könnten. Am Ende habe ich Nachrichten für Benachrichtigungen mit Variablen verwendet, die von allen Komponenten eines Game-Objekts gemeinsam genutzt werden (sodass sie keine Nachricht senden müssen, um einen Wert für eine Variable zu erhalten).
ADB
1
Abhängig von der Art des Spiels werden Sie wahrscheinlich keine Spielobjekte haben, die eine Gesundheitskomponente und keine Animationskomponente haben. Und all diese Spielobjekte repräsentieren wahrscheinlich so etwas wie Unit. Auf diese Weise können Sie die Integritätskomponente entfernen und eine UnitComponent erstellen, die einen Feldintegritätszustand aufweist und über alle Komponenten Bescheid weiß, die die Unit sein muss. Diese Granularität der Komponenten hilft eigentlich nichts - es ist realistischer, eine Komponente pro Domäne zu haben (Rendering, Audio, Physik, Spielelogik).
Kikaimaru

Antworten:

11

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.

Blecki
quelle
Okey, das macht Sinn. Aber wer erklärt die "Zustände" für "tot" oder "Portal ist kaputt" usw. Die Komponente oder die Engine? Weil das Hinzufügen eines Zustands 'tot' zu einer Sache, an die die Gesundheitskomponente niemals angehängt wird, für mich eine Verschwendung ist. Ich schätze, ich tauche einfach ein und beginne, Code zu testen und zu sehen, was funktioniert.
Hayer
Michael und Patrick Hughes haben oben die richtige Antwort. Komponenten sind nur Daten; Es handelt sich also nicht wirklich um die Gesundheitskomponente, die erkennt, wann die Entität gestorben ist und die Nachricht sendet, sondern um ein höherstufiges Stück spielspezifischer Logik. Wie man das abstrahiert, liegt bei Ihnen. Der tatsächliche Todeszustand muss niemals irgendwo gespeichert werden. Das Objekt ist tot, wenn sein Zustand <0 ist, und die Integritätskomponente kann dieses Bit der Datenprüflogik einkapseln, ohne ein "Nein" -Verhalten zu unterbrechen! Einschränkung, wenn Sie nur Dinge als Verhalten betrachten, die den Status der Komponente ändern.
Blecki
Nur neugierig, wie würden Sie mit einer MovementComponent umgehen? Wenn Eingaben erkannt werden, muss die Geschwindigkeit in der PositionComponent erhöht werden. Wie würde die Nachricht aussehen?
Tipps48
8

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.

Michael
quelle
Dies ist eine gute Möglichkeit, das Problem zu lösen: Komponenten stellen Eigenschaften dar, während Systeme unterschiedliche Eigenschaften verknüpfen und diese für die Arbeit verwenden. Es ist eine enorme Abkehr vom traditionellen OOP-Denken und macht die Köpfe einiger Leute verletzt =)
Patrick Hughes
Okey, jetzt bin ich wirklich verloren. "Im Gegensatz dazu haben Sie in einem ES, wenn Sie 100 Einheiten auf einem Schlachtfeld haben, jede durch eine Entität repräsentiert, keine Kopien jeder Methode, die für eine Einheit aufgerufen werden kann - weil Entitäten enthalten weder Methoden noch Komponenten Methoden. Stattdessen verfügen Sie für jeden Aspekt über ein externes System, und dieses externe System enthält alle Methoden, die für Entitäten aufgerufen werden können, die über die Komponente verfügen, die es als kompatibel mit dieser Komponente kennzeichnet System." Wo werden Daten in einer GunComponent gespeichert? Wie Runden usw. Wenn alle Entitäten dieselbe Komponente haben.
Hayer
1
Soweit ich weiß, teilen nicht alle Entitäten dieselbe Komponente, jeder Entität können N Komponenteninstanzen zugeordnet werden. Ein System fragt dann das Spiel nach einer Liste aller Entitäten ab, denen Komponenteninstanzen zugeordnet sind, die sie interessieren, und führt dann die entsprechende Verarbeitung durch
Jake Woods,
Dies verschiebt nur das Problem. Woher weiß ein System, welche Komponenten zu verwenden sind? Ein System könnte auch andere Systeme benötigen (das StateMachine-System möchte beispielsweise eine Animation aufrufen). Es löst jedoch das Problem, dass die Daten der WHO gehören. Tatsächlich wäre eine einfachere Implementierung, ein Wörterbuch im Spielobjekt zu haben und jedes System erstellt dort seine Variablen.
ADB
Es verschiebt das Problem, aber an einen Ort, der haltbarer ist. Bei Systemen sind die relevanten Komponenten fest verdrahtet. Systeme können über Komponenten miteinander kommunizieren (StateMachine kann einen Komponentenwert festlegen, den Animation einliest, um zu wissen, was zu tun ist (oder ein Ereignis auslösen kann). Der Dictionary-Ansatz klingt wie das Properties-Pattern, das auch funktionieren kann. Das Schöne daran Komponenten ist , dass ähnliche Eigenschaften zusammengefasst sind und sie statisch überprüft werden können keine bizarre Fehler , weil Sie „dammage“ an einem Ort hinzugefügt , aber versucht , es zurückzuholen in einem anderen mit „Damage“..
Michael
6

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:

  1. Nachricht senden.
  2. Liest direkt Daten von der Komponente.

1) Prüfen Sie, ob eine AnimationComponent angeschlossen ist oder nicht (entweder im Komponentencode oder auf der Motorseite).

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.

2) Komponenten erfordern andere Komponenten, aber das scheint das gesamte Komponentendesign zu beeinträchtigen.

In einigen Artikeln habe ich gelesen, dass in Ideal Systemkomponenten nicht voneinander abhängen, aber im wirklichen Leben ist es nicht so.

3) Schreiben Sie wie HealthWithAnimationComponent, HealthNoAnimationComponent und so weiter, was wiederum die gesamte Idee des Komponentendesigns zu bekämpfen scheint.

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.

Yevhen
quelle
1
Nun, google.no/… @ slide 16
hayer
@ Bobenko, bitte geben Sie einen Link zum Artikel über CBES. Ich bin auch sehr interessant darin;)
Edward83
1
Und lambdor.net/?p=171 @ bottom, dies ist eine Art Zusammenfassung meiner Frage. Wie können verschiedene Funktionen in Bezug auf relativ komplexe, nicht elementare Komponenten definiert werden? Was sind die elementarsten Komponenten? Inwiefern unterscheiden sich elementare Komponenten von reinen Funktionen? Wie können vorhandene Komponenten automatisch mit neuen Nachrichten von neuen Komponenten kommunizieren? Was ist der Grund, eine Nachricht zu ignorieren, von der eine Komponente nichts weiß? Was ist schließlich mit dem Input-Process-Output-Modell passiert?
Hayer
1
Hier ist eine gute Antwort auf CBES stackoverflow.com/a/3495647/903195. Die meisten Artikel, die ich recherchiert habe, stammen aus dieser Antwort. Ich habe mit cowboyprogramming.com/2007/01/05/evolve-your-heirachy angefangen und mich inspirieren lassen, dann gab es in Gems 5 (wie ich mich erinnere) einen guten Artikel mit Beispielen.
Yevhen
Aber was ist mit einem anderen Konzept funktional-reaktiver Programmierung, für mich ist diese Frage noch offen, aber für Sie ist es vielleicht eine gute Richtung für die Forschung.
Yevhen
3

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.

ähm runter
quelle