数值模型在手游竞技场中的对比分析

发表于2015-08-10
评论5 4.1k浏览
  

最近在做竞技场方面的设计,遇到一些细节问题,在最初设计的时候,并没有太过在意,但与程序沟通的过程中,注意到如果想要计算的过程更为简单明了,需要考虑东西就多了起来。在此,把遇到的问题和思考的过程写了下来,和大家一起讨论。在最终的设计方案里,很可能已经不是文中所提及的了,但思考的过程依旧会遵循这些逻辑,提炼需求点,建立模型,设计方案,验证,优化等等。

竞技场目前广泛的存在于手机游戏的设计中,除了对应的战斗系统之外,还有一些地方需要数值支撑,一般为以下三个方面:

1)竞技场对手的随机规则。

2)竞技场首次最高名次奖励。

3)竞技场每日排行的奖励。

3个方面读表即可完成,不做讨论。本文将对前2个方面作简要介绍,并根据实际过程中的问题提出一些解决思路。

  1. 竞技场对手的随机规则

以《刀塔传奇》为例,在这一游戏中,玩家根据当前名次可以选择一些排名更高的玩家进行挑战。由于手机硬件,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判断,如果67在乘以一个数字后,结果的整数部分是不同的,有可能解决这一问题。同时我们考虑将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名,能够一次性看到123名;第3名,可以看到12名。

  1. 竞技场首次最高名次奖励

在现有的一些手游中,为了给予玩家一些初期的资源,通常会通过竞技场发放一些钻石/金币(人民币充值获得,以下统称为钻石)。仍旧以《刀塔传奇》为例,玩家的历史最高名次每提高一次,玩家可以获得一定的钻石奖励。设计这套系统,会有以下的考虑:

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=400a9=555-400=155,所以公差d1=(a1-a9)/8,约等于30

2)同理依次设计10~100,100~10001000~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=30a1的奖励为 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~100d=10,100~10000取基数d=1000,每阶段的钻石奖励如下表。然后整体做一次修正即可,并不会给求和函数带来太多麻烦。

10

2500

100

2045.455

1000

2045.455

10000

2045.455

对于这个函数还可以做进一步优化,其中的一种思路为,裂项的分母不使用整数,而使用LOG10函数。令a1=1/LOG1010-1/LOG10(10+1)aN=1/LOG1010+N-1-1/LOG10(10+N),每阶段的分布如下表,同样可以得到一些满足特定需求的函数。对于LOG10的底数和分母中的基数10做调整,可以进一步优化,对于这一过程在此不再展开,欢迎大家对于裂项也做出更多的讨论。使用LOG函数也在一定程度上增加了程序的负担。

10

2313.782

100

2787.601

1000

1570.078

10000

828.81

 

本文更多的侧重于遇到实际问题时思考的过程。总结解决问题的方法并不断的自我优化,有利于更好地解决可能遇到的问题。方案的缺点欢迎批评指正,最终的解决方案也可能已经不是上文中提及的方式,对于其他的一些思考方式,更欢迎大家多多讨论。

 

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