«The Game Object» - et conception basée sur les composants

25

Je travaille sur des projets de loisirs depuis 3 ou 4 ans. Des jeux 2D et 3D simples. Mais dernièrement, j'ai commencé un plus grand projet. Au cours des deux derniers mois, j'ai essayé de concevoir une classe d'objets de jeu qui peut être la base de tous mes objets de jeu. Donc, après de nombreux essais, je me suis tourné vers Google, ce qui m'a rapidement indiqué certains PDF GDC et PowerPoints. Et maintenant, j'essaie de saisir les objets de jeu basés sur des composants.

Je comprends que le moteur crée un objet de jeu et attache ensuite différents composants qui gèrent des choses comme la santé, la physique, le réseautage et tout ce que vous leur faites faire. Mais ce que je ne comprends pas, c'est comment le composant X sait si Y a changé l'état de l'objet. Comme comment le Composant Physique sait-il si le joueur est vivant, parce que la santé est contrôlée par le Composant Santé ..? Et comment le HealthComponent joue-t-il "l'animation du joueur décédé"?

J'avais l'impression que c'était quelque chose comme ça (dans le HealthComponent):

if(Health < 0) {
   AnimationComponent.PlayAnimation("played-died-animation")
}

Mais là encore, comment le HealthComponent sait-il que l'objet de jeu auquel il est attaché a un AnimationComponent attaché? La seule solution que je vois ici est

  1. Vérifiez si un composant d'animation est attaché ou non (à l'intérieur du code du composant ou du côté moteur)

  2. Avoir des composants nécessite d'autres composants, mais cela semble combattre toute la conception des composants.

  3. Écrivez comme, HealthWithAnimationComponent, HealthNoAnimationComponent, et ainsi de suite, ce qui semble encore une fois lutter contre toute l'idée de conception des composants.

hayer
la source
1
J'adore la question. J'aurais dû poser la question il y a les mêmes mois, mais je n'y suis jamais allé. Un problème supplémentaire que j'ai rencontré est lorsqu'un objet de jeu a plusieurs instances du même composant (plusieurs animations par exemple). Ce serait formidable si les réponses pouvaient toucher à cela. J'ai fini par utiliser des messages pour les notifications, avec des variables partagées entre tous les composants d'un objet Game (donc ils n'ont pas besoin d'envoyer de message pour obtenir une valeur pour une variable).
ADB
1
Selon le type de jeu, vous n'aurez probablement pas d'objets de jeu ayant un composant de santé et aucun composant d'animation. Et tous ces objets de jeu sont probablement une représentation de quelque chose comme Unit. Vous pouvez donc jeter le composant d'intégrité et créer UnitComponent qui aurait la santé du champ et connaître tous les composants que l'unité doit être. Cette granularité des composants n'aide vraiment rien - il est plus réaliste d'avoir un composant par domaine (rendu, audio, physique, logique de jeu).
Kikaimaru

Réponses:

11

Dans tous vos exemples, il y a un terrible problème. Le composant de santé doit connaître chaque type de composant qui pourrait avoir besoin de répondre à la mort de l'entité. Par conséquent, aucun de vos scénarios n'est approprié. Votre entité a une composante santé. Il a un composant d'animation. Ni dépendre ni connaître l'autre. Ils communiquent via un système de messagerie.

Lorsque le composant de santé détecte que l'entité est «morte», il envoie un message «Je suis mort». Il est de la responsabilité du composant d'animation de répondre à ce message en jouant l'animation appropriée.

Le composant d'intégrité n'envoie pas le message directement au composant d'animation. Peut-être qu'il les diffuse à tous les composants de cette entité, peut-être à l'ensemble du système; peut-être que le composant d'animation doit faire savoir au système de messagerie qu'il est intéressé par les messages «Je suis mort». Il existe de nombreuses façons d'implémenter le système de messagerie. Quelle que soit l'implémentation, le fait est que le composant d'intégrité et le composant d'animation n'ont jamais besoin de savoir ou de se soucier si l'autre est présent, et l'ajout de nouveaux composants ne nécessitera jamais de modifier ceux qui existent pour leur envoyer les messages appropriés.

