我正在阅读《游戏编码完成》,作者建议游戏对象和模块之间进行事件驱动通信。
基本上,所有在场的游戏参与者都应通过内部事件消息传递系统与关键模块(物理,AI,游戏逻辑,游戏视图等)进行通信。这意味着必须设计一个高效的事件管理器。设计不良的系统会占用CPU周期,特别是影响移动平台。
这是一种行之有效的推荐方法吗?我应该如何决定是否使用它?
我正在阅读《游戏编码完成》,作者建议游戏对象和模块之间进行事件驱动通信。
基本上,所有在场的游戏参与者都应通过内部事件消息传递系统与关键模块(物理,AI,游戏逻辑,游戏视图等)进行通信。这意味着必须设计一个高效的事件管理器。设计不良的系统会占用CPU周期,特别是影响移动平台。
这是一种行之有效的推荐方法吗?我应该如何决定是否使用它?
Answers:
如建议的那样,这是我的评论扩展为完整的答案。
是的,简单明了。交流需要发生,并且在某些情况下“我们还在那里吗?” 类型轮询是必需的,检查事物是否应该执行其他操作通常会浪费时间。相反,您可以让他们对被告知要做的事情做出反应。此外,对象/系统/模块之间的明确定义的通信路径可显着提高并行设置。
我已经在Stack Overflow上对事件消息系统进行了概述。从学校开始,我就将其用于专业游戏标题和现在的非游戏产品中,并且每次都适合该用例。
编辑:解决您如何知道该消息的对象的注释问题:应该Request
将事件通知给对象本身。您的EventMessagingSystem
(EMS)既需要Register(int iEventId, IEventMessagingSystem * pOjbect, (EMSCallback)fpMethodToUseForACallback)
匹配,又需要匹配Unregister
(为iEventId
对象指针和回调之外的对象创建唯一的条目)。这样,当一个对象想知道一条消息时,它就可以Register()
与系统通信。当不再需要了解事件时,它可以Unregister()
。显然,您需要这些回调注册对象的池,以及从列表中添加/删除它们的有效方法。(我通常使用自排序数组;一种奇特的说法是,它们跟踪未使用对象的池栈和需要时将其大小移动到适当位置的数组之间的分配)。
编辑:对于具有更新循环和事件消息传递系统的完全正常工作的游戏,您可能想查看我的旧学校项目。上面链接的Stack Overflow帖子也引用了它。
我的意见是,您应该开始制作游戏并实现自己喜欢的功能。当您这样做时,您会发现自己在某些地方使用了MVC,但在其他地方却没有。在某些地方发生事件,但在其他地方则没有;组成某些地方,但继承其他地方;清洁一些地方的设计,而另一些地方进行粗暴的设计。
没关系,因为完成后您实际上将拥有一个游戏,并且游戏比消息传递系统更酷。
一个更好的问题是,那里有哪些替代方案?在这样一个复杂的系统中,它具有适当划分的物理,人工智能等模块,您还可以如何编排这些系统?
消息传递似乎确实是此问题的“最佳”解决方案。我现在想不出其他选择。但是在实践中有大量的消息传递示例。实际上,操作系统将消息传递用于其多个功能。来自维基百科:
消息也通常与进程间通信相同地使用。另一种常见的技术是流或管道,其中,数据是作为一系列基本数据项发送的(虚拟电路的高级版本)。
(进程间通信是操作系统环境中进程(即,正在运行的程序实例)之间的通信)
因此,如果它对操作系统足够好,那么对游戏来说可能就足够了,对吧?同样还有其他好处,但是我将让Wikipedia上有关消息传递的文章进行解释。
我还问了一个有关堆栈溢出的问题,“程序中传递消息的数据结构?” ,您可能需要阅读。
到目前为止,除了James和Ricket的好答案之外,我只想补充一点警告。消息传递/事件驱动的通信无疑是开发人员手中的重要工具,但很容易被过度使用。当您拿着锤子时,一切看起来都像钉子,依此类推。
您绝对(100%)应该考虑标题周围的数据流,并充分了解信息是如何从一个子系统传递到另一个子系统的。在某些情况下,消息传递显然是最好的。在其他情况下,一个子系统在共享对象列表上进行操作可能更为合适,而其他子系统也可以在同一共享列表上进行操作;还有很多其他方法
在任何时候,您都应该知道哪些子系统需要访问哪些数据以及何时需要访问它们。这会影响您进行并行化和优化的能力,并帮助您避免在引擎的不同部分纠缠不清时出现的更棘手的问题。您需要考虑最小化不必要的数据复制,以及数据布局如何影响缓存使用率。所有这些都将指导您找到每种情况下的最佳解决方案。
话虽这么说,但我从事过的几乎每款游戏都具有可靠的异步消息传递/事件通知系统。即使面对复杂且快速变化的设计需求,它也允许您编写简洁,有效的可维护代码。
好吧,我知道这个帖子已经很老了,但是我无法抗拒。
我最近建立了一个游戏引擎。它使用3d方库进行渲染和物理处理,但是我写了核心部分,定义和处理实体和游戏逻辑。
发动机肯定遵循传统方法。有一个主更新循环,为所有实体调用更新功能。冲突由实体上的回调直接报告。实体之间的通信是使用实体之间交换的智能指针进行的。
有一个原始消息系统,该系统仅处理一小部分实体以生成消息。这些消息最好在游戏交互结束时进行处理(例如创建或销毁实体),因为它们可能会干扰更新列表。因此,在每个游戏循环结束时,都会消耗一小部分消息。
尽管有原始的消息系统,但我想说该系统很大程度上是“基于更新循环”的。
好。使用该系统后,我认为它非常简单,快速且组织良好。游戏逻辑是可见的,并且自身包含在实体内部,而不像消息队列那样动态。我真的不会让它成为事件驱动的,因为我认为事件系统会给游戏逻辑带来不必要的复杂性,并使游戏代码难以理解和调试。
但是,我也认为像我这样的纯“基于更新循环”系统也存在一些问题。
例如,在某些时刻,一个实体可能处于“什么都不做的状态”,可能正在等待玩家靠近或发生其他事情。在大多数情况下,实体一无所有地消耗处理器时间,最好关闭实体,并在发生特定事件时将其打开。
因此,在我的下一个游戏引擎中,我将采用另一种方法。实体将为引擎操作注册自己,例如更新,绘制,碰撞检测等。这些事件中的每一个将具有用于实际实体的实体接口的单独列表。
是。这是游戏系统相互通信的一种非常有效的方式。事件可以帮助您使许多系统脱钩,甚至可以在不知道彼此存在的情况下分别编译事物。这意味着您的类可以更容易地原型化,编译时间也更快。更重要的是,您最终得到的是平面代码设计,而不是依赖混乱。
事件的另一个重要好处是可以轻松地通过网络或其他文本通道流式传输事件。您可以录制它们以供以后播放。可能性是无止境。
另一个好处:您可以让多个子系统侦听同一事件。例如,您的所有远程视图(播放器)都可以自动订阅实体创建事件,并在每个客户端上繁琐的工作就可以生成实体。想象一下,如果不使用事件,将需要做多少工作:您将不得不在Update()
某个地方放置调用,或者可能是view->CreateEntity
从Game逻辑中调用(其中不包含有关视图的知识以及所需的信息)。没有事件就很难解决这个问题。
有了事件,您将获得一个优雅的,解耦的解决方案,该解决方案支持无限数量的无限种类的对象,这些对象都可以简单地订阅事件并在游戏中发生某些事情时就可以做自己的事情。这就是为什么活动很棒。
更多细节和实现在这里。
从我所看到的来看,完全基于消息传递的引擎似乎并不常见。当然,有些子系统非常适合此类消息传递系统,例如网络,GUI以及其他。总的来说,我会想到一些问题:
因此,使用“必须尽可能高效”的通用游戏开发人员方法,这种消息传递系统并不那么普遍。
但是,我同意您应该继续尝试。这样的系统肯定有很多优点,并且今天可以廉价地获得计算能力。