Diablo类游戏的掉落系统的设计与开

发表于2015-09-26
评论0 3.7k浏览
  事实上,这应该是一种机制,而不是简单的系统,对于策划而言,这应该说是一种设计思路,因为这种思路/机制/系统,可以被利用于任何游戏。之所以我想说说这类游戏的开发,还是看了几个朋友公司的道具掉落系统和表项之后有感而发,他们各有各的做法,各有优点,但是和她们的策划聊下来,策划们认为并不能满足自己的需求,因此我也向他们推荐了这个系统。


1,工作原理与数据结构

  掉落实际上是一个比较“冷”的系统,因为大多游戏策划认为它是水到渠成的,或者说不包含创意的,或者种种原因它应该是数值策划的事情或者只是填个表就OK了,但事实上优秀的掉落系统才能保证游戏的Fun>Anti-Fun。那么我们就以标题说的来分析下,Diablo类游戏的掉落系统。

  首先我们不难发现,几乎所有的怪物都有自己的随机掉落,这是废话;其次一些怪物有必定掉落的东西,但是这些东西看起来也是从一个列表中抽选出来的,总是那几个里面固定掉落;事实上另外还存在了一种让很多在职但缺乏经验的策划忽略的东西,那就是任务掉落,与其他掉落不太一样的是,任务掉落是当角色持有任务的时候才会进行掉落的,并且当玩家的任务完成还未交还开始,任务道具已经不再掉落,这里还有更奇妙的2个事情,1个是当玩家任务道具收集满了之后丢掉几个之后,仍然会再出任务道具;2是有时候任务道具的掉落会超过需求上限。这两个现象表现到游戏现实中就是:某任务让我收集100颗鲨鱼牙齿,干死一个鲨鱼我可能收集到18-36个不等的牙齿,当我收集到100颗的时候,它就不应该继续掉落了,而此时我丢掉了28颗,那么他应该继续掉落,但是我杀死2个鲨鱼后,它们分别掉落了23颗和26颗,可见此时已经超我的上限了(鲨鱼牙齿目标100个,策划很可能在道具数量上限中也会设定100个)。

  以上一段我们可以清晰的看到这个系统的需求,这也是玩家随随便便就能分析出来的,那么作为设计师,我们就要进一步的开始设计的第一步——分析工作了,首先我们必须确定一个定义,我个人比较推崇D3的掉落,各掉各的,服务器为各个人单独运算,既减少一些逻辑麻烦,也让玩家体验非常好,一举两得。但这并不是很影响设计,即使不要这个设定的话。我们先来看,掉落的数据结构,事实上掉落被分为了3大块:

1)固定掉落,我称它为MustDrop,每个怪物都可能存在MustDrop,每次击杀怪物后,怪物必定会从MustDrop中进行一定次数的随机抽取,决定掉落什么(至于表现成尸体闪光、一个箱子、洒落一地,与这部分逻辑并无关连),那么可以肯定我们还需要一个MustDropCount,代表会循环多少次,对于MustDropCount==0或者MustDrop.length<=0的情况直接Return,因为这个怪物不存在固定掉落。而对于固定掉落的每一个掉落道具来说,结构相对简单很多,他们需要的是道具的id,道具掉落时候的堆叠个数,道具掉落的加权值,这3个的组合,我们可以称之为一个MustDropPackage。

2)也许掉落,我称它为MayDrop,它的工作原理和MustDrop基本相似,但是有一点最大的不同是,我们需要设定一个MayNotDropRating,这个Rating意味着道具不掉落率,每次循环计算掉落率的时候很可能发生不掉落的情况。而每个道具本身的结构实际上和MustDropPackage一致,虽然我们应该称呼它为MayDropPackage。

3)任务掉落,我称它为QuestDrop,它的工作方式和以上两个掉落不太一样,且更接近于传统的掉落系统的做法,For循环遍历整个表,如果玩家接受有任务,且任务需求的该道具数量玩家持有个数小于需求数,则会进入随机数计算,决定是否掉落。为工作原理服务,QuestDropPackage的东西的结构应该是:id、Count和QuestId(相关任务的编号,我认为这不该是个数组,而应该是单独的)和DropRating,这里是一个Percentage的,因为每个是单独百分比,不用考虑加权值做法。

  那么在明确了以上这些的时候,事实上整个掉落系统的逻辑和数据表等,已经基本上清晰的表现在面前了,我们接下来的工作就是:

1)设计道具掉落表,并安排好它如何被挂向一个事物(我并不认为它只能被挂向怪物,也许还有宝箱,特殊任务奖励等等,看你怎么用了)。

2)3个掉落的逻辑代码实现。

3)提供一些后期维护接口或者更现实的说法是“方法”。


2,表项设计与接口设计

  每一条道具掉落的数据,它的存在意义是被挂向某个需要“掉落”它的事物,因此,我们需要从策划或者说表弟表妹的角度思考,如何去填写他,那么这个表应该包含的数据项要有:

1)Id(Int):任何表都不可以缺少这个,虽然有可能他不被其他的索引。


2)Tag(String):这是作为本掉落数据的一个标签,我可以把多条掉落标签设定为同一个Tag,这样在有必要的时候,一个怪的整体掉落会以For循环的方式或者其他什么方式(看项目需求)来对多个掉落数据进行处理。而更重要的意义是——比如一个制作NPC表的表弟表妹,他们只要事先和制作掉落表的表弟表妹(有可能还是他们自己)商量好tag,就不需要等待这个表并直接开始工作了。

3)MustDropCount(Int):如果小于等于0,则不存在必定掉落的运算,否则就是MustDrop会被抽取多少个,既然是MustDrop,那么这个填写几,只要MustDropList不空,就必定会掉落几个东西(而且是至少掉落,因为还会有MayDrop阿)。