Blecki
la source
Okey, cela a du sens. Mais qui déclare les «états» comme «morts» ou «portail-est-cassé», etc. Le composant ou le moteur? Parce que l'ajout d'un état «mort» à une chose qui n'aura jamais le composant santé attaché me semble un peu gaspillé. Je suppose que je vais plonger et commencer à tester du code et voir ce qui fonctionne.
hayer
Michael et Patrick Hughes ont la bonne réponse ci-dessus. Les composants ne sont que des données; donc ce n'est pas vraiment le composant de santé qui détecte le moment où l'entité est morte et envoie le message, c'est un élément de logique spécifique au jeu de niveau supérieur. Comment résumer cela dépend de vous. L'état réel de la mort n'a jamais besoin d'être stocké nulle part. L'objet est mort si sa santé est <0, et le composant de santé peut encapsuler ce bit de logique de vérification des données sans rompre un "pas de comportement!" restriction si vous ne considérez que les éléments qui modifient l'état du composant comme comportement.
Blecki
Juste curieux, comment géreriez-vous un composant MovementComponent? Lorsqu'il détecte une entrée, il doit augmenter la vitesse dans le PositionComponent. À quoi ressemblerait le message?
Tips48
8

La façon dont Artemis résout le problème est de ne pas effectuer de traitement dans les composants. Les composants contiennent uniquement les données dont ils ont besoin. Les systèmes lisent plusieurs types de composants et effectuent le traitement nécessaire.

Donc, dans votre cas, vous pouvez avoir un RenderSystem qui lit dans le HealthComponent (et autres) et lit les files d'attente des animations appropriées. La séparation des données des fonctions de cette façon facilite la bonne gestion des dépendances.

Michael
la source
Cela finit par être une bonne façon de gérer le problème: les composants représentent des propriétés tandis que les systèmes lient des propriétés disparates et les utilisent pour faire du travail. C'est un énorme changement par rapport à la pensée OOP traditionnelle et fait mal à la tête de certaines personnes =)
Patrick Hughes
Okey, maintenant je suis vraiment perdu. "En revanche, dans un ES, si vous avez 100 unités sur un champ de bataille, chacune représentée par une entité, alors vous n'avez aucune copie de chaque méthode qui peut être invoquée sur une unité - parce que Les entités ne contiennent pas de méthodes. Les composants ne contiennent pas non plus de méthodes. Au lieu de cela, vous avez un système externe pour chaque aspect, et ce système externe contient toutes les méthodes qui peuvent être invoquées sur toute entité qui possède le composant qui le marque comme étant compatible avec ce système." Eh bien, où sont stockées les données dans un GunComponent? Comme les tours, etc. Si toutes les entités partagent le même composant.
hayer
1
Si je comprends bien, toutes les entités ne partagent pas le même composant, chaque entité peut être associée à N instances de composant. Un système interroge ensuite le jeu pour obtenir une liste de toutes les entités qui ont des instances de composants qui leur sont attachées, puis effectue tout traitement sur celles-ci
Jake Woods
Cela déplace simplement le problème. Comment un système sait-il quels composants utiliser? Un système peut également avoir besoin d'autres systèmes (le système StateMachine peut vouloir appeler une animation par exemple). Cependant, cela résout le problème de la possession des données par l'OMS. En fait, une implémentation plus simple serait d'avoir un dictionnaire dans l'objet de jeu et chaque système y crée ses variables.
ADB
Cela déplace le problème mais vers un endroit plus tenable. Les systèmes ont leurs composants pertinents câblés. Les systèmes peuvent communiquer entre eux via des composants (StateMachine peut définir une valeur de composant que l'animation lit pour savoir quoi faire (ou il pourrait déclencher un événement). L'approche par dictionnaire ressemble au modèle de propriétés qui peut également fonctionner. Les composants sont que les propriétés connexes sont regroupées et peuvent être vérifiées statiquement. Pas d'erreurs bizarres car vous avez ajouté "Dommages" à un endroit mais avez essayé de le récupérer en utilisant "Dommages" à un autre.
Michael
6

Dans votre code, il existe des moyens (je les ai utilisés, peut-être d'autres moyens existent) de savoir si l'objet a changé d'état:

  1. Envoyer le message.
  2. Lisez directement les données du composant.

1) Vérifiez si un composant d'animation est attaché ou non (à l'intérieur du code du composant ou du côté moteur)

