Wie aktualisiere ich Entitätszustände und Animationen in einem komponentenbasierten Spiel?

10

Ich versuche, ein komponentenbasiertes Entitätssystem für Lernzwecke zu entwerfen (und später für einige Spiele zu verwenden), und ich habe einige Probleme beim Aktualisieren von Entitätszuständen.

Ich möchte keine update () -Methode in der Komponente haben, um Abhängigkeiten zwischen Komponenten zu vermeiden.

Was ich derzeit im Sinn habe, ist, dass Komponenten Daten enthalten und Systemaktualisierungskomponenten.

Wenn ich also ein einfaches 2D-Spiel mit einigen Entitäten (z. B. Spieler, Feind1, Feind2) habe, die Transformations-, Bewegungs-, Status-, Animations- und Rendering-Komponenten enthalten, sollte ich Folgendes haben:

  • Ein Bewegungssystem, das alle Bewegungskomponenten verschiebt und die Statuskomponenten aktualisiert
  • Und ein RenderSystem, das die Animationskomponenten aktualisiert (die Animationskomponente sollte eine Animation (dh eine Reihe von Frames / Texturen) für jeden Status haben und diese aktualisieren, bedeutet, dass die Animation ausgewählt wird, die dem aktuellen Status entspricht (z. B. Springen, Bewegen_Links usw.) Aktualisierung des Frame-Index). Anschließend aktualisiert das RenderSystem die Renderkomponenten mit der Textur, die dem aktuellen Frame der Animation jeder Entität entspricht, und rendert alles auf dem Bildschirm.

Ich habe einige Implementierungen wie das Artemis-Framework gesehen, weiß aber nicht, wie ich diese Situation lösen soll:

Nehmen wir an, mein Spiel hat die folgenden Entitäten. Jede Entität hat eine Reihe von Zuständen und eine Animation für jeden Zustand:

  • Spieler: "Leerlauf", "Moving_right", "Springen"
  • feind1: "bewegung_auf", "bewegung_abwärts"
  • Feind2: "Moving_Left", "Moving_Right"

Was sind die am meisten akzeptierten Ansätze, um den aktuellen Status jeder Entität zu aktualisieren? Das einzige, was ich mir vorstellen kann, sind separate Systeme für jede Gruppe von Entitäten und separate Status- und Animationskomponenten, sodass ich PlayerState, PlayerAnimation, Enemy1State, Enemy1Animation ... PlayerMovementSystem, PlayerRenderingSystem ... hätte, aber ich denke, das ist schlecht Lösung und bricht den Zweck eines komponentenbasierten Systems.

Wie Sie sehen können, bin ich hier ziemlich verloren, daher würde ich mich über jede Hilfe sehr freuen.

EDIT: Ich denke, die Lösung, um diese Arbeit so zu machen, wie ich es beabsichtige, ist folgende:

Sie machen statecomponent und animationcomponent generisch genug, um für alle Entitäten verwendet zu werden. Die darin enthaltenen Daten sind der Modifikator für Änderungen, z. B. welche Animationen abgespielt werden oder welche Zustände verfügbar sind. - Byte56

Jetzt versuche ich herauszufinden, wie diese beiden Komponenten generisch genug gestaltet werden, damit ich sie wiederverwenden kann. Könnte es eine gute Lösung sein, für jeden Zustand eine UID zu haben (z. B. Gehen, Laufen ...) und Animationen in einer Karte in der Animationskomponente zu speichern, die mit dieser Kennung versehen ist?

Miviclin
quelle
Ich gehe davon aus, dass Sie Folgendes gesehen haben: Statusänderungen in Entitäten oder Komponenten ? Unterscheidet sich Ihre Frage grundlegend von dieser?
MichaelHouse
@ Byte56 Ja, das habe ich vor einigen Stunden gelesen. Die Lösung, die Sie dort vorgeschlagen haben, ähnelt der Idee, die ich hier vorgestellt habe. Mein Problem tritt jedoch auf, wenn StateComponent und AnimationComponent nicht für alle Entitäten im System gleich sind. Sollte ich dieses System in kleinere Systeme aufteilen, die Gruppen von Entitäten verarbeiten, die dieselben möglichen Zustände und Animationen haben? (siehe letzten Teil meines ursprünglichen Beitrags für eine bessere Klarstellung)
Miviclin
1
Sie machen statecomponentund animationcomponentgenerisch genug, um für alle Entitäten verwendet zu werden. Die darin enthaltenen Daten sind der Modifikator für Änderungen, z. B. welche Animationen abgespielt werden oder welche Zustände verfügbar sind.
MichaelHouse
Wenn Sie über Abhängigkeit sprechen, meinen Sie damit Datenabhängigkeit oder Abhängigkeit von der Ausführungsreihenfolge? Außerdem muss das MovementSystem in Ihrem Lösungsvorschlag jetzt alle verschiedenen Arten implementieren, wie sich etwas bewegen kann. Dies sieht so aus, als würde es die Idee des komponentenbasierten Systems brechen ...
ADB
@ADB Ich spreche von Datenabhängigkeit. Um die Animation zu aktualisieren (z. B. Wechsel von move_right-Animation zu move_left-Animation), muss ich den aktuellen Status der Entität kennen und sehe nicht, wie diese beiden Komponenten allgemeiner gestaltet werden können.
Miviclin

