Einführung
Entity-Component-Systeme sind eine objektorientierte Architekturtechnik.
Es besteht kein allgemeiner Konsens darüber, was der Begriff bedeutet, genau wie bei der objektorientierten Programmierung. Es ist jedoch klar, dass Entity-Component-Systeme speziell als architektonische Alternative zur Vererbung gedacht sind . Vererbungshierarchien sind ein natürlicher Ausdruck dafür, was ein Objekt ist . Bei bestimmten Arten von Software (z. B. Spielen) möchten Sie jedoch eher ausdrücken, was ein Objekt tut .
Es handelt sich um ein anderes Objektmodell als das „Klassen- und Vererbungsmodell“, an das Sie wahrscheinlich aus der Arbeit in C ++ oder Java gewöhnt sind. Entitäten sind genauso aussagekräftig wie Klassen, genau wie Prototypen wie in JavaScript oder Self - alle diese Systeme können in Bezug auf einander implementiert werden.
Beispiele
Angenommen, dies Player
ist eine Entität mit Position
, Velocity
und KeyboardControlled
Komponenten, die die offensichtlichen Dinge tun.
entity Player:
Position
Velocity
KeyboardControlled
Wir wissen, Position
dass davon Velocity
und Velocity
von etwas betroffen sein muss KeyboardControlled
. Die Frage ist, wie wir diese Effekte modellieren möchten.
Entitäten, Komponenten und Systeme
Angenommen, Komponenten haben keine Referenzen zueinander. ein externes Physics
System durchläuft alle Velocity
Komponenten und aktualisiert die Position
der entsprechenden Entität; Ein Input
System durchläuft alle KeyboardControlled
Komponenten und aktualisiert die Velocity
.
Player
+--------------------+
| Position | \
| | Physics
/ | Velocity | /
Input | |
\ | KeyboardControlled |
+--------------------+
Dies erfüllt die Kriterien:
Keine Spiel- / Geschäftslogik wird von der Entität ausgedrückt.
Komponenten speichern Daten, die das Verhalten beschreiben.
Die Systeme sind jetzt für die Behandlung von Ereignissen und die Umsetzung des von den Komponenten beschriebenen Verhaltens verantwortlich. Sie sind auch für die Handhabung von Interaktionen zwischen Entitäten verantwortlich, z. B. für Kollisionen.
Einheiten und Komponenten
Allerdings wird angenommen , dass Komponenten zu tun haben Referenzen zueinander. Jetzt ist die Entität einfach ein Konstruktor, der einige Komponenten erstellt, sie zusammenfügt und ihre Lebensdauer verwaltet:
class Player:
construct():
this.p = Position()
this.v = Velocity(this.p)
this.c = KeyboardControlled(this.v)
Die Entität kann jetzt Eingabe- und Aktualisierungsereignisse direkt an ihre Komponenten senden. Velocity
würde auf Aktualisierungen und KeyboardControlled
auf Eingaben reagieren. Dies erfüllt immer noch unsere Kriterien:
Die Entität ist ein "dummer" Container, der Ereignisse nur an Komponenten weiterleitet.
Jede Komponente führt ein eigenes Verhalten aus.
Hier werden Komponentenwechselwirkungen explizit und nicht von außen von einem System auferlegt. Die Daten, die ein Verhalten beschreiben (wie hoch ist die Geschwindigkeit?) Und der Code, der es ausführt (wie hoch ist die Geschwindigkeit?), Sind gekoppelt, jedoch auf natürliche Weise. Die Daten können als Parameter für das Verhalten angesehen werden. Und einige Komponenten wirken überhaupt nicht - a Position
ist das Verhalten , an einem Ort zu sein .
Interaktionen können auf der Ebene der Entität ("wenn eine Player
mit einer Enemy
... kollidiert ") oder auf der Ebene einzelner Komponenten ("wenn eine Entität Life
mit einer Entität mit Strength
... kollidiert ") behandelt werden.
Komponenten
Was ist der Grund für die Existenz des Unternehmens? Wenn es sich lediglich um einen Konstruktor handelt, können wir ihn durch eine Funktion ersetzen, die eine Reihe von Komponenten zurückgibt. Wenn wir später Entitäten nach ihrem Typ abfragen möchten, können wir auch eine Tag
Komponente haben, mit der wir genau das tun können:
function Player():
t = Tag("Player")
p = Position()
v = Velocity(p)
c = KeyboardControlled(v)
return {t, p, v, c}
Interaktionen müssen jetzt durch abstrakte Abfragen behandelt werden, wodurch Ereignisse vollständig von Entitätstypen entkoppelt werden. Es sind nicht mehr Entitätstypen abzufragen - beliebige Tag
Daten werden wahrscheinlich besser zum Debuggen verwendet als die Spielelogik.
Fazit
Entitäten sind keine Funktionen, Regeln, Akteure oder Datenflusskombinatoren. Sie sind Substantive, die konkrete Phänomene modellieren - mit anderen Worten, sie sind Objekte. Wikipedia sagt: Entity-Component-Systeme sind ein Software-Architekturmuster zur Modellierung allgemeiner Objekte.
NEIN. Und ich bin überrascht, wie viele Leute anders abgestimmt haben!
Paradigma
Es ist datenorientierte aka Datengetriebene , weil wir über die reden Architektur und nicht die Sprache es in geschrieben. Architekturen sind Realisierungen von Arten oder Programmierparadigmen , die in der Regel unadvisably um in einer bestimmten Sprache gearbeitet werden.
Funktionell?
Ihr Vergleich zur funktionalen / prozeduralen Programmierung ist ein relevanter und aussagekräftiger Vergleich. Beachten Sie jedoch, dass sich eine "funktionale" Sprache vom "prozeduralen" Paradigma unterscheidet . Und Sie können ein ECS in einer funktionalen Sprache wie Haskell implementieren , was die Leute getan haben.
Wo Zusammenhalt passiert
Ihre Beobachtung ist relevant und genau richtig :
ECS / ES ist nicht EC / CE
Es gibt einen Unterschied zwischen den komponentenbasierten Architekturen "Entity-Component" und "Entity-Component-System". Da dies ein sich entwickelndes Entwurfsmuster ist, habe ich gesehen, dass diese Definitionen synonym verwendet werden. "EC" - oder "CE" - oder "Entity-Component" -Architekturen ordnen Verhalten in Komponenten ein , wohingegen "ES" - oder "ECS" -Architekturen Verhalten in Systemen ordnen . Hier sind einige ECS-Artikel, die beide irreführende Nomenklaturen verwenden, aber die allgemeine Vorstellung vermitteln:
Wenn Sie versuchen, diese Begriffe im Jahr 2015 zu verstehen, stellen Sie sicher, dass der Verweis auf "Entity Component System" nicht "Entity-Component-Architektur" bedeutet.
quelle
Entity Component Systems (ECSs) können je nach Systemdefinition in OOP- oder funktionaler Weise programmiert werden.
OOP Weg:
Ich habe an Spielen gearbeitet, in denen eine Entität ein Objekt war, das aus verschiedenen Komponenten bestand. Die Entität verfügt über eine Aktualisierungsfunktion, die das vorhandene Objekt ändert, indem sie nacheinander update für alle ihre Komponenten aufruft. Dies ist eindeutig OOP im Stil - das Verhalten ist mit Daten verknüpft und die Daten sind veränderlich. Entitäten sind Objekte mit Konstruktoren / Destruktoren / Aktualisierungen.
Mehr funktionale weg:
Eine Alternative besteht darin, dass die Entität Daten ohne Methoden ist. Diese Entität kann für sich existieren oder einfach eine ID sein, die mit verschiedenen Komponenten verknüpft ist. Auf diese Weise ist es möglich (aber nicht allgemein üblich), voll funktionsfähig zu sein und unveränderliche Entitäten und reine Systeme zu haben, die neue Komponentenzustände erzeugen.
Es scheint (aus eigener Erfahrung), dass der letztere Weg aus gutem Grund mehr Bodenhaftung gewinnt. Die Trennung von Entitätsdaten und Verhalten führt zu flexiblerem und wiederverwendbarem Code (imo). Insbesondere die Verwendung von Systemen zum Aktualisieren von Komponenten / Entitäten in Stapeln kann leistungsfähiger sein und die Komplexität von Inter-Entity-Messaging, die viele OOP-ECS-Systeme plagt, vollständig vermeiden.
TLDR: Sie können es auf beide Arten tun, aber ich würde argumentieren, dass die Vorteile guter Entitätskomponentensysteme von ihrer funktionaleren Natur herrühren.
quelle
Datenorientierte Entität Komponentensysteme können mit objektorientierten Paradigmen koexistieren: - Komponentensysteme eignen sich für Polymorphismus. - Komponenten können sowohl POD (einfache alte Daten) als auch ALSO-Objekte (mit einer Klasse und Methoden) sein, und das Ganze ist immer noch "datenorientiert", vorausgesetzt, dass Komponentenklassenmethoden nur Daten manipulieren, die dem lokalen Objekt gehören.
Wenn Sie diesen Pfad auswählen, wird empfohlen, die Verwendung virtueller Methoden zu vermeiden, da Ihre Komponente bei Vorhandensein dieser Methoden nicht mehr nur aus Komponentendaten besteht und der Aufruf dieser Methoden mehr kostet - dies ist keine COM. Halten Sie Ihre Komponentenklassen in der Regel frei von externen Verweisen.
Ein Beispiel wäre vec2 oder vec3, ein Datencontainer mit einigen Methoden zum Berühren dieser Daten und nichts weiter.
quelle
Ich halte ECS für grundlegend anders als OOP und neige dazu, es so zu sehen, wie Sie es tun, als funktionaler oder vor allem prozeduraler Natur mit einer sehr deutlichen Trennung von Daten und Funktionalität. Es gibt auch einen Anschein für eine Programmierung, die sich mit zentralen Datenbanken befasst. Natürlich bin ich die schlechteste Person, wenn es um formale Definitionen geht. Es geht mir nur darum, wie die Dinge sind und nicht wie sie konzeptionell definiert sind.
Ich gehe von einer Art ECS aus, bei der Komponenten Datenfelder aggregieren und sie öffentlich / global zugänglich machen, Entitäten Komponenten aggregieren und Systeme Funktionen / Verhalten für diese Daten bereitstellen. Das führt zu radikal schwierigen architektonischen Merkmalen, die wir normalerweise als objektorientierte Codebasis bezeichnen würden.
Und natürlich verschwimmen die Grenzen in der Art und Weise, wie Menschen ein ECS entwerfen / implementieren, und es gibt Debatten darüber, was genau ein ECS überhaupt ausmacht. Solche Grenzen verschwimmen jedoch auch im Code, der in einer von uns als funktional oder prozedural bezeichneten Sprache geschrieben ist. Bei all dieser Unschärfe scheint mir die grundlegende Konstante eines ECS mit einer Trennung von Daten und Funktionalität der funktionalen oder prozeduralen Programmierung viel näher zu sein als OOP.
Ich Einer der Hauptgründe , glaube nicht , ist es hilfreich, ECS zu prüfen , in einer Klasse von OOP zu gehören , ist , dass die meisten der SE Praktiken mit OOP assoziiert drehen sich um öffentliche Schnittstelle Stabilität, mit öffentlichen Schnittstellen Modellierungsfunktionen , keine Daten. Die Grundidee ist, dass der Großteil der öffentlichen Abhängigkeiten zu abstrakten Funktionen fließt, nicht zu konkreten Daten. Aus diesem Grund macht es OOP in der Regel sehr kostspielig, grundlegende Design-Verhaltensweisen zu ändern, während es sehr kostengünstig ist, konkrete Details (wie Daten und Code, die zur Implementierung der Funktionalität erforderlich sind) zu ändern.
In dieser Hinsicht unterscheidet sich ECS grundlegend von der Kopplung, da der Großteil der öffentlichen Abhängigkeiten zu konkreten Daten fließt: von Systemen zu Komponenten. Infolgedessen dreht sich jede mit ECS verbundene SE-Praxis um Datenstabilität , da die am häufigsten verwendeten öffentlichen Schnittstellen (Komponenten) eigentlich nur Daten sind.
Aus diesem Grund ist es mit einem ECS sehr einfach, eine DirectX-Engine durch eine OpenGL-Rendering-Engine zu ersetzen, auch wenn beide mit völlig unterschiedlichen Funktionen implementiert sind und nicht dasselbe Design aufweisen, vorausgesetzt, sowohl die DX- als auch die GL-Engine Zugriff auf dieselben stabilen Daten haben. In der Zwischenzeit wäre es sehr teuer und es müsste eine Reihe von Systemen neu geschrieben werden, um beispielsweise die Datendarstellung von a zu ändern
MotionComponent
.Dies steht im Gegensatz zu dem, was wir traditionell mit OOP assoziieren, zumindest in Bezug auf die Kopplungseigenschaften und was "öffentliche Schnittstelle" im Vergleich zu "privaten Implementierungsdetails" darstellt. Natürlich sind "Implementierungsdetails" in beiden Fällen leicht zu ändern, aber in ECS ist es das Design von Daten, deren Änderung kostspielig ist (Daten sind in ECS kein Implementierungsdetail), und in OOP ist es das Design von Funktionen, deren Änderung kostspielig ist (Der Entwurf von Funktionen ist kein Implementierungsdetail in OOP.) Das ist also eine ganz andere Vorstellung von "Implementierungsdetails", und eine der Hauptattraktionen eines ECS aus der Sicht der Wartung war die in meiner Domäne. Es war einfacher, die erforderlichen Daten zu stabilisieren und ein für alle Mal richtig zu entwerfen, als all die verschiedenen Dinge, die wir mit diesen Daten tun konnten (die sich ständig ändern würden, wenn Kunden ihre Meinung änderten und neue Benutzervorschläge eintrafen). Infolgedessen sanken die Wartungskosten, als wir damit begannen, Abhängigkeiten von abstrakten Funktionen auf rohe, zentrale Daten umzulenken (wobei immer noch darauf geachtet wurde, welche Systeme auf welche Komponenten zugreifen, damit Invarianten trotz aller konzeptionellen Daten in einem vernünftigen Ausmaß beibehalten werden können global erreichbar).
Und zumindest in meinem Fall ist das ECS-SDK mit der API und allen Komponenten tatsächlich in C implementiert und weist keine Ähnlichkeit mit OOP auf. Ich habe festgestellt, dass C für einen solchen Zweck mehr als angemessen ist, da ECS-Architekturen inhärent keine OO aufweisen und eine Plug-in-Architektur gewünscht wird, die von den unterschiedlichsten Sprachen und Compilern verwendet werden kann. Die Systeme sind immer noch in C ++ implementiert, da C ++ die Dinge dort sehr bequem macht und die Systeme den Großteil der Komplexität modellieren. Dort finde ich Verwendung für viele Dinge, die näher an OOP liegen könnten, aber das ist für Implementierungsdetails. Das architektonische Design selbst ähnelt immer noch sehr prozeduralen C.
Daher finde ich es zumindest etwas verwirrend zu sagen, dass ein ECS per Definition OO ist. Zumindest machen die Grundlagen Dinge, die eine vollständige 180-Grad-Drehung von vielen der Grundprinzipien sind, die im Allgemeinen mit OOP verbunden sind, angefangen mit der Einkapselung und möglicherweise endend mit dem, was als erwünschte Kopplungseigenschaften angesehen werden würde.
quelle