Ich entwerfe eine Game-Engine für ein Top-Down-2D-Shooter-Multiplayer-Spiel, das ich für andere Top-Down-Shooter-Spiele wiederverwenden möchte. Im Moment denke ich darüber nach, wie so etwas wie ein Entitätssystem in ihm gestaltet werden sollte. Zuerst habe ich darüber nachgedacht:
Ich habe eine Klasse namens EntityManager. Es sollte eine Methode namens Update und eine andere namens Draw implementieren. Der Grund, warum ich Logic und Rendering trenne, ist, dass ich die Draw-Methode weglassen kann, wenn ein eigenständiger Server ausgeführt wird.
EntityManager besitzt eine Liste von Objekten des Typs BaseEntity. Jede Entität besitzt eine Liste von Komponenten wie EntityModel (die darstellbare Darstellung einer Entität), EntityNetworkInterface und EntityPhysicalBody.
EntityManager verfügt auch über eine Liste von Komponentenmanagern wie EntityRenderManager, EntityNetworkManager und EntityPhysicsManager. Jeder Komponentenmanager speichert Verweise auf die Entitätskomponenten. Es gibt verschiedene Gründe, diesen Code aus der eigenen Klasse der Entität zu verschieben und ihn stattdessen kollektiv auszuführen. Zum Beispiel verwende ich eine externe Physikbibliothek, Box2D, für das Spiel. In Box2D fügen Sie zuerst die Körper und Formen zu einer Welt hinzu (in diesem Fall Eigentum des EntityPhysicsManager) und fügen Kollisionsrückrufe hinzu (die an das Entitätsobjekt selbst in meinem System gesendet würden). Dann führen Sie eine Funktion aus, die alles im System simuliert. Ich finde es schwierig, eine bessere Lösung dafür zu finden, als dies in einem externen Komponentenmanager wie diesem zu tun.
Die Erstellung von Entitäten erfolgt folgendermaßen: EntityManager implementiert die Methode RegisterEntity (entityClass, factory), die registriert, wie eine Entität dieser Klasse erstellt wird. Es implementiert auch die Methode CreateEntity (entityClass), die ein Objekt vom Typ BaseEntity zurückgibt.
Nun kommt mein Problem: Wie würde der Verweis auf eine Komponente bei den Komponentenmanagern registriert? Ich habe keine Ahnung, wie ich die Komponentenmanager aus einer Fabrik / Schließung referenzieren würde.
quelle
Antworten:
Systeme sollten ein Schlüsselwertpaar von Entity to Component in einer Art Map, Dictionary Object oder Associative Array speichern (abhängig von der verwendeten Sprache). Außerdem würde ich mir beim Erstellen Ihres Entitätsobjekts keine Gedanken darüber machen, es in einem Manager zu speichern, es sei denn, Sie müssen es von einem der Systeme abmelden können. Eine Entität ist eine Zusammenstellung von Komponenten, sollte jedoch keine Komponentenaktualisierungen verarbeiten. Das sollte von den Systemen übernommen werden. Behandeln Sie Ihre Entität stattdessen als einen Schlüssel, der allen in den Systemen enthaltenen Komponenten zugeordnet ist, sowie als einen Kommunikationsknoten, über den diese Komponenten miteinander kommunizieren können.
Der große Teil der Entity-Component-System-Modelle besteht darin, dass Sie die Möglichkeit implementieren können, ganz einfach Nachrichten von einer Komponente an die übrigen Komponenten einer Entität zu übergeben. Auf diese Weise kann eine Komponente mit einer anderen Komponente kommunizieren, ohne zu wissen, wer diese Komponente ist oder wie mit der zu ändernden Komponente umgegangen wird. Stattdessen wird eine Nachricht übergeben und die Komponente kann sich selbst ändern (falls vorhanden).
Zum Beispiel würde ein Positionssystem nicht viel Code enthalten, sondern nur Entitätsobjekte verfolgen, die ihren Positionskomponenten zugeordnet sind. Wenn sich eine Position ändert, kann eine Nachricht an die betreffende Entität gesendet werden, die wiederum an alle Komponenten dieser Entität weitergeleitet wird. Eine Position ändert sich aus welchem Grund auch immer? Das Positionssystem sendet der Entität eine Nachricht, die besagt, dass sich die Position geändert hat, und dass die Bildwiedergabekomponente dieser Entität diese Nachricht abruft und aktualisiert, wo sie sich als Nächstes selbst zeichnet.
Umgekehrt muss ein Physiksystem wissen, was alle seine Objekte tun. Es muss in der Lage sein, alle Weltobjekte zu sehen, um Kollisionen zu testen. Wenn eine Kollision auftritt, wird die Richtungskomponente der Entität aktualisiert, indem eine Art "Richtungsänderungsnachricht" an die Entität gesendet wird, anstatt direkt auf die Entitätskomponente zu verweisen. Dies entkoppelt den Manager von der Notwendigkeit, mithilfe einer Nachricht die Richtung ändern zu müssen, anstatt sich darauf zu verlassen, dass eine bestimmte Komponente vorhanden ist (die möglicherweise überhaupt nicht vorhanden ist). In diesem Fall stößt die Nachricht nur auf taube Ohren, anstatt auf einen Fehler auftreten, weil ein erwartetes Objekt nicht vorhanden war).
Sie werden einen enormen Vorteil davon bemerken, da Sie erwähnt haben, dass Sie eine Netzwerkschnittstelle haben. Eine Netzwerkkomponente würde alle eingehenden Nachrichten abhören, über die alle anderen Bescheid wissen sollten. Es liebt den Klatsch. Wenn das Netzwerksystem aktualisiert wird, senden die Netzwerkkomponenten diese Nachrichten an andere Netzwerksysteme auf anderen Clientcomputern, die sie dann erneut an alle anderen Komponenten senden, um die Playerpositionen usw. zu aktualisieren. Möglicherweise ist eine spezielle Logik erforderlich, damit nur bestimmte Entitäten dies können Nachrichten über das Netzwerk senden, aber das ist das Schöne am System. Sie können diese Logik einfach steuern lassen, indem Sie die richtigen Dinge dafür registrieren.
Zusamenfassend:
Entität ist eine Zusammensetzung von Komponenten, die Nachrichten empfangen können. Die Entität kann Nachrichten empfangen und diese an alle Komponenten delegieren, um sie zu aktualisieren. (Position geändert Nachricht, Geschwindigkeitsänderung Richtung, etc.) Es ist wie eine zentrale Mailbox, die alle Komponenten voneinander hören können, anstatt direkt miteinander zu sprechen.
Die Komponente ist ein kleiner Teil einer Entität, in dem ein bestimmter Status der Entität gespeichert ist. Diese können bestimmte Nachrichten analysieren und andere Nachrichten wegwerfen. Beispielsweise würde sich eine "Richtungskomponente" nur um "Richtungsänderungsnachrichten" kümmern, nicht aber um "Positionsänderungsnachrichten". Komponenten aktualisieren ihren eigenen Status basierend auf Nachrichten und aktualisieren dann den Status anderer Komponenten, indem sie Nachrichten von ihrem System senden.
Das System verwaltet alle Komponenten eines bestimmten Typs und ist dafür verantwortlich, die Komponenten in jedem Frame zu aktualisieren und Nachrichten von den von ihnen verwalteten Komponenten an die Entitäten zu senden, zu denen die Komponenten gehören
Systeme könnten in der Lage sein, alle ihre Komponenten parallel zu aktualisieren und alle Nachrichten zu speichern, wenn sie gehen. Wenn die Ausführung der Aktualisierungsmethoden aller Systeme abgeschlossen ist, bitten Sie jedes System, seine Nachrichten in einer bestimmten Reihenfolge zu senden. Kontrollen zuerst möglich, gefolgt von Physik, gefolgt von Richtung, Position, Rendering usw. Es ist wichtig, in welcher Reihenfolge sie gesendet werden, da ein Richtungswechsel in der Physik eine kontrollbasierte Richtungsänderung immer ausgleichen sollte.
Hoffe das hilft. Es ist eine Hölle von Design Patterns, aber es ist lächerlich mächtig, wenn es richtig gemacht wird.
quelle
Ich verwende ein ähnliches System in meiner Engine und so, wie ich es gemacht habe, enthält jede Entität eine Liste von Komponenten. Über den EntityManager kann ich alle Entitäten abfragen und feststellen, welche eine bestimmte Komponente enthalten. Beispiel:
Offensichtlich ist dies nicht der genaue Code (Sie benötigen tatsächlich eine Vorlagenfunktion, um nach verschiedenen Komponententypen zu suchen, anstatt sie zu verwenden
typeof
), aber das Konzept ist da. Dann können Sie diese Ergebnisse verwenden, die gesuchte Komponente referenzieren und sie bei Ihrer Fabrik registrieren. Dies verhindert eine direkte Kopplung zwischen Komponenten und ihren Managern.quelle
typedef long long int Entity
; Eine Komponente ist ein Datensatz (möglicherweise eine Objektklasse oder nur einestruct
), der einen Verweis auf die Entität enthält, der sie zugeordnet ist. und ein System wäre eine Methode oder eine Sammlung von Methoden. Das ECS-Modell ist nicht sehr kompatibel mit dem OOP-Modell, obwohl eine Komponente ein (größtenteils) Nur-Daten-Objekt und ein System ein Nur-Code-Singleton-Objekt sein kann, dessen Zustand sich in Komponenten befindet ... obwohl es sich um "hybride" Systeme handelt häufiger als "reine", verlieren sie viele der angeborenen Vorteile.1) Ihrer Factory-Methode sollte ein Verweis auf den EntityManager übergeben werden, der sie aufgerufen hat (ich verwende C # als Beispiel):
2) Lassen Sie CreateEntity neben der Klasse / dem Typ der Entität auch eine ID erhalten (z. B. eine Zeichenfolge, eine Ganzzahl, die Sie selbst bestimmen), und registrieren Sie die erstellte Entität automatisch in einem Dictionary, indem Sie diese ID als Schlüssel verwenden:
3) Fügen Sie EntityManager einen Getter hinzu, um eine Entität nach ID abzurufen:
Und das ist alles, was Sie brauchen, um innerhalb Ihrer Factory-Methode auf einen ComponentManager zu verweisen. Zum Beispiel:
Neben Id können Sie auch eine Art Type-Eigenschaft verwenden (eine benutzerdefinierte Aufzählung oder einfach das Typensystem der Sprache verwenden) und einen Getter erstellen, der alle BaseEntities eines bestimmten Typs zurückgibt.
quelle
typedef unsigned long long int EntityID;
; Das Ideal ist, dass jedes System auf einer separaten CPU oder einem separaten Host ausgeführt werden kann und nur Komponenten abgerufen werden müssen, die für dieses System relevant bzw. in diesem System aktiv sind. Bei einem Entity-Objekt muss möglicherweise auf jedem Host dasselbe Entity-Objekt instanziiert werden, was die Skalierung erschwert. Bei einem reinen Entity-Component-System-Modell wird die Verarbeitung auf Knoten (Prozesse, CPUs oder Hosts) nach System und nicht in der Regel nach Entität aufgeteilt.