Communication événementielle dans un moteur de jeu: oui ou non?

62

Je lis Game Coding Complete et l'auteur recommande la communication événementielle entre les objets et les modules du jeu.

Fondamentalement, tous les acteurs du jeu vivants doivent communiquer avec les modules clés (physique, intelligence artificielle, logique de jeu, vue du jeu, etc.) via un système de messagerie d'événements interne. Cela signifie qu'il faut concevoir un gestionnaire d'événements efficace. Un système mal conçu consomme des cycles du processeur, en particulier pour les plates-formes mobiles.

Est-ce une approche éprouvée et recommandée? Comment devrais-je décider de l'utiliser?

Bunkai.Satori
la source
Bonjour James, merci pour votre réponse. Je l’aime bien, même si le livre décrit la communication événementielle comme un système très complexe consommant apparemment beaucoup de ressources processeur.
Bunkai.Satori
Mettez-le dans une réponse et ajoutez un lien vers l'aperçu de haut niveau d'un système de messagerie d'événements que j'ai donné comme réponse à un dépassement de capacité de la pile. Prendre plaisir! N'oubliez pas que ce que certaines personnes considèrent comme complexe ne doit pas forcément être :)
James
Vous pouvez vérifier cette réponse aussi bien en utilisant c ++ 11: stackoverflow.com/a/22703242/2929762
user2826084
libuva récemment émergé comme une bibliothèque d'événements réussie. (C'est dans C. Node.js est son cas d'utilisation le plus célèbre.)
Anko

Réponses:

46

Ceci est une extension de mon commentaire à une réponse complète, comme suggéré.

Oui , tout simplement. La communication doit avoir lieu et bien qu'il y ait des situations où "Sommes-nous là?" Une interrogation de type type est requise, faire vérifier par les choses si elles doivent faire autre chose fait perdre du temps. Vous pourriez plutôt les faire réagir aux choses qu'on leur dit de faire. De plus, une voie de communication bien définie entre les objets / systèmes / modules optimise considérablement les configurations en parallèle.

J'ai donné un aperçu de haut niveau d'un système de messagerie d'événements sur le dépassement de capacité . Je l'ai utilisé dès l'école pour des titres de jeux professionnels et des produits non destinés aux jeux, adaptés au cas d'utilisation à chaque fois.

