游戏设计中的每帧函数调用与事件驱动消息传递


11

据我所知,传统的游戏设计使用多态和虚拟功能来更新游戏对象的状态。换句话说,在游戏中的每个对象上以规则的间隔(例如,每帧)调用同一组虚拟函数。

最近,我发现,还有另一个- 事件驱动的消息传递系统可用于更新游戏对象的状态。在这里,对象通常不会按帧进行更新。取而代之的是,构建了高效的事件消息传递系统,并且仅在收到有效事件消息之后才更新游戏对象。

事件驱动的游戏体系结构Mike McShaffry编写的《Game Coding Complete》中有很好的描述。

我可以在以下问题上寻求帮助:

  • 两种方法的优缺点是什么?
  • 哪里比另一个更好?
  • 事件驱动游戏设计在所有领域都通用且更好吗?因此,即使在可移植的平台中,也建议使用它吗?
  • 哪一个更有效,哪个更难开发?

要澄清的是,我的问题不是要从游戏设计中完全消除多态性。我只是希望了解使用事件驱动消息传递与常规(每帧)调用虚拟函数以更新游戏状态的区别和好处。


示例: 这个问题在这里引起了一些争议,因此让我为您提供示例:根据MVC,游戏引擎分为三个主要部分:

  1. 应用层(硬件和操作系统通信)
  2. 游戏逻辑
  3. 游戏画面

在赛车游戏中,“游戏视图”负责以最快的速度(至少30fps)渲染屏幕。Game View也会侦听玩家的输入。现在发生这种情况:

  • 玩家将燃油踏板踩到80%
  • GameView构造一条消息“将Car 2油门踏板压到80%”,并将其发送给Game Logic。
  • Game Logic会获取消息,评估,计算新车的位置和行为,并为GameView创建以下消息:“绘制2踩下燃油2的燃油踏板”,“ 2声音加速”,“ 2坐标X,Y” .. 。
  • GameView接收消息并进行相应处理

你在哪里找到的?一些链接或参考?我不知道这种方法(但是我总体上知道相当复杂的设计模式,并且我建议总体上很好地使用面向对象的原理),但是我认为基于事件的方法更好。轮询异步调用..在计算和抽象级别上进行了优化
nkint 2011年

嗨,金特,谢谢您的评论。我基本上希望将事件驱动的通信与对虚拟函数的调用进行比较。我会稍微修改我的问题。顺便说一句,请看一下包含游戏设计模式的链接:gameprogrammingpatterns.com
Bunkai.Satori 2011年

5
也许我很笨,但是如何在使用多态的情况下使消息传递系统正常工作?您是否不需要某种基类(抽象或其他)来定义事件接收器接口?(编辑:假定您使用的语言不具有适当的反思能力。)
Tetrad

2
而且,效率将高度依赖于实施细节。我不认为“哪个效率更高”是可以用当前形式回答的问题。
四分

1
游戏对象需要打勾。游戏对象需要彼此通信。这两件事都需要发生。首先,您不使用消息传递,因为消息传递是多余的(您可能在某处列出了所有对象的列表,只需调用update它们即可)。由于种种原因,您可以使用第二个消息。
四分

Answers:


8

我完全相信必须成为发明之母。我不喜欢编写任何东西,除非它的需求明确且定义明确。我认为,如果您通过设置事件消息传递系统来启动项目,那么您做错了。我相信,只有在创建并测试了基础架构并将所有组件都定义为良好的工作模块之后,您才应该专注于如何将这些模块彼此连接。在您了解每个模块的形状和需求之前,尝试设计一个事件消息传递系统。

两种方法的优缺点是什么?

我认为有人会争辩说,有好的消息传递系统可以随时安装和使用。也许这是真的。当然,专门为您量身定制的定制解决方案的性能将比通用消息总线更好。最大的问题是-您将花费多少时间来构建消息系统而不是使用已经构建的系统。显然,如果您可以找到具有所需功能集的某种事件系统,那么这是一个非常简单的决定。这就是为什么在做出该决定之前,请确保我完全了解自己的需求。

哪里比另一个更好?

我们可以在这里讨论内存和资源。如果您要开发任何资源有限的硬件,那么使用事件系统无疑会成为问题,这是一个问题。但是,实际上,我认为问题取决于您的需求,正如我已经提到的那样,直到您看到所有部件的外观,您才知道这一点。如果您有足够的经验来构建这些系统,并且您事先确切知道所有组件的外观,那么您也可以提前回答此问题。我猜您没有这种经验,因为您不会问这个问题。

