introduction
Les systèmes à composants d'entité sont une technique architecturale orientée objet.
Il n'y a pas de consensus universel sur ce que signifie le terme, de même que la programmation orientée objet. Cependant, il est clair que les systèmes à composants d'entité sont spécifiquement conçus comme une alternative architecturale à l' héritage . Hiérarchies d'héritage sont naturels pour exprimer ce qu'est un objet est , mais dans certains types de logiciels (comme les jeux), vous préférez exprimer ce qu'un objet fait .
Il s'agit d'un modèle d'objet différent de celui des «classes et héritage» auquel vous êtes probablement habitué à travailler en C ++ ou Java. Les entités sont aussi expressives que les classes, tout comme les prototypes comme en JavaScript ou Self - tous ces systèmes peuvent être implémentés les uns par rapport aux autres.
Exemples
Le mot Let qui Player
est une entité Position
, Velocity
et des KeyboardControlled
composants qui font les choses évidentes.
entity Player:
Position
Velocity
KeyboardControlled
Nous savons qu'il Position
faut être affecté par Velocity
et Velocity
par KeyboardControlled
. La question est de savoir comment nous aimerions modéliser ces effets.
Entités, composants et systèmes
Supposons que les composants n'aient aucune référence l'un à l'autre; un Physics
système externe traverse tous les Velocity
composants et met à jour l' Position
entité correspondante; un Input
système traverse tous les KeyboardControlled
composants et met à jour le Velocity
.
Player
+--------------------+
| Position | \
| | Physics
/ | Velocity | /
Input | |
\ | KeyboardControlled |
+--------------------+
Cela répond aux critères:
Les systèmes sont désormais responsables de la gestion des événements et de la mise en œuvre du comportement décrit par les composants. Ils sont également chargés de gérer les interactions entre les entités, telles que les collisions.
Entités et composants
Cependant, supposons que les composants n'ont des références les uns aux autres. Maintenant, l'entité est simplement un constructeur qui crée certains composants, les lie et gère leur durée de vie:
class Player:
construct():
this.p = Position()
this.v = Velocity(this.p)
this.c = KeyboardControlled(this.v)
L'entité peut désormais envoyer des événements d'entrée et de mise à jour directement à ses composants. Velocity
répondrait aux mises à jour et KeyboardControlled
répondrait aux commentaires. Cela répond toujours à nos critères:
Ici, les interactions entre composants sont explicites, et non imposées de l'extérieur par un système. Les données décrivant un comportement (quelle est la quantité de vitesse?) Et le code qui le décrète (quelle est la vitesse?) Sont couplées, mais de manière naturelle. Les données peuvent être considérées comme des paramètres du comportement. Et certains composants n'agissent pas du tout - a Position
est le comportement d' être dans un endroit .
Les interactions peuvent être gérées au niveau de l'entité («quand une Player
collision avec un Enemy
…») ou au niveau des composants individuels («lorsqu'une entité avec Life
entre en collision avec une entité avec Strength
…»).
Composants
Quelle est la raison pour laquelle l'entité existe? S'il s'agit simplement d'un constructeur, nous pouvons le remplacer par une fonction renvoyant un ensemble de composants. Si nous voulons plus tard interroger des entités par leur type, nous pouvons tout aussi bien avoir un Tag
composant qui nous permet de faire exactement cela:
function Player():
t = Tag("Player")
p = Position()
v = Velocity(p)
c = KeyboardControlled(v)
return {t, p, v, c}
Les interactions doivent maintenant être gérées par des requêtes abstraites, découplant complètement les événements des types d'entités. Il n'y a plus de types d'entités à interroger - les Tag
données arbitraires sont probablement mieux utilisées pour le débogage que la logique de jeu.
Conclusion
Les entités ne sont pas des fonctions, des règles, des acteurs ou des combinateurs de flux de données. Ce sont des noms qui modélisent des phénomènes concrets - en d'autres termes, ce sont des objets. C'est comme le dit Wikipedia - les systèmes à composants d'entité sont un modèle d'architecture logicielle pour la modélisation d'objets généraux.
NON. Et je suis surpris du nombre de personnes qui ont voté autrement!
Paradigme
Il est orienté données alias données, car nous parlons de l' architecture et non du langage dans lequel il est écrit. Les architectures sont des réalisations de styles ou de paradigmes de programmation , qui peuvent généralement être déconseillés dans un langage donné.
Fonctionnel?
Votre comparaison avec la programmation fonctionnelle / procédurale est une comparaison pertinente et significative. Notez, cependant, qu'un langage "fonctionnel" est différent du paradigme "procédural" . Et vous pouvez implémenter un ECS dans un langage fonctionnel comme Haskell , ce que les gens ont fait.
Là où la cohésion se produit
Votre observation est pertinente et précise :
ECS / ES n'est pas EC / CE
Il existe une différence entre les architectures basées sur les composants, "Entity-Component" et "Entity-Component-System". Puisqu'il s'agit d'un modèle de conception en évolution, j'ai vu ces définitions utilisées de manière interchangeable. Les architectures "EC" ou "CE" ou "Entity-Component" placent le comportement dans les composants , tandis que les architectures "ES" ou "ECS" mettent le comportement dans les systèmes . Voici quelques articles ECS, qui utilisent tous deux une nomenclature trompeuse, mais donnent une idée générale:
Si vous essayez de comprendre ces termes en 2015, assurez-vous que la référence de quelqu'un à "Entity Component System" ne signifie pas "Entity-Component architecture".
la source
Les systèmes de composants d'entité (ECS) peuvent être programmés d'une manière OOP ou fonctionnelle selon la façon dont le système est défini.
Voie OOP:
J'ai travaillé sur des jeux où une entité était un objet composé de divers composants. L'entité a une fonction de mise à jour qui modifie l'objet en place en appelant successivement update sur tous ses composants. Il s'agit clairement de POO dans le style - le comportement est lié aux données et les données sont modifiables. Les entités sont des objets avec des constructeurs / destructeurs / mises à jour.
Manière plus fonctionnelle:
Une alternative est que l'entité soit des données sans aucune méthode. Cette entité peut exister à part entière ou simplement être un identifiant lié à divers composants. De cette façon, il est possible (mais ce n'est pas courant) d'être pleinement fonctionnel et d'avoir des entités immuables et des systèmes purs qui génèrent de nouveaux états de composants.
Il semble (par expérience personnelle) que cette dernière voie gagne en traction et pour une bonne raison. La séparation des données d'entité du comportement entraîne un code plus flexible et réutilisable (imo). En particulier, l'utilisation de systèmes pour mettre à jour des composants / entités par lots peut être plus performante et évite complètement les complexités de la messagerie interentités qui affligent de nombreux ECO POO.
TLDR: Vous pouvez le faire dans les deux cas, mais je dirais que les avantages de bons systèmes de composants d'entité découlent de leur nature plus fonctionnelle.
la source
Les systèmes de composants d'entité orientés données peuvent coexister avec des paradigmes orientés objet: - Les systèmes de composants se prêtent au polymorphisme. - Les composants peuvent être à la fois des objets POD (données anciennes simples) et ALSO (avec une classe et des méthodes), et le tout est toujours `` orienté données '', à condition que les méthodes de classe de composants manipulent uniquement les données appartenant à l'objet local.
Si vous choisissez ce chemin, je vous recommande d'éviter d'utiliser des méthodes virtuelles, car si vous les avez, votre composant n'est plus uniquement des données de composant, plus ces méthodes coûtent plus cher à appeler - ce n'est pas COM. Gardez vos classes de composants propres de toute référence à tout élément externe, en règle générale.
L'exemple serait vec2 ou vec3, un conteneur de données avec quelques méthodes pour toucher ces données, et rien de plus.
la source
Je pense que ECS est fondamentalement distinct de la POO et a tendance à le voir de la même manière que vous, comme plus proche de la nature fonctionnelle ou surtout procédurale avec une séparation très distincte des données de la fonctionnalité. Il y a aussi un semblant de programmation d'un genre traitant des bases de données centrales. Bien sûr, je suis la pire personne en ce qui concerne les définitions formelles. Je me préoccupe uniquement de la façon dont les choses ont tendance à être, pas de ce qu'elles sont définies conceptuellement.
Je suppose une sorte d'ECS où les composants agrègent les champs de données et les rendent accessibles au public / au niveau mondial, les entités agrégent les composants et les systèmes fournissent des fonctionnalités / un comportement sur ces données. Cela conduit à des caractéristiques architecturales radicalement difficiles à partir de ce que nous appellerions généralement une base de code orientée objet.
Et bien sûr, il y a un certain flou des limites dans la façon dont les gens conçoivent / implémentent un ECS, et il y a un débat sur ce qui constitue exactement un ECS en premier lieu. Pourtant, ces limites sont également floues dans le code écrit dans ce que nous appelons les langages fonctionnels ou procéduraux. Parmi tous ces flous, la constante fondamentale d'un ECS avec une séparation des données de la fonctionnalité me semble bien plus proche de la programmation fonctionnelle ou procédurale que la POO.
L'une des principales raisons pour lesquelles je ne pense pas qu'il soit utile de considérer ECS comme appartenant à une classe de POO est que la plupart des pratiques SE associées à la POO tournent autour de la stabilité de l'interface publique, avec des fonctions de modélisation des interfaces publiques , pas des données. L'idée fondamentale est que la majeure partie des dépendances publiques va vers des fonctions abstraites, pas vers des données concrètes. Et à cause de cela, la POO a tendance à coûter très cher à changer les comportements de conception fondamentaux, tout en rendant très bon marché la modification de détails concrets (comme les données et le code requis pour implémenter la fonctionnalité).
ECS est radicalement différent à cet égard, compte tenu de la façon dont les choses sont couplées alors que la majeure partie des dépendances publiques se dirigent vers des données concrètes: des systèmes aux composants. En conséquence, toutes les pratiques SE associées à ECS tourneraient autour de la stabilité des données , car les interfaces (composants) les plus publiques et les plus utilisées sont en fait uniquement des données.
En conséquence, un ECS rend très facile de faire des choses comme substituer un moteur de rendu OpenGL à un moteur DirectX, même si les deux sont implémentés avec des fonctionnalités radicalement différentes et ne partagent pas les mêmes conceptions, à condition que les moteurs DX et GL avoir accès aux mêmes données stables. En attendant, cela coûterait très cher et nécessiterait de réécrire un tas de systèmes pour changer, par exemple, la représentation des données d'un
MotionComponent
.C'est très opposé à ce que nous associons traditionnellement à la POO, au moins en termes de caractéristiques de couplage et de ce qui constitue une «interface publique» par rapport à des «détails d'implémentation privés». Bien sûr, dans les deux cas, les «détails d'implémentation» sont faciles à modifier, mais dans ECS, c'est la conception des données qui est coûteuse à modifier (les données ne sont pas un détail d'implémentation dans ECS), et dans la POO, c'est la conception de fonctionnalités coûteuses à modifier (la conception des fonctions n'est pas un détail d'implémentation dans la POO). C'est donc une idée très différente des «détails de mise en œuvre», et l'un des principaux appels pour moi d'un ECS du point de vue de la maintenance était que dans mon domaine, les données nécessaires pour faire les choses étaient plus faciles à stabiliser et à concevoir correctement une fois pour toutes à l'avance que toutes les différentes choses que nous pouvions faire avec ces données (qui changeraient tout le temps à mesure que les clients changeaient d'avis et que de nouvelles suggestions d'utilisateurs arrivaient). En conséquence, j'ai trouvé les coûts de maintenance en chute libre lorsque nous avons commencé à orienter les dépendances des fonctions abstraites vers des données brutes et centrales (mais toujours avec soin quels systèmes accèdent à quels composants pour permettre de maintenir les invariants à un degré raisonnable malgré toutes les données conceptuellement étant accessible au niveau mondial).
Et dans mon cas au moins, le SDK ECS avec l'API et tous les composants sont réellement implémentés en C et ne ressemble en rien à la POO. J'ai trouvé C plus que suffisant pour un tel objectif étant donné le manque inhérent d'OO dans les architectures ECS et le désir d'avoir une architecture de plugin qui peut être utilisée par la plus large gamme de langages et de compilateurs. Les systèmes sont toujours implémentés en C ++ car C ++ rend les choses très pratiques là-bas et les systèmes modélisent l'essentiel de la complexité et là je trouve utile pour beaucoup de choses qui pourraient être considérées comme plus proches de la POO, mais c'est pour les détails d'implémentation. La conception architecturale elle-même ressemble toujours à un C. très procédural
Je pense donc qu'il est un peu déroutant, à tout le moins, d'essayer de dire qu'un ECS est OO par définition. À tout le moins, les principes fondamentaux font un virage complet à 180 degrés par rapport à bon nombre des principes fondamentaux généralement associés à la POO, à commencer par l'encapsulation et peut-être se terminant par ce qui serait considéré comme les caractéristiques de couplage souhaitées.
la source