最近比较忙,工作上的事情很多,家里也有事要处理,所以掉落系统拖了很久才发出来。这个模块主要复杂在需求的分析和确认,为了做成一个功能强大且灵活的基础模块,跟策划前前后后大概讨论了快一星期。需求分析清楚了之后最后的实现其实没什么难度,总共也就100多行代码。
说明一下,本文所讨论的掉落系统是一个游戏中的通用模块,不仅局限于打怪时掉落物品,包括抽卡、开宝箱、任务奖励、活动奖励等功能都可以使用。抽象地说,掉落系统是由给定参数按照特定的算法生成一系列可附加在玩家身上的东西的模块。
我先罗列一下整个的需求列表,会比较零乱,随后我们再来抽丝剥茧理清这里面的逻辑关系。
为了解决第1点需求,我们可以一份通用物品数据结构,这样就能使用统一的配置来描述各类不同的物品了。关于通用物品及相关的分发模块设计,前一篇博客游戏开发手记:抽象物品已经做了说明,这里不再赘述。
有了通用物品,策划基本上可以根据需要配置上任何物品了。但有一些特殊情况要特殊处理,也就是物品的数值不一定是固定的。比如开宝箱,可能需要开出的金币有一定的随机效果,在一定的范围内随机;再如战斗产生的经验需要跟玩家的等级挂钩,玩家等级越高,所获取的经验也越高。
我们想把这些复杂性约束在掉落系统内解决,让具体逻辑写起来更简洁,也可以避免后面各个模块都过于复杂产生bug。所以最后我们的解决方案可能有些复杂,但综合来看是可以接受的。
具体方案就是把通用物品的每一项数值扩展为4列(逻辑类型,参数1,参数2,参数3),最后由四列综合计算出一项数值(如物品数量)。计算方式如下:
固定数值
,则数值为参数1
范围随机
,则数值为random(min=参数1,max=参数2)
玩家等级
,则数值为参数1*玩家等级^2+参数2*玩家等级+参数3
VIP等级
,则数值为参数1*VIP等级^2+参数2*VIP等级+参数3
这样一来,基本上可以满足策划任何关于物品数值的需求。
需求中第4点提到了,掉落的过程是从一系列预先配置的物品中选择一项或多项,选择的过程可能是独立随机或权重随机。我们的做法是把随机的过程划分成多个阶段,第一个阶段进行独立随机,第二个阶段进行权重随机,这样一来策划只需要根据自己的需求进行组合配置,程序这边来看逻辑是统一的。
具体地说,一个dropId对应多个DropGroup,每个DropGroup都带有一个命中概率。第一阶段我们拿到dropId后遍历其对应的所有DropGroup并依次检查概率是否命中,命中则被选出。每个DropGroup又对应多个DropDetail,每个DropDetail带有对应的DropItem和其随机权重。第二阶段我们从上面计算出的每个DropGroup中按照权重随机选择一项DropDetail。
第5点需求掉落的改变可以换个角度来认知,我们把需求理解成:一个dropId可能在某些条件下转变为另一个dropId。比如抽10次卡提高得到好卡的概率,可以配置两份掉落,一份对应于低概率,一份对应于高概率,正常情况下掉落低概率,当低概率的次数是整10时则转为高概率。
在我们的游戏中,掉落的转换总是跟掉落的次数息息相关的。我们定义了一份transform配置,其中指明某dropId在何种条件下变为另一个dropId。游戏运行过程中记录transform表中出现的dropId的掉落次数,在掉落生成之前检查并进行转换,再用转换后的dropId生成真正的掉落。
配表的结构大致是这样的:
FromDropId | Operator | Value | ToDropId
1001 | > | 100 | 1003
1001 | % | 5 | 1002
1002 | = | 10 | 1003
1003 | < | 50 | 1004
解释一下。掉落1001在掉落次数大于100次时则改为掉落1003,否则每生成5次改为掉落1002;掉落1002在第10次改为1003;掉落1003在生成次数小于50时总是转为1004。
转换dropId时先递增该dropId的掉落次数,再检查该id的所有转换条件,若条件满足则转为新的dropId,递增新的dropId次数后再检查转换条件,一直到不能再进行转换为止。
最后再整理一下完整的生成掉落的过程。