成就系统——观察者模式
百无聊赖,周末来写一个博客吧。苦于前几次去面试都被问到同一个问题,今天来好好想一下,该怎么做,怎么做才会更好一些。
游戏中的成就系统,是一个根据一些特定事件去完成一些目标,然后获得奖励的玩法。游戏中的各种事件遍布游戏各处,成就类型又是多样化的。
先说一下我自己的做法,在游戏中成就是和玩家相关的,所以直接在玩家的系统组件中添加了一个成就系统。我们要做的就是在成就系统中有一个事件触发的接口,所有的事件触发都会调用到这个地方,然后通过许许多多的事件定义来区分各个成就的处理(当然这里的前提是玩家只有在线的时候才能获得成就)。
void onNotify(Event event,int param, int num);
所以我理解的成就系统就是对一些成就类型的特定参数的计数(比如对进入场景类型,特定参数是A场景)。
struct Achievement
{
int id_;
int param_;
int num_;
Event event_;
};
但是,游戏里还包含这样的需求,比如说这个目标是进入A场景,那个目标是进入B场景。一开始的时候我们试图使用一个参数 param_来给他们找一个共同点。但是后来仍然失败了,然后就不得不扩展到param1_,param2_。
感觉这是一种比较通常的做法了,但是有一天我看了项目的技能使用的做法,我就想改进我的成就系统了。技能释放是用的是一个消息队列实现的。具体做法是,释放技能者(游戏中的一些实体)将释放的技能添加到消息队列线程,消息队列线程管理消息列表,以确定消息什么时候以及,被哪个对象接收。消息队列的好处是解耦了消息释放者与接受者关系,而且还能保证消息的有序性。
学习设计模式就是让你和其他同类从业者交流的时候能有一个统一的名字来指明同一种系统结构的实现方式,这个消息队列模式在《游戏编程模式》里也有提到,里面讲的是事件队列。但我现在还没搞清楚事件队列和消息队列有什么不一样,看上去很像同一个东西。
就这样在面对面试官的一个致命问题时,我展现出了灵活的一面——你觉得这个系统还有什么地方做的不够好吗?如果有,有想过怎么改进吗?这个问题我本来是想用消息队列或者事件队列来改进优化的,但现在看来这完全是一种错误的想法。当然我也有理由:同步调用会阻塞当前程序执行,要等到所有调用结束后才会继续执行,而且当前成就也许会触发其他的游戏事件,这样就加大了调用的深度;所以我的理由就是降低调用深度。(但事实上调用深度很大的的只是少数情况)
比较一下事件队列和观察者最大的不同在于事件队列是使用异步处理的方式解耦了事件触发者和事件关注者,而观察者通常是同步调用的。我们想一下这个情况,在游戏角色获得一个物品时,要触发一个成就事件的改变,但是玩家在获得这个物品后马上又使用了它;这就导致,系统在接收到物品增加的消息时再去查看这个物品,发现它已经不存在了。
以上我成就系统是基于玩家组件方式实现,当然还可以基于系统组件来实现,这时候我想*onNotify()*就要换一种写法,把Actor指针也传进去。
void onNotify(const Actor actor, Event event,int param, int num);
the end…
2018年10月21日