Comment éviter les «Blob-Systems» dans un système de composants d'entité?

10

Actuellement, je suis confronté au problème suivant:

J'essaie d'écrire un clone de pong en utilisant un système de composants d'entité (ECS). J'ai écrit le "framework" tout seul. Il existe donc une classe qui gère les entités avec tous les composants. Ensuite, il y a les classes de composants elles-mêmes. Et enfin, il y a mes systèmes qui obtiennent juste toutes les entités qui ont des composants dont le système a besoin.

Ainsi, par exemple, mon système de mouvement recherche toutes les entités qui ont un composant de position et un composant de mouvement. Le composant de position tient juste la position et le composant de mouvement tient la vitesse.

Mais le vrai problème est mon système de collision. Cette classe est comme un blob logique. J'ai tellement de cas particuliers dans cette classe.

Par exemple: Mes pagaies peuvent entrer en collision avec les bordures. Si cela se produit, leur vitesse est réglée sur zéro. Ma balle peut aussi bien entrer en collision avec les bordures. Mais dans ce cas, sa vitesse est simplement reflétée à la normale de la frontière afin qu'elle se reflète. Pour ce faire, j'ai donné au ballon une composante physique supplémentaire qui dit simplement: "Hé, cette chose ne s'arrête pas, elle réfléchit." Donc, en fait, le composant physique n'a pas de données réelles. Il s'agit d'une classe vide qui est juste là pour indiquer au système si un objet se reflète ou s'arrête.

Ensuite, il y a ceci: je veux rendre quelques particules lorsque la balle entre en collision avec les pagaies ou les bordures. Je pense donc que la balle doit obtenir un autre composant qui indique au système de collision de créer des particules lors de la collision.
Ensuite, je veux avoir des power-ups qui peuvent entrer en collision avec les pagaies mais pas avec les bordures. Si cela se produit, les power-ups doivent disparaître. J'aurais donc besoin de beaucoup plus de boîtiers et de composants (pour dire au système que certaines entités ne peuvent entrer en collision qu'avec certaines autres, pas avec tous même si d'autres sont réellement capables de se heurter, en outre, le système de collision a dû appliquer les power-ups à les pagaies, etc., etc., etc.).

Je vois que le système de composants d'entité est une bonne chose car il est flexible et vous n'avez pas de problèmes d'héritage. Mais je suis totalement bloqué actuellement.

Suis-je trop compliqué? Comment dois-je faire face à ce problème?

Bien sûr, je dois créer des systèmes qui sont en fait responsables de la "post-collision", donc le système de collision ne dit que "Oui, nous avons une collision dans la dernière image" et puis il y a un tas de systèmes "post-collision" qui tous nécessitent des composants (combinaisons de) différents, puis changent les composants. Par exemple, il y aurait un système de mouvement post-collision qui arrête les choses qui doivent s'arrêter lorsque la collision se produit. Puis un système physique post-collision qui reflète les choses, etc.

Mais cela ne me semble pas non plus être une bonne solution, car par exemple:

  1. Mon système de post-collision de mouvement aurait besoin d'entités qui ont un composant de position, un composant de mouvement et un composant de collision. Ensuite, il mettrait la vitesse de l'entité à zéro.
  2. Le système physique post-collision aurait besoin d'entités qui ont un composant position, un composant mouvement, un composant collision et un composant physique. Il refléterait alors le vecteur vitesse.

Le problème est évident: le mouvement post-collision a besoin d'entités qui sont un sous-ensemble des entités du système physique post-collision. Ainsi, deux systèmes post-collision fonctionneraient sur les mêmes données, l'effet étant: Bien qu'une entité ait une composante physique, sa vitesse serait nulle après une collision.

Comment ces problèmes sont-ils résolus en général dans un système de composants d'entité? Ces problèmes sont-ils même habituels ou est-ce que je fais quelque chose de mal? Si oui, que faut-il faire et comment?

