数值模型在手游竞技场中的对比分析
最近在做竞技场方面的设计,遇到一些细节问题,在最初设计的时候,并没有太过在意,但与程序沟通的过程中,注意到如果想要计算的过程更为简单明了,需要考虑东西就多了起来。在此,把遇到的问题和思考的过程写了下来,和大家一起讨论。在最终的设计方案里,很可能已经不是文中所提及的了,但思考的过程依旧会遵循这些逻辑,提炼需求点,建立模型,设计方案,验证,优化等等。
竞技场目前广泛的存在于手机游戏的设计中,除了对应的战斗系统之外,还有一些地方需要数值支撑,一般为以下三个方面:
1)竞技场对手的随机规则。
2)竞技场首次最高名次奖励。
3)竞技场每日排行的奖励。
第3个方面读表即可完成,不做讨论。本文将对前2个方面作简要介绍,并根据实际过程中的问题提出一些解决思路。
竞技场对手的随机规则
以《刀塔传奇》为例,在这一游戏中,玩家根据当前名次可以选择一些排名更高的玩家进行挑战。由于手机硬件,UI,以及服务器控制数据量等方面的考虑,竞技场通常显示有限个对手,通过刷新机制可以查看到不同的对手。在《刀塔传奇》的设计中,玩家一次可以刷新出3个对手,并且每个对手都是在一个区间范围内。本节也将以3个对手为例。
在通常的观念里,设计这个区间是比较简单的事情。我们可以规定第一个人是在自身当前名次的40%~60%,第二个人是在60%~80%,第三个人是在80%~100%。在实际的体验中,区间个数和大小根据实际的需求各异,比如第一个人的上限,将影响着玩家到达最高名次的预期次数,会根据实际需求而做设定。
在设计开始的时候,这个想法的实现方案很可能是
令当前的名词为M
第三个人:[Int(M*0.8),M) 左边闭集,右边开集
第二个人:[Int(M*0.6), Int(M*0.8)) 左边闭集,右边开集
第一个人:[Int(M*0.4), Int(M*0.6)) 左边闭集,右边开集
下表为玩家排名对应的3个区间段。
名次 | 第一个人 |
| 第二个人 |
| 第三个人 |
|
| 上限 | 下限 | 上限 | 下限 | 上限 | 下限 |
1 | 0 | -1 | 0 | -1 | 0 | 0 |
2 | 0 | 0 | 1 | 0 | 1 | 1 |
3 | 1 | 0 | 1 | 1 | 2 | 2 |
4 | 1 | 1 | 2 | 2 | 3 | 3 |
5 | 2 | 2 | 3 | 3 | 4 | 4 |
6 | 2 | 2 | 3 | 3 | 4 | 5 |
7 | 2 | 3 | 4 | 4 | 5 | 6 |
8 | 3 | 3 | 4 | 5 | 6 | 7 |
9 | 3 | 4 | 5 | 6 | 7 | 8 |
10 | 4 | 5 | 6 | 7 | 8 | 9 |
20 | 8 | 11 | 12 | 15 | 16 | 19 |
30 | 12 | 17 | 18 | 23 | 24 | 29 |
50 | 20 | 29 | 30 | 39 | 40 | 49 |
75 | 30 | 44 | 45 | 59 | 60 | 74 |
100 | 40 | 59 | 60 | 79 | 80 | 99 |
300 | 120 | 179 | 180 | 239 | 240 | 299 |
500 | 200 | 299 | 300 | 399 | 400 | 499 |
1000 | 400 | 599 | 600 | 799 | 800 | 999 |
这个设计并无太大的问题,从表中,我们能够看出名次较大时,符合预期。但是前10名的用户中,第4名才可以看到第一名,在刀塔传奇的原作中,是第6名可以看到第1名。暂且不讨论刀塔中的设计是否合理,如果为了增加游戏竞争,我们会希望有一种可控的修正,对于前几名的挑战名次进行调整。
所以一个需求是对前10名做修正,使可以挑战第1名的人数增加,而在名次较大时不对挑战区间的百分比产生较大影响。
修正思路1:第一个人上限乘以更小的百分比,如20%,这会造成名次较小与较大时修正的百分比一致,导致名次较大时上限提升过多。
修正思路2:直接在第一个人的区间上限上减去固定值,即 第一个人的区间为:[Int(M*0.4)-1, Int(M*0.6)),这样第7名直接可以看到第一名了,在名次较大时,影响也很小。这是一种较为合理的方案,简单直接,满足需求。如果我们想获得更大的自由度,比如第6名开始才能看到第1名,就需要重新修正了。我们可以加上一个条件,当前名次小于XX名,第一个数字的上限固定减少一个值,相对灵活。在此我们做更进一步的设想,有没有可能不让程序做特殊处理,而使用公式解决,公式尽可能的简单明了。
为了区分从第6名开始还是从第7名开始,我们考虑如何通过公式来解决程序的IF判断,如果6和7在乘以一个数字后,结果的整数部分是不同的,有可能解决这一问题。同时我们考虑将0.4分拆成两个数的乘积,比如0.4约等于1.3*0.3,这样会把保证名次较大时上限接近于40%。具体的区间设计如下。
第三个人区间:[INT(M*0.8),M)
第二个人区间:[INT(M*0.6), INT(M*0.8))
第一个人区间:[INT(INT(M*1.3)*0.3), INT(M*0.8))
(如果想进一步提高接近0.4的精度,可换成1.33*0.3,在此为只让程序进行1位小数的计算,所以近似为1.3*0.3)
下表为3个区间对应的区间段
名次 | 第一个人 |
| 第二个人 |
| 第三个人 |
|
| 上限 | 下限 | 上限 | 下限 | 上限 | 下限 |
1 | 0 | -1 | 0 | -1 | 0 | 0 |
2 | 0 | 0 | 1 | 0 | 1 | 1 |
3 | 0 | 0 | 1 | 1 | 2 | 2 |
4 | 1 | 1 | 2 | 2 | 3 | 3 |
5 | 1 | 2 | 3 | 3 | 4 | 4 |
6 | 1 | 2 | 3 | 3 | 4 | 5 |
7 | 2 | 3 | 4 | 4 | 5 | 6 |
8 | 2 | 3 | 4 | 5 | 6 | 7 |
9 | 2 | 4 | 5 | 6 | 7 | 8 |
10 | 3 | 5 | 6 | 7 | 8 | 9 |
20 | 7 | 11 | 12 | 15 | 16 | 19 |
30 | 11 | 17 | 18 | 23 | 24 | 29 |
50 | 19 | 29 | 30 | 39 | 40 | 49 |
75 | 28 | 44 | 45 | 59 | 60 | 74 |
100 | 39 | 59 | 60 | 79 | 80 | 99 |
这种设计能够在一定程度上对前面的名次作较好的控制,同时不会影响到名次较大时提升的上限比例。这里面还有一个附带的细节,我们并没有展开,比如第4名,能够一次性看到1、2、3名;第3名,可以看到1、2名。
竞技场首次最高名次奖励
在现有的一些手游中,为了给予玩家一些初期的资源,通常会通过竞技场发放一些钻石/金币(人民币充值获得,以下统称为钻石)。仍旧以《刀塔传奇》为例,玩家的历史最高名次每提高一次,玩家可以获得一定的钻石奖励。设计这套系统,会有以下的考虑:
1)玩家排名越靠前,提升单位名次的奖励越大。比如从历史最高第2名提升到历史最高第1名,可能会奖励100钻石。玩家从1000名提高到900名奖励50钻石。
2)玩家从最后1名提升到第1名获得的钻石基本是固定的,我们在此可以规定,玩家从第10000名到第1名可以获得10000钻石。
一个可能的思路是:
1)对数值进行分配,1~10名分配2500钻石,10~100名分配2500钻石,100~1000钻分配2500钻石,1000~10000钻石分配2500钻石。
2)每一段内,进行等差数列设计,首尾相加等于固定值,对于首位数做设计,同时可以得出等差数列的公差。
实例如下:
1)前10名中,共可前进9次,首尾相加为2500/9*2=555名,令第2名升到第1名奖励为400,则a1=400,a9=555-400=155,所以公差d1=(a1-a9)/8,约等于30。
2)同理依次设计10~100,100~1000,1000~10000名之间的奖励。
使用等差数列有一个好处,如果玩家两次的最高排名都在一个区间,通过等差数列的求和公式能够非常方便的获得结果。而在整个生命周期内,玩家只会有3次的区间跨越,分别是1000,100,10,对跨越区间的时候做特殊处理即可。
这个思路,是一个可以考虑的方案。尽管可以实现需求,但我们依旧想追求更简约的方案。
等差数列如果只使用固定的公差,不做成阶段函数,保证不了前面名次和后面名次的差距,固定的公差,很容易造成后面名次是负值奖励。等比数列,一个数的1000次方,这种计算可以暂时不考虑,也可能存在一些较好的修正方式,自己并未做过多考虑。前面等差后面等比的函数,也同样不适合。
转变思考角度,我们需要一个递减的数列,同时方便求和。1/2+1/6+1/12+1/20。。。这个数列求和就已经非常熟悉了。通过裂项分解可以很方便的进行进行求和。
根据这个思路设计游戏中的实际函数,如果直接使用这个函数会遇到一些问题,比如10000*1/2=5000,这种名次上升的奖励明显不合理。
首先我们会对第2名上升到第1名奖励的钻石量级做设定,在此我们可以取300左右,10000/300=33,为了简便取30左右。
令a1作为第二名升到第一名的奖励,我们取奖励的开基数为d=30,a1的奖励为 10000*d*(1/d-1/(d+1)),
第N+1名上升到N名的奖励aN=10000*d*(1/(d+N-1)-1/(d+N))。
根据这一设计,对应名次的奖励见下表。
1 | 302.4194 |
2 | 284.0909 |
3 | 267.3797 |
4 | 252.1008 |
5 | 238.0952 |
6 | 225.2252 |
7 | 213.3713 |
8 | 202.4291 |
9 | 192.3077 |
10 | 182.9268 |
20 | 117.6471 |
30 | 81.96721 |
50 | 46.2963 |
75 | 26.95418 |
100 | 17.61597 |
200 | 5.646527 |
300 | 2.746498 |
500 | 1.065984 |
750 | 0.492465 |
1000 | 0.282504 |
2000 | 0.072764 |
3000 | 0.032666 |
5000 | 0.011855 |
7500 | 0.00529 |
10000 | 0.002982 |
为了更直观的看这张表,对1~10名,10~100名,100~1000名,1000~10000名的奖励做了统计
10 | 2500 |
100 | 5192.308 |
1000 | 2016.43 |
10000 | 261.3519 |
从表中能够清晰的看到,不同等级段所占的比例。在一定程度上,这个函数是可以满足需求的,但我们也很明显的发现,10~100名的分配很多。如果我们的设计本意是将发放集中在前1000名,在到达100名,可以完成1次十连抽,控制刷十连抽的节奏,这个分布是相对合理的。
如果想对区间段做更多控制,需要寻找造成这种现象的原因。
在上文的情况下10~100的奖励总数的计算规则为:
10000*30*(1/40-1/130)
可以理解30*(1/40-1/130)为这一阶段奖励的占比。
下表为d为不同值的对比,
| d=5 | d=10 | d=20 | d=30 | d=40 | d=50 | d=75 | d=100 | d=1000 |
10 | 6667 | 5000 | 3333 | 2500 | 2000 | 1667 | 1176 | 909 | 99 |
100 | 2857 | 4091 | 5000 | 5192 | 5143 | 5000 | 4538 | 4091 | 810 |
1000 | 426 | 810 | 1471 | 2016 | 2473 | 2857 | 3588 | 4091 | 4091 |
10000 | 45 | 89 | 176 | 261 | 345 | 426 | 623 | 810 | 4091 |
通过控制d的大小,可以满足一些不同的需求。d=100的时候可以让100名之前和之后的发放较为接近。
这个函数适合于将总体发放分开成两部分,相比于等差数列更容易控制。如果仍然需要分成4段接近的发放,这个函数并不能很好解决,这和函数本身的特性相关。可以考虑分段,1~100分配5000,100~1000分配5000,1~100取d=10,100~10000取基数d=1000,每阶段的钻石奖励如下表。然后整体做一次修正即可,并不会给求和函数带来太多麻烦。
10 | 2500 |
100 | 2045.455 |
1000 | 2045.455 |
10000 | 2045.455 |
对于这个函数还可以做进一步优化,其中的一种思路为,裂项的分母不使用整数,而使用LOG10函数。令a1=1/LOG10(10)-1/LOG10(10+1),aN=1/LOG10(10+N-1)-1/LOG10(10+N),每阶段的分布如下表,同样可以得到一些满足特定需求的函数。对于LOG10的底数和分母中的基数10做调整,可以进一步优化,对于这一过程在此不再展开,欢迎大家对于裂项也做出更多的讨论。使用LOG函数也在一定程度上增加了程序的负担。
10 | 2313.782 |
100 | 2787.601 |
1000 | 1570.078 |
10000 | 828.81 |
本文更多的侧重于遇到实际问题时思考的过程。总结解决问题的方法并不断的自我优化,有利于更好地解决可能遇到的问题。方案的缺点欢迎批评指正,最终的解决方案也可能已经不是上文中提及的方式,对于其他的一些思考方式,更欢迎大家多多讨论。