Wie vermeide ich "Blob-Systeme" in einem Entitätskomponentensystem?

10

Derzeit stehe ich vor folgendem Problem:

Ich versuche, einen Pong-Klon mithilfe eines Entity-Component-Systems (ECS) zu schreiben . Ich habe das "Framework" ganz alleine geschrieben. Es gibt also eine Klasse, die die Entitäten mit allen Komponenten verwaltet. Dann gibt es die Komponentenklassen selbst. Und zuletzt gibt es meine Systeme, die nur alle Entitäten erhalten, die Komponenten haben, die das System benötigt.

So sucht mein Bewegungssystem beispielsweise nach allen Objekten, die eine Positionskomponente und eine Bewegungskomponente haben. Die Positionskomponente hält nur die Position und die Bewegungskomponente hält die Geschwindigkeit.

Das eigentliche Problem ist jedoch mein Kollisionssystem. Diese Klasse ist wie ein logischer Blob. Ich habe so viele Sonderfälle in dieser Klasse.

Zum Beispiel: Meine Paddel können mit den Rändern kollidieren. In diesem Fall wird ihre Geschwindigkeit auf Null gesetzt. Mein Ball kann auch mit den Rändern kollidieren. In diesem Fall wird die Geschwindigkeit jedoch nur auf der Normalen der Grenze gespiegelt, sodass sie reflektiert wird. Dazu habe ich dem Ball eine zusätzliche Physikkomponente gegeben, die nur sagt: "Hey, dieses Ding hört nicht auf, es reflektiert." Tatsächlich hat die Physikkomponente also keine realen Daten. Es ist eine leere Klasse, die nur dazu dient, dem System mitzuteilen, ob ein Objekt reflektiert oder stoppt.

Dann kommt Folgendes: Ich möchte einige Partikel rendern, wenn der Ball mit den Paddeln oder den Rändern kollidiert. Ich denke, der Ball muss eine weitere Komponente erhalten, die das Kollisionssystem anweist, bei einer Kollision Partikel zu erzeugen.
Dann möchte ich Power-Ups haben, die mit den Paddeln kollidieren können, aber nicht mit den Rändern. In diesem Fall müssen die Power-Ups verschwinden. Ich würde also viel mehr Fälle und Komponenten benötigen (um dem System mitzuteilen, dass einige Entitäten nur mit bestimmten anderen kollidieren können, aber nicht mit allen, selbst wenn andere tatsächlich kollidieren können, musste das Kollisionssystem außerdem die Power-Ups anwenden die Paddel usw. usw. usw. usw.).

Ich sehe, dass das Entitätskomponentensystem eine gute Sache ist, weil es flexibel ist und Sie keine Probleme mit der Vererbung haben. Aber ich stecke momentan total fest.

Denke ich zu kompliziert Wie soll ich mit diesem Problem umgehen?

Klar, ich muss Systeme erstellen, die tatsächlich für "Nachkollision" verantwortlich sind, also sagt das Kollisionssystem nur "Ja, wir haben eine Kollision im letzten Frame" und dann gibt es eine Reihe von "Nachkollisions" -Systemen, die Alle erfordern unterschiedliche (Kombinationen von) Komponenten und ändern dann die Komponenten. Zum Beispiel würde es ein Bewegungs-Nachkollisionssystem geben, das Dinge stoppt, die anhalten müssen, wenn eine Kollision auftritt. Dann ein Physik-Nachkollisionssystem, das Dinge usw. widerspiegelt.

Aber dies scheint mir auch keine richtige Lösung zu sein, weil zum Beispiel:

  1. Mein Bewegungsnachkollisionssystem würde Entitäten benötigen, die eine Positionskomponente, eine Bewegungskomponente und eine Kollisionskomponente haben. Dann würde es die Geschwindigkeit der Entität auf Null setzen.
  2. Das Physik-Nachkollisionssystem würde Entitäten benötigen, die eine Positionskomponente, eine Bewegungskomponente, eine Kollisionskomponente und eine Physikkomponente aufweisen. Dann würde es den Geschwindigkeitsvektor widerspiegeln.

Das Problem liegt auf der Hand: Die Bewegung nach der Kollision benötigt Entitäten, die eine Teilmenge der Entitäten im Physik-Nachkollisionssystem sind. Zwei Nachkollisionssysteme würden also mit denselben Daten arbeiten, was zur Folge hätte: Obwohl eine Entität eine physikalische Komponente hat, wäre ihre Geschwindigkeit nach einer Kollision Null.

