Je conçois un moteur de jeu pour un jeu de tir 2D multijoueur de haut en bas, que je veux être raisonnablement réutilisable pour d'autres jeux de tir de haut en bas. En ce moment, je réfléchis à la façon de concevoir quelque chose comme un système d'entités. J'ai d'abord pensé à ceci:
J'ai une classe appelée EntityManager. Il devrait implémenter une méthode appelée Update et une autre appelée Draw. La raison pour laquelle je sépare Logic et Rendering est qu'alors je peux omettre la méthode Draw si j'exécute un serveur autonome.
EntityManager possède une liste d'objets de type BaseEntity. Chaque entité possède une liste de composants tels que EntityModel (la représentation graphique d'une entité), EntityNetworkInterface et EntityPhysicalBody.
EntityManager possède également une liste de gestionnaires de composants tels que EntityRenderManager, EntityNetworkManager et EntityPhysicsManager. Chaque gestionnaire de composants conserve des références aux composants d'entité. Il existe plusieurs raisons pour déplacer ce code hors de la propre classe de l'entité et le faire collectivement à la place. Par exemple, j'utilise une bibliothèque de physique externe, Box2D, pour le jeu. Dans Box2D, vous ajoutez d'abord les corps et les formes à un monde (appartenant à EntityPhysicsManager dans ce cas) et ajoutez des rappels de collision (qui seraient envoyés à l'objet entité lui-même dans mon système). Ensuite, vous exécutez une fonction qui simule tout dans le système. J'ai du mal à trouver une meilleure solution pour ce faire que de le faire dans un gestionnaire de composants externe comme celui-ci.
La création d'entité se fait comme ceci: EntityManager implémente la méthode RegisterEntity (entityClass, fabrique) qui enregistre comment créer une entité de cette classe. Il implémente également la méthode CreateEntity (entityClass) qui retournerait un objet de type BaseEntity.
Voici maintenant mon problème: comment la référence à un composant serait-elle enregistrée auprès des gestionnaires de composants? Je ne sais pas comment je référencerais les gestionnaires de composants d'une usine / fermeture.
Réponses:
Les systèmes doivent stocker une paire clé-valeur Entité-Composant dans une sorte de carte, d'objet de dictionnaire ou de tableau associatif (selon la langue utilisée). En outre, lorsque vous créez votre objet entité, je ne me soucierais pas de le stocker dans un gestionnaire, sauf si vous devez être en mesure de le désinscrire de l'un des systèmes. Une entité est un composite de composants, mais elle ne doit gérer aucune des mises à jour des composants. Cela devrait être géré par les systèmes. Traitez plutôt votre entité comme une clé mappée à tous les composants qu'elle contient dans les systèmes, ainsi que comme un hub de communication permettant à ces composants de communiquer entre eux.
La grande partie des modèles Entity-Component-System est que vous pouvez implémenter la possibilité de passer des messages d'un composant au reste des composants d'une entité assez facilement. Cela permet à un composant de parler à un autre composant sans vraiment savoir qui est ce composant ni comment gérer le composant qu'il change. Au lieu de cela, il transmet un message et laisse le composant se changer lui-même (s'il existe)
Par exemple, un système de positionnement ne contiendrait pas beaucoup de code, ne faisant que suivre les objets d'entité mappés à leurs composants de position. Mais lorsqu'une position change, ils peuvent envoyer un message à l'entité impliquée, qui à son tour est transmis à tous les composants de cette entité. Une position change pour quelle raison que ce soit? Le système de position envoie à l'entité un message disant que la position a changé, et quelque part, le composant de rendu d'image de cette entité reçoit ce message et se met à jour où il se dessinera ensuite.
Inversement, un système de physique doit savoir ce que font tous ses objets; Il doit pouvoir voir tous les objets du monde pour tester les collisions. Lorsqu'une collision se produit, il met à jour le composant de direction de l'entité en envoyant une sorte de "message de changement de direction" à l'entité au lieu de se référer directement au composant de l'entité. Cela dissocie le gestionnaire d'avoir besoin de savoir comment changer de direction en utilisant un message au lieu de compter sur un composant spécifique (ce qui peut ne pas être là du tout, auquel cas le message tomberait dans l'oreille d'un sourd au lieu d'une erreur). survenant parce qu’un objet attendu était absent).
Vous remarquerez un énorme avantage car vous avez mentionné que vous avez une interface réseau. Un composant réseau écouterait tous les messages reçus que tout le monde devrait connaître. Il aime les potins. Ensuite, lorsque le système réseau est mis à jour, les composants réseau envoient ces messages à d'autres systèmes réseau sur d'autres ordinateurs clients, qui renvoient ensuite ces messages à tous les autres composants pour mettre à jour les positions des joueurs, etc. Une logique spéciale peut être nécessaire pour que seules certaines entités puissent envoyer des messages sur le réseau mais c'est la beauté du système, vous pouvez simplement lui faire contrôler cette logique en lui enregistrant les bonnes choses.
En bref:
L'entité est une composition de composants pouvant recevoir des messages. L'entité peut recevoir des messages, en déléguant ces messages à tous leurs composants pour les mettre à jour. (Message de position modifiée, Direction de changement de vitesse, etc.) C'est comme une boîte aux lettres centrale que tous les composants peuvent entendre les uns des autres au lieu de se parler directement.
Le composant est une petite partie d'une entité qui stocke un état de l'entité. Ceux-ci sont capables d'analyser certains messages et de jeter d'autres messages. Par exemple, un "composant de direction" ne se soucierait que des "messages de changement de direction" mais pas des "messages de changement de position". Les composants mettent à jour leur propre état en fonction des messages, puis mettent à jour les états des autres composants en envoyant des messages à partir de leur système.
Le système gère tous les composants d'un certain type et est responsable de la mise à jour desdits composants à chaque trame, ainsi que de l'envoi des messages des composants qu'ils gèrent aux entités auxquelles les composants appartiennent.
Les systèmes pourraient être en mesure de mettre à jour tous leurs composants en parallèle et de stocker tous les messages au fur et à mesure. Ensuite, lorsque l'exécution de toutes les méthodes de mise à jour des systèmes est terminée, vous demandez à chaque système d'envoyer ses messages dans un ordre spécifique. Contrôle d'abord possible, suivi de physique, suivi de direction, position, rendu, etc. Il importe dans quel ordre ils sont envoyés car un changement de direction physique doit toujours peser un changement de direction basé sur le contrôle.
J'espère que cela t'aides. C'est un enfer d'un modèle de conception, mais il est ridiculement puissant s'il est bien fait.
la source
J'utilise un système similaire dans mon moteur et la façon dont je l'ai fait est que chaque entité contient une liste de composants. Depuis EntityManager, je peux interroger chacune des entités et voir celles qui contiennent un composant donné. Exemple:
Évidemment, ce n'est pas le code exact (vous auriez en fait besoin d'une fonction de modèle pour vérifier les différents types de composants, plutôt que d'utiliser
typeof
), mais le concept est là. Ensuite, vous pouvez prendre ces résultats, référencer le composant que vous recherchez et l'enregistrer auprès de votre usine. Cela empêche tout couplage direct entre les composants et leurs gestionnaires.la source
typedef long long int Entity
; un composant est un enregistrement (il peut être implémenté en tant que classe d'objets, ou simplement astruct
) qui a une référence à l'entité à laquelle il est attaché; et un système serait une méthode ou un ensemble de méthodes. Le modèle ECS n'est pas très compatible avec le modèle OOP, bien qu'un composant puisse être un objet (principalement) de données uniquement, et un système un objet singleton uniquement de code dont l'état vit dans des composants ... bien que les systèmes "hybrides" soient plus courants que les «purs», ils perdent de nombreux avantages innés.1) Votre méthode Factory doit recevoir une référence à l'EntityManager qui l'a appelée (je vais utiliser C # comme exemple):
2) Demandez à CreateEntity de recevoir également un identifiant (par exemple une chaîne, un entier, c'est à vous) en plus de la classe / type de l'entité, et enregistrez automatiquement l'entité créée dans un dictionnaire en utilisant cet identifiant comme clé:
3) Ajoutez un Getter à EntityManager pour obtenir n'importe quelle entité par ID:
Et c'est tout ce dont vous avez besoin pour référencer n'importe quel ComponentManager depuis votre méthode Factory. Par exemple:
Outre Id, vous pouvez également utiliser une sorte de propriété Type (une énumération personnalisée ou simplement compter sur le système de types du langage) et créer un getter qui renvoie toutes les BaseEntities d'un certain type.
la source
typedef unsigned long long int EntityID;
:; l'idéal est que chaque système puisse vivre sur un processeur ou un hôte distinct, et ne nécessite que de récupérer les composants pertinents pour / actifs dans ce système. Avec un objet Entity, il peut être nécessaire d'instancier le même objet Entity sur chaque hôte, ce qui rend la mise à l'échelle plus difficile. Un modèle purement entité-composant-système répartit le traitement entre les nœuds (processus, processeurs ou hôtes) par système, plutôt que par entité, généralement.