游戏开发笔记十七 游戏基础算法(一) 游戏随机系统初步
每一款游戏,或大或小,都是由一段段默默无闻的算法在支撑着他们的运作,我们不能只欣赏绚丽的游戏成品表现在我们面前的华丽与光鲜,还要看到那些支撑在华丽与光鲜背后的,鲜为人知的算法。
篇章一 引言
我们知道,在游戏领域里,围绕随机性与随机数展开的一系列技术有着非常广阔的运用空间。
比如所有游戏都离不开的宝物掉落系统。极品装备的掉落永远牵动着玩家们的心,譬如盛大《热血传奇》盛行的年代一把霸气的“屠龙刀”,比如《魔兽世界》70级版本里一把帅气的“伊利丹的双刀”
,又如《地下城与勇士》60级版本时一把拉风的“流光星陨刀”
。
比如几乎同样是所有游戏都离不开的装备强化系统。闪闪发光的高强化武器永远是每一个玩家的梦寐以求,但在这高强化武器光鲜的背后,有那么多的玩家因为不高的强化成功率而黯然神伤甚至倾家荡产。不高的强化成功率往往是玩家“烧钱”的罪魁祸首,同样这恰恰就是游戏运营商盈利的主要来源之一(商城的强化防爆保护药)。
比如《地下城与勇士》,《龙之谷》等网游中的通关后翻牌(翻箱子)奖励机制,又比如梦幻西游中变异宝宝的出现等等,以上这些网络游戏中最吸引人的地方,表面上是明丽的图画与彩色的提示语,其实游戏程序要实现这一个个可玩性十足的游戏系统,全都离不开随机数的产生。
我们来假设一个场景,你很喜欢玩DNF,今天你去凯莉那里强化,心爱的武器【死亡舞步】直接一路上了15。看着散发出璀璨光芒的【+15死亡舞步】,你肯定会想,哇,今天人品真好~其实这样的人品好,只不过是计算机的随机数算法得出了一个个合适的随机数数值,能满足强化成功条件设定的临界值罢了。又假设你刚刚单刷深渊爆出了一把【光炎剑-烈日裁决】,其实也是一样的道理,如果深渊BOSS掉落【光炎剑-烈日裁决】的概率是五千分之一,需要的数值是386到390之间,也只不过是在你杀死BOSS的瞬间,计算机的随机数算法算出了一个刚好在386到390之间的随机数值,刚好满足掉落这件PK神器的条件罢了。
引言说了这么多了,无非就是想强调随机数的产生在游戏开发中的重要性,下面就进入正题吧,讲解计算机中随机数的产生方式。
篇章二 知识讲解
在开始展开讲之前,我们必须牢记一个概念,计算机中一般不能产生绝对随机的随机数。计算机产生随机数的过程,是根据一个数(我们可以称它为种子)为基准以某个递推公式推算出来的一系列数,当这系列数很大的时候,就符合正态公布,从而相当于产生了随机数,但这不是真正的随机数,当计算机正常开机后,这个种子的值是确定的,除非你对系统进行了更改。
即计算机一般情况下只能生成相对的随机数,即伪随机数。
当然,也不是说计算机没有能力产生绝对随机的真随机数。前段时间看到过一篇用计算机产生“真随机数”的论文,这里先不作考虑,感兴趣的朋友可以去看看相关的文章。
伪随机数并不是假随机数,这里的“伪”是有规律的意思,就是计算机产生的伪随机数既是随机的又是有规律的。怎样理解呢?产生的伪随机数有时遵守一定的规律,有时不遵守任何规律;伪随机数有一部分遵守一定的规律;另一部分不遵守任何规律。比如“世上没有两片形状完全相同的树叶”,这正是点到了事物的特性,即随机性,但是每种树的叶子都有近似的形状,这正是事物的共性,即规律性。
在很多时候,我们会使用rand()函数与srand()配合来达到产生随机数的效果,srand初始化随机种子,rand产生随机数,下面进行展开的分析(当然我们在这里先不考虑某些游戏引擎会另外设计自己的随机数产生机制。):
一、随机数发生器rand()函数的用法
函数名: rand
功 能: 随机数发生器
用 法: int rand(void);
所在头文件: stdlib.h
函数说明 :
▲rand()的内部实现是用的线性同余法,它不是真的随机数,因其周期特别长,故在一定的范围里可看成是随机的。
▲这种伪随机数是由小M多项式序列生成的,其中产生每个小序列都有一个初始值,即随机种子。(注意: 小M多项式序列的周期是65535,即每次利用一个随机种子生成的随机数的周期是65535,当你取得65535个随机数后它们又重复出现了。)
▲目前,计算机中用来产生随机数的算法基本上都是“线性同余”法。rand()返回一随机数值的范围在0至RAND_MAX 间。RAND_MAX的范围最少是在32767之间(int)。
▲用unsigned int 双字节是65535,四字节是4294967295的整数范围。0~RAND_MAX每个数字被选中的机率是相同的。
▲用户未设定随机数种子时,系统默认的随机数种子为1。
▲rand( )产生的是伪随机数字,每次执行时是相同的;若要不同,用函数srand()初始化它。
下面我们给出第一个小例子
- //MyRand01.cpp
- #include <iostream>
- using namespace std;
- #include <stdlib.h>
- #include <time.h>
- #define MIN 1 //随机数产生的范围
- #define MAX 10
- int main()
- {
- int i;
- srand((unsigned)time(0));
- cout<<"10个随机数从 "<<MIN<<
- " 到 "<<MAX<<" :\n"<<endl;
- for(i=0; i<10; i++) //产生随机数
- {
- cout<<MIN + (int)MAX * rand() / (RAND_MAX + 1)<<"\t";
- }
- cout<<endl;
- return 0;
- }
//MyRand01.cpp #include <iostream> using namespace std; #include <stdlib.h> #include <time.h> #define MIN 1 //随机数产生的范围 #define MAX 10 int main() { int i; srand((unsigned)time(0)); cout<<"10个随机数从 "<<MIN<< " 到 "<<MAX<<" :\n"<<endl; for(i=0; i<10; i++) //产生随机数 { cout<<MIN + (int)MAX * rand() / (RAND_MAX + 1)<<"\t"; } cout<<endl; return 0; }
二、初始化随机数发生器srand( )函数的用法
函数名: srand
功 能: 初始化随机数发生器
用 法: void srand(unsigned int seed);
所在头文件: stdlib.h
函数说明:
▲srand()用来设置rand()产生随机数时的随机数种子。
▲参数seed必须是个整数,通常可以利用time(0)的返回值或NULL来当做seed。
▲如果每次seed都设相同值,rand()所产生的随机数值每次就会一样。
下面我们给出第二个小例子
[cpp] view plaincopyprint?
- //MyRand02.cpp
- #include <iostream>
- using namespace std;
- #include <stdlib.h>
- #include <time.h>
- #define MIN 0 //随机数产生的范围
- #define MAX 99
- int main()
- {
- int i;
- srand((unsigned)time(NULL));
- cout<<"10个随机数从"<<MIN<<
- " 到"<<MAX<<" :\n"<<endl;
- for(i=0; i<10; i++) //产生随机数
- {
- cout<<MIN + rand() % (MAX + MIN + 1)<<"\t";
- }
- cout<<endl;
- return 0;
- }
//MyRand02.cpp #include <iostream> using namespace std; #include <stdlib.h> #include <time.h> #define MIN 0 //随机数产生的范围 #define MAX 99 int main() { int i; srand((unsigned)time(NULL)); cout<<"10个随机数从"<<MIN<< " 到"<<MAX<<" :\n"<<endl; for(i=0; i<10; i++) //产生随机数 { cout<<MIN + rand() % (MAX + MIN + 1)<<"\t"; } cout<<endl; return 0; }
三、rand( )和srand( )的联系
rand( )和srand( )要一起使用,其中srand( )用来初始化随机数种子,rand( )用来产生随机数。
因为默认情况下随机数种子为1,而相同的随机数种子产生的随机数是一样的,失去了随机性的意义,所以为使每次得到的随机数不一样,用函数srand()初始化随机数种子。srand()的参数,用time函数值(即当前时间),因为两次调用rand()函数的时间通常是不同的,这样就可以保证随机性了。
四、产生相同的随机数的原因
计算机的随机数都是由伪随机数,即是由小M多项式序列生成的,其中产生每个小序列都有一个初始值,即随机种子。(注意: 小M多项式序列的周期是65535,即每次利用一个随机种子生成的随机数的周期是65535,当你取得65535个随机数后它们又重复出现了。)
我们知道rand()函数可以用来产生随机数,这里我再啰嗦一遍。计算机中一般不能产生绝对随机的随机数。计算机产生随机数的过程,是根据一个数(我们可以称它为种子)为基准以某个递推公式推算出来的一系列数,当这系列数很大的时候,就符合正态公布,从而相当于产生了随机数,但这不是真正的随机数,当计算机正常开机后,这个种子的值是确定的,除非你对系统进行了更改。
下面我们给出第三个小例子
[cpp] view plaincopyprint?
- //MyRand03.cpp
- #include <iostream>
- using namespace std;
- #include <stdlib.h>
- #include <time.h>
- int main()
- {
- int i;
- for (i=0; i<10; i++) //产生10个随机数
- {
- cout<<rand()<<"\t";
- }
- cout<<endl;
- return 0;
- }
//MyRand03.cpp #include <iostream> using namespace std; #include <stdlib.h> #include <time.h> int main() { int i; for (i=0; i<10; i++) //产生10个随机数 { cout<<rand()<<"\t"; } cout<<endl; return 0; }
每次运行得到相同的随机序列:
41 18467 6334 26500 19169 15724 11478 29358 26962 24464
41 18467 6334 26500 19169 15724 11478 29358 26962 24464
为得到不同的随机数序列,则需改变这个种子的值。方法:在开始产生随机数前,调用一次srand(time(NULL))(注意:srand()一定要放在循环外面或者是循环调用的外面,否则的话得到的是相同的随机数)。
下面我们给出第四个小例子
[cpp] view plaincopyprint?
- //MyRand04.cpp
- #include <iostream>
- using namespace std;
- #include <stdlib.h>
- #include <time.h>
- int main( )
- {
- int i;
- srand((unsigned)time(NULL)); //初始化随机数种子
- for (i=0; i<10; i++) //产生10个随机数
- {
- cout<<rand()<<"\t";
- }
- cout<<endl;
- return 0;
- }
//MyRand04.cpp #include <iostream> using namespace std; #include <stdlib.h> #include <time.h> int main( ) { int i; srand((unsigned)time(NULL)); //初始化随机数种子 for (i=0; i<10; i++) //产生10个随机数 { cout<<rand()<<"\t"; } cout<<endl; return 0; }
每次运行得到不同的随机序列:
1294 18562 14141 18165 11910 29784 11070 13225 131 24405
1774 25714 18734 16528 20825 17189 9848 8899 2503 5375
五、几种随机数的简单算法
1.产生一个范围内的随机数
一般地,我们可用j=1+(int)(n*rand()/(RAND_MAX+1.0))来生成一个0到n之间的随机数。
若用int x = rand() % 101;来生成 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 = new Array(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-a) * rand() / (RAND_MAX + 1)。
▲要取得[a,b] 的随机整数 另一种表示:a + (int)(b-a) * rand() / (RAND_MAX )。
▲要取得[0,1] 之间的浮点数 ,可以使用rand() / double(RAND_MAX)。
了解了随机数产生的基础知识和一些产生随机数的算法,相信大家心里应该有底了,比如如何设置各阶段装备强化的成功率,副本里装备的掉落率,通关奖励翻牌的掉落率,攻击暴击的概率,攻击MISS的几率,梦幻西游里碰到变异宝宝的概率等等。
最后,我们提出两个要点,就算这篇文章你看过后不能留下深刻印象,只要记住以下两个要点,就算是我的这篇文章没白写,
因为它让你有收获了:
1.计算机的伪随机数是由随机种子根据一定的计算方法计算出来的数值。所以,只要计算方法一定,随机种子一定,那么产生的随机数就是固定的。
2.只要用户或第三方不设置随机种子,那么在默认情况下随机种子值为1,来自系统时钟。
本节笔记基本上就讲解完了。
每一款游戏,或大或小,都是由一段段默默无闻的算法在支撑着他们的运作,我们不能只欣赏绚丽的游戏成品表现在我们面前的华丽与光鲜,还要看到那些支撑在华丽与光鲜背后的,鲜为人知的算法。
接下来的这几节浅墨准备讲解游戏相关的算法研究,注重算法的思想,配起漂亮的图来不是那么方便,看过我之前笔记的喜欢图文并茂的朋友们请体谅一下浅墨。
游戏开发笔记十七 游戏基础算法(一) 游戏随机系统初步相关推荐
- 【Visual C++】游戏开发笔记十七 游戏基础算法(一) 游戏随机系统初步
本系列文章由zhmxy555编写,转载请注明出处.http://blog.csdn.net/zhmxy555/article/details/7468011 作者:毛星云 邮箱: happyli ...
- 【Visual C++】游戏开发笔记十七 游戏基础算法 一 游戏随机系统初步
分享一下我老师大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow 本系列文章由zhm ...
- 【Visual C++】游戏开发笔记三十一 回归季:游戏开发资料整理打包下载专栏行文思路整理
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. http://blog.csdn.net/zhmxy555/article/details/8147229 作者:毛星云 邮箱: h ...
- 【Visual C++】游戏开发笔记二十七 Direct3D 11入门级知识介绍
游戏开发笔记二十七 Direct3D 11入门级知识介绍 作者:毛星云 邮箱: happylifemxy@163.com 期待着与志同道合的朋友们相互交流 上一节里我们介绍了在迈入Dire ...
- 【Visual C++】游戏开发笔记四十七 浅墨DirectX教程十五 翱翔于三维世界 摄像机的实现
分享一下我老师大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow 本系列文章由zhm ...
- 【Visual C++】游戏开发笔记三十七 浅墨DirectX提高班之五 顶点缓存的红颜知己 索引缓存的故事
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 本系列文 ...
- 【Visual C++】游戏开发笔记二十七 Direct3D 11入门级知识介绍
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. http://blog.csdn.net/zhmxy555/article/details/7707628 作者:毛星云 邮箱: happ ...
- 【Visual C++】游戏开发笔记二十三 游戏基础物理建模 五 粒子系统模拟 二
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 本系列文 ...
- 【Visual C++】游戏开发笔记四十七 浅墨DirectX教程十五 翱翔于三维世界:摄像机的实现...
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接:http://blog.csdn.net/zhmxy555/article/details/8657656 作者:毛星云(浅墨) ...
最新文章
- 屏幕后处理——Bloom
- 从零到一编码实现Redis分布式锁
- shiro+redis实现session共享
- 【Linux 内核】进程管理 ( 进程特殊形式 | 内核线程 | 用户线程 | C 标准库与 Linux 内核中进程相关概念 | Linux 查看进程命令及输出字段解析 )
- 她,既是一个风华绝代的演员,更是WiFi之母...
- 【Effective Java】第二章:静态工厂、构建器、强化Singleton属性、私有构造器、
- DRDoS分布反射式拒绝服务攻击(什么是DRDoS)
- centos系统 查看当前python执行的进程
- php实现最后登录时间,php实例之基于Cookie的登录表单和获取最后登录时间
- 维控触摸屏编程手册_维控触摸屏ab plc地址编程实例
- matlab如何找出相似的图,图像相似性搜索的MATLAB实现
- item_search_img - 拍立淘搜索淘宝商品(淘宝API)
- do.....while(0)让你虎躯一震的用法
- DSPE-PEG12-Mal,C72H135N2O23P长臂亲水性小分子PEG试剂
- java初中学历_20岁学java初中学历
- geany配置python_Geany配置python教程解析
- 关于flask入门教程-ajax+echarts实现热力图
- JeecgBoot学习
- 五款开源虚拟化技术软件大推荐
- 浅谈外挂常识和如何防御
热门文章
- Python题目——实现人机对战的尼姆游戏:假设有一堆物品,计算机和人类玩家轮流从其中拿走一部分。在每一步中,人或计算机可以自由选择拿走多少物品,但是必须至少拿走一个并且最多只能拿走一半物品
- 李晓江:“双碳”目标下的社区绿色更新
- 微信小程序页面3秒后自动跳转
- 2012年4月微软MVP申请开始!
- 单例模式的实现-懒汉模式【大话设计模式之爱你一万年:单例模式:我的女朋友只有你一个】
- 让我们一起来书写人生
- 一步步学习微软InfoPath2010和SP2010--第四章节--处理SP列表表单(8)--关键点
- slam十四讲,第五讲中相机坐标系,像素平面坐标系,世界坐标系,归一化坐标系总结
- 数学中的 s.t. 含义
- 国产光刻机再突破后,能实现7nm芯片量产?专家:别再盲目自大