Ceci est une question difficile. Je vais juste essayer de répondre à certaines des questions basées sur mes expériences particulières (YMMV):
Les composants doivent accéder aux données des autres composants. Par exemple, la méthode de dessin du composant de rendu doit accéder à la position du composant de transformation. Cela crée des dépendances dans le code.
Ne sous-estimez pas la quantité et la complexité (pas le degré) de couplage / dépendances ici. Vous pourriez être en train de regarder la différence entre cela (et ce diagramme est déjà ridiculement simplifié à des niveaux semblables à des jouets, et l'exemple du monde réel aurait des interfaces entre les deux pour desserrer le couplage):
... et ça:
... ou ca:
Les composants peuvent être polymorphes, ce qui introduit en outre une certaine complexité. Par exemple, un composant de rendu d'image-objet peut remplacer la méthode de dessin virtuel du composant de rendu.
Donc? L'équivalent analogique (ou littéral) d'une répartition virtuelle et virtuelle peut être invoqué via le système plutôt que l'objet cachant son état / données sous-jacent. Le polymorphisme est encore très pratique et réalisable avec l'implémentation ECS "pure" lorsque la table analogique ou le ou les pointeurs de fonction se transforment en "données" de toutes sortes pour que le système puisse les invoquer.
Étant donné que le comportement polymorphe (par exemple pour le rendu) doit être implémenté quelque part, il est juste externalisé dans les systèmes. (par exemple, le système de rendu de sprite crée un noeud de rendu de sprite qui hérite du noeud de rendu et l'ajoute au moteur de rendu)
Donc? J'espère que cela ne se présente pas comme un sarcasme (ce n'est pas mon intention bien que j'en ai été souvent accusé mais je souhaite que je puisse mieux communiquer les émotions par le biais du texte), mais le comportement polymorphe "d'externalisation" dans ce cas n'engendre pas nécessairement un supplément coût à la productivité.
La communication entre les systèmes peut être difficile à éviter. Par exemple, le système de collision peut avoir besoin de la boîte englobante qui est calculée à partir de n'importe quel composant de rendu concret.
Cet exemple me semble particulièrement bizarre. Je ne sais pas pourquoi un moteur de rendu renverrait des données sur la scène (je considère généralement les moteurs de rendu en lecture seule dans ce contexte), ou pour qu'un moteur de rendu détermine les AABB au lieu d'un autre système pour le faire à la fois pour le moteur de rendu et collision / physique (je suis peut-être accroché au nom du "composant de rendu" ici). Pourtant, je ne veux pas trop m'attarder sur cet exemple car je me rends compte que ce n'est pas le point que vous essayez de faire valoir. Néanmoins, la communication entre les systèmes (même sous la forme indirecte de lecture / écriture dans la base de données centrale ECS avec des systèmes dépendant plutôt directement des transformations effectuées par d'autres) ne devrait pas avoir besoin d'être fréquente, si nécessaire. Cette'
Cela peut entraîner des problèmes si l'ordre d'appel des fonctions de mise à jour du système n'est pas défini.
Cela doit absolument être défini. L'ECS n'est pas la solution ultime pour réorganiser l'ordre d'évaluation du traitement du système de chaque système possible dans la base de code et renvoyer exactement le même type de résultats à l'utilisateur final traitant des trames et des FPS. C'est l'une des choses, lors de la conception d'un ECS, que je suggérerais au moins fortement devrait être anticipée quelque peu à l'avance (bien qu'avec beaucoup de marge de manœuvre pour pardonner de changer d'avis plus tard, à condition que cela ne modifie pas les aspects les plus critiques de la commande de invocation / évaluation du système).
Cependant, recalculer l'intégralité du tilmap à chaque image est coûteux. Par conséquent, une liste serait nécessaire pour garder une trace de toutes les modifications apportées pour ensuite les mettre à jour dans le système. De la manière OOP, cela pourrait être encapsulé par le composant de carte de tuiles. Par exemple, la méthode SetTile () mettrait à jour le tableau de sommets chaque fois qu'il est appelé.
Je n'ai pas bien compris celui-ci, sauf qu'il s'agit d'une préoccupation axée sur les données. Et il n'y a aucun piège quant à la représentation et au stockage des données dans un ECS, y compris la mémorisation, pour éviter de tels pièges de performances (les plus grands avec un ECS ont tendance à se rapporter à des choses comme les systèmes interrogeant les instances disponibles de types de composants particuliers, ce qui est l'un des les aspects les plus difficiles de l’optimisation d’un ECS généralisé). Le fait que la logique et les données soient séparées dans un ECS "pur" ne signifie pas que vous devez soudainement recalculer des choses que vous auriez pu autrement mettre en cache / mémoriser dans une représentation OOP. C'est un point théorique / non pertinent à moins que je ne passe sous silence quelque chose de très important.
Avec l'ECS «pur», vous pouvez toujours stocker ces données dans le composant de carte de tuiles. La seule différence majeure est que la logique de mise à jour de ce tableau de sommets se déplacerait quelque part vers un système.
Vous pouvez même vous appuyer sur l'ECS pour simplifier l'invalidation et la suppression de ce cache de l'entité si vous créez un composant distinct comme TileMapCache
. À ce stade, lorsque le cache est souhaité mais n'est pas disponible dans une entité avec un TileMap
composant, vous pouvez le calculer et l'ajouter. Lorsqu'il est invalidé ou n'est plus nécessaire, vous pouvez le supprimer via l'ECS sans avoir à écrire plus de code spécifiquement pour une telle invalidation et suppression.
Les dépendances entre les composants existent toujours bien qu'elles soient cachées dans les systèmes
Il n'y a pas de dépendance entre les composants d'un représentant "pur" (je ne pense pas qu'il soit tout à fait juste de dire que les dépendances sont cachées ici par les systèmes). Les données ne dépendent pas des données, pour ainsi dire. La logique dépend de la logique. Et un ECS «pur» a tendance à promouvoir la logique à écrire de manière à dépendre du sous-ensemble minimal absolu de données et de logique (souvent aucun) qu'un système nécessite pour fonctionner, contrairement à de nombreuses alternatives qui encouragent souvent en fonction beaucoup plus de fonctionnalités que nécessaire pour la tâche réelle. Si vous utilisez le droit ECS pur, l'une des premières choses que vous devriez apprécier est les avantages du découplage tout en remettant en question simultanément tout ce que vous avez appris à apprécier dans la POO sur l'encapsulation et plus précisément la dissimulation d'informations.
Par découplage, j'entends spécifiquement le peu d'informations dont vos systèmes ont besoin pour fonctionner. Votre système de mouvement n'a même pas besoin de connaître quelque chose de beaucoup plus complexe comme un Particle
ou Character
(le développeur du système n'a même pas nécessairement besoin de savoir que de telles idées d'entités existent même dans le système). Il a juste besoin de connaître les données minimales nues comme un composant de position qui pourrait être aussi simple que quelques flottants dans une structure. C'est encore moins d'informations et de dépendances externes que ce qu'une interface pure a IMotion
tendance à emporter. C'est principalement en raison de cette connaissance minimale que chaque système nécessite de travailler, ce qui rend l'EC souvent indulgent pour gérer les changements de conception très imprévus avec le recul sans faire face à des ruptures d'interface en cascade partout.
L'approche «impure» que vous suggérez diminue quelque peu cet avantage puisque maintenant votre logique n'est pas strictement localisée aux systèmes où les changements ne provoquent pas de ruptures en cascade. La logique serait désormais centralisée dans une certaine mesure dans les composants accessibles par plusieurs systèmes qui doivent désormais répondre aux exigences d'interface de tous les différents systèmes qui pourraient l'utiliser, et maintenant, c'est comme si chaque système avait alors besoin de connaître (dépendre) de plus les informations dont il a strictement besoin pour travailler avec ce composant.
Dépendances des données
L'un des éléments controversés de l'ECS est qu'il tend à remplacer ce qui pourrait autrement être des dépendances à des interfaces abstraites avec uniquement des données brutes, et cela est généralement considéré comme une forme de couplage moins souhaitable et plus stricte. Mais dans les types de domaines comme les jeux où ECS peut être très bénéfique, il est souvent plus facile de concevoir la représentation des données à l'avance et de la maintenir stable que de concevoir ce que vous pouvez faire avec ces données à un niveau central du système. C'est quelque chose que j'ai douloureusement observé même chez les vétérans chevronnés dans des bases de code qui utilisent davantage une approche d'interface pure de style COM avec des choses comme IMotion
.
Les développeurs ont continué à trouver des raisons d'ajouter, de supprimer ou de modifier des fonctions à cette interface centrale, et chaque changement était effroyable et coûteux car il aurait tendance à casser chaque classe implémentée IMotion
ainsi que chaque place dans le système utilisé IMotion
. Pendant ce temps, avec tant de changements douloureux et en cascade, les objets mis en œuvre ne IMotion
faisaient que stocker une matrice 4x4 de flotteurs et toute l'interface se souciait simplement de savoir comment transformer et accéder à ces flotteurs; la représentation des données était stable depuis le début, et beaucoup de douleur aurait pu être évitée si cette interface centralisée, si susceptible de changer avec des besoins de conception imprévus, n'existait même pas en premier lieu.
Tout cela peut sembler presque aussi dégoûtant que les variables globales, mais la nature de la façon dont ECS organise ces données en composants récupérés explicitement par type via les systèmes le rend ainsi, tandis que les compilateurs ne peuvent rien imposer comme la dissimulation d'informations, les endroits qui accèdent et mutent les données sont généralement très explicites et suffisamment évidentes pour maintenir efficacement les invariants et prédire le type de transformations et d'effets secondaires qui se produisent d'un système à l'autre (en fait, d'une manière qui peut sans doute être plus simple et plus prévisible que la POO dans certains domaines, étant donné le système se transforme en une sorte de pipeline plat).
Enfin, je veux poser la question de savoir comment je gérerais l'animation dans un ECS pur. Actuellement, j'ai défini une animation comme un foncteur qui manipule une entité en fonction d'une progression entre 0 et 1. Le composant d'animation a une liste d'animateurs qui a une liste d'animations. Dans sa fonction de mise à jour, il applique ensuite toutes les animations actuellement actives à l'entité.
Nous sommes tous pragmatiques ici. Même dans gamedev, vous aurez probablement des idées / réponses contradictoires. Même l'ECS le plus pur est un phénomène relativement nouveau, un territoire pionnier, pour lequel les gens n'ont pas nécessairement formulé les opinions les plus fortes sur la façon de dépouiller les chats. Ma réaction instinctive est un système d'animation qui incrémente ce type de progression d'animation dans les composants animés pour que le système de rendu s'affiche, mais cela ignore tellement de nuances pour l'application et le contexte particuliers.
Avec l'ECS, ce n'est pas une solution miracle et je me retrouve toujours avec des tendances à entrer et à ajouter de nouveaux systèmes, à en supprimer, à ajouter de nouveaux composants, à changer un système existant pour récupérer ce nouveau type de composant, etc. tout va bien du premier coup. Mais la différence dans mon cas est que je ne change rien de central lorsque je n'arrive pas à anticiper certains besoins de conception dès le départ. Je n'obtiens pas l'effet d'entraînement des ruptures en cascade qui m'obligent à aller partout et à changer tellement de code pour gérer un nouveau besoin qui surgit, et c'est tout à fait un gain de temps. Je trouve également cela plus facile pour mon cerveau parce que lorsque je m'assois avec un système particulier, je n'ai pas besoin de savoir / me souvenir de beaucoup d'autres choses que les composants pertinents (qui ne sont que des données) pour y travailler.