Antworten:

5

IMHO sollte die MovementKomponente den aktuellen Status ( Movement.state) halten und die AnimationKomponente sollte Änderungen der Movement.stateaktuellen Animation ( Animation.animation) beobachten und entsprechend aktualisieren , indem eine einfache Suche der Status-ID zur Animation verwendet wird (wie am Ende des OP vorgeschlagen). Offensichtlich wird dieses Mittel Animationdavon abhängen Movement.

Eine alternative Struktur wäre, eine generische StateKomponente zu haben , die Animationbeobachtet und Movementmodifiziert, was im Grunde genommen eine Modellansichtssteuerung ist (in diesem Fall eine Zustandsanimationsbewegung).

Eine andere Alternative wäre, die Entität ein Ereignis an ihre Komponenten senden zu lassen, wenn sich ihr Status ändert. Animationwürde dieses Ereignis anhören und seine Animation entsprechend aktualisieren. Dadurch wird die Abhängigkeit beseitigt, obwohl Sie argumentieren könnten, dass die abhängige Version ein transparenteres Design ist.

Viel Glück.

Torious
quelle
Animation beobachtet also Staat und Staat beobachtet Bewegung ... Abhängigkeiten sind immer noch da, aber ich könnte es versuchen. Wäre die letzte Alternative ungefähr so: Bewegung benachrichtigt Änderungen an der Entität und die Entität sendet ein Ereignis an State, und dann würde der gleiche Vorgang für State und Animation wiederholt? Wie könnte sich dieser Ansatz auf die Leistung auswirken?
Miviclin
Erster Fall: Movementwürde kontrollieren State (nicht beobachten). Letzter Fall: Ja Movement, entity.dispatchEvent(...);oder so, und alle anderen Komponenten, die diese Art von Ereignis hören, erhalten es. Die Leistung ist natürlich schlechter als reine Methodenaufrufe, aber nicht viel. Sie können beispielsweise Ereignisobjekte bündeln. Übrigens müssen Sie die Entität nicht als "Ereignisknoten" verwenden, sondern können auch einen dedizierten "Ereignisbus" verwenden, sodass Ihre Entitätsklasse vollständig aus dieser Entität herausfällt.
Torious
2

Wenn der STATE nur in Animationen verwendet wird, müssen Sie dies nicht einmal anderen Komponenten aussetzen. Wenn es mehr als eine Verwendung hat, müssen Sie es verfügbar machen.

Das von Ihnen beschriebene System von Komponenten / Subsystemen fühlt sich eher hierarchiebasiert als komponentenbasiert an. Was Sie als Komponenten bezeichnen, sind schließlich Datenstrukturen. Das bedeutet nicht, dass es ein schlechtes System ist, nur dass ich nicht denke, dass es zu gut zum komponentenbasierten Ansatz passt.

Wie Sie bereits bemerkt haben, sind Abhängigkeiten in komponentenbasierten Systemen ein großes Problem. Es gibt verschiedene Möglichkeiten, damit umzugehen. Einige verlangen von jeder Komponente, dass sie ihre Abhängigkeiten deklariert und eine strenge Überprüfung durchführt. Andere fragen nach Komponenten, die eine bestimmte Schnittstelle implementieren. Wieder andere verweisen auf die abhängigen Komponenten, wenn sie jede von ihnen instanziieren.

Unabhängig von der von Ihnen verwendeten Methode benötigen Sie ein GameObject, um als Sammlung von Komponenten zu fungieren. Was GameObject bietet, kann sehr unterschiedlich sein und Sie können Ihre Abhängigkeiten zwischen Komponenten vereinfachen, indem Sie einige häufig verwendete Daten auf die GameObject-Ebene verschieben. Unity macht das mit der Transformation zum Beispiel, indem Sie alle Spielobjekte dazu zwingen, eines zu haben.

In Bezug auf das Problem, das Sie von verschiedenen Zuständen / Animationen für verschiedene Spielobjekte verlangen, würde ich Folgendes tun. Erstens würde ich in dieser Phase der Implementierung nicht allzu ausgefallen sein: Implementieren Sie nur das, was Sie jetzt benötigen, um es zum Laufen zu bringen, und fügen Sie dann nach Bedarf Schnickschnack hinzu.

Ich würde also mit einer 'State'-Komponente beginnen: PlayerStateComponent, Enemy1State, Enemy2State. Die Zustandskomponente würde dafür sorgen, dass der Zustand zum richtigen Zeitpunkt geändert wird. Der Status ist so ziemlich alles, was Ihre Objekte haben werden, sodass er sich im GameObject befinden kann.

