最近利用碎片的时间读到了《游戏编程模式》(作者:Robert Nystrom)一书,感叹于作者对游戏编程模式这块的理解之深,总结出来的内容深入浅出,样例也是从经典的场景中抽象出来,恰到好处。
虽然整本书的内容都是从游戏客户端的角度出发,同时并不影响其他端人员的阅读,所以说我写这篇文章的目的也十分简单:想从游戏服务端的角度出发,参照原书的章节,总结一下我对于游戏编程设计模式理解。
文章未对各种设计模式的经典实现进行讲解,因为这类资料太多,还望见谅。
关于游戏服务端设计模式的个人理解概述:
- 相比较GOF书里提到的,能完全契合使用的设计模式比较少,一些需要变化后使用;
- 在满足需求的前提下,使用设计模式的目的是为了实现高内聚、低耦合,增加可维护性;
- 合适、合理、优雅的使用;切勿强行滥用、炫技(为了解耦而解,导致代码已经支离破碎,不利于阅读维护)!
命令模式
游戏中通常会有一个充满UI交互界面,通过点击UI上面的控件发送命令字与服务端交互,参考下面这个拙劣的比喻图:
每种交互基本与一个服务端的命令字对应(xxxx Command)。原始的实现大致如下:
class Command { public: virtual ~Command() {} //命令字的核心执行,纯虚函数需要在子类实现 virtual void execute() = 0; //命令字的操作撤销,纯虚函数需要在子类实现 virtual void undo() = 0; }; class MoveUnitCommand : public Command { //MoveUnit命令的实现 };
作为服务端,撤销操作这种需求显然是不适合在命令字处理侧单独实现的;其次,为了每个命令字单独实现一个类,成本有点高。当然并不是说这种方式不好,只是说我们核心需求是“execute”这个执行函数,完全可以简化一下:
class Command { typedef int32_t (*cmdFunc)(PlayerData *, const string &, string &); public: int32_t regCmd() { //在cmdFuncMap注册命令字,可以定义成虚函数,继承时重写 } int32_t procCmd(int32_t cmdId, PlayerData *p, const string &req, string &rsp) { map<int32_t, cmdFunc>::iterator iter = cmdFuncMap.find(cmdId); if (iter == cmdFuncMap.end()) { return -1; } return iter->second(p, req, rsp); } private: map<int32_t, cmdFunc> cmdFuncMap; };
把原本需要实现的类退化成函数,在满足需求的同时简化了整体结构。当然你也可以自己封装类似于class PlayerCommand和class FightCommand继承类,在内部添加一些特定处理。
享元模式
享元模式的存在是为了解决有些含有共性数据的问题,将内部状态(the intrinsic state)与外部状态(the extrinsic state)区分开来。其中内部状态是一些共性的数据,外部数据是每个实例特有的数据。
就服务端来说我们不关心图形的绘制以及场景的生成步骤,但是需要关心场景由什么组成,场景长宽高(3D场景)以及场景中存在哪些的实例。这些实例可能会达到数以千计,以怪物实例来说,不推荐把实例实现成这样(注意这是不推荐的例子):
class MonsterModel { //怪物内部状态 }; class MonsterA { public: MonsterModel *model; //A怪物的外部状态 };
或许直接写成这样,直接依靠ID来区分怪物会更加直观
class Monster { int64_t uid;//全局唯一id int32_t id; //怪物的类型id //...其他属性 };
虽然可能会说这样增加耦合度:A拥有的属性B不一定拥有,会造成内存与效率上的“浪费”。
但是对于服务端来说这样聚合度更高,更加方便于局部的管理,这也是一个寻求平衡的过程。
观察者模式
如果你是一个有经验的游戏服务端从业人员,并且游戏的主逻辑是放在服务端来算的话,那么肯定会对“统计类”系统(例如成就系统)的“插入式”代码苦恼不已。
观察者模式契合了这类需求:变化->通知改变->接收处理。
但是对于游戏这类需求的几宗罪:
- 强行耦合原有的系统代码;
- 需求加入的点比较分散,甚至会出现一需多点的情况;
个人觉得这个模式在游戏服务端来说不是必须的,有三个原因:
- 观察者模式并不能解决上面的问题,但是可以利用这个模式降低耦合度;
- 每个类需要强行加了一层继承关系,实现特有的Update方法;
- 有些异步callback接口没有类从属关系,会导致代码逻辑统一性的问题;
另外从搜索引擎的结果来看,这个模式没有十分优雅的应用。当然,如果您有更好关于观察者模式的游戏服务端实现样例欢迎交流一下:D。
这篇文章的是游戏服务端设计模式系列的上篇,章节结构是根据《游戏编程模式》,下篇会谈谈个人对原型、单例、状态模式在游戏服务端中的使用心得。
(全文结束)
转载文章请注明出处:漫漫路 - lanindex.com
good