Question

Pour un jeu 2D que je fais (pour Android), j'utilise un système basé sur des composants où un GameObject contient plusieurs objets GameComponent. Les composants de jeu peuvent être des éléments tels que des composants d'entrée, des composants de rendu, des composants émettant des balles, etc. Actuellement, GameComponants a une référence à l'objet qui les possède et peut le modifier, mais le jeu GameObject a juste une liste de composants et il ne se soucie pas de ce que les composants sont tant qu'ils peuvent être mis à jour lorsque l'objet est mis à jour.

Parfois, un composant a des informations que le GameObject doit connaître. Par exemple, pour la détection de collision, GameObject se regarde avec le sous-système de détection de collision à notifier lorsqu'il entre en collision avec un autre objet. Le sous-système de détection de collision doit connaître la boîte de délimitation de l'objet. Je stocke x et y dans l'objet directement (car il est utilisé par plusieurs composants), mais la largeur et la hauteur ne sont connues que du composant de rendu qui contient le bitmap de l'objet. Je voudrais avoir une méthode Getboundingbox ou GetWidth dans le jeu GameObject qui obtient ces informations. Ou en général, je souhaite envoyer des informations d'un composant à l'objet. Cependant, dans ma conception actuelle, GameObject ne sait pas quels composants spécifiques il dispose dans la liste.

Je peux penser à plusieurs façons de résoudre ce problème:

  1. Au lieu d'avoir une liste de composants complètement générique, je peux laisser le GameObject avoir un champ spécifique pour certains des composants importants. Par exemple, il peut avoir une variable de membre appelée RendringComponent; Chaque fois que j'ai besoin d'obtenir la largeur de l'objet que j'utilise renderingComponent.getWidth(). Cette solution permet toujours une liste générique de composants, mais elle traite certaines d'entre elles différemment, et j'ai peur que je finisse par avoir plusieurs champs exceptionnels car davantage de composants doivent être interrogés. Certains objets n'ont même pas de composants de rendu.

  2. Ayez les informations requises en tant que membres du GameObject mais permettez aux composants de les mettre à jour. Ainsi, un objet a une largeur et une hauteur de 0 ou -1 par défaut, mais un composant de rendu peut les définir sur les valeurs correctes dans sa boucle de mise à jour. Cela ressemble à un hack et je pourrais finir par pousser beaucoup de choses à la classe GameObject pour plus de commodité même si tous les objets n'en ont pas besoin.

  3. Demandez aux composants d'implémenter une interface qui indique le type d'informations pour lesquelles ils peuvent être interrogés. Par exemple, un composant de rendu implémenterait l'interface HASSIZE qui comprend des méthodes telles que GetWidth et Getheight. Lorsque le GameObject a besoin de la largeur, il boucle sur ses composants qui vérifient s'il implémente l'interface HASSIZE (en utilisant le instanceof mot-clé en java, ou is en C #). Cela semble être une solution plus générique, un inconvénient est que la recherche du composant pourrait prendre un certain temps (mais alors, la plupart des objets ont 3 ou 4 composants uniquement).

Cette question ne concerne pas un problème spécifique. Cela apparaît souvent dans mon design et je me demandais quelle était la meilleure façon de le gérer. Les performances sont quelque peu importantes car il s'agit d'un jeu, mais le nombre de composants par objet est généralement faible (le maximum est 8).

La version courte

Dans un système basé sur des composants pour un jeu, quelle est la meilleure façon de transmettre des informations des composants à l'objet tout en gardant le design générique?

Était-ce utile?

La solution

Nous obtenons des variations sur cette question trois ou quatre fois par semaine sur Gamedev.net (où le GameObject est généralement appelé une «entité») et jusqu'à présent, il n'y a pas de consensus sur la meilleure approche. Plusieurs approches différentes se sont révélées être réalisables, mais je ne m'en inquiéterais pas trop.

Cependant, généralement les problèmes concernant la communication entre les composants. Les gens se soucient-ils rarement d'obtenir des informations d'un composant à l'entité - si une entité sait de quelle information il a besoin, alors elle sait vrai les données. Si vous devez être réactif plutôt qu'actif, enregistrez les rappels ou faites configurer un modèle d'observateur avec les composants pour informer l'entité quand quelque chose dans le composant a changé et lire la valeur à ce stade.

Les composants entièrement génériques sont largement inutiles: ils doivent fournir une sorte d'interface connue, sinon il y a peu de points. Sinon, vous pouvez aussi bien avoir un large éventail associatif de valeurs non typées et en faire. Dans Java, Python, C # et d'autres langages légèrement plus élevés que C ++, vous pouvez utiliser la réflexion pour vous donner un moyen plus générique d'utiliser des sous-classes spécifiques sans avoir à coder les informations de type et d'interface dans les composants eux-mêmes.

Quant à la communication:

