其实建立数据表是一门需要一定编程思维的活,对于一个游戏策划来说,你可以按照这样的思路去做这件事情:
分清Model和Obj
在一个游戏中,是存在model和obj两种东西的,简而言之——model就是策划需要填表的数据,也就是这个问题的核心所在;obj则是在游戏中运行的数据,一个游戏从开发的角度来说,无非就是很多逻辑在修改一段段数据(因此ECS架构非常适合游戏gameplay的开发)。通常很多技术水平不够的团队或者开发者,包括一些市面上的主流引擎的范例,对于model和obj都是没有概念的。
举个最常见的例子,在一款游戏中,有很多法术,其中一个是火球术,然后玩家可以学会火球术1级,火球术2级,火球术3级……对于外行来说,这个设计就是有一张法术表,里面火球术1到3级是3条数,它们是不同的法术,玩家在游戏中“升级”法术,实际上就是更换了法术。这个做法看似非常合理,但实际上却是在凑效果,根本原因是没有意识到,在这里model和obj混淆了。
怎样理解Obj
Obj是游戏运行中的数据,因此在这份数据中,往往会有这样几个成分:
- 这是什么东西,即来自于Model产生的数据,注意,是“来自model产生的数据”,也就是说Model是个初始化的东西,并不代表你可以用一个SkillObj.modelId来直接索引到SkillModel中的火球术。在上述的火球术的例子中,玩家学会的火球术应该是火球术.Clone()获得的数据。因为在游戏实际运行中,所有的Obj中的Model都是可能被改变的,比如游戏中有设计,玩家获得了一本邪能火球书,这本书使用之后,玩家的火球术发出的火球不再是红色的而是绿色的了,但是这并不等于策划设计的数据表(model)中的火球术就得是绿色的,或者说得配置一条绿色火焰的火球术,因为火球术可能不是只有颜色这一条属性的,而我们实际的需求,只是改变现在玩家学会的火球术这个SkillObj.model.sightEffect这个值而已。当值变化之后,他已经和model完全不同了,所以应该使用clone的数据,产生一个新的实体数据,而非引用。
- 一些运行时才能赋值的值,火球术这个例子中,等级就是一个运行时才能赋值的东西,玩家将火球术这个SkillObj.level从2提升到3的渠道可能不是唯一的,完全可以这么设计,升级的规则可以有很多,但是无论如何,都只有在游戏运行的过程中,这个等级才会发生变化,因此类似等级这样的属性,是在游戏运行过程中才能被准确赋值的,所以它是属于Obj的属性。再比如,如果游戏设计使用20\30\40…次火球术就能升级,这个多少次,也是运行时才能赋值的属性,因此也是运行时。当然最典型的还有CD,也就是还剩多久可以再次使用,而类似charge的概念(也就是有几次充能),则是Array
作为CD即可。
怎样理解Model
Model则是用来初始化某个元素的核心数据,但是仅仅依靠Model是无法完成一个Obj的初始化的。因此在Model中,需要的只是一些“基础属性”的数据,这里说的“基础属性”应该具备以下特质:
- 这些值可以构成对元素的描述。这是最基础的要求,就是通过这些属性,配合运行时的那些变量,可以获得出一个完整的Obj,我们还是具体到火球术这个例子——现在我们要确定的是,他是一个dotalike moba、或者大菠萝like、或者wowlike、或者魂like的这种即时的回合制ARPG游戏。现在我们要一个火球术这样的法术,他应该至少有些什么属性:
- 名称、icon、描述等显示数据。
- Timeline:在这类游戏中,一切法术技能最终产生的都是一个Timeline,由施法者作为主体去播放这个Timeline,Timeline中的时间点去做事情,比如让角色播放动作、产生一个视觉特效、产生一个aoeObj、产生一个buffObj、产生一个bulletObj等等行为。因此,你应该嗅到了一个苗头——这个技能表可能会跟一个timeline表关联,如果你理解的表仅仅是excel的话,因为excel的表格是二维数组,如果你理解的表是“数据结构”,excel也好、json也好,都只是input,那么这个Timeline也只是一种结构的数据而已。
- 条件、消耗:条件是指可以使用技能的条件,比如需要mp>=20;为什么条件和消耗是不同的条目?因为完全可以有一个技能是“当玩家mp有20或更多的时候,消耗全部mp……”。
- CD等:技能的cd信息。
- 其他辅助信息:至少在一个最简单的arpg中是可以不要的。
- 在游戏内容中,他们应该是“公知”。当有了上面这个结构的数据之后,我们就可以精确地填写出一个火球术,但是为什么我们需要在model表(也就是数据表)里面填写一条这样的数据?是因为他在游戏中会被大量的使用,形成一种“公知”。假如我们上面举例的绿火,需不需要一条数据?实际上是不需要的,这只是“通过一个流程改变了一些运行时数据”而已。
我们实际上在设计一个游戏的玩法的时候,其中每一个元素(也就是我们可能需要设计数据表的东西),如果我们想这个东西的时候,想的情景是游戏玩的过程中的,比如玩家背包中的道具,在地图上行走着的怪物,出现在场景里的宝箱等等,这些在游戏运行时存在的实体,那么这些就是Obj,他其中的很多属性,比如坐标、比如产生时间、再比如自动消失时间等,这些都是属于Obj独有的值,他们不应该被填表。而除去这些Obj独有的值之后,剩下的元素就是需要作为表头的内容,也就是Model的数据了。
明确数据的依赖性和扩展性
可能说到这里,有人会对火球术的表结构提出一些想法——比如我的火球术等级上限为什么表里没有?再比如我游戏中的火球术是要找训练师学习的,每一级火球术的学费价格为什么在表里没有?这里就涉及到一个依赖性和扩展性的问题了——既当我们设计一个表的时候,首先要考虑这个业务的内容,是谁依赖了谁,然后要从扩展性的角度出发考虑这样的依赖性是否准确。
谁依赖了谁?
这是基于业务出发来理解的,就比如刚才我们说的“火球术等级上限”,我们得回到具体的游戏设计中去,问自己几个问题:
- 每个角色的火球术上限等级是一样的吗?如果不一样,比如猴叔我的火球术可以20级,你作为普通玩家最多10级,那这就是跟人不跟技能的,所以等级上限不是技能的属性。
- 火球术的等级上限变化因子是什么?如果是不管是谁,不管什么情况,火球术的上限都是10级(策划预设值),那么等级上限就应该属于技能的属性。反之,在玩家还是见习魔法师的时候,火球术上限4级,正规魔法师的时候到5级,然后完成了魔法师挑战任务达到6级上限,成为牛逼魔法师之后变成7级,然后去某个岛修炼变成9级上限,岛上出师了变成10级上限了,然后出岛的时候遇到飞机事故,等级上限降低到5级……各种各样原因都会改变等级上限,那么等级上限就不是技能的属性。
- 改变因素本身是不是技能?这里用技能可能并不是很好的例子,比如道具,我们买个西瓜,可能在鲁大师这里买20G,到猴叔这里买就要35G,那么这时候价格并不属于道具,而是属于贩卖道具这件事情。
基于这些思考,我们会对游戏设计进行更深一步的审视,包括很多细节的可扩展性都会进一步讨论,就比如道具的贩卖价格等。
表的可读性设计
到此,其实设计一张表的表头的思路已经非常清晰了——根据你对于游戏内容设计的理解,抽选出作为Model的数据,撇清和“这件事”无关的数据之后,我们就能得到有哪些数据属于这个表。但是接着就会有下一个问题——这些数据的值,如何确保可读性?毕竟,并不是每一项值都是数字、布尔或者字符串的。这里我推荐各位遵守几个原则:
能使用string不使用number
常见的是id,通常很多菜鸟喜欢用number作为id,当然我们所听说过的id比如身份证号码的确是一串数字,但这不代表游戏的数据表的id得是个number。为什么我们需要设计id这条数据?是因为他需要被别的地方引用,比如掉落表里需要道具的id,索引到道具表里面。很多“聪明的策划”用数字的规则来代表道具的信息,便于自己记录,比如10000开始是消耗品,20000开始是防具,22000是鞋子……类似这样的规则,可是当你真的需要找到“钢铁头盔”的时候,或者在掉落表里看到id=24316的数据的时候,你真的能知道谁是谁吗?不知道,那为啥不直接就是“Steel_Helm”呢?只要保证字符串不出现重复的,或者出现重复的有一个解决方式就行了。
是枚举的的不使用number
这也是常见的坏习惯,比如装备的部位,有帽子、衣服、鞋子。很多菜鸟喜欢用一个number来代替,比如0=帽子、1=衣服、2=鞋子。但这是一个极坏的习惯,要不就用String,要不就好好用枚举(比如excel中提供了“可填写值范围”的功能),或者在编辑器里面做下拉列表(excel就可以是下拉列表选择)。
No Magic Numbers
Magic Number通常是为了凑效果临时产生的一些特殊的数字,比如最常见的“-1代表无限”,这与原本数字的含义并不相同,致命性更高的是“如果这里填写1-7就代表星期几,填写8就代表是整周”,在开始制作的时候,设计表的人和上帝都知道几代表什么,但是几周后,就只有上帝才知道了。
善用Tag解决索引问题
Tag这个概念你往往在一些购物软件中可以看到,比如“服装”、“快餐”、“汉堡”等,都是一种Tag。在我们设计数据表的时候,不妨也加入一个Array
为什么需要Tag
举两个实际例子:
比如在设计掉落表的时候,我们希望有些道具是圣诞节掉落的。那圣诞节掉落的东西可能有几十种,并且这几十种是不稳定的,第二年圣诞节的时候,我们有新增了“圣诞老人的帽子”等数十种新的道具,他们也会掉落,如果按照掉落的东西是指向id的来填写,那么当我们增加一个新的“圣诞节掉落”的时候,你就得维护掉落表的好多好多数据。但是如果我们一开始掉落表里掉落的东西就是按照Tag走的,比如30%概率掉落“圣诞节道具”,那么当我们新添加一个道具的时候,只要贴上“圣诞节掉落”的标签,就会被认为是30%概率掉落的道具中的一种。在这个基础上进一步设计好算法,就可以节省非常非常庞大的无意义重复填表劳动。
再比如我们设计技能和buff的时候,会有这样的想法:角色身上的每个中毒效果可以使伤害提高2%,那么最终伤害到底提高多少?我怎么知道一个效果是不是中毒?我们知道“效果”其实指的是角色身上的buffObj,怎么知道一个buffObj是不是中毒?花毒是中毒(一条buffModel),蛇毒也是重度(另一条buffModel),“被猴叔的鼻屎弹中”是不是中毒?因此在buffModel中,我们可以有一条Array
可见,tag不仅解决了输入(填表)问题,也解决了很多设计逻辑的问题,因此善于在设计一个元素的数据的时候加入tag,并且管理好tag的范围(之所以不是枚举,是因为他的扩展频率还是非常大的),是非常重要的工作。