编辑:下面我描述了一个我反复使用的基本事件消息传递系统。我突然想到,这两个学校项目都是开源的并且是在网络上的。您可以在http://sourceforge.net/projects/bpfat/找到这个消息传递系统的第二个版本(以及更多)。享受,并阅读下面的系统更全面的描述!
我编写了一个通用的消息传递系统,并将其引入到少数已在 PSP 上发布的游戏以及一些企业级应用软件中。消息传递系统的要点是仅传递处理消息或事件所需的数据,具体取决于您要使用的术语,以便对象不必相互了解。
用于完成此操作的对象列表的简要概述如下:
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)
现在有点解释。第一个对象 TEventMessage 是表示消息系统发送的数据的基础对象。默认情况下,它将始终具有正在发送的消息的 Id,因此如果您想确保收到了您期望的消息(通常我只在调试中这样做)。
接下来是 Interface 类,它为消息传递系统提供了一个通用对象,用于在执行回调时进行强制转换。此外,这还为 Post() 向消息传递系统发送不同的数据类型提供了一个“易于使用”的接口。
之后我们有我们的回调类型定义,简单地说它需要一个接口类类型的对象,并将传递一个 TEventMessage 指针......您可以选择将参数设为 const,但我之前使用过涓流处理,例如消息系统的堆栈调试等。
最后也是核心是 CEventMessagingSystem 对象。此对象包含一组回调对象堆栈(或链表或队列,或者您想要存储数据)。上面未显示的回调对象需要维护(并且由其唯一定义)指向对象的指针以及调用该对象的方法。当您 Register() 时,您在消息 id 的数组位置下的对象堆栈上添加一个条目。当您 Unregister() 删除该条目时。
基本上就是这样。现在,这确实规定了一切都需要了解 IEventMessagingSystem 和 TEventMessage 对象......但是这个对象不应该经常更改,并且只传递对被调用事件所指示的逻辑至关重要的信息部分。这样,玩家无需直接了解地图或敌人即可向其发送事件。托管对象也可以调用 API 到更大的系统,而无需了解它的任何信息。
例如:当敌人死亡时,您希望它播放声音效果。假设您有一个继承 IEventMessagingSystem 接口的声音管理器,您将为接受 TEventMessagePlaySoundEffect 或类似的消息系统设置回调。然后,声音管理器将在启用声音效果时注册此回调(或在您想要静音所有声音效果以轻松打开/关闭功能时取消注册回调)。接下来,您将让敌人对象也从 IEventMessagingSystem 继承,将 TEventMessagePlaySoundEffect 对象放在一起(需要 MSG_PlaySound 作为其消息 ID,然后是要播放的音效的 ID,无论是 int ID 还是声音的名称效果)并简单地调用 Post(&oEventMessagePlaySoundEffect)。
现在这只是一个非常简单的设计,没有实现。如果您立即执行,则无需缓冲 TEventMessage 对象(我主要在控制台游戏中使用)。如果您处于多线程环境中,那么这是一种非常明确的方式,可以让在不同线程中运行的对象和系统相互通信,但您需要保留 TEventMessage 对象,以便在处理时数据可用。
另一个更改是对于只需要 Post() 数据的对象,您可以在 IEventMessagingSystem 中创建一组静态方法,这样它们就不必从它们继承(这是为了便于访问和回调能力,而不是直接- Post() 调用需要)。
对于所有提到 MVC 的人来说,这是一个非常好的模式,但是您可以以多种不同的方式和不同的级别来实现它。我正在专业从事的当前项目是一个 MVC 设置大约 3 倍,有整个应用程序的全局 MVC,然后明智地设计每个 MV 和 C 也是一个自包含的 MVC 模式。所以我在这里尝试做的是解释如何制作一个足够通用的 C 来处理几乎任何类型的 M 而无需进入视图...
例如,一个对象“死”时可能想要播放声音效果。您可以为声音系统创建一个结构,如 TEventMessageSoundEffect,它继承自 TEventMessage 并添加声音效果 ID(无论是预加载的 Int,还是sfx 文件的名称,但它们在您的系统中被跟踪)。然后所有对象只需将 TEventMessageSoundEffect 对象与适当的死亡噪声放在一起并调用 Post(&oEventMessageSoundEffect); 对象..假设声音没有静音(您想要取消注册声音管理器。
编辑:关于下面的评论澄清一点:任何发送或接收消息的对象只需要知道 IEventMessagingSystem 接口,这是 EventMessagingSystem 需要知道的所有其他对象的唯一对象。这就是给你超脱的原因。任何想要接收消息的对象只需为其注册(MSG,对象,回调)。然后,当一个对象调用 Post(MSG,Data) 时,它会通过它知道的接口将其发送到 EventMessagingSystem,然后 EMS 将通知每个已注册的对象该事件。您可以执行其他系统处理的 MSG_PlayerDied,或者播放器可以调用 MSG_PlaySound、MSG_Respawn 等,让监听这些消息的事物对它们采取行动。将 Post(MSG,Data) 视为游戏引擎中不同系统的抽象 API。
哦!向我指出的另一件事。我上面描述的系统在给出的其他答案中符合观察者模式。因此,如果您想要更一般的描述以使我的描述更有意义,那是一篇简短的文章,可以对其进行很好的描述。
希望这会有所帮助并享受!