Certaines personnes font des hypothèses selon lesquelles une entité contiendra toujours un ensemble connu de types de composants (où chaque instance est l'une des nombreuses sous-classes possibles) et peut donc simplement saisir une référence directe à l'autre composant et lire / écrire via son interface publique.

Certaines personnes utilisent publier / souscrit, signaux / emplacements, etc., pour créer des connexions arbitraires entre les composants. Cela semble un peu plus flexible, mais en fin de compte, vous avez toujours besoin de quelque chose avec une connaissance de ces dépendances implicites. (Et si cela est connu au moment de la compilation, pourquoi ne pas simplement utiliser l'approche précédente?)

Ou, vous pouvez mettre toutes les données partagées dans l'entité elle-même et l'utiliser comme zone de communication partagée (liée à la système de tableau noir en ai) que chacun des composants peut lire et écrire. Cela nécessite généralement une certaine robustesse face à certaines propriétés qui ne sont pas existantes lorsque vous vous y attendez. Il ne se prête pas non plus au parallélisme, bien que je doute que ce soit une préoccupation massive sur un petit système intégré ...?

Enfin, certaines personnes ont des systèmes où l'entité n'existe pas du tout. Les composants vivent dans leurs sous-systèmes et la seule notion d'une entité est une valeur d'identification dans certains composants - si un composant de rendu (dans le système de rendu) et un composant de joueur (dans le système des joueurs) ont le même ID, alors vous pouvez supposer Le premier gère le dessin de ce dernier. Mais il n'y a aucun objet unique qui agrége l'un ou l'autre de ces composants.

Autres conseils

Comme d'autres l'ont dit, il n'y a pas toujours de bonne réponse ici. Différents jeux se prêteront à différentes solutions. Si vous construisez un grand jeu complexe avec de nombreux types d'entités différents, une architecture générique plus découplée avec une sorte de messagerie abstraite entre les composants peut valoir la peine pour la maintenabilité que vous obtenez. Pour un jeu plus simple avec des entités similaires, il peut être le plus logique de simplement pousser tout cet état dans GameObject.

Pour votre scénario spécifique où vous devez stocker la boîte de délimitation quelque part et seul le composant de collision s'en soucie, je le ferais:

  1. Conservez-le dans le composant de collision lui-même.
  2. Faites en sorte que le code de détection de collision fonctionne directement avec les composants.

Ainsi, au lieu de faire en sorte que le moteur de collision itére à travers une collection de GameObjects pour résoudre l'interaction, demandez-le directement d'une collection de Collision Componsents. Une fois qu'une collision s'est produite, il appartiendra au composant de pousser cela à son parent gameObject.

Cela vous donne quelques avantages:

  1. Laisse un état spécifique à la collision de GameObject.
  2. Vous épargne d'itérer sur des gameobjects qui n'ont pas de composants de collision. (Si vous avez beaucoup d'objets non interactifs comme les effets visuels et la décoration, cela peut économiser un nombre décent de cycles.)
  3. Vous épargne des cycles de brûlure marchant entre l'objet et son composant. Si vous itérez à travers les objets, faites getCollisionComponent() Sur chacun, ce suivi du pointeur peut provoquer une ratée de cache. Faire cela pour chaque cadre pour chaque objet peut brûler beaucoup de CPU.

Si vous êtes intéressé, j'ai plus sur ce modèle ici, bien qu'il semble que vous compreniez déjà la plupart de ce qui est dans ce chapitre.

Utilisez un "bus d'événement". (Notez que vous ne pouvez probablement pas utiliser le code tel quel, mais il devrait vous donner l'idée de base).

Fondamentalement, créez une ressource centrale où chaque objet peut s'inscrire en tant qu'auditeur et dire "si X se passe, je veux savoir". Lorsque quelque chose se passe dans le jeu, l'objet responsable peut simplement envoyer un événement X dans le bus de l'événement et toutes les parties intéressantes le remarqueront.

Modifier] pour une discussion plus détaillée, voir Message qui passe (grâce à snk_kid pour souligner cela).

Une approche consiste à initialiser un conteneur de composants. Chaque composant peut fournir un service et peut également nécessiter des services d'autres composants. Selon votre langage de programmation et votre environnement, vous devez proposer une méthode pour fournir ces informations.

Dans sa forme la plus simple, vous avez des connexions individuelles entre les composants, mais vous aurez également besoin de connexions un-à-plusieurs. Par exemple le CollectionDetector aura une liste de composants implémentant IBoundingBox.

Pendant l'initialisation, le conteneur câblera les connexions entre les composants et pendant l'exécution, il n'y aura pas de coût supplémentaire.

Ceci est proche de votre solution 3), attendez-vous à ce que les connexions entre les composants ne soient câblées qu'une seule fois et ne sont pas vérifiées à chaque itération de la boucle de jeu.

La Cadre d'extensibilité géré pour .NET est une belle solution à ce problème. Je me rends compte que vous avez l'intention de vous développer sur Android, mais vous pouvez toujours vous inspirer de ce cadre.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top