EDIT : Je décris ci-dessous un système de base de messagerie événementielle que j'ai utilisé maintes et maintes fois. Et il m'est apparu que ces deux projets scolaires sont open source et sur le web. Vous pouvez trouver la deuxième version de ce système de messagerie (et bien d'autres choses) à l'adresse suivante http://sourceforge.net/projects/bpfat/ .. Profitez-en, et lisez ci-dessous pour une description plus approfondie du système !
J'ai écrit un système de messagerie générique et je l'ai introduit dans une poignée de jeux qui sont sortis sur la PSP ainsi que dans certains logiciels d'application d'entreprise. Le but du système de messagerie est de ne faire circuler que les données nécessaires au traitement d'un message ou d'un événement, selon la terminologie que vous souhaitez utiliser, afin que les objets n'aient pas à se connaître les uns les autres.
Voici un bref aperçu de la liste des objets utilisés à cette fin :
struct TEventMessage
{
int _iMessageID;
}
class IEventMessagingSystem
{
Post(int iMessageId);
Post(int iMessageId, float fData);
Post(int iMessageId, int iData);
// ...
Post(TMessageEvent * pMessage);
Post(int iMessageId, void * pData);
}
typedef float(*IEventMessagingSystem::Callback)(TEventMessage * pMessage);
class CEventMessagingSystem
{
Init ();
DNit ();
Exec (float fElapsedTime);
Post (TEventMessage * oMessage);
Register (int iMessageId, IEventMessagingSystem* pObject, FObjectCallback* fpMethod);
Unregister (int iMessageId, IEventMessagingSystem* pObject, FObjectCallback * fpMethod);
}
#define MSG_Startup (1)
#define MSG_Shutdown (2)
#define MSG_PlaySound (3)
#define MSG_HandlePlayerInput (4)
#define MSG_NetworkMessage (5)
#define MSG_PlayerDied (6)
#define MSG_BeginCombat (7)
#define MSG_EndCombat (8)
Et maintenant, un peu d'explication. Le premier objet, TEventMessage, est l'objet de base pour représenter les données envoyées par le système de messagerie. Par défaut, il aura toujours l'Id du message envoyé, donc si vous voulez vous assurer que vous avez reçu un message que vous attendiez, vous pouvez le faire (généralement, je ne le fais qu'en débogage).
Ensuite, il y a la classe Interface qui donne un objet générique pour le système de messagerie à utiliser pour le casting tout en faisant des callbacks. En outre, elle fournit également une interface "facile à utiliser" pour l'envoi de différents types de données au système de messagerie.
Après cela, nous avons notre typedef Callback. Pour faire simple, il attend un objet du type de la classe d'interface et transmet un pointeur TEventMessage... En option, vous pouvez rendre le paramètre constant, mais j'ai déjà utilisé le traitement en amont pour des choses comme le débogage de la pile et autres du système de messagerie.
Le dernier et le plus important est l'objet CEventMessagingSystem. Cet objet contient un tableau de piles d'objets de rappel (ou des listes liées ou des files d'attente ou tout autre moyen de stocker les données). Les objets de rappel, non représentés ci-dessus, doivent maintenir (et sont définis de manière unique par) un pointeur vers l'objet ainsi que la méthode à appeler sur cet objet. Lorsque vous enregistrez(), vous ajoutez une entrée sur la pile d'objets sous la position du tableau de l'id du message. Lorsque vous désenregistrez(), vous supprimez cette entrée.
C'est à peu près tout. Maintenant, il est stipulé que tout doit connaître l'IEventMessagingSystem et l'objet TEventMessage... mais cet objet ne doit pas changer souvent et ne doit transmettre que les parties de l'information qui sont vitales pour la logique dictée par l'événement appelé. De cette façon, un joueur n'a pas besoin de connaître la carte ou l'ennemi directement pour lui envoyer des événements. Un objet géré peut également appeler une API vers un système plus large, sans avoir besoin de savoir quoi que ce soit à son sujet.
Par exemple : Quand un ennemi meurt, vous voulez qu'il joue un effet sonore. En supposant que vous ayez un gestionnaire de sons qui hérite de l'interface IEventMessagingSystem, vous pourriez configurer un callback pour le système de messagerie qui accepterait un TEventMessagePlaySoundEffect ou quelque chose de ce genre. Le Sound Manager enregistrerait alors ce callback lorsque les effets sonores sont activés (ou désenregistrerait le callback lorsque vous souhaitez couper tous les effets sonores pour faciliter les capacités d'activation et de désactivation). Ensuite, l'objet ennemi hérite également de IEventMessagingSystem, crée un objet TEventMessagePlaySoundEffect (il a besoin de MSG_PlaySound pour son ID de message, puis de l'ID de l'effet sonore à jouer, qu'il s'agisse d'un ID int ou du nom de l'effet sonore) et appelle simplement Post(&oEventMessagePlaySoundEffect).
Il ne s'agit que d'une conception très simple, sans mise en œuvre. Si vous avez une exécution immédiate, vous n'avez pas besoin de mettre en mémoire tampon les objets TEventMessage (ce que j'ai utilisé principalement dans les jeux de console). Si vous êtes dans un environnement multithread, il s'agit d'un moyen très bien défini pour que les objets et les systèmes fonctionnant dans des threads séparés puissent communiquer entre eux, mais vous voudrez conserver les objets TEventMessage afin que les données soient disponibles lors du traitement.
Une autre modification est que pour les objets qui n'ont jamais besoin de poster des données, vous pouvez créer un ensemble statique de méthodes dans le IEventMessagingSystem afin qu'ils n'aient pas à en hériter (cela est utilisé pour faciliter l'accès et les capacités de rappel, et n'est pas -directement- nécessaire pour les appels Post()).
Pour toutes les personnes qui mentionnent MVC, c'est un très bon modèle, mais vous pouvez le mettre en œuvre de tellement de manières différentes et à des niveaux différents. Le projet actuel sur lequel je travaille professionnellement est une configuration MVC environ 3 fois plus, il y a le MVC global de l'application entière et ensuite, au niveau de la conception, chaque M V et C est aussi un modèle MVC autonome. Donc, ce que j'ai essayé de faire ici est d'expliquer comment faire un C qui est assez générique pour gérer à peu près n'importe quel type de M sans avoir besoin d'entrer dans un View...
Par exemple, un objet qui meurt peut vouloir jouer un effet sonore Vous feriez une structure pour le système de son comme TEventMessageSoundEffect qui hérite du TEventMessage et ajoute un ID d'effet sonore (que ce soit un Int préchargé, ou le nom du fichier sfx, peu importe comment ils sont suivis dans votre système). Ensuite, il suffit à l'objet d'assembler un objet TEventMessageSoundEffect avec le bruit de mort approprié et d'appeler Post(&oEventMessageSoundEffect) ; objet En supposant que le son n'est pas coupé (ce que vous voudriez pour Unregister les Sound Managers.
EDIT : Pour clarifier un peu ceci en ce qui concerne le commentaire ci-dessous : Tout objet pour envoyer ou recevoir un message a juste besoin de connaître l'interface IEventMessagingSystem, et c'est le seul objet que l'EventMessagingSystem doit connaître de tous les autres objets. C'est ce qui permet le détachement. Tout objet qui veut recevoir un message doit simplement l'enregistrer (MSG, Object, Callback). Ensuite, lorsqu'un objet appelle Post(MSG,Data), il l'envoie à l'EventMessagingSystem via l'interface qu'il connaît, l'EMS notifiera alors chaque objet enregistré de l'événement. Vous pourriez faire un MSG_PlayerDied que d'autres systèmes gèrent, ou le joueur peut appeler MSG_PlaySound, MSG_Respawn, etc. pour permettre aux objets qui écoutent ces messages d'agir en conséquence. Considérez la fonction Post(MSG,Data) comme une API abstraite pour les différents systèmes d'un moteur de jeu.
Oh ! Une autre chose qui m'a été signalée. Le système que je décris ci-dessus correspond au schéma de l'observateur dans l'autre réponse donnée. Donc si vous voulez une description plus générale pour que la mienne ait un peu plus de sens, voici un court article qui en donne une bonne description.
J'espère que cela vous aidera et profitez-en !