Que dois-je considérer lors de la conception d'un système de gestionnaire d'événements?

9

Je me suis frayé un chemin avec les bases d'un moteur de jeu Java et j'ai atteint le point où je suis prêt à ajouter un système de gestionnaire d'événements.

Je sais, en théorie , ce qu'un gestionnaire d'événements devrait faire: permettre aux objets de "s'inscrire" pour certains événements, et chaque fois que le gestionnaire d'événements est informé d'un événement, diffuser l'événement aux auditeurs "enregistrés". Ce que je suis perplexe, c'est comment commencer à le mettre en œuvre.

Je n'ai pas pu trouver quoi que ce soit, en ligne, sur la mise en œuvre d'un système d'événements à partir de zéro, donc je cherche des informations sur les meilleures pratiques dans ce cas - ce que je dois et ne dois pas faire.

Par exemple, est-il vraiment nécessaire que chacun de mes objets de jeu ait un EventManagerchamp? Étant donné que tous mes objets de jeu héritent d'une seule classe parent abstraite, je pense que je devrais pouvoir utiliser une référence statique afin qu'il n'y ait qu'une seule instance du gestionnaire d'événements, partagée entre tous les objets de jeu. Je fais quelque chose de similaire, avec l'applet, que j'utilise pour rendre chaque objet, déjà.

Je suppose que je devrais maintenir une collection quelconque pour chaque événement souscrit possible - ajouter et supprimer des objets de jeu de la liste, au besoin. Je pense qu'il devrait être possible de faire une file d'attente d'événements qui doivent être diffusés, auquel cas je pourrais simplement ajouter "EventManager.Update ()" à la boucle de jeu principale, et avoir la Update()méthode pour diffuser les événements qui se sont produits à la fin de chaque image. Enfin, chaque objet aurait une HandleEvent(Event e)méthode, qu'ils pourraient ensuite analyser et répondre de manière appropriée.


Cela ressemble-t-il à la direction appropriée vers la mise en œuvre d'un tel système, ou suis-je loin de la voie et / ou manque quelque chose de tout à fait évident?

Raven Dreamer
la source
2
gamedev.stackexchange.com/questions/7718/… Ceci est un Q / AI donné précédemment qui peut vous aider à démarrer.
James
@James Ah - ressemble à un bon lien. Merci! Je pense que je l'ai manqué plus tôt.
Raven Dreamer
4
Quelques conseils supplémentaires: décidez si certains événements doivent réellement prendre effet immédiatement plutôt qu'à la fin de la trame, en particulier si un gestionnaire d'événements déclenche des événements supplémentaires; pensez à ce qui se passe lorsque vous obtenez des boucles d'événements; si vous optez pour une file d'attente, vous aurez peut-être besoin d'un système prioritaire ou d'un moyen de contourner la file d'attente.
sam hocevar

Réponses:

6

Cela peut être aussi simple que vous le souhaitez.

for each object in the subscriber list:
    object.notify(this event) // (or 'HandleEvent' if you prefer)

N'essayez pas de déterminer ce que devrait faire un gestionnaire d'événements - déterminez ce dont vous avez besoin. Le reste devrait suivre à partir de là, ou au moins, devrait suggérer des questions plus spécifiques.

Kylotan
la source
3
+1 pour "N'essayez pas de déterminer ce que doit faire un X - déterminez ce dont vous avez besoin." En fait, n'ajoutez pas de gestionnaire d'événements du tout jusqu'à ce que vous sentiez que vous avez besoin d'un moyen découplé mais centralisé d'appeler des fonctions.
@JoeWreschnig Oh mon dieu oui =) "déterminez ce dont vous avez besoin pour faire" C'est parmi les 3 premiers conseils que tout développeur devrait prendre à cœur.
Patrick Hughes,
Au début, je voulais être en désaccord avec votre deuxième phrase à propos de ne pas ajouter de gestionnaire d'événements, car la plupart des jeux peuvent bénéficier d'une manière découplée mais centralisée d'appeler des fonctions, mais j'ai ensuite pensé au nombre de petits jeux que j'ai créés qui ne l'ont pas fait. avoir une sorte de gestionnaire d'événements. Alors, oui
jhocking
3

Les trois méthodes essentielles dont un système d'événements a besoin sont une méthode addListener (), une méthode removeListener () et une méthode pour dispatchEvent (). C'est-à-dire, une méthode utilisée par les objets pour s'inscrire à un événement, une méthode utilisée pour se désinscrire et une méthode pour diffuser réellement un événement à tous les écouteurs. Tout le reste est de la sauce.

