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?