4)MustDropList(String):对于程序的处理来说,它不是String的,但是对于设计一个表给道经验尚浅的表弟表妹时,它应该是一个String,再通过一个工具将excel表格转化为我们需要的格式,并在此时将数据分析后变成我们需要的样子,这是很多公司做得比较不好的一部,他们的工具只负责将excel生成的csv或者其它什么直接无脑转换,而我们的做法则是支持多种excel格式,表弟表妹来自五湖四海,有人会存为csv,有人习惯2003存成xls,有人喜欢07甚至12,存了个xlsx。不论怎样,这个数据的填写内容范例“[food03_3_22][armor31_1_30]",类似这样的结构(MayDrop同这个),这个的实际意义是,[掉落的道具Tag_堆叠个数_加权值]组成一组,tag的好处不再重复,让表弟表妹们尽可能所想既所得,顺着他们的思路就是”我要掉落3块肉,有XX%概率“不是吗?

5)MayDropCount:原理同MustDropCount。

6)MayDropList:原理同MustDropList。

7)MayNotDropRating(Int):首先确保他大于等于0,如果等于0,我会质疑这位表弟表妹的智商,但从程序逻辑上来说没问题,如果小于0会出现一些麻烦的问题。这个作为加权值进入到MayDropList。

8)QuestDropList(String):与MustDropList不同的是,它的每一项都是类似 ”[QItem01_KillMob03_20_6733]" 这种结构的,[道具Tag_任务Tag_堆叠个数_万分之掉落率],当然最后一个之所以这么做是我懒,做字符串分析的时候不太高兴处理Float,也就是Delphi里面的Extended(如果你用Real,则无法使用TryFloatToString了,Delphi的类型严格性太高了。

  当然根据需要还可以加入一些其它的数据项,但是核心的内容,我觉得以上这些足够了。那么接下来我们看还需要增样的接口去为后期开发或者运营人员准备呢?我的经验是:

1)AddMustDrop(ItemTag:String, StackCount:Int, DropRate:Int):Float,添加到必定掉落表,并人性化的返回一个这个道具的实际掉落率给表弟表妹们。

2)AddMayDrop同上

3)AddQuestDrop(ItemTag:String, QuestTag:String, StackCount:Int, DropRate:Float)这个不需要返回什么了,因为概率已经填写了,从策划的想法来说,掉落率填写的内容,如果88.37%还是直接传88.37比较舒服,不是吗?

4)Remove相关的。

5)SetCount相关的。


3,实际运用与工作分配

  这里要说的是,我建议掉落的算法用加权,实际上产生到游戏程序的数据都是itemId, count, Rating的,而苦恼的转化Tag到数据的过程是Delphi弄得工具作掉的事情。那么比如我要做MayDrop的时候逻辑非常简单(Haxe3语法伪代码,最近用这个比较多,Delphi C++什么的倒用少了)
  1. var totalRate:Int = xxx.mayNotDropRate;//我需要一个总掉落率
  2. for (mayDropItem in xxx.mayDropList){
  3.   totalRate+=mayDropItem.Rating;
  4. }
  5. if (totalRate<=0){return [];}//我认为这个函数应该返回掉落什么可以更好的运用到整体程序,即使没有不要返回null处理麻烦,不如返回一个空的列表。
  6. var thisRan:Int=Math.round(Math.random()*totalRate);
  7. var res:Array<SMayDropItem> = new Array<SMayDropItem>//这个SMayDropItem就是一个Struct,MayDropItem的结构。
  8. for (m in 0...xxx.mayDropCount){
  9. for (i in 0...xxx.mayDropList.length){
  10.   thisRan-=xxx.mayDropList.Rating;
  11.   if (thisRan<=0){
  12.      res.push(xxx.mayDropList);
  13.      break;
  14.   }
  15. }
  16. }
复制代码
这样的做法更适合于加权值表项的做法,从数学的角度上也很公平的对待了每一个在策划心目中应该公平对待的掉落物。

  而这部分的工作分配已经很清晰了:作为主策划,我去完成这些逻辑代码,建立表项,Debug;而表弟表妹们则可以完全独立的对于这个表进行填写,但是他们之间还是需要沟通一些协议的,主要是tag的定义,因为tag机制的特色就是自由。而程序员的工作则是完成掉落的表现(这就是之前提到的,爆一地还是掉个箱子),美术则根据爆一地还是掉个箱子去处理资源,音效也得根据这个设定去制作对应落地音效。


一些废话

  但愿以上的这些能帮到大家在日后的工作中更好地完成掉落模块的设计制作,并且千万不要小看了这个看似没有价值的部分在游戏中的地位。当作抛砖引玉,我觉得大家也应该把自己的一些新的Share出来,如我母亲所说(好吧,按照那些90后流行的语法:我母亲也是标准985 211的,不过是退休教职工)我们这个行业之所以这么久了还年轻、缺乏标准,是因为我们太缺乏Share精神,她的举例是汽车驾驶技术,如乔布斯说的,好的出租车驾驶员比一般的好2倍最多了,但是为什么会如此接近,技术含量是一个限制,更多的是驾驶员之间的交流,他们在交流中互相Share自己的遭遇和经验,并把一些处理方式传给了他人,大家互相之间利用到了对方的长处,所以逐渐的这门技术成熟了。我认为也是这样,知识不是苹果,互相分享大家有份,见过一些策划,总觉得自己擅长的要是说出来了就会让自己丢了饭碗了,或者认为自己的一些“独特的”经验想法很值钱,可是在我看来那些一文不值——“Knowledge is useless if it is not used”——激战2某NPC。


原文作之:猴与花果山

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引