每一款游戏,或大或小,都是由一段段默默无闻的算法在支撑着他们的运作,我们不能只欣赏绚丽的游戏成品表现在我们面前的华丽与光鲜,还要看到那些支撑在华丽与光鲜背后的,鲜为人知的算法。
篇章一引言
我们知道,在游戏领域里,围绕随机性与随机数展开的一系列技术有着非常广阔的运用空间。
比如《地下城与勇士》,《龙之谷》等网游中的通关后翻牌(翻箱子)奖励机制,又比如梦幻西游中变异宝宝的出现等等,以上这些网络游戏中最吸引人的地方,表面上是明丽的图画与彩色的提示语,其实游戏程序要实现这一个个可玩性十足的游戏系统,全都离不开随机数的产生。
我们来假设一个场景,你很喜欢玩DNF,今天你去凯莉那里强化,心爱的武器【死亡舞步】直接一路上了15。看着散发出璀璨光芒的【+15死亡舞步】,你肯定会想,哇,今天人品真好~其实这样的人品好,只不过是计算机的随机数算法得出了一个个合适的随机数数值,能满足强化成功条件设定的临界值罢了。又假设你刚刚单刷深渊爆出了一把【光炎剑-烈日裁决】,其实也是一样的道理,如果深渊BOSS掉落【光炎剑-烈日裁决】的概率是五千分之一,需要的数值是386到390之间,也只不过是在你杀死BOSS的瞬间,计算机的随机数算法算出了一个刚好在386到390之间的随机数值,刚好满足掉落这件PK神器的条件罢了。
引言说了这么多了,无非就是想强调随机数的产生在游戏开发中的重要性,下面就进入正题吧,讲解计算机中随机数的产生方式。
篇章二知识讲解
在开始展开讲之前,我们必须牢记一个概念,计算机中一般不能产生绝对随机的随机数。计算机产生随机数的过程,是根据一个数(我们可以称它为种子)为基准以某个递推公式推算出来的一系列数,当这系列数很大的时候,就符合正态公布,从而相当于产生了随机数,但这不是真正的随机数,当计算机正常开机后,这个种子的值是确定的,除非你对系统进行了更改。
即计算机一般情况下只能生成相对的随机数,即伪随机数。
伪随机数并不是假随机数,这里的“伪”是有规律的意思,就是计算机产生的伪随机数既是随机的又是有规律的。怎样理解呢?产生的伪随机数有时遵守一定的规律,有时不遵守任何规律;伪随机数有一部分遵守一定的规律;另一部分不遵守任何规律。比如“世上没有两片形状完全相同的树叶”,这正是点到了事物的特性,即随机性,但是每种树的叶子都有近似的形状,这正是事物的共性,即规律性。
在很多时候,我们会使用rand()函数与srand()配合来达到产生随机数的效果,srand初始化随机种子,rand产生随机数,下面进行展开的分析(当然我们在这里先不考虑某些游戏引擎会另外设计自己的随机数产生机制。):
一、随机数发生器rand()函数的用法
函数名:rand
功能:随机数发生器
用法:intrand(void);
所在头文件:stdlib.h
函数说明:
▲rand()的内部实现是用的线性同余法,它不是真的随机数,因其周期特别长,故在一定的范围里可看成是随机的。
▲这种伪随机数是由小M多项式序列生成的,其中产生每个小序列都有一个初始值,即随机种子。(注意:小M多项式序列的周期是65535,即每次利用一个随机种子生成的随机数的周期是65535,当你取得65535个随机数后它们又重复出现了。)
▲目前,计算机中用来产生随机数的算法基本上都是“线性同余”法。rand()返回一随机数值的范围在0至RAND_MAX间。RAND_MAX的范围最少是在32767之间(int)。
▲用unsignedint双字节是65535,四字节是4294967295的整数范围。0~RAND_MAX每个数字被选中的机率是相同的。
▲用户未设定随机数种子时,系统默认的随机数种子为1。
▲rand()产生的是伪随机数字,每次执行时是相同的;若要不同,用函数srand()初始化它。
下面我们给出第一个小例子
//MyRand01.cpp#include 函数名:srand 功能:初始化随机数发生器 用法:voidsrand(unsignedintseed); 所在头文件:stdlib.h 函数说明: ▲srand()用来设置rand()产生随机数时的随机数种子。 ▲参数seed必须是个整数,通常可以利用time(0)的返回值或NULL来当做seed。 ▲如果每次seed都设相同值,rand()所产生的随机数值每次就会一样。 下面我们给出第二个小例子 //MyRand01.cpp#include rand()和srand()要一起使用,其中srand()用来初始化随机数种子,rand()用来产生随机数。 四、产生相同的随机数的原因 计算机的随机数都是由伪随机数,即是由小M多项式序列生成的,其中产生每个小序列都有一个初始值,即随机种子。(注意:小M多项式序列的周期是65535,即每次利用一个随机种子生成的随机数的周期是65535,当你取得65535个随机数后它们又重复出现了。) 我们知道rand()函数可以用来产生随机数,这里我再啰嗦一遍。计算机中一般不能产生绝对随机的随机数。计算机产生随机数的过程,是根据一个数(我们可以称它为种子)为基准以某个递推公式推算出来的一系列数,当这系列数很大的时候,就符合正态公布,从而相当于产生了随机数,但这不是真正的随机数,当计算机正常开机后,这个种子的值是确定的,除非你对系统进行了更改。 下面我们给出第三个小例子 //MyRand01.cpp#include 4118467633426500191691572411478293582696224464 为得到不同的随机数序列,则需改变这个种子的值。方法:在开始产生随机数前,调用一次srand(time(NULL))(注意:srand()一定要放在循环外面或者是循环调用的外面,否则的话得到的是相同的随机数)。 下面我们给出第四个小例子 //MyRand01.cpp#include 12941856214141181651191029784110701322513124405 177425714187341652820825171899848889925035375 五、几种随机数的简单算法 1.产生一个范围内的随机数 一般地,我们可用j=1+(int)(n*rand()/(RAND_MAX+1.0))来生成一个0到n之间的随机数。 若用intx=rand()%100;来生成0到100之间的随机数这种方法是不可取的,比较好的做法是: j=(int)(100.0*rand()/(RAND_MAX+1.0)) 当然,如果是在gcc,vc之外的编译器,我们也可以使用random(100)。下面的例子都是用了random(n)(VC无法识别random这个函数,VC下我们还是采用j=(int)(100.0*rand()/(RAND_MAX+1.0)). 2、筛选型随机数如希望取0-99的随机数,但不能是6。 解决方法: x=random(100);while(x==6){x=random(100);}又如希望取0-99的随机数,但不要5的倍数解决方法: x=random(100);while((x%5)==0){x=random(100);}3、从连续的一段范围内取随机数。 如从40--50的范围内取随机数。解决方法:x=random(11)+40 4、从一组乱数中取随机数。如:从67,87,34,78,12,5,9,108,999,378十个数中随机取数。解决方法:可以用数组将些十个数存贮,然后把0--9中取出的随机数作为序号,实现随机取数。 a=newArray(67,87,34,78,12,5,9,108,999,378); j=random(10); x=a[j]; 六、产生一定范围随机数的通用算法公式 ▲要取得[a,b)的随机整数,使用(rand()%(b-a))+a(结果值含a不含b)。 ▲要取得[a,b]的随机整数,使用(rand()%(b-a+1))+a(结果值含a和b)。 ▲要取得(a,b]的随机整数,使用(rand()%(b-a))+a+1(结果值不含a含b)。 ▲即(通用公式:a+rand()%n;取得[a,a+n)的随机整数,其中的a是起始值,n是整数的范围。) ▲要取得[a,b)的随机整数,另一种表示:a+(int)b*rand()/(RAND_MAX+1)。 ▲要取得[a,b]的随机整数另一种表示:a+(int)b*rand()/(RAND_MAX)。 ▲要取得[0,1]之间的浮点数,可以使用rand()/double(RAND_MAX)。 了解了随机数产生的基础知识和一些产生随机数的算法,相信大家心里应该有底了,比如如何设置各阶段装备强化的成功率,副本里装备的掉落率,通关奖励翻牌的掉落率,攻击暴击的概率,攻击MISS的几率,梦幻西游里碰到变异宝宝的概率等等。 因为它让你有收获了: 1.计算机的伪随机数是由随机种子根据一定的计算方法计算出来的数值。所以,只要计算方法一定,随机种子一定,那么产生的随机数就是固定的。 2.只要用户或第三方不设置随机种子,那么在默认情况下随机种子值为1,来自系统时钟。