EDIT : Pour répondre à une question de commentaire sur comment savoir quels objets doivent recevoir le message : Les objets eux-mêmes doivent Requestêtre informés des événements. Votre EventMessagingSystem(EMS) aura besoin Register(int iEventId, IEventMessagingSystem * pOjbect, (EMSCallback)fpMethodToUseForACallback)d'une correspondance ainsi que d'une correspondance Unregister(en créant une entrée unique pour un iEventIdpointeur et un rappel en dehors de l'objet). De cette façon, lorsqu'un objet veut savoir un message, il peut le faire Register()avec le système. Lorsqu'il n'a plus besoin de connaître les événements, il peutUnregister(). Clairement, vous voudriez un pool de ces objets d’enregistrement de rappel et un moyen efficace de les ajouter / supprimer des listes. (J'ai généralement utilisé des tableaux d'auto-classement; une façon élégante de dire qu'ils suivent leurs propres allocations entre une pile de pools d'objets inutilisés et des tableaux qui modifient leur taille en place en cas de besoin).

EDIT : Pour un jeu complètement fonctionnel avec une boucle de mise à jour et un système de messagerie d’événement, vous pouvez consulter un de mes anciens projets scolaires . Le message Stack Overflow lié ci-dessus s'y réfère également.

James
la source
Bonjour James, alors que je réfléchis davantage au système de messagerie d’événements interne, j’estime qu’il n’a pas besoin d’ajouter de coût supplémentaire au moteur. Par exemple, s'il y a 50 objets à l'écran, mais que seulement 5 d'entre eux sont supposés changer de comportement; Dans le système traditionnel, tous les 50 objectifs auraient à vérifier toutes leurs actions possibles, pour voir s’ils devaient faire quelque chose. Toutefois, avec l'utilisation de la messagerie d'événements, seuls 5 messages seront envoyés à ces 5 objets avec un changement d'action spécifique. Cela ressemble à une approche permettant de gagner du temps.
Bunkai.Satori
La façon dont le système lié à la réponse ci-dessus fonctionne est que les objets s'enregistrent uniquement pour entendre les messages qu'ils veulent. Nous l'utiliserions à notre avantage dans les jeux vidéo où il peut y avoir 100-200 objets dans un niveau, mais vous Activez celles avec lesquelles le joueur peut interagir directement, en limitant le nombre d'écoutes à l'écoute à une dizaine. Dans l'ensemble, ce type de système devrait être «plus intelligent» qu'un «sommes-nous encore là? système de vote, et devrait réduire les frais généraux d'un moteur, au moins en ce qui concerne la communication.
James
Un élément lié au système événementiel est source de confusion: sous un système traditionnel où chaque objet dispose d'un ensemble prédéfini de méthodes (OnUpdate (), OnMove (), OnAI (), OnCollisionCheck () ...) régulièrement appelé, chaque objet est responsable de la vérification et de la gestion de son état. Sous le système événementiel, il doit exister une condition de vérification du système maître de chaque objet, puis l'envoi de messages à ceux pour lesquels un événement a été détecté. Pour moi, cela normalise les états possibles des objets et limite la liberté de créer un comportement d'objet unique. Est-ce vrai?
Bunkai.Satori
Le modèle Observer est l’alternative typique à la scrutation. fr.wikipedia.org/wiki/Observer_pattern
Ricket
1
@ hamlin11 Heureux que ces informations continuent d'aider les gens :) En ce qui concerne l'enregistrement, si cela se produit souvent, rappelez-vous que vous souhaitez l'optimiser pour plus de rapidité, disposer d'un pool d'objets d'enregistrement à partir desquels puiser, etc.
James
22

Mon opinion est que vous devriez juste commencer à créer votre jeu et à mettre en œuvre ce avec quoi vous êtes à l'aise. En faisant cela, vous utiliserez MVC à certains endroits, mais pas à d’autres; événements certains endroits, mais pas d'autres; composants certains endroits, mais héritage d'autres; nettoyer la conception de certains endroits, et la conception crue d'autres.

Et ce n'est pas grave, car lorsque vous aurez terminé, vous aurez réellement un jeu. Un jeu est bien plus cool qu'un système de transmission de messages.

utilisateur744
la source
2
Bonjour Joe, merci pour votre réponse, j'aime bien. Vous croyez qu'il n'est pas nécessaire de pousser artificiellement une approche. Je devrais concevoir des applications comme je le pense, et si quelque chose ne fonctionnait pas plus tard, je le refais simplement.
Bunkai.Satori
C'est pourquoi le système existe. Beaucoup de gens ont fait exactement ce que vous avez dit, ont été témoins de ce cauchemar et ont dit qu'il devait y avoir un meilleur moyen. Vous pouvez répéter leurs erreurs ou en tirer des leçons et peut-être même les faire progresser davantage.
user441521
16

Une meilleure question est, quelles sont les alternatives? Dans un système aussi complexe avec des modules bien divisés pour la physique, l'IA, etc., comment pouvez-vous orchestrer ces systèmes?

La transmission de messages semble être la "meilleure" solution à ce problème. Je ne peux pas penser à des alternatives pour le moment. Mais il existe de nombreux exemples de transmission de messages dans la pratique. En fait, les systèmes d'exploitation utilisent la transmission de messages pour plusieurs de leurs fonctions. De Wikipedia :

Les messages sont également couramment utilisés dans le même sens en tant que moyen de communication interprocessus; l'autre technique courante étant les flux ou les canaux, dans lesquels les données sont envoyées sous la forme d'une séquence d'éléments de données élémentaires (la version de niveau supérieur d'un circuit virtuel).