Wie werden diese Probleme im Allgemeinen in einem Entitätskomponentensystem gelöst? Sind diese Probleme überhaupt üblich oder mache ich etwas falsch? Wenn ja, was und wie soll es stattdessen gemacht werden?

M0rgenstern
quelle

Antworten:

11

Ja, du denkst zu kompliziert.

Es hört sich so an, als könnten viele Ihrer Probleme mit einem Messagingsystem und einigen zusätzlichen Attributen gelöst werden, mit denen Sie einige Filter angeben können , und sich schließlich keine Sorgen darüber machen, dass Entitäten / Komponenten so streng sind.

Messaging hilft Ihnen bei einigen Aspekten wie dem Auslösen von Partikeln, Powerups usw. Beispielsweise könnten Sie ein Weltobjekt haben, das Partikelereignisse abonniert und Partikel an der im Ereignis beschriebenen Position erstellt.

Filter helfen Ihnen bei Kollisionen sehr. Filter können definieren, ob ein Objekt mit einem anderen kollidiert und welche Reaktion es haben wird. Sie fügen Ihrer Physikkomponente einige Attribute hinzu, die definieren, um welche Art von Physikkörper es sich handelt, mit welchen anderen Arten von Physikkörpern er kollidiert und wie die Reaktion aussehen soll. Beispielsweise kollidiert ein Ballphysikobjekt mit einem Paddelphysikobjekt und reagiert mit Reflexion und Partikeln.

Seien Sie nicht so streng bei Ihrer Implementierung. Wenn Sie einen Weg finden, es zum Laufen zu bringen, es aber nicht wirklich ein EC-System ist, tun Sie es. Wie in meinem obigen Beispiel müssen die Partikel überhaupt nicht von einem System oder einem Teil des EC-Systems verwaltet werden. Es ist wichtiger, das Spiel zu beenden, als sich strikt an eine Methode zu halten, die bereits ziemlich schlecht definiert ist.

MichaelHouse
quelle
Vielen Dank. Ich wollte ein Spiel mit einem ECS erstellen, um zu sehen, wie es skaliert und ob es wirklich so schön ist, wenn ich es in Artikeln und Tutorials lese. Mein Problem war, dass ich dachte: "Ich habe jetzt ein ECS und alles muss damit verwaltet werden." Also hatte ich auch vor, das Partikelsystem in Verbindung mit dem ECS zu schreiben. Außerdem habe ich in einigen Artikeln gelesen, dass jede Komponente eigentlich nur einige Basisdaten haben sollte und nicht mehr. Das ist oft mein Problem ... Ich denke zu kompliziert.
M0rgenstern
Ich wollte ein Spiel mit einem ECS erstellen, um zu sehen, wie es skaliert und ob es wirklich so schön ist, wenn ich es in Artikeln und Tutorials lese . Wenn dies Ihr Ziel ist, empfehle ich Ihnen, sich vorhandene Komponenten- / Entitätssysteme anzusehen, anstatt Ihre eigenen zu erstellen. Laden Sie Unity3D herunter, das wahrscheinlich "so reine Komponenten wie möglich" ist, und spielen Sie dort herum. Viel schnellere Einblicke, IMHO.
Imi
3
@lmi: Unity ist kein Entity Component System, obwohl es komponentenbasiert ist. ECS hat einige strengere Richtlinien ( denken Sie niemals an ein Muster als Regeln), als nur Spielobjektkomponenten zu haben und zu verwenden. Aufgrund einer Reihe von Artikeln ist ECS derzeit bei einigen Segmenten von Spieleentwicklern beliebt, daher gibt es viele Fragen zu ECS speziell und nicht zu komponentenbasiertem Design im Allgemeinen.
Sean Middleditch
12

Sie machen die Dinge zu kompliziert. Ich würde sogar sagen, dass selbst die Verwendung von komponentenbasiertem Design für ein so einfaches Spiel einfach übertrieben ist. Mach die Dinge so, dass dein Spiel schnell und einfach zu entwickeln ist. Komponenten helfen bei der Iteration in größeren Projekten mit einer Vielzahl von Verhaltensweisen und Spielobjektkonfigurationen, aber ihr Nutzen für ein so einfaches, genau definiertes Spiel ist fraglicher. Ich habe letztes Jahr darüber gesprochen: Sie können in wenigen Stunden lustige kleine Spiele erstellen, wenn Sie sich darauf konzentrieren , ein Spiel zu erstellen , anstatt sich an eine Architektur zu halten . Die Vererbung bricht zusammen, wenn Sie 100 oder sogar 20 verschiedene Arten von Objekten haben, aber es funktioniert gut, wenn Sie nur eine Handvoll haben.