事件驱动游戏设计在所有领域都通用且更好吗?因此,即使在移动平台上也建议使用它吗?

在所有领域通用且更好?像这样的笼统的声明很容易被拒绝。任何增加开销的事情都需要承担其工作量的份额。如果您使用的事件不足以保证设计的开销,那么那是错误的设计。

哪一个更有效,哪个更难开发?

效率取决于其实施方式。我认为,发达的传统设计将在一周的任何一天都优于基于事件的系统。这是因为各部分之间的通信可以进行微管理,并且效率很高。当然,从完成设计和提高效率所需的经验和时间的角度来看,这也使设计变得更加困难。另一方面,如果您缺乏经验或没有时间很好地开发应用程序,那么使用适合您需求的基于事件的设计将更加高效。如果您有不容易适应基于事件的结构的自定义事件需求,这可能会使基于事件的结构非常难以设计-尤其是要保持设计效率。


嗨,埃里克(Eric),谢谢您对我的问题的详细意见。当我阅读您和他人的回复时,实施事件消息可能不会导致整体游戏性能提高的奇迹。相反,它将使事情复杂化很多。在结束此问题之前,我希望看到更多答案。整本书涵盖了这一主题,因此,考虑它是一个好主意。我不太确定,是否可以在项目中间做出使用或不使用消息的决定。我想说,如果使用事件消息,则也必须调整整个对象设计。
Bunkai.Satori 2011年

我不同意。如果整个对象设计得到很好的封装,则它应足够灵活以用于任何类型的框架。我已经能够用很少的工作交换出大型项目中的通信方法,因为每一部分都被很好地封装了。
Erick Robertson

7

我认为您在这里比较苹果和桔子。多态性根本不会被消息传递所取代。您可能希望事件/消息连接松散耦合的组件。例如。在发生碰撞时从实体发送消息,更新玩家分数或触发声音效果。这样这些单独的类就不知道其他类,而只是发送和/或处理消息。

但是您的游戏很可能会在某个地方有一个更新循环,并且由于您具有该更新循环,因此可以轻松地使用它来更新所有需要在每一帧进行更新的游戏实体。但这并不会阻止您使用消息传递...

如果您具有某种在其中添加/删除游戏实体的结构,则可以将它们包括在更新循环中,而不必在每一帧都向它们发送更新消息。那么,当您使用消息传递来连接游戏的不同子系统时,为什么不直接在游戏实体上调用更新呢?我也喜欢类似事件的系统的Signal / Slot概念(请参阅qt示例)。

底线:没有更好的方法,也不是排他的。


嗨,布姆扎克 终于,第一个答案到了。谢谢:-)当我提到事件驱动的体系结构时,我的意思是说MVC系统具有三个关键层:Appliaction,GameView和GameLogic。这些框架之间的所有通信将通过消息传递完成。这与具有更新循环的传统系统有很大不同。每个系统具有不同的体系结构,优点和缺点,并且它们具有不同的性能成本。因此,我相信,在某些领域会更好一些。经过良好的分析,我们应该能够得出结论,哪一个更好。
Bunkai.Satori 2011年

3
我看不出为什么会有什么不同?您将需要在某个地方进行更新循环,如果您要坚持使用MVC,则可能会更新Controller。然后,控制器可以向模型和视图发送消息。但是由于控制器通常知道视图和模型,因此它也可以直接更新它们。但这并不能取代任何多态性。您的问题和假设听起来很理论。.也许用一些示例代码或参考文献来支持它们?
bummzack,2011年

上面提到的《 Game Coding Complete》一书建议通过直接调用虚拟方法进行事件消息传递。尽管消息传递系统看起来更复杂,但是它的优点是,每个游戏对象都不需要检查游戏世界。游戏逻辑仅对游戏世界进行一次检查,然后仅寻址那些应该更改其状态的对象。这是一个区别,可以节省成本。如果您确定游戏对象将通过消息进行通信,则不会在每一帧都调用它们-因此,这两种方法是互斥的。
Bunkai.Satori 2011年

1
投票赞成在原始问题下方反映Tetrad评论的答案。@ Bunkai.Satori“游戏世界仅被检查一次”是更新循环,需要更新的所有内容都可以获取。事件消息本来很少完成,但仍然是引擎中的关键内容(PlayerDied,MonsterDied等),这会浪费Update()循环中的每一帧,但很可能是由它们生成的。 Update()循环本身。
詹姆斯

