欢迎参与讨论,转载请注明出处。
前言
随机掉落可谓时下RPG的流行设定,DFQ自然也不例外。而掉落业务自然也有其值得细说之处,不然也就不会有本文了(笑)。接下来将一步步引申出随机掉落的实现演进。
粗劣的实现
在以往的开发生涯中,对于掉落业务,我采取了很粗劣的实现:
这种实现的槽点可谓数不胜数:掉落池的选取可谓暴力代码,而池中的道具也只能通过塞入相同的多份来扩充概率,对于概率的控制度很生硬。哪怕是将掉落池采取与道具相同的做法(将pools做成list)以去除暴力代码,对于概率控制度的问题依旧没有解决。且进行了两次取随机数,从概率而言并不纯粹。实际效果而言也导致了经常重复掉落,并不可取。
Alias Method
那么如果选择将多个掉落池合而为一,使之只有一个list呢?
如此确实能让概率纯粹了,但是对于道具概率的控制度依然很差。这个问题可以通过构建道具概率表({a = 0.1, b = 0.5, ...}
)以生成掉落池({a, b, b, b,...}
)解决。但这样生成的掉落池未免也太大了(最后可能会达上千个元素),这太不环保了,那怎么办呢?
长达廿二年的人生经验告诉我:我们做的绝大多数事情都是前人做过的,遇到不会的问题看看前人是怎么做的就对了。果不其然,这就遇上了个合适的算法:Alias Method。
本文并不打算详解其中的奥妙,这是愚蠢的复读机行为。直接上代码:
算法的代码量并不多,也就三十多行,输入参数items
为道具的的概率list({0.1, 0.1, 0.5, ...}
),即代表需要配套的paths
来表示对应的道具标识({"stone", "potion", "gold", ...}
)。至于返回值alias, probs
,先来看看获取随机掉落的代码:
以上代码很好理解,首先随机获取一个道具的索引,根据索引获取到probs[index]
的值,与随机数(0-1)比较,由此可见probs存放的是一种运算后的概率值。若是随机数大于概率值,索引则改为alias[index]
,由此可见alias存放的是一种与原索引相对应的新索引,而新的索引自然会有对应的道具。
如此我们便可理解这套算法的做法了:为每个道具设置一个概率值以及相对应的另一个道具,随机到一个道具后,仍需二次随机进行二选一。这么做很好理解,就是将一些高概率的道具填充到一些低概率的道具里:
如图所示的第二项紫色的占比(概率)为1,表示不需要进行二次随机了,如此即可保证整个掉落池的概率是可以平分干净的(多出的部分就作为1概率项)。不得不说这种做法十分绝妙,完美解决了先前做法中掉落池元素过大的问题,美中不足在于需要进行二次随机,相对破坏了概率的纯粹性,但由于只是二选一,实际上效果是可接受的。
掉落池的维护
虽说Alias Method方案的掉落池配置变得相当容易,只需如此这般填写概率值即可,再分别生成items与paths:
然而实际上掉落项的种类与数量都相当的多,并且会时常更改。所以这般直接的配置是无法满足需求的,于是演进为:
新配置明显就方便了不少,若是概率填写为0则表示剩余总概率的平均值(sword=0 => 0.9/3 => 0.3
),且填写的概率是相对于本层的(skill的总概率为0.1,故flash=1 => 0.1
)。算是基于原配置进行了一波封装,可维护性大幅提升,如此便可面对变化频繁的需求了。
后记
本文所展示的掉落业务只是基础,在业界会有复杂度远超于此的需求(与时间、职业等因素挂钩,掉落池数量等),但DFQ的需求也仅此而已,期待日后能接触到更主流的设计。