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 EvilTreesous-classe de Treeou 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 Treeet Enemyplus 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 Treeet Entity(ce qui en fait des interfaces dans le cas extrême) afin que le EvilTreepuisse 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 Treeet Enemyen différents composants - disons Position, Healthet AI- et implémentent des systèmes, tels qu'un AISystemqui 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 EvilTreepouvez récupérer un bonus et infliger des dégâts? Nous avons d'abord besoin d'un CollisionSystemet d'un DamageSystem(nous en avons probablement déjà). Le CollisionSystembesoin de communiquer avec le DamageSystem: Chaque fois que deux choses entrent en collision, le CollisionSystemenvoie un message au DamageSystemafin 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 PowerupComponentque nous attachons aux entités? Mais alors leDamageSystema 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 StatComponentqui 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 attackdoit ê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?