1
如果您已经有了第二版,请参阅《控制主循环》一章。在第3版中应将其命名为相同的名称。那真的应该回答你的问题。
Ray Dey

4

这起初是对bummzack的回答的评论,但持续了很长时间。

除非您实际上使它异步(在新线程上调度事件),否则您的对象仍将同步获取备忘录。假设事件调度程序使用基本哈希表以及单元格中的链接列表,则该顺序将是将对象添加到该单元格的顺序。

此外,除非出于某种原因决定使用函数指针,否则您仍将使用虚拟函数调用,因为获取消息的每个对象都必须实现IEventListener(或任何您调用的对象)。事件是使用多态性构建的高级抽象。

使用事件时,您发现自己必须调用各种游戏中发生的事件的方法清单,以便同步游戏中的不同系统,或者在各种对象和系统的反应使您无法明确定义所说事件的情况下使用为将来发生的事情增加更多反应。

它们是一个很好的工具,但是就像bummzack所说的那样,不要以为多态和事件可以解决相同的问题。


您好Bearcdp,谢谢您的答复。对于我,这说得通。我想到的一件事是,对事件消息进行投票:例如,场景中有200个对象。如果对所有对象都使用逐帧函数调用,则必须每帧检查所有200个对象是否有粘菌病(示例;当然可以管理调用间隔。)使用事件消息传递,仅检查游戏世界一次。如果发生5次碰撞,则只有5个对象会收到有关碰撞事件的消息通知,而不是通过每帧调用进行200次碰撞检测测试。你有什么意见?
Bunkai.Satori 2011年

2
检查游戏世界?这意味着什么?它必须意味着运行相同的200次检查以隔离发送消息所需的5条检查。有了虚拟功能概念,游戏世界也只需检查一次(依次检查每个对象的功能),然后仅进入需要状态更改的5个……而无需消息系统的开销。
史蒂夫·H

1
听史蒂夫·H,世界不会自相矛盾。我主要将事件用于高级事件,例如PlayerJump,EnemyHit。为了提高碰撞检查的效率,您需要选择一个数据结构来组织游戏中对象的物理位置,以便您可以优先确定需要检查和不需要检查碰撞的对象。一旦确定了所有碰撞的对象对,便可以使用相关的碰撞数据(两个对象,速度/位置/法线数据等)调度事件。
michael.bartnett

嗨,史蒂夫(Steve)和Beardcp,谢谢您的评论。你写的东西很有道理。看起来事件消息传递系统可能有许多可能的实现。正如上面提到的书所述,可以完全替代每帧虚拟功能概念。但是,如您所说,消息传递系统也可以与每帧虚拟功能概念共存。
Bunkai.Satori 2011年

为什么在引用函数指针时说“出于某种原因”?这几乎是我从头开始编写事件系统时实现事件系统的方式(我使用的语言具有一流的功能,但思路相同;将函数传递给事件中心对象,然后该对象将侦听器函数映射到事件),所以我想知道这种方法是否有不利之处。
2012年

2

我会说选择一个或另一个是完全错误的。有些对象需要调用每一帧,有些则不需要。如果您有要渲染的对象,则需要在每一帧对其进行渲染调用。活动无法进行此更改。对于任何基于时间的物理对象也是如此,您必须在每一帧都调用它们。

但是,我不会在每个帧中都调用对象管理对象,用户输入对象或类似对象。对框架上的每个对象进行大量虚拟调用是一个糟糕的主意。

用于任何特定对象的设计应基于那些对象的需求,而不是基于整个系统的某些意识形态。

编辑以使我的第一句话更加清楚。


嗨,DeadMG,让我解释一下:在上述情况下,设计分为三个主要部分:应用层(硬件访问),游戏逻辑,游戏视图。每帧渲染的是游戏视图。但是,当Game View记录用户输入(例如,在追逐游戏中踩下制动踏板)时,它会向Game Logic发送消息“ Car 2 Brake Pressed”以进行处理。Game Logic会评估此动作,计算汽车的行为,然后将消息发送到“游戏视图”:“ Car 2 Block Tires”,“ Car 2 Move to X,Y”。这样,不需要在每辆车上检查制动器是否被踩下。
Bunkai.Satori 2011年

@Bunkai:因此,您有一些事件驱动的设计,还有一些被称为每一帧的设计。这几乎与我的回答一致。
DeadMG 2011年

很高兴与您达成谅解,因为这个问题今天在这里引起了争议:-)
Bunkai.Satori 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.