本文所讨论的掉落系统是一个游戏中的通用模块,不仅局限于打怪时掉落物品,包括抽卡、开宝箱、任务奖励、活动奖励等功能都可以使用。抽象地说,掉落系统是由给定参数按照特定的算法生成一系列可附加在玩家身上的东西的模块。
需求
我先罗列一下整个的需求列表,会比较零乱,随后我们再来抽丝剥茧理清这里面的逻辑关系。
- 掉落系统可以掉落任何能附加于玩家的物品。包括金币、宝石、经验、英雄经验、道具、英雄、宠物、装备、积分、体力等等。
- 策划需要控制掉落物品的数值。如金币宝石的数量,道具的 ID、数量,英雄的 Id、等级、星级等。
- 掉落物品涉及数值可能是固定的,也可能是在一定范围内随机的,也可能是根据公式计算出来的。
- 一次掉落可能掉落多个物品。掉落的多个物品可能是固定的,也可能是随机的。随机方式有两种:从多个物品中独立随机(针对每个物品判断概率是否命中),从多个物品中权重随机(按照一组物品概率的比例选出一个)。
- 特殊情况下会改变掉落。例如抽卡连续抽 10 次后提高得到更好卡的概率,打同个副本次数过多后降低产出概率。
使用通用物品
为了解决第 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 次数后再检查转换条件,一直到不能再进行转换为止。
总结
最后再整理一下完整的生成掉落的过程。
- 由产生掉落的模块提供 dropId 交给掉落系统。
- 掉落系统尝试对 dropId 进行转换,并递增涉及 dropId 的掉落次数,最后返回新的 dropId(大部分情况下与原来的相同)。
- 依照 dropId 查出对应的一个或多个 DropGroup,依次进行概率判定,最后选出一个或多个 DropGroup。
- 对于每个 DropGroup,查出其对应的一个或多个 DropDetail,依照权重选出一项 DropDetail。
- 对于每项 DropDetail,计算其配置的数值并生成一项通用物品。
- 收集所有生成的通用物品,即为此次掉落的产出。