Mediator(中介者)模式在MonoGame游戏开发中的应用

By | 2023年5月20日

背景

游戏开发中,一个非常常见的行为就是,游戏中的角色需要对周围发生的事物做出反应:飞机在被子弹打中后要出现爆炸效果并从屏幕上消失;台球在撞击到桌面边缘时应能够根据速度和角度反弹等等。在这些场景中,至少会有两个参与者(飞机与子弹,台球与桌面)进行交互,以共同完成一个游戏行为。两个参与者的情况是最为简单的:一个对象调用另一个对象的某个方法即可完成交互,然而即便是一款最为简单的游戏,一个场景中,也会无时不刻地产生这种角色与角色之间的交互,而且往往同一个场景下参与交互的角色都不止两个。

就拿《贪吃蛇》游戏来说,在蛇头碰到食物的时候,会发生这些事情:

  1. 蛇尾需要根据规则向后增加骨节
  2. 蛇头碰到的食物会消失
  3. 场景在棋盘的可选位置上,随机派发一颗食物
  4. 用户得分增加并显示在场景上

注意上面这段话的关键部分:“当….的时候,会发生….”。这让我们自然而然地想到了消息派发和处理机制,很明显,应对这种复杂的对象交互行为,我们可以选用事件模型来处理,在某个条件成熟时,发出事件消息,而整个场景中的每个对象,都可以选择订阅(或者不订阅)这个事件消息,来决定是否应该成为(或者不成为)整个行为的参与者,以及对于这种事件的发生,应该如何应答(如何处理)。

初步设计

于是,可以参考GoF95设计模式中的Mediator(中介者)模式来实现这样的设计:引入一个负责事件派发的组件,游戏中的对象可以使用这个组件派发消息,也可以将定义在内部的事件消息处理函数,以委托的形式注册到这个组件中,以便当消息被派送时,这些委托函数都能够被正确调用。下面的UML类图大致表达了这样的设计:

在上图中:

  1. IMessageDispatcher是一个消息派发组件,它有两个方法:RegisterHandler,用于注册消息处理函数;DispatchMessage,用于派发消息
  2. IVisibleComponent是游戏中所有可见对象的接口定义,它与IMessageDispatcher有关联关系,内部包含一个IMessageDispatcher的实现。所有实现了该接口的类,都可以使用IMessageDispatcher的实例来派发消息,或者使用自己内部的某个事件处理函数来订阅消息
  3. MessageDispatcherImpl是IMessageDispatcher的实现类,它会通过IVisibleComponent的具体类中的委托,将事件处理函数与所要处理的消息类型对应起来,所以,它与每个IVisibleComponent接口的实现类之间是依赖关系

职责的分离

根据GRASP原则,面向对象软件系统需要明确对象职责。因此,在设计这个游戏框架的时候,可以考虑对事件消息的产生对象和事件消费对象进行职责分离:

  1. 有些参与者只负责发送消息,比如游戏中的服务(例如:FPS服务向游戏场景发送FPS Updated事件,它并不接收和处理来自其它参与者的消息
  2. 有些参与者只接收消息
  3. 有些参与者既接收消息,也发送消息。绝大多数的游戏参与者都属于此类

区分职责的一个重要原因是进行关注点分离,以满足开-闭原则的基本需求(不应该暴露出来的接口,就不能暴露出来)。在这样的设计思想指导下,我们的游戏框架大致会有如下的结构:

简单介绍一下:

  1. 游戏中的所有参与者都是Component
  2. 游戏中所有能够被看到并且操作的参与者,都是VisibleComponent
  3. 游戏由多个场景(Scene)组成,每个场景由1到多个Component组成,Scene负责管理这些Component的生命周期,在需要的时候,发出事件消息,Scene也可以接收来自其它组件的消息,因此,Scene成为了Mediator模式实现中的Colleague角色,它聚合IMessageDispatcher的实现
  4. VisibleComponent和GameService都需要被加载到某个Scene才能正常执行,因此,它们都会使用Scene所聚合的IMessageDispatcher实现来完成事件消息的发送和接收
  5. IMessageDispatcher的实现类型,会通过委托方式,将来自GameService、Scene和Sprite的事件处理委托注册到消息处理器队列中

实现效果

下面的例子中:

  1. 当足球Sprite与另一个足球Sprite碰撞时,碰撞检测服务就会发出CollisionDetectedMessage,通知参与碰撞的两个足球向相反方向弹开
  2. 当足球Sprite与界面边界碰撞时,碰撞检测服务就会发出BoundaryReachedMessage,通知触碰边界的足球Sprite弹回
  3. FPS服务会不时地将FPS参数(Frames Per Second)以FpsMessage发出,当前场景接到通知后,将FPS的数值显示在屏幕左上角

下面的《俄罗斯方块》游戏中,通过碰撞检测服务,判断下落方块是否应该与棋盘融合:

参考链接

 

(总访问量:226;当日访问量:1)

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据