Angenommen, Sie möchten weiterhin Komponenten für Lernzwecke verwenden, gibt es einige offensichtliche Probleme mit Ihrem Ansatz, die auffallen.

Machen Sie Ihre Komponenten nicht so klein. Es gibt keinen Grund, feinkörnige Komponenten wie "Bewegung" zu haben. Es gibt keine generische Bewegung in Ihrem Spiel. Sie haben Paddel, deren Bewegung eng mit der Eingabe oder der KI verbunden ist (und verwenden Geschwindigkeit, Beschleunigung, Restitution usw. nicht wirklich), und Sie haben den Ball, der über einen genau definierten Bewegungsalgorithmus verfügt. Haben Sie einfach eine PaddleController-Komponente und eine BouncingBall-Komponente oder etwas in dieser Richtung. Wenn Sie ein komplizierteres Spiel erhalten, können Sie sich Sorgen über eine allgemeinere PhysicsBody-Komponente machen (die in "echten" Engines im Grunde nur eine Verbindung zwischen dem Spielobjekt und dem von Havok / PhysX / Bullet / verwendeten internen API-Objekt darstellt). Box2D / etc.), Die eine größere Vielfalt von Situationen bewältigt.

Sogar eine Positionskomponente ist fraglich, aber sicherlich nicht ungewöhnlich. Physik-Engines haben normalerweise ihre eigene interne Vorstellung davon, wo sich ein Objekt befindet, Grafiken haben möglicherweise eine interpolierte Darstellung und AI hat möglicherweise eine weitere Darstellung derselben Daten in einem anderen Zustand. Es kann vorteilhaft sein, einfach jedem System die Möglichkeit zu geben, seine eigene Vorstellung von der Transformation in den systemeigenen Komponenten zu verwalten und dann eine reibungslose Kommunikation zwischen den Systemen sicherzustellen. Weitere Informationen finden Sie im BitSquid-Blogbeitrag zu Ereignisströmen .

Denken Sie bei benutzerdefinierten Physik-Engines daran, dass Sie Daten zu Ihren Komponenten haben dürfen. Möglicherweise enthält eine generische Pong-Physikkomponente Daten, die angeben, auf welchen Achsen sie sich bewegen kann (z. vec2(0,1)B. als Multiplikator für Paddel, die sich nur auf der Y-Achse vec2(1,1)bewegen können, und für den Ball, der angibt, dass sie sich bewegen kann), eine Flagge oder einen Schwimmer, die die Sprungkraft anzeigen (die Ball wäre in der Regel an 1.0und Paddel an0.0), Beschleunigungseigenschaften, Geschwindigkeit usw. Der Versuch, dies in eine Unmenge verschiedener Mikrokomponenten für jedes hochverwandte Datenelement aufzuteilen, widerspricht dem, was ECS ursprünglich tun sollte. Behalten Sie Dinge, die zusammen verwendet werden, soweit möglich in derselben Komponente bei und teilen Sie sie nur dann auf, wenn es einen großen Unterschied gibt, wie jedes Spielobjekt diese Daten verwendet. Es gibt ein Argument dafür, dass für Pong die Physik zwischen dem Ball und den Paddeln unterschiedlich genug ist, um separate Komponenten zu sein, aber für ein größeres Spiel gibt es wenig Grund, 20 Komponenten zu erstellen, um das zu tun, was in 1-3 gut funktioniert.

Denken Sie daran, wenn / wenn Ihre Version von ECS im Weg ist, tun Sie, was Sie brauchen, um Ihr Spiel tatsächlich zu machen, und vergessen Sie die hartnäckige Einhaltung eines Designmusters / einer Architektur.

