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?
quelle
statecomponent
undanimationcomponent
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.Antworten:
IMHO sollte die
Movement
Komponente den aktuellen Status (Movement.state
) halten und dieAnimation
Komponente sollte Änderungen derMovement.state
aktuellen 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 MittelAnimation
davon abhängenMovement
.Eine alternative Struktur wäre, eine generische
State
Komponente zu haben , dieAnimation
beobachtet undMovement
modifiziert, 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.
Animation
wü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.
quelle
Movement
würde kontrollierenState
(nicht beobachten). Letzter Fall: JaMovement
,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.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:
quelle
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/
quelle