M0rgenstern
la source

Réponses:

11

Oui, tu penses trop compliqué.

Il semble que beaucoup de vos problèmes pourraient être résolus avec un système de messagerie et certains attributs supplémentaires qui vous permettent de spécifier certains filtres , et enfin de ne pas vous soucier d'être aussi stricts avec les entités / composants.

La messagerie vous aidera avec certains aspects comme le déclenchement de particules, les bonus, etc. Par exemple, vous pourriez avoir un objet universel qui souscrit aux événements de particules et crée des particules à la position décrite dans l'événement.

Les filtres vous aideront beaucoup dans les collisions. Les filtres peuvent définir si un objet entre en collision avec un autre et quelle réponse il aura. Vous ajoutez des attributs à votre composant physique qui définissent de quel type de corps physique il s'agit, avec quels autres types de corps physiques il entre en collision et quelle doit être la réponse. Par exemple, un objet physique de balle entre en collision avec un objet physique de palette et répond par la réflexion et les particules.

Enfin, ne soyez pas aussi strict sur votre implémentation. Si vous pouvez trouver un moyen de le faire fonctionner, mais que ce n'est pas vraiment le système EC, faites-le. Comme dans mon exemple ci-dessus, les particules ne doivent pas du tout être gérées par un système ou une partie du système EC. Il est plus important de terminer le jeu que de suivre strictement une méthode qui est déjà assez mal définie.

MichaelHouse
la source
Merci beaucoup. Je voulais construire un jeu en utilisant un ECS juste pour voir comment il évolue et s'il est vraiment aussi agréable à utiliser que je l'ai lu dans les articles et les tutoriels. Mon problème était que je pensais: "J'ai maintenant un ECS et tout doit être géré par cela." J'ai donc également prévu d'écrire le système de particules en relation avec l'ECS. De plus, j'ai lu dans certains articles que chaque composant ne devrait vraiment avoir que des données de base et rien de plus. C'est souvent mon problème ... Je pense que c'est trop compliqué.
M0rgenstern
Je voulais construire un jeu en utilisant un ECS juste pour voir comment il évolue et s'il est vraiment aussi agréable à utiliser que je l'ai lu dans les articles et les tutoriels . Si tel est votre objectif, je vous recommande de regarder les systèmes de composants / entités existants au lieu de créer les vôtres. Téléchargez Unity3D, qui est probablement "aussi pur que possible" et jouez là-bas. Des informations beaucoup plus rapides, à mon humble avis.
Imi
3
@lmi: Unity n'est pas un système de composants d'entité bien qu'il soit basé sur des composants. ECS a des directives plutôt strictes ( ne pensez jamais à un modèle comme des règles) que de simplement avoir et utiliser des composants d'objets de jeu. En raison d'une série d'articles, ECS est populaire auprès de certains segments de développeurs de jeux en ce moment, il y a donc beaucoup de questions sur ECS en particulier plutôt que sur la conception basée sur les composants en général.
Sean Middleditch
12

Vous compliquez trop les choses. J'irais jusqu'à dire que même l'utilisation d'une conception basée sur les composants est juste exagérée pour un jeu aussi simple. Faites les choses de la manière qui rend votre jeu rapide et facile à développer. Les composants aident à l'itération dans des projets plus grands avec une grande variété de comportements et de configurations d'objets de jeu, mais leur avantage pour un jeu aussi simple et bien défini est plus discutable. J'ai fait un discours l'année dernière à ce sujet: vous pouvez créer de petits jeux amusants en quelques heures si vous vous concentrez sur la création d'un jeu au lieu d' adhérer à une architecture . L'héritage se décompose lorsque vous avez 100 ou même 20 types d'objets différents, mais cela fonctionne très bien si vous n'en avez qu'une poignée.

En supposant que vous souhaitez continuer à utiliser des composants à des fins d'apprentissage, votre approche présente des problèmes évidents.