Sean Middleditch
quelle
1
Wirklich danke dir! Ich weiß, dass ein ECS für ein kleines Spiel wie Pong nicht sehr gut skaliert. Aber ich habe es nur benutzt, um zu sehen, wie so etwas tatsächlich implementiert wird und wie es funktioniert. Ich habe die Komponenten so klein gemacht, weil ich das meistens in einigen Artikeln gelesen habe. Viele Komponenten zu haben und jede Komponente enthält nur elementare Daten. Verstehe ich Sie also richtig, dass Sie vorschlagen, eine Mischung aus Vererbung und ECS zu verwenden? Wie Sie sagen "der Ball und die Paddel sind unterschiedlich genug, um separate Komponenten zu sein". So gebe ich ihnen zum Beispiel sowohl eine Bewegungs- / Positionskomponente (vielleicht als eine Komponente) als auch
M0rgenstern
1
Was auch immer funktioniert. Konzentriere dich darauf, das Spiel zu machen. Ich hätte buchstäblich nur eine Komponente namens Ball, die die gesamte Logik für den Ball wie Bewegung, Hüpfen usw. enthält, und eine PaddleKomponente, die Eingaben entgegennimmt, aber das bin ich. Was für Sie am sinnvollsten ist und Ihnen aus dem Weg geht und Sie das Spiel machen lässt, ist der "richtige Weg", Dinge zu tun.
Sean Middleditch
3
Ich hätte buchstäblich nur eine Komponente namens Ball, die die gesamte Logik für den Ball wie Bewegung, Hüpfen usw. enthält, und eine Paddelkomponente, die Eingaben übernimmt, aber das bin ich. Und deshalb gibt es für jeden Programmierer eine Meinung darüber, "worum es bei einem Komponentensystem geht". Ich empfehle, es NICHT wie diesen Vorschlag zu tun, außer Sie denken völlig in klassischen Entitätssystemen und sind gezwungen, ein Komponentensystem zu verwenden, möchten aber nicht nach den tatsächlichen Unterschieden suchen.
Imi
2
@lmi: Nachdem wir an einigen großen Spielen / Engines mit Komponenten gearbeitet haben und aus erster Hand gesehen haben, warum wir Komponenten verwenden, sind übermäßig granulare Komponenten einfach mehr Probleme als sie wert sind. Komponenten sind kein Wundermittel. Sie sind eines von vielen Tools in der Toolbox eines Spieleentwicklers. Verwenden Sie sie so und an einem Ort, an dem sie helfen, und nicht so, dass sie dem System nur mehr mentalen und Laufzeit-Overhead hinzufügen. Wenn das einzige, was Ballphysik hat, der Ball ist, hat es keinen Vorteil, ihn von anderen Balleigenschaften zu trennen. Wenn sich dies ändert, teilen Sie es nur dann und dann auf.
Sean Middleditch
1
Ich stimme dem Prinzip zu, pragmatisch zu sein und ein bestimmtes Geplapper nicht in die Quere kommen zu lassen. ABER wenn ECS dieses triviale Spiel nicht ohne Abweichung bewältigen kann, welche Hoffnung gibt es für ein großes Spiel? Auch ich versuche zu lernen, wie man ECS effektiv einsetzt, aber ich versuche, der ECS-Philosophie so nahe wie möglich zu kommen. Andernfalls weiß ich, dass ich, sobald ich Ausnahmen und Sonderfälle mache, mit einem nicht zu wartenden Durcheinander enden werde.
Ken
-2

Meiner Meinung nach *) ist Ihr größtes Problem mit Komponenten Folgendes: Komponenten sind NICHT hier, um anderen zu sagen , was zu tun ist. Komponenten sind hier, um Dinge zu tun. Sie haben keine Komponente, um nur den Speicher einer Sache zu speichern, und lassen dann andere Komponenten daran arbeiten. Sie möchten Komponenten, die mit den erhaltenen Daten arbeiten.

Wenn Sie sehen, wie Sie auf das Vorhandensein anderer Komponenten testen (und dort Funktionen aufrufen), ist dies ein klares Zeichen dafür, dass eines von zwei Dingen zutrifft:

  • Sie möchten die Abhängigkeit tatsächlich invertieren: Die andere Komponente sollte Ereignisse / Nachrichten / Broadcasts / Hooks / jedoch abrufen, um ihre Logik als Antwort auf Ihre aktuelle Komponente auszuführen. Die aktuelle Komponente muss nicht einmal wissen, dass es eine "andere" Komponente gibt. Dies ist meistens der Fall, wenn Sie verschiedene Komponenten (auch in verschiedenen else / case-Blöcken) mit Funktionen aufrufen, die nicht wirklich mit Ihrer aktuellen Methode verbunden sind. Denken Sie an all dies Invalidate()oder SetDirty()rufen Sie andere Komponenten auf.
  • Möglicherweise haben Sie zu viele Komponenten. Wenn zwei Komponenten einfach nicht ohne einander leben können und ständig Daten abrufen und Methoden miteinander aufrufen müssen, führen Sie sie einfach zusammen. Offensichtlich sind die Funktionen, die sie bieten, so eng miteinander verbunden, dass es eigentlich nur eine Sache ist.

Dies gilt übrigens für alle Arten von Systemen, nicht nur für Entity / Component-Systeme, sondern auch für die klassische Vererbung mit einfachen "GameObject" - oder sogar Bibliotheksfunktionen.

*) Wirklich nur meins . Die Meinungen über Whats Da Real Component Systemz (TM) sind sehr unterschiedlich.

Imi
quelle