(La communication entre processus est la communication entre les processus (c'est-à-dire les instances de programmes en cours d'exécution) au sein de l'environnement du système d'exploitation.

Donc, si cela suffit pour un système d'exploitation, il l'est probablement pour un jeu, n'est-ce pas? Il y a également d'autres avantages, mais je laisserai l'article de Wikipedia sur la transmission de message faire l'explication.

J'ai également posé une question sur le dépassement de pile, "Structures de données pour la transmission de messages dans un programme?" , que vous voudrez peut-être lire.

Rachitisme
la source
Bonjour Ricket, merci pour votre réponse. Vous avez demandé comment utiliser d'autres moyens pour permettre aux objets de jeu de se communiquer. Ce qui me vient à l’esprit, ce sont les appels directs aux méthodes. C’est vrai, cela ne vous donne pas autant de souplesse, mais en revanche, il évite la génération de messages d’événements, la transmission, la lecture, etc. Nous parlons toujours de plateformes mobiles, pas de jeux haut de gamme. Le système d'exploitation utilise à la hâte le système de messagerie interne. Cependant, il n'est pas conçu pour fonctionner en temps réel, où même des retards minimes peuvent provoquer des perturbations.
Bunkai.Satori
Laissez-moi garder cette question ouverte pendant un moment. Je voudrais recueillir plus d’opinions avant de la clore.
Bunkai.Satori
Les appels de méthode directs aboutissent généralement à un couplage des classes qui s'appellent. Ce n'est pas bon pour un système séparé en composants censés être interchangeables. Certains modèles peuvent faciliter les appels directs de méthode avec moins de cohésion (par exemple, la partie "contrôleur" de MVC sert essentiellement à faciliter les requêtes d'affichage du modèle, à moins que vous ne les coupliez), mais en général, la transmission de messages est le seul moyen de un système doit avoir un couplage nul entre les sous-systèmes (ou du moins le seul moyen que je connaisse).
Ricket
Ah, alors maintenant nous avons commencé à discuter de modèles. J'ai un peu entendu parler de cette approche de programmation. Maintenant, je commence à comprendre ce que sont les modèles. Il est complètement différent de la conception de l'application. Vous contrôlez généralement les objets, tout en utilisant le même code pour vérifier chaque objet. Après avoir vérifié l'objet, vous définissez ses attributs en conséquence.
Bunkai.Satori
1
La question "Structures de données ..." a une réponse incroyable. Je dirais que la réponse que vous avez choisie et l'article qui l'accompagne sont la meilleure description de l'architecture de moteur de jeu de cette taille que j'ai jamais vue.
deft_code
15

Les messages fonctionnent généralement bien lorsque:

  1. L'envoi du message n'a aucune importance s'il est reçu.
  2. L'expéditeur n'a pas besoin de recevoir une réponse immédiate du destinataire.
  3. Il peut y avoir plusieurs destinataires écoutant un seul expéditeur.
  4. Les messages seront envoyés rarement ou de manière imprévisible. (En d'autres termes, si chaque objet doit recevoir un message de "mise à jour" à chaque image, les messages ne vous achètent pas beaucoup.)

Plus vos besoins correspondent à cette liste, meilleurs seront vos messages. À un niveau très général, ils sont plutôt bons. Ils réussissent bien à ne pas gaspiller les cycles de la CPU pour ne rien faire à la différence des sondages ou d'autres solutions plus globales. Ils sont fantastiques pour découpler des parties d’une base de code, ce qui est toujours utile.

munificent
la source
4

Excellentes réponses jusqu'à présent de James et Ricket, je ne réponds que pour ajouter une note de prudence. La communication événementielle / transmission de messages / événement est certes un outil important dans l'arsenal du développeur, mais elle peut facilement être surutilisée. Quand vous avez un marteau, tout ressemble à un clou, etc.

Vous devez absolument, à 100%, penser au flux de données autour de votre titre et être parfaitement conscient de la manière dont l'information passe d'un sous-système à un autre. Dans certains cas, la transmission du message est clairement la meilleure; dans d'autres, il peut être plus approprié que l'un des sous-systèmes opère sur une liste d'objets partagés, ce qui permet à d'autres sous-systèmes de fonctionner également sur la même liste partagée; et beaucoup d'autres méthodes en plus.

Vous devez à tout moment savoir quels sous-systèmes ont besoin d'accéder à quelles données et à quel moment ils doivent y accéder. Cela affecte votre capacité à paralléliser et à optimiser et vous aide également à éviter les problèmes plus insidieux lorsque différentes parties de votre moteur deviennent trop étroitement liées. Vous devez penser à réduire au minimum la copie inutile de données et à la manière dont la disposition de vos données affecte votre utilisation du cache. Tout cela vous guidera vers la meilleure solution dans chaque cas.

Cela dit, à peu près tous les jeux sur lesquels j'ai travaillé ont un système de notification de message / événement asynchrone solide. Il vous permet de rédiger un code succinct, efficace et maintenable, même face à des besoins de conception complexes et en évolution rapide.

MrCranky
la source
+1 pour de bonnes informations supplémentaires. Salut, MrCranky, Merci. Selon ce que vous dites, cela devrait valoir la peine d’envisager le système de messagerie d’événement même pour les jeux mobiles. Cependant, la planification ne doit pas être négligée et il convient de bien prendre en compte les endroits où le système de messagerie sera utilisé.
Bunkai.Satori
4

Eh bien, je sais que ce message est assez ancien, mais je ne pouvais pas résister.

J'ai récemment construit un moteur de jeu. Il utilise des bibliothèques 3D pour le rendu et la physique, mais j’ai écrit la partie principale, qui définit et traite les entités et la logique du jeu.

Le moteur suit sûrement une approche traditionnelle. Il existe une boucle principale de mise à jour qui appelle la fonction de mise à jour pour toutes les entités. Les collisions sont directement signalées par rappel sur les entités. Les communications entre entités sont établies à l'aide de pointeurs intelligents échangés entre entités.

Il existe un système de messagerie primitif, qui ne traite qu'un petit groupe d'entités pour alimenter les messages. Il est préférable que ces messages soient traités à la fin d'une interaction de jeu (par exemple, la création ou la destruction d'une entité), car ils peuvent perturber la liste de mises à jour. Ainsi, à la fin de chaque boucle de jeu, une petite liste de messages est consommée.

Malgré le système de messagerie primitif, je dirais que le système est en grande partie "basé sur une boucle de mise à jour".

Bien. Après avoir utilisé ce système, je pense qu’il est très simple, rapide et bien organisé. La logique du jeu est visible et autonome dans les entités, pas dynamique comme un message quewe. Je ne voudrais vraiment pas que les événements soient axés sur les événements car, à mon avis, les systèmes d'événements introduisent une complexité inutile dans la logique du jeu et rendent le code du jeu très difficile à comprendre et à déboguer.

Mais, je pense aussi qu’un système pur "basé sur la boucle de mise à jour" comme le mien a aussi des problèmes.

Par exemple, à certains moments, une entité peut être dans un état "ne rien faire", peut attendre que le joueur s'approche ou quelque chose d'autre. Dans la plupart des cas, l'entité consomme le temps processeur pour rien et il est préférable de la désactiver, et de l'activer lorsqu'un certain événement se produit.

Donc, dans mon prochain moteur de jeu, je vais adopter une approche différente. Les entités s'enregistrent pour les opérations du moteur, telles que la mise à jour, le dessin, la détection de collision, etc. Chacun de ces événements aura des listes d'interfaces d'entités séparées pour les entités réelles.

Artur Correa
la source
Vous constaterez que les entités deviennent des bases de code monstres difficiles à gérer avec toute sorte de complexité dans le jeu. Vous finirez par les coupler et il sera nul d'ajouter ou de modifier des fonctionnalités. En décomposant vos fonctionnalités en petits composants, il sera plus facile de maintenir et d'ajouter des fonctionnalités. La question est alors de savoir comment ces composants communiquent. La réponse est que des événements d’un composant émettent des fonctions d’un autre tout en faisant passer des données primitives dans les arguments.
user441521
3

Oui. C'est un moyen très efficace pour les systèmes de jeu de communiquer les uns avec les autres. Les événements vous aident à découpler de nombreux systèmes et permettent même de compiler des éléments séparément sans connaître l’existence de chacun. Cela signifie que vos classes peuvent être plus facilement prototypées et que les temps de compilation sont plus rapides. Plus important encore, vous vous retrouvez avec une conception de code plat au lieu d'un désordre de dépendance.

Un autre grand avantage des événements est qu'ils sont facilement diffusés sur un réseau ou un autre canal de texte. Vous pouvez les enregistrer pour une lecture ultérieure. Les possibilités sont infinies.

Autre avantage: plusieurs sous-systèmes peuvent écouter le même événement. Par exemple, toutes vos vues distantes (lecteurs) peuvent automatiquement s'abonner à l'événement de création d'entité et générer des entités sur chaque client avec peu de travail de votre part. Imaginez à quel point cela demanderait plus de travail si vous n'utilisiez pas les événements: vous deviez passer des Update()appels quelque part ou peut-être appeler view->CreateEntityde la logique du jeu (où la connaissance de la vue et des informations dont elle a besoin n'appartient pas). Il est difficile de résoudre ce problème sans événements.

Avec les événements, vous obtenez une solution élégante et découplée qui prend en charge un nombre infini d’objets de types illimités qui peuvent tout simplement s’abonner à des événements et faire ce qu’il faut quand quelque chose se passe dans votre jeu. C'est pourquoi les événements sont géniaux.

Plus de détails et une implémentation ici .

utilisateur2826084
la source
Grands points, même si à sens unique. Je me méfie toujours de la généralité: existe-t-il des cas anti -usage pour les événements?
Anko
1
Points anti-utilisation: lorsque vous devez absolument avoir une réponse directe. Lorsque tout ce que vous voulez faire sur un événement est toujours exécuté directement et dans le même fil. Lorsqu'il n'y a absolument aucun délai extérieur susceptible de retarder l'achèvement des appels. Lorsque vous n'avez que des dépendances linéaires. Lorsque vous faites rarement plusieurs choses en même temps. Si vous examinez node.js, tous les appels io sont basés sur des événements. Node.js est un endroit où les événements ont été implémentés à 100% correctement.
user2826084
Le système d'événements n'a pas besoin d'être asynchrone. Vous pouvez utiliser des coroutines pour simuler l'async, tout en obtenant la synchronisation quand vous en avez besoin.
user441521
2

D'après ce que j'ai vu, il ne semble pas très courant de disposer d'un moteur entièrement basé sur la messagerie. Bien sûr, il existe des sous-systèmes qui se prêtent très bien à un tel système de messagerie, tels que la mise en réseau, l'interface graphique et éventuellement d'autres. En général cependant, il y a quelques problèmes auxquels je peux penser:

  • Les moteurs de jeu traitent de grandes quantités de données.
  • Les jeux doivent être rapides (20-30 FPS devrait être le minimum).
  • Il est souvent nécessaire de savoir si quelque chose a été fait ou quand cela sera fait.

Avec l’approche commune des développeurs de jeux consistant à "il doit être aussi efficace que possible", ce système de messagerie n’est donc pas si courant.

Cependant, je conviens que vous devriez simplement aller de l'avant et l'essayer. Un tel système présente certainement de nombreux avantages et la puissance de calcul est aujourd'hui disponible à moindre coût.

mrbinary
la source
Je ne sais pas si je suis d'accord avec ça. L'alternative est le vote qui est un gaspillage. Répondre aux événements uniquement est le plus efficace. Oui, il y aura toujours des sondages et des événements, mais bon nombre de choses sont également axées sur les événements.
user441521
Je ne suis pas d'accord avec ça non plus. Le nombre de moteurs de jeux de données traités est en grande partie hors de propos, car la messagerie d'événements est conçue pour la communication entre sous-systèmes. Les objets de jeu ne doivent pas communiquer directement avec d'autres objets de jeu (bien que les gestionnaires d'événements personnalisés dans les objets puissent constituer une exception). En raison de ce nombre relativement petit de sous-systèmes et de leurs domaines "d'intérêt", le coût en performances d'un réseau fédérateur de messagerie bien conçu, dans un moteur multithread, est négligeable.
Ian Young