游戏系统开发笔记(六)——对象系统设计
对象系统顾名思义自然是指OO中的程序对象了,OO编程中除了少部分以算法为主的编程,多数时间我们的工作都在围绕各个对象的生命周期进行着。因为游戏中所使用到的对象较多,对象间的关系也颇为复杂,自需要一套概念上的“系统”来描述其设计及其背后所涉思想。这就是这里提到的“对象系统”了。
个人认为游戏程序中两类对象最为关键:
其一是app对象
其二是角色对象
app对象可以理解成和MFC中的那个全局变量app类似,负责维护整个程序运行中的一些状态,推动程序不断执行下去,这种对象对于一个进程来说通常只有一个实体存在。
而角色对象算是整个游戏逻辑层面的核心了,它定义了角色的状态和角色所能进行的行为,可以说整个游戏程序其余的一切实际上都是为了这个部分更好的工作而存在的。
当然,一般来说,这里谈到的东西没有天然就很复杂的东西,之所以把它写这么复杂不过是为了使我们的程序有能力应对一定复杂程度的游戏设计罢了。
有的游戏类型只需要非常简单的角色对象,比如《贪吃蛇》这样的游戏,往简单了做其实就是一个主角对象(蛇),需要记录的信息:1、速度 2、长度 3、位置 4、移动方向,还有一个物品对象就更加简单,只需要:1、位置 2、物品类型。
这样一来我们可以粗糙的对这个游戏做如下设计:
1、一个跑每秒10个逻辑帧的app对象。
2、创建一个蛇对象(速度、长度、位置信息、方向)
3、在蛇身之外的地方创建一个物品对象(pos、type)
4、根据蛇速度做一定时间间隔的定时器,每个时间点根据方向更新蛇位置,判断是否和物品坐标重叠,是则根据物品类型对蛇做操作,并且重复3。
5、当玩家输入方向时变更方向。
这样一个贪吃蛇游戏的脉络差不多就出来了,在这大框架下添加一些独特的道具玩法就是一件相对简单的事情。
但像这种简单的游戏,实际上没必要花太多心思考虑对象系统的设计,但对于RPG类型的游戏就不然了。个人觉得RPG虽然风格不尽相同,但那种代入感的体验非常重要,而要获得这种代入感,首先便要求游戏要把虚拟现实做到一定水平之上。
既然如此,许多现实中有的元素,我们要对其进行抽象化和离散化、概念化,使它们(现实中的一些元素)有机的融入虚拟世界中。考虑到这点,RPG尤其是ARPG中,角色对象的设计便复杂许多。
对于玩家来说一般来说至少会包含如下几个大的方面:
1、属性 2、动作 3、移动 4、技能 5、背包 6、装备
而对于Npc来说,则是:
1、属性 2、动作 3、移动 4、技能 5、AI
可以看出还是有不少模块是有交集的,既然如此我们就可以定义一组继承关系来描述他们的公共/特有的行为。
当然这是一件通常需要根据游戏设计反复琢磨试验才能敲定的事情,这里只给出一种参考设计:
首先把上述功能组织成Manager的形式如:PropertyMgr、ActionMgr、MoveMgr、SkillMgr ...(这里关于继承还是组合有许多说法,个人倾向组合多于继承)
为了减少逻辑对象的“负担”,这里考虑做分层处理,把一些基础的游戏行为和游戏内容关联性大的行为隔离,如:
根据游戏中物体运动的状态,主要可以分为静态(StaticObj,静止不动的)和动态(DynamicObj,运动的)两类对象,把MoveMgr挂到DynamicObj上,其它若有必要组件则分别挂上。
逻辑对象比如掉落物、陷阱之类的就继承StaticObj,玩家、Npc去继承DynamicObj,就分别具有了相关能力。
而上面谈到几类逻辑对象除了玩家和Npc,其他则没有明显的关联关系,行为也大都迥异,所以分别实现几种不同的类,根据游戏设计挂上相应的Mgr即可。
玩家和Npc的情况是多数行为类似,少部分行为有异,一般来说主角对象虽然通常不需要AI,但主角对象的行为通常覆盖多数的Npc行为。所以如果让主角对象继承Npc对象,程序开发上应是可以省去不少麻烦,而我们上面所提的Mgr,以Impl*的方式来组合,主角用不到Npc某个mgr时留个空指针在那里想必也不会有多少负担。btw,把AI模块留给主角实际上也不是完全没意义的,对于网游而言,有些游戏其实会给玩家做挂机内挂...
这种设计使得逻辑层需要管理的对象还是比较简单明了的,可以直观的和玩家所见的对象一一对应,而它们的生命周期也完全随着游戏行为创建和结束。
再来说说这种对象设计在C/S中的情况:
网游中的Npc必然都是服务器指挥的,这没啥好说的,对于网游来说,多数对象逻辑都在服务器,所以上面谈到的设计也是主要对应服务端的对象设计。
对于客户端多数思路都是通用的,一点区别就是对于一个具体的客户端来说,只有玩家自己的那个对象是比较特殊的(需要和玩家互动,而其他角色都是由服务端指挥),而其他玩家和Npc区别都不大,所以设计上也需要有所变化。所以对于客户端来说,不妨主要围绕这条区别来做角色对象设计。
比如,把一些受控行为抽象出来,都包装到一个类中,Npc和非当前玩家的玩家都用这个类来表示,而当前玩家对象则继承这个类,再额外添加一些主动操作来表示。