Tout d'abord, ne faites pas vos composants si petits. Il n'y a aucune raison d'avoir des composants à grain fin comme le «mouvement». Il n'y a pas de mouvement générique dans votre jeu. Vous avez des palettes, dont le mouvement est étroitement lié à l'entrée ou à l'IA (et n'utilisez pas vraiment la vitesse, l'accélération, la restitution, etc.), et vous avez la balle, qui a un algorithme de mouvement bien défini. Il suffit d'avoir un composant PaddleController et un composant BouncingBall ou quelque chose du genre. Si / lorsque vous obtenez un jeu plus compliqué, vous pouvez vous inquiéter d'avoir un composant PhysicsBody plus générique (qui, dans les moteurs «réels», est simplement un lien entre l'objet de jeu et tout objet API interne utilisé par Havok / PhysX / Bullet / Box2D / etc.) Qui gère une plus grande variété de situations.

Même une composante «position» est discutable, mais certainement pas rare. Les moteurs physiques ont généralement leur propre idée interne de l'endroit où se trouve un objet, les graphiques peuvent avoir une représentation interpolée et l'IA peut avoir encore une autre représentation des mêmes données dans un état différent. Il peut être avantageux de donner à chaque système la possibilité de gérer sa propre idée de la transformation des composants du système, puis de garantir une communication fluide entre les systèmes. Voir le billet de blog BitSquid sur les flux d'événements .

Pour les moteurs de physique personnalisés, n'oubliez pas que vous êtes autorisé à avoir des données sur vos composants. Peut-être qu'un composant physique générique de Pong a des données indiquant sur quels axes il peut se déplacer (par exemple, vec2(0,1)comme un multiplicateur pour les pagaies qui ne peuvent se déplacer que sur l'axe Y et vec2(1,1)pour la balle indiquant qu'il peut se déplacer cependant), un drapeau ou un flotteur indiquant le rebond (le balle serait généralement à 1.0et pagaies à0.0), les caractéristiques d'accélération, la vitesse, etc. Essayer de diviser cela en un milliard de micro-composants différents pour chaque élément de données hautement liées est contraire à ce que ECS était censé faire à l'origine. Conservez les éléments qui sont utilisés ensemble dans le même composant lorsque cela est possible et divisez-les uniquement lorsqu'il existe une grande différence dans la façon dont chaque objet de jeu utilise ces données. Il y a un argument pour dire que pour Pong, la physique entre le ballon et les palettes est suffisamment différente pour être des composants séparés, mais pour un jeu plus grand, il n'y a pas de raison d'essayer de faire 20 composants pour faire ce qui fonctionne très bien en 1-3.

N'oubliez pas, si / quand votre version d'ECS vous gêne, faites ce dont vous avez besoin pour créer votre jeu et oubliez l'adhésion obstinée à un modèle / architecture de conception.

Sean Middleditch
la source
1
Vraiment merci! Je sais, qu'un ECS ne s'adapte pas très bien pour un petit jeu comme le pong. Mais je l'ai utilisé juste pour voir comment une telle chose est réellement mise en œuvre et comment elle fonctionne. J'ai fait les composants si petits, car c'est ce que j'ai lu principalement dans certains articles. Avoir de nombreux composants et chaque composant ne contient que des données élémentaires. Donc, je vous comprends bien, que vous proposez d'utiliser un mélange entre héritage et ECS? Comme vous le dites, "la balle et les palettes sont suffisamment différentes pour être des composants séparés". Ainsi, par exemple, je leur donne à la fois un composant Mouvement / Position (peut-être en tant qu'un composant) et
M0rgenstern
1
Tout ce qui fonctionne. Concentrez-vous sur la création du jeu. J'aurais littéralement juste un composant appelé Ballqui contient toute la logique de la balle comme le mouvement, le rebond, etc. et un Paddlecomposant qui prend en entrée, mais c'est moi. Tout ce qui a le plus de sens pour vous, vous échappe et vous permet de créer le jeu est la "bonne façon" de faire les choses.
Sean Middleditch
3
J'aurais littéralement juste un composant appelé Ball qui contient toute la logique de la balle comme le mouvement, le rebond, etc. et un composant Paddle qui prend en entrée, mais c'est moi. Et c'est pourquoi il y a une opinion pour chaque programmeur sur "ce qu'est un système de composants". Je recommande de ne pas le faire comme cette suggestion, sauf que vous pensez totalement dans les systèmes Entity classiques et que vous êtes obligé d'utiliser un système Component mais que vous ne voulez pas voir quelles sont les différences.
Imi
2
@lmi: après avoir travaillé sur quelques gros jeux / moteurs avec des composants et avoir vu de première main pourquoi nous utilisons des composants, non, les composants trop granulaires sont tout simplement plus problématiques qu'ils n'en valent la peine. Les composants ne sont pas une solution miracle; ils sont l'un des nombreux outils de la boîte à outils d'un développeur de jeux. Utilisez-les d'une manière et d'un lieu qu'ils aident et non de manière à ajouter simplement plus de surcharge mentale et d'exécution au système. Si la seule chose qui a la physique de la balle est la balle, il n'y a aucun avantage à la séparer des autres propriétés de la balle. Si et quand cela change, divisez-le ensuite, puis seulement.
Sean Middleditch
1
J'accepte le principe d'être pragmatique et de ne pas laisser un bagarreur particulier se mettre en travers de leur chemin. MAIS si ECS est incapable de gérer ce jeu trivial sans déviation, quel espoir y a-t-il pour un grand jeu. Moi aussi, j'essaie d'apprendre à utiliser ECS efficacement, mais j'essaie de rester aussi proche que possible de la philosophie ECS, sinon, une fois que je commencerai à faire des exceptions et des cas spéciaux, je sais que je vais me retrouver avec un gâchis irrécupérable.
Ken
-2

