游戏设计模式阅读笔记14——解耦模式(事件队列)

       

目录

一、意图

二、动机

1.GUI事件循环

2.中心事件总线

3.播放声音的事件

        1.API在音源引擎完成对请求的处理前阻塞了调用者。

        2.请求无法合并处理。

        3.请求在错误的线程上执行

三、事件队列

        1.事件队列的描述

        2.合适使用的场景:

        3.注意事项

        i.中心事件队列

        ii.事件的处理可能并不及时

        iii.避免陷于反馈系统环路中

        4.实例代码

        5.环状缓存  

        6.处理合并

        7.分离线程


        事件队列也叫消息队列,是更广为人知的观察者模式的异步实现。

一、意图

         解耦发出消息或事件的时间和处理它的时间。

二、动机

一些常见的场景:

1.GUI事件循环

         当用户与程序交互时,操作系统就会生成一个事件。程序需要去获取这些事件,然后做出相应的反应。而操作系统生成的事件,并不会去主动调用你的程序的代码。而是存在一个地方,这个地方就是事件队列。

2.中心事件总线

        在大多数游戏中使用事件循环来支撑中枢系统是常见的。

        比如:想做个新手教程,当玩家在打到第一个敌人时xxxxx。就可以使用中心事件队列。任何游戏系统都可以发送事件给队列。任何游戏系统也都能从队列中接收事件。教程系统只需要在队列中注册自己,并表明需要收到“敌人死亡”事件。就可以把消息从战斗系统传到教程引擎。

3.播放声音的事件

        如果在播放声音的按钮下,直接调用播放声音的函数。而声音函数需要加载对应音源,就会出现以下问题。

        1.API在音源引擎完成对请求的处理前阻塞了调用者。

                有时屏幕会被冻住几帧。

        2.请求无法合并处理。

                当需要在打到怪物时就播放一个怪物哀嚎的音效,在同帧打到多个怪物时,就会播放多个相同的音效。而这时两个相同的波形的相加,就是一个声音的两倍响,就会很刺耳。

        3.请求在错误的线程上执行

                音频引擎不一定在收到这个请求时,就能立即去处理。但是这种简单的做法没有给它选择。

        事件队列就可以很好的处理这些问题。

三、事件队列

        1.事件队列的描述

        事件队列在队列中按先入先出的顺序存储一系列的时间。发送的系统,将请求放入队列,立即返回。处理请求的系统之后从队列中获取请求并处理。

        就解耦了发送者和接收者,既静态又及时。

        2.合适使用的场景:

        观察者模式和命令模式都可以解耦接受者和发送者。

        但是在解耦某些需要及时处理的东西时使用队列。因为这里比其他模式多设置了缓存。这样可以在处理方方便时将请求拉入。而队列可以及时返回发送方,不堵塞发送方的流程。也可以在事件队列中处理合并。

        注意这里发送者的操作只是发送了事件,不会收到回复。所以在发送者需要收到回复时,队列不是好的选择。

        3.注意事项

        i.中心事件队列

        中心事件队列是一个全局变量。程序每个部分都可以解除到其中的一小个状态。有全局变量引发的全部危险。

        ii.事件的处理可能并不及时

        这个事件处理时可能已经是几帧后了,如果需要结合当时现场的一些信息才能处理的事件。就不能让处理方自己再去观察场景,因为可能已经改变了。就需要在发送的时候,将一些细节一起发送出去,方便使用。

        iii.避免陷于反馈系统环路中

        A发送事件,B接收然后发送事件作为回应,A又关注了事件并回应。然后B又。。。。

       同步的消息系统会让栈溢出游戏崩溃。使用队列,就会异步地使用栈,这些无用的事件不会阻碍游戏继续运行。难以找到问题。

        解决:避免在处理事件的代码中发送事件。

        4.实例代码

        事件队列可以使用:Fibonacci heap或者skip list或者最起码链表。 但是在实践中,存储一堆同类事物最好的办法是使用一个平凡无奇的经典数组:

                i.没有动态分配。

                ii.没有为记录信息造成的额外的开销或者多余的指针。

                iii.对缓存友好的连续存储空间。

示意为声音系统的一个消息队列:使用数组在存储消息。将数组大小设置为最糟情况下的大小。

简单的处理方:这里的处理时立即返回的,然后在Updata函数中播放声音

        5.环状缓存  

        环状缓存保留了数组的所有优点,同时能不断从队列的前方移除事物。

        这里可以使用链表,也可以使用数组。相比之前的 numPending。取代他的是head_和tail_

,一个指向正需要被处理的事件位置,一个指向事件队列空闲的下一个。并且这里的环状体现在当尾部指针指向了数组的尾部时,能从尾部再回到头部。那些已被处理的空闲事件位置。

折回的实现:

这里的断言保护了,不会队列满了,新消息覆盖未处理的旧消息的情况。

        6.处理合并

                需要在请求入队时合并。

        7.分离线程

                队列应该与程序其他部分隔离。避免其他程序的调用阻塞了事件队列的正常运行。

                

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中常用的解耦设计模式包括工厂模式、抽象工厂模式、适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式、观察者模式和命令模式等。这些设计模式的共同点是将程序的业务逻辑与实现细节分离,从而提高代码的可读性和可维护性,同时也方便了代码的重用和扩展。 其中,工厂模式和抽象工厂模式可以将对象的创建和使用分离,使得程序更加灵活;适配器模式可以将不兼容的接口转换为兼容的接口,使得不同的类可以协同工作;装饰者模式可以动态地为对象添加新的功能,而不需要修改原有的代码;代理模式可以为其他对象提供一个代理,从而控制对该对象的访问;外观模式可以为复杂的子系统提供一个简单的接口,使得客户端可以更加方便地使用该子系统;桥接模式可以将抽象部分和实现部分分离,从而使得它们可以独立地变化;组合模式可以将对象组合成树形结构,从而使得客户端可以像处理单个对象一样处理整个树形结构;享元模式可以共享对象,从而减少内存的使用;观察者模式可以将对象的状态和行为分离,从而使得对象可以更加灵活地响应变化;命令模式可以将请求封装成对象,从而使得请求可以被记录、撤销和重做。 下面是一个使用工厂模式的例子: ```c++ #include <iostream> using namespace std; // 抽象产品类 class Product { public: virtual void use() = 0; }; // 具体产品类A class ProductA : public Product { public: void use() { cout << "ProductA" << endl; } }; // 具体产品类B class ProductB : public Product { public: void use() { cout << "ProductB" << endl; } }; // 抽象工厂类 class Factory { public: virtual Product* createProduct() = 0; }; // 具体工厂类A class FactoryA : public Factory { public: Product* createProduct() { return new ProductA(); } }; // 具体工厂类B class FactoryB : public Factory { public: Product* createProduct() { return new ProductB(); } }; int main() { Factory* factory = new FactoryA(); Product* product = factory->createProduct(); product->use(); delete product; delete factory; return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值