Le titre est intentionnellement hyperbolique et c'est peut-être juste mon inexpérience avec le motif mais voici mon raisonnement:
La façon «habituelle» ou sans doute simple d'implémenter des entités est de les implémenter en tant qu'objets et de sous-classer les comportements communs. Cela conduit au problème classique de "est une EvilTree
sous-classe de Tree
ou Enemy
?". Si nous autorisons l'héritage multiple, le problème du diamant se pose. Nous pourrions à la place tirer la fonctionnalité combinée de Tree
et Enemy
plus haut dans la hiérarchie qui mène aux classes de Dieu, ou nous pouvons intentionnellement laisser de côté le comportement dans nos classes Tree
et Entity
(ce qui en fait des interfaces dans le cas extrême) afin que le EvilTree
puisse implémenter cela lui-même - ce qui conduit à duplication de code si nous en avons un SomewhatEvilTree
.
Les systèmes d'entité-composant essaient de résoudre ce problème en divisant l' objet Tree
et Enemy
en différents composants - disons Position
, Health
et AI
- et implémentent des systèmes, tels qu'un AISystem
qui change la position d'une entité en fonction des décisions de l'IA. Jusqu'ici tout va bien, mais que se passe-t-il si vous EvilTree
pouvez récupérer un bonus et infliger des dégâts? Nous avons d'abord besoin d'un CollisionSystem
et d'un DamageSystem
(nous en avons probablement déjà). Le CollisionSystem
besoin de communiquer avec le DamageSystem
: Chaque fois que deux choses entrent en collision, le CollisionSystem
envoie un message au DamageSystem
afin qu'il puisse soustraire la santé. Les dégâts sont également influencés par les bonus, nous devons donc les stocker quelque part. Créons-nous un nouveau PowerupComponent
que nous attachons aux entités? Mais alors leDamageSystem
a besoin de savoir quelque chose qu'il préfère ne rien savoir - après tout, il y a aussi des choses qui infligent des dégâts qui ne peuvent pas capter les bonus (par exemple a Spike
). Autorisons-nous le PowerupSystem
à modifier un StatComponent
qui est également utilisé pour les calculs de dommages similaires à cette réponse ? Mais maintenant, deux systèmes accèdent aux mêmes données. À mesure que notre jeu devient plus complexe, il deviendrait un graphe de dépendance intangible où les composants sont partagés entre de nombreux systèmes. À ce stade, nous pouvons simplement utiliser des variables statiques globales et nous débarrasser de tout le passe-partout.
Existe-t-il un moyen efficace de résoudre ce problème? Une idée que j'ai eue était de laisser les composants avoir certaines fonctions, par exemple donner le StatComponent
attack()
qui retourne juste un entier par défaut mais peut être composé quand une mise sous tension se produit:
attack = getAttack compose powerupBy(20) compose powerdownBy(40)
Cela ne résout pas le problème qui attack
doit être enregistré dans un composant accessible par plusieurs systèmes mais au moins je pourrais taper correctement les fonctions si j'ai un langage qui le supporte suffisamment:
// In StatComponent
type Strength = PrePowerup | PostPowerup
type Damage = Int
type PrePowerup = Int
type PostPowerup = Int
attack: Strength = getAttack //default value, can be changed by systems
getAttack: PrePowerup
// these functions can be defined in other components or in PowerupSystems
powerupBy: Strength -> PostPowerup
powerdownBy: Strength -> PostPowerup
subtractArmor: Strength -> Damage
// in DamageSystem
dealDamage: Damage -> () = attack compose subtractArmor compose hurtSomeEntity
De cette façon, je garantis au moins un ordre correct des différentes fonctions ajoutées par les systèmes. Quoi qu'il en soit, il semble que j'approche rapidement de la programmation réactive fonctionnelle ici, donc je me demande si je n'aurais pas dû utiliser cela depuis le début (je viens juste de regarder FRP, donc je peux me tromper ici). Je vois que ECS est une amélioration par rapport aux hiérarchies de classes complexes, mais je ne suis pas convaincu que ce soit idéal.
Y a-t-il une solution à cela? Y a-t-il une fonctionnalité / un modèle qui me manque pour découpler ECS plus proprement? Le FRP est-il strictement mieux adapté à ce problème? Ces problèmes découlent-ils simplement de la complexité inhérente à ce que j'essaie de programmer; c'est-à-dire que FRP aurait des problèmes similaires?
la source
Réponses:
ECS ruine complètement la dissimulation des données. Il s'agit d'un compromis du modèle.
ECS est excellent pour le découplage. Un bon ECS permet à un système de déplacement de déclarer qu'il fonctionne sur n'importe quelle entité qui a une vitesse et un composant de position, sans avoir à se soucier des types d'entités existants ou des autres systèmes qui accèdent à ces composants. Ceci est au moins équivalent en découplant la puissance à avoir des objets de jeu implémentant certaines interfaces.
Deux systèmes accédant aux mêmes composants est une fonctionnalité, pas un problème. Il est tout à fait attendu et ne couple en aucun cas les systèmes. Il est vrai que les systèmes auront un graphe de dépendances implicites, mais ces dépendances sont inhérentes au monde modélisé. Dire que le système de dommages ne devrait pas avoir la dépendance implicite du système de mise sous tension revient à affirmer que les mises sous tension n'affectent pas les dommages, et c'est probablement faux. Cependant, bien que la dépendance existe, les systèmes ne sont pas couplés - vous pouvez supprimer le système de mise sous tension du jeu sans affecter le système de dégâts, car la communication s'est produite via le composant stat et était complètement implicite.
La résolution de ces dépendances et des systèmes de commande peut être effectuée dans un seul emplacement central, de la même manière que la résolution des dépendances dans un système DI. Oui, un jeu complexe aura un graphe complexe de systèmes, mais cette complexité est inhérente, et au moins elle est contenue.
la source
Il n'y a presque aucun moyen de contourner le fait qu'un système doit accéder à plusieurs composants. Pour que quelque chose comme un VelocitySystem fonctionne, il aurait probablement besoin d'accéder à un VelocityComponent et un PositionComponent. Pendant ce temps, le RenderingSystem doit également accéder à ces données. Peu importe ce que vous faites, le système de rendu doit à un moment donné savoir où rendre l'objet et le VelocitySystem doit savoir où déplacer l'objet.
Ce qu'il vous faut pour cela, c'est l' explicitation des dépendances. Chaque système doit être explicite sur les données qu'il lira et sur quelles données il écrira. Lorsqu'un système souhaite récupérer un composant particulier, il doit pouvoir le faire de manière explicite uniquement . Dans sa forme la plus simple, il a simplement les composants pour chaque type dont il a besoin (par exemple, le RenderSystem a besoin des RenderComponents et PositionComponents) comme arguments et renvoie tout ce qu'il a changé (par exemple les RenderComponents uniquement).
Vous pouvez commander dans une telle conception. Rien ne dit que pour ECS, vos systèmes doivent être indépendants de l'ordre ou de toute autre chose de ce genre.
L'utilisation de cette conception de système de composants d'entité et de FRP n'est pas mutuellement exclusive. En fait, les systèmes ne peuvent être vus comme rien d'autre comme n'ayant aucun état, effectuant simplement des transformations de données (les composants).
FRP ne résoudrait pas le problème d'avoir à utiliser les informations dont vous avez besoin pour effectuer certaines opérations.
la source