掉落系统

本文所讨论的掉落系统是一个游戏中的通用模块,不仅局限于打怪时掉落物品,包括抽卡、开宝箱、任务奖励、活动奖励等功能都可以使用。抽象地说,掉落系统是由给定参数按照特定的算法生成一系列可附加在玩家身上的东西的模块。

需求

我先罗列一下整个的需求列表,会比较零乱,随后我们再来抽丝剥茧理清这里面的逻辑关系。

  1. 掉落系统可以掉落任何能附加于玩家的物品。包括金币、宝石、经验、英雄经验、道具、英雄、宠物、装备、积分、体力等等。
  2. 策划需要控制掉落物品的数值。如金币宝石的数量,道具的ID、数量,英雄的Id、等级、星级等。
  3. 掉落物品涉及数值可能是固定的,也可能是在一定范围内随机的,也可能是根据公式计算出来的。
  4. 一次掉落可能掉落多个物品。掉落的多个物品可能是固定的,也可能是随机的。随机方式有两种:从多个物品中独立随机(针对每个物品判断概率是否命中),从多个物品中权重随机(按照一组物品概率的比例选出一个)。
  5. 特殊情况下会改变掉落。例如抽卡连续抽10次后提高得到更好卡的概率,打同个副本次数过多后降低产出概率。

使用通用物品

为了解决第1点需求,我们可以一份通用物品数据结构,这样就能使用统一的配置来描述各类不同的物品了。关于通用物品及相关的分发模块设计,前一篇博客游戏开发手记:抽象物品已经做了说明,这里不再赘述。

通用物品数值

有了通用物品,策划基本上可以根据需要配置上任何物品了。但有一些特殊情况要特殊处理,也就是物品的数值不一定是固定的。比如开宝箱,可能需要开出的金币有一定的随机效果,在一定的范围内随机;再如战斗产生的经验需要跟玩家的等级挂钩,玩家等级越高,所获取的经验也越高。

我们想把这些复杂性约束在掉落系统内解决,让具体逻辑写起来更简洁,也可以避免后面各个模块都过于复杂产生bug。所以最后我们的解决方案可能有些复杂,但综合来看是可以接受的。

具体方案就是把通用物品的每一项数值扩展为4列(逻辑类型,参数1,参数2,参数3),最后由四列综合计算出一项数值(如物品数量)。计算方式如下:

  1. 逻辑类型==固定数值,则数值为参数1
  2. 逻辑类型==范围随机,则数值为random(min=参数1,max=参数2)
  3. 逻辑类型==玩家等级,则数值为参数1*玩家等级^2+参数2*玩家等级+参数3
  4. 逻辑类型==VIP等级,则数值为参数1*VIP等级^2+参数2*VIP等级+参数3
  5. …其他类型

这样一来,基本上可以满足策划任何关于物品数值的需求。

独立随机和权重随机

需求中第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次数后再检查转换条件,一直到不能再进行转换为止。

总结

最后再整理一下完整的生成掉落的过程。

  1. 由产生掉落的模块提供dropId交给掉落系统。
  2. 掉落系统尝试对dropId进行转换,并递增涉及dropId的掉落次数,最后返回新的dropId(大部分情况下与原来的相同)。
  3. 依照dropId查出对应的一个或多个DropGroup,依次进行概率判定,最后选出一个或多个DropGroup。
  4. 对于每个DropGroup,查出其对应的一个或多个DropDetail,依照权重选出一项DropDetail。
  5. 对于每项DropDetail,计算其配置的数值并生成一项通用物品。
  6. 收集所有生成的通用物品,即为此次掉落的产出。