À mon avis *), votre plus gros problème avec les composants est le suivant: les composants ne sont PAS là pour dire à quelqu'un d'autre quoi faire. Les composants sont là pour FAIRE des choses. Vous n'avez pas de composant juste pour conserver la mémoire de quelque chose et ensuite faire fonctionner d'autres composants. Vous voulez des composants qui FONT des trucs avec les données qu'ils ont obtenues.

Si vous vous voyez tester la présence d'autres composants (puis appeler des fonctions là-bas), alors c'est un signe clair, que l'une des deux choses est vraie:

  • Vous voulez réellement inverser la dépendance: l'autre composant doit écouter les événements / messages / diffusions / hooks / comment tous les nommer pour exécuter leur logique en réponse à votre composant actuel. Le composant actuel n'a même pas besoin de savoir qu'il existe un "autre" composant. C'est le plus souvent le cas, si vous vous retrouvez à appeler différents composants (même dans des blocs else / case différents) avec des fonctionnalités qui ne sont pas vraiment connectées à votre méthode actuelle. Pensez à tous ces appels Invalidate()ou SetDirty()à d'autres composants.
  • Vous pouvez avoir trop de composants. Si deux composants ne peuvent tout simplement pas vivre l'un sans l'autre et ont constamment besoin de récupérer des données et d'appeler des méthodes, il suffit de les fusionner. De toute évidence, les fonctionnalités qu'ils fournissent sont tellement enchevêtrées que ce n'est vraiment qu'une seule chose.

Soit dit en passant, cela s'applique à toutes sortes de systèmes, non seulement aux systèmes Entité / Composant, mais aussi à l'héritage classique avec de simples "GameObject" ou même des fonctions de bibliothèque.

*) Vraiment seulement le mien . Les opinions varient considérablement sur Whats Da Real Component Systemz (TM)

Imi
la source