Dann würde es einen AnimationCompoment geben. Dies hätte ein Wörterbuch mit Animationen, die auf den Staat zugeschnitten sind. Ändern Sie in update () die Animation, wenn sich der Status ändert.

Es gibt einen großartigen Artikel über das Erstellen von Frameworks, den ich nicht finden kann. Wenn Sie keine Erfahrung in der Domäne haben, sollten Sie ein Problem auswählen und die einfachste Implementierung durchführen, die das aktuelle Problem löst . Dann fügen Sie ein weiteres Problem / einen weiteren Anwendungsfall hinzu und erweitern das Framework im Laufe der Zeit, sodass es organisch wächst. Ich mag diesen Ansatz sehr, besonders wenn Sie wie Sie mit einem neuen Konzept arbeiten.

Die von mir vorgeschlagene Implementierung ist ziemlich naiv, aber hier sind einige mögliche Verbesserungen, wenn Sie weitere Anwendungsfälle hinzufügen:

  • Ersetzen Sie die GameObject-Variable durch ein Wörterbuch. Jede Komponente verwendet das Wörterbuch zum Speichern von Werten. (Achten Sie darauf, Kollision richtig zu behandeln ...)
  • Ersetzen Sie stattdessen das Wörterbuch der einfachen Werte durch Referenzen: class FloatVariable () {public value [...]}
  • Erstellen Sie anstelle mehrerer Statuskomponenten eine generische StateComponent, in der Sie variable Statusmaschinen erstellen können. Sie benötigen einen allgemeinen Satz von Bedingungen, unter denen sich ein Status ändern kann: Tastendruck, Mauseingabe, Variablenänderungen (Sie können dies mit der obigen FloatVariable verknüpfen).
ADB
quelle
Dieser Ansatz funktioniert, ich habe vor einem Jahr etwas Ähnliches implementiert, aber das Problem dabei ist, dass fast jede Komponente von anderen Komponenten abhängt, sodass es mir weniger flexibel erscheint. Ich habe auch darüber nachgedacht, die gängigsten Komponenten (z. B. transformieren, rendern, angeben ...) in die Entität zu verschieben, aber ich denke, dies bricht den Zweck von Komponenten, da einige von ihnen an die Entität gebunden sind und einige Entitäten sie möglicherweise nicht benötigen. Aus diesem Grund versuche ich, es mit Systemen neu zu gestalten, die für die Aktualisierung der Logik verantwortlich sind, damit die Komponenten nichts voneinander wissen, da sie sich nicht selbst aktualisieren.
Miviclin
0

Zusätzlich zur Antwort von ADB können Sie http://en.wikipedia.org/wiki/Dependency_injection verwenden. Dies ist hilfreich , wenn Sie viele Komponenten erstellen müssen , indem Sie sie als Verweise auf ihre Konstruktoren übergeben. Natürlich hängen sie immer noch voneinander ab (wenn dies in Ihrer Codebasis erforderlich ist), aber Sie können all diese Abhängigkeiten an einem Ort ablegen, an dem die Abhängigkeiten eingerichtet sind und der Rest Ihres Codes nichts über die Abhängigkeit wissen muss.

Dieser Ansatz funktioniert auch gut, wenn Sie Schnittstellen verwenden, da jede Komponentenklasse nur anfordert, was sie benötigt oder wo sie registriert werden muss, und nur das Abhängigkeitsinjektionsframework (oder der Ort, an dem Sie alles einrichten, normalerweise die App) weiß, wer was benötigt .

Bei einfachen Systemen, bei denen Sie möglicherweise ohne DI oder sauberen Code davonkommen, klingen Ihre RenderingSystem-Klassen so, als müssten Sie sie statisch aufrufen oder zumindest in jeder Komponente verfügbar haben, was sie ziemlich voneinander abhängig und schwer zu ändern macht. Wenn Sie an einem saubereren Ansatz interessiert sind, überprüfen Sie die Links des DI-Wiki-Links oben und lesen Sie mehr über Clean Code: http://clean-code-developer.com/

exDreamDuck
quelle
Ich habe bereits ein System, in dem Komponenten ziemlich voneinander abhängig sind. Ich habe dort stark von der Abhängigkeitsinjektion Gebrauch gemacht, und obwohl ich sie tiefen Hierarchien vorziehe, versuche ich, eine neue zu erstellen, um eine Komponentenkopplung zu vermeiden, wenn dies möglich wäre. Ich würde nichts statisch anrufen. Ich hätte einen ComponentManager, auf den jedes System Zugriff hat (jedes System sollte einen Verweis darauf haben), und das RendererSystem würde alle Animationskomponenten vom Komponentenmanager abrufen und den aktuellen Status jeder Animation rendern.
Miviclin