新手教学中的观察者模式

手游的新手教学是再常见不过了,我们的手游新手教学也进行了三次比较大得迭代,每次迭代不仅包括策划案的迭代,也包括游戏逻辑代码的设计迭代。新手的实现中最主要使用了观察者模式,本文先大体讲一下新手模块的设计方案,之后终点介绍观察者模式在游戏中的使用。

1.新手教学的设计实现

我们希望将新手模块设计成一个尽量独立的模块,不去影响其他模块的开发。举个简单的例子,在装备界面我们会设计一个新手教学,但我们不希望由于新手教学的加入导致装备界面的逻辑还要进行过多的修改。基于这样的需求或者说最基本的初衷,我们可以将新手模块设计如下:

每一个新手步骤可以理解为用户随着新手指引的每一次点击操作(或者滑动,拖拽操作),从新手步骤1到新手步骤2的条件就是event_1这个事件被触发了,同时我们可以发现event_1事件的触发是通过用户操作装备界面的”操作”进行触发的。这样整个流程就很明白了,UI层接受用户的操作,根据不同的操作下放不同的事件,UI层是不需要关心是谁去接收或者说处理这些事件的,事件可以有监听者也可以没有,在这里,新手模块就是某些事件的监听者,它监听到某些事件后,根据策划的需求转换自己的当前状态,最明显的就是转换到下一个新手阶段。这种设计就是非常常见的观察者模式
游戏逻辑的各个角落都在使用这种模式,但接下来我们先回顾一下观察者模式到底长什么样。

2.观察者模式

上面是最基本的观察者模式的UML图

  • Dispatcher: 分发消息的对象,也成为目标对象。一个目标对象可以有多个观察者,目标可以添加或者删除观察者。
  • Listener:观察者,具有do_event方法,接收Dispatcher的调用。

但在我们的游戏中,并没有完全参照观察者模式的架构进行实现,我们的实现很简单:一个Dispatcher类,维护者所有的listener,并且listener其实只是PyFunction。

class Dispatcher(object):

    def __init__(self):
        super(Dispatcher, self).__init__()
        // event -> listener_list
        self.listeners = {}

    def add_listener(self, event, listener):
        if not self.listeners.get(event, None):
            self.listeners[event] = []
        self.listeners[event].append(listener)

    def remove_listener(self, event, listener):
        if listener in self.listeners.get(event, []):
            self.listeners[event].remove(listener)

    def dispatch_event(self, event, data):
        if event in self.listeners:
            for listener in self.listeners[event]:
                listener(data)

3.观察者模式在游戏中的使用

成就系统又是一个典型使用观察者模式的模块,比如获得十件金装,每一次获得一件金装,变会dispatch一个获得金装的事件,成就系统获取事件并记录,当达到十件金装的时候通知玩家达成此成就。
观察者模式的使用,降低了模块直接的耦合度,我们删除或者关闭一个模块的功能,其他模块并不需要关心,因为他们直接的”交流”都是通过事件传输的,而不是直接的方法调用。

4.Reference

  1. Game Programming Pattern —— Observer