Pour cela, j'ai utilisé, 1. la fonction HasComponent de GameObject, ou 2. lorsque vous attachez un composant, vous pouvez vérifier les dépendances dans une fonction de construction, ou 3. Si je sais avec certitude que l'objet a ce composant, je l'utilise simplement.

2) Avoir des composants nécessite d'autres composants, mais cela semble combattre toute la conception des composants.

Dans certains articles que j'ai lus, les composants du système Idéal ne dépendent pas les uns des autres, mais dans la vraie vie, ce n'est pas le cas.

3) Écrivez comme, HealthWithAnimationComponent, HealthNoAnimationComponent, et ainsi de suite, ce qui semble encore une fois combattre l'idée de conception de composant.

C'est une mauvaise idée d'écrire de tels composants. Dans mon application, j'ai créé le composant Santé le plus indépendant. Maintenant, je pense à un modèle Observer qui informe les abonnés de certains événements spécifiques (par exemple, "hit", "heal", etc.). Donc AnimationComponent doit décider par lui-même quand jouer l'animation.

Mais quand j'ai lu un article sur CBES, cela m'a impressionné, donc je suis très heureux maintenant quand j'utilise CBES et en découvre de nouvelles possibilités.

Yevhen
la source
1
Eh bien, google.no/… @ slide 16
hayer
@bobenko, veuillez donner un lien vers l'article sur CBES. Je suis aussi très intéressant dedans;)
Edward83
1
Et lambdor.net/?p=171 @ bottom, ce genre de résumé de ma question Comment définir différentes fonctionnalités en termes de composants relativement complexes et non élémentaires? Quels sont les composants les plus élémentaires? En quoi les composants élémentaires sont-ils différents des fonctions pures? Comment les composants existants peuvent-ils communiquer automatiquement avec les nouveaux messages des nouveaux composants? Quel est l'intérêt d'ignorer un message qu'un composant ne connaît pas? Qu'est-il arrivé au modèle d'entrée-processus-sortie après tout?
hayer
1
voici une bonne réponse sur CBES stackoverflow.com/a/3495647/903195 la plupart des articles que j'ai recherchés proviennent de cette réponse. J'ai commencé et inspiré avec cowboyprogramming.com/2007/01/05/evolve-your-heirachy puis dans Gems 5 (si je me souviens bien) il y avait un bon article avec des exemples.
Yevhen
Mais qu'en est-il d'un autre concept de programmation fonctionnelle-réactive, pour moi cette question est toujours ouverte, mais peut être pour vous une bonne direction pour les recherches.
Yevhen
3

C'est comme Michael, Patrick Hughes et Blecki le disent. La solution pour éviter de simplement déplacer le problème est d'abandonner l'idéologie qui cause le problème en premier lieu.

C'est moins OOD et plus comme la programmation fonctionnelle. Lorsque j'ai commencé à expérimenter la conception basée sur les composants, j'ai repéré ce problème en cours de route. J'ai googlé un peu plus, et j'ai trouvé que "la programmation réactive Functive" était la solution.

Maintenant, mes composants ne sont rien d'autre qu'une collection de variables et de champs qui décrivent son état actuel. Ensuite, j'ai un tas de classes "System" qui mettent à jour tous les composants qui leur sont pertinents. La partie réactive est obtenue en exécutant les systèmes dans un ordre bien défini. Cela garantit que quel que soit le système en ligne pour effectuer son traitement et sa mise à jour, et quels que soient les composants et entités qu'il a l'intention de lire et de mettre à jour, il travaille toujours sur des données à jour.

Cependant, d'une certaine manière, vous pouvez toujours affirmer que le problème a encore évolué. Et si ce n'est pas directement l'ordre dans lequel vos systèmes doivent fonctionner? Et s'il y a des relations cycliques et que ce n'est qu'une question de temps avant de regarder un désordre d'instructions if-else et switch? C'est une forme implicite de messagerie, non? À première vue, je pense que c'est un petit risque. Habituellement, les choses sont traitées dans l'ordre. Quelque chose comme: Entrée du joueur -> Positions d'entité -> Détection de collision -> Logique de jeu -> Rendu -> Recommencer. Dans ce cas, vous auriez un système pour chacun, fournir à chaque système une méthode update (), puis les exécuter en séquence dans votre gameloop.

uhmdown
la source