Comme vous le constatez, vous aurez certainement besoin d'une sorte de structure de données pour garder une trace des auditeurs enregistrés. L'approche la plus simple serait un tableau associatif (dictionnaire ou objet en JavaScript) qui associe un vecteur (ou simplement un tableau selon la langue) à un événement. Ce vecteur est une liste de tous les auditeurs enregistrés; ajoutez des écouteurs à la liste ou supprimez-les dans les méthodes add / removeListener ().

Pour diffuser un événement dans dispatchEvent (), cela peut être aussi simple que de parcourir le vecteur. Vous pouvez ajouter plus de complexité à la répartition en triant la priorité des événements ou autre chose, mais ne vous en faites pas tant que vous n'en avez pas besoin. Dans de nombreux cas, vous n'en aurez pas besoin.

Il y a une petite nuance à dispatchEvent () lorsque vous considérez les données à transmettre avec l'événement. Au niveau le plus élémentaire, vous pourriez ne pas transmettre de données supplémentaires; tout ce que l'auditeur doit savoir, c'est qu'un événement s'est produit. Cependant, la plupart des événements ont des données supplémentaires qui les accompagnent (par exemple, où l'événement s'est produit). Vous voudrez donc que dispatchEvent () accepte et transmette certains paramètres.

Par exemple, est-il vraiment nécessaire que chacun de mes GameObjects ait un champ "EventManagner"? Étant donné que tous mes GameObjects héritent d'une seule classe parent abstraite, je pense que je devrais pouvoir utiliser une référence statique afin qu'il n'y ait qu'une seule instance de EventManager, partagée entre tous les objets de jeu.

Il existe de nombreuses façons de donner à tous vos objets de jeu une référence à la classe EventManager. Une référence statique est certainement un moyen; un autre est avec un Singleton. Cependant, ces deux approches sont assez rigides, donc la plupart des gens ici recommandent un localisateur de services ou une injection de dépendance. J'ai fait l'injection de dépendance; cela signifie un champ EventManager distinct pour chaque objet, ce que vous semblez vouloir éviter, mais je ne sais pas pourquoi c'est un problème. Ce n'est pas comme s'il y avait beaucoup de frais généraux liés au stockage d'un tas de pointeurs.

jhocking
la source
oui, la première fois que j'ai vraiment saisi et implémenté l'injection de dépendance, c'était pour un objet EventDispatcher. Une fois que j'ai mis cela sous ma ceinture, j'ai eu hâte de refactoriser beaucoup de choses avec ce motif.
jhocking
0

AVERTISSEMENT

Je ne suis pas un programmeur Java mais j'ai écrit un article expliquant comment créer un système d'événements en C89, l'un des langages les plus simples (à mon humble avis), consultez-le

https://prdeving.wordpress.com/2017/04/03/event-driven-programming-with-c-89/

Les bases de la gestion des événements sont assez simples.

  • enregistrer un événement avec rappel
  • événement déclencheur
  • parcourir les auditeurs pour voir s'il y a une correspondance
  • pompier si trouvé
  • répéter

Sachant cela (mais ne sachant pas comment l'implémenter en Java), il est facile de comprendre que vous avez besoin de certains gestionnaires (fonctions pour gérer les événements) et de les ajouter à un tableau (avec des pointeurs, en C) associé à un nom d'événement. Une fois que vous avez déclenché un événement, vous stockez le nom de l'événement déclenché et ses arguments dans une pile, c'est ce qu'on appelle le pool d'événements.

Ensuite, vous avez un résumé d'événement (une boucle simple), qui affiche le premier événement de cette pile, essaie de trouver un gestionnaire pour celui-ci, et s'il y en a un, l'exécute avec les paramètres.

Bien sûr, ce résumé d'événement peut être déclenché quand vous le souhaitez, par exemple une fois par image après avoir appelé votre Update()fonction.

PRDeving
la source
-2

Pour le champ EventManager, utilisez une variable statique. Un Singleton pour EventManager serait également une excellente idée.

Votre approche semble bonne, mais n'oubliez pas de la rendre thread-safe.

Il est bon d'exécuter des IO-Events avant ou après les "GameEvent", donc dans un cadre chaque "GameEvent" traite les mêmes données.

Sibbo
la source
"Thread-save"? Hmmm
U62
-1 pour la suggestion de singleton entièrement inutile et injustifiée.