我是靠谱客的博主 阔达芹菜,最近开发中收集的这篇文章主要介绍游戏思考12:游戏的随机数考究(重点在目录的第三章),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

    • 一、如何实现乱序的随机排序,而不占用空间
      • (1)想法来源
      • (2)算法作用
      • (3)效果展示
      • (4)代码
        • 1)cmakelists.txt
        • 2)main.cpp
        • 3)Prandgen.cpp
        • 4)Prandgen.h
    • 二、rand()和srand()的使用
      • 0)补充背景说明
      • 1)效果展示
      • 2)代码使用举例
    • 三、游戏中的随机算法简单分类(主指伪随机)
      • 1)出现原因
      • 2)分类
        • (1)(Pseudo Random Distribution)PRD
        • (2)洗牌算法
        • ()补充计算机产生随机数的方法:赝随机数算法(Pseudo-Random Number Generator,简称PRNG)

一、如何实现乱序的随机排序,而不占用空间

(1)想法来源

最近看了游戏开发精粹1的可预测随机数,感觉有点用处,就记录下来,其实没啥大用

(2)算法作用

在资源有限的环境下,在游戏空间中给出无限空间的幻觉

(3)效果展示

在这里插入图片描述

  • 参数说明
    1)seed 种子,seed is a seed value between 1 and the maximum long integer value allowed by ANSI.
    2)max 序列的最大值
    3)runs 展示序列的多少个元素
Prandgen <seed> <n_max> <n_runs>

(4)代码

因为是老代码,所以我用cmake重新简单改造了下

  • 目录
    在这里插入图片描述

  • 代码

1)cmakelists.txt

cmake_minimum_required(VERSION 2.8)
PROJECT(prand_1)
SET(SRC_LIST main.cpp Prandgen.cpp Prandgen.h)
ADD_EXECUTABLE(prand ${SRC_LIST})

2)main.cpp

#include <stdlib.h>
#include <string.h>

#include "Prandgen.h"

using namespace std;
CPseudorandom * rand_gen = NULL;

int main (int argc, char * argv[])
{
	uint32_t lSeed, lMax, lRand, i;
	int32_t nPad, nPerLine, nRuns, nLinePos;

	// Expect the seed, maximum and runs as arguments
	if (argc < 4)
	{
		printf("nPrandgen <seed> <n_max> <n_runs>n");
		return -1;
	}

	lSeed = atol(argv[1]);//把字符串改成长整型
    //is a seed value between 1 and the maximum long integer value allowed by ANSI.

	lMax = atol(argv[2]);
	nPad = strlen(argv[2]); // The width of field
	nRuns = atoi(argv[3]);  //argument determines the number of pseudorandom numbers to generate.

	rand_gen = new CPseudorandom(); // Create a new random number object
    if(!rand_gen) return -1;

	rand_gen->SRand(lSeed);	       // Initialise it with the seed value

	nPerLine = (79-nPad) / nPad;   // Calculate number of numbers per line

	nLinePos = 0;
	while (nRuns > 0)
	{
		if (nLinePos > nPerLine)
		{
			nLinePos = 0;
			printf("n");
		}

		lRand = rand_gen->Rand(lMax);//80
		printf("%ld",lRand);
		if (lRand == 0) lRand = 1;
		for (i = lRand; i < lMax; i = (i * 10)) printf(" ");

		nRuns--;
		nLinePos++;
	}

	delete rand_gen;  // Get rid of the object tidily
}

3)Prandgen.cpp

#include "Prandgen.h"



CPseudorandom::CPseudorandom()
{
    m_nLSeed = 0;
    m_nLGen1 = 0;
    m_nLGen2 = 0;
}

CPseudorandom::~CPseudorandom()
{

}

uint32_t CPseudorandom::Rand(uint32_t lMax)
{
    uint32_t lNewSeed = this->m_nLSeed;//5
    uint32_t lReturn = 0;

    lNewSeed = (this->m_nLGen1 * lNewSeed) + this->m_nLGen2;//  3719*3.5  = 3719*(lSeed+0.5)
  // Use modulo operator to ensure < ulMax

    this->m_nLSeed = lNewSeed;  //3719*(lSeed+0.5)
    lReturn = this->m_nLSeed % lMax;//3719*(lSeed+0.5) %80
    //printf("lReturn_1:%dn",lReturn);
    if (lReturn < 1) lReturn = lReturn * -1; // Keep it positive
    
    //printf("lReturn_2:%dn",lReturn);

    return lReturn;
}


void CPseudorandom::SRand(uint32_t lSeed)
{
	this->m_nLSeed = lSeed;
	// Pick two large integers such that
	// one is double the other
	this->m_nLGen2 = 3852;
	this->m_nLGen1 = (m_nLGen2 / 2);
}

4)Prandgen.h

#ifndef _PRANDGEN_H__
#define _PRANDGEN_H__

#include <stdint.h>
#include <stdio.h>
class CPseudorandom
{

public:
	CPseudorandom();
	~CPseudorandom();

	uint32_t Rand(uint32_t lMax);
    void SRand(uint32_t lSeed);
    
private:
	uint32_t m_nLSeed;
	uint32_t m_nLGen1;
    uint32_t m_nLGen2;
    
};




#endif

二、rand()和srand()的使用

rand()产生伪随机数,需要srand()来设置一个种子,通常用系统时间

0)补充背景说明

(1)rand()函数用来产生随机数,但是,rand()的内部实现是用线性同余法实现的,是伪随机数,由于周期较长,因此在一定范围内可以看成是随机的。rand()会返回一个范围在0到RAND_MAX(32767)之间的伪随机数(整数)。
(2)在调用rand()函数之前,可以使用srand()函数设置随机数种子,如果没有设置随机数种子,rand()函数在调用时,自动设计随机数种子为1。随机种子相同,每次产生的随机数也会相同。
(3)rand()函数需要的头文件是:<stdlib.h>
(4)使用rand()函数产生0-99以内的随机整数:int number1 = rand() % 100;
(5)因此,如要产生[m,n]范围内的随机数num,可用:

int num=rand()%(n-m+1)+m;

其中的rand()%(n-m+1)+m算是一个公式,记录一下方便以后查阅。

1)效果展示

在这里插入图片描述

2)代码使用举例


#include <stdlib.h>
#include <string.h>
#include <time.h>

using namespace std;
int main (int argc, char * argv[])
{
	srand(time(NULL));//使用系统时间产生随机数
	for (int i = 0; i < 10; i++)
	{
		printf("%dt", rand());
	}
	printf("n");

}

三、游戏中的随机算法简单分类(主指伪随机)

1)出现原因

真随机对于玩家来说有些不近人情,例如在某些抽卡游戏中,如果是真随机的话,有些玩家运气不好,可能抽上百次也抽不到SSR。为了照顾玩家的感受,引入了伪随机。

2)分类

(1)(Pseudo Random Distribution)PRD

  • 说明
    通常用于游戏中计算概率,为了平衡一些极端情况对游戏体验的影响(例如Dota中虚空先知有百分之25的概率免疫所有伤害,如果有玩家运气特别好,一个丝血的JB脸,就是死不掉,会很让对面崩溃),所以设计师引便了伪随机:例如第一次JB脸被攻击时,不是有25%的概率躲掉,第二次是10%,如果这次没躲掉,那么下次概率躲掉的概率变为20%,再变为30%。个人认为可能阴阳师抽卡也是伪随机

  • 背景
    PRD算法诞生与《魔兽争霸3》,可以说其诞生就是为了解决游戏中暴击概率所存在的问题。 现在其广泛应于与Dota2、LoL等MOBA游戏和其它竞技性较高的游戏暴击概率运算中。

  • 算法

在这里插入图片描述
为了便于理解,这里直接给出一个具体例子:
(1)设我们当前玩家暴击率还是0.3,那么对于 PRD算法,此时的 C = 0.3此时第一次攻击时的实际暴击率,即 P(1) = 0.3 * 1 = 0.3,
(2)若没有暴击,则 N + 1,N = 2此时第二次攻击时的实际暴击率,即
P(2) = 0.3 * 2 = 0.6,
(3)若没有暴击,则 N + 1,N = 3此时第三次攻击时的实际暴击率,即
P(3) = 0.3 * 3 = 0.9,
此时对于大部分玩家来说这第三次攻击就会产暴击了,但如果玩家是个非酋,这次仍没有暴击,没关系,N + 1,N = 4
(3)第四次攻击,
P(4) = 0.3 * 4 = 1.2 >= 1,
这一次是一定会暴击的。可以看到,使用 PRD 算法,对于攻击是否会暴击这⼀问题,仍然是存在着随机性即玩家的运气因素的,但即使是运气最差的玩家,仍然也会在第四次攻击时产生暴击,因此PRD算法可以在保存随机性的同时,减少玩家运气因素对游戏结果的影响。上面的例子展示了PRD算法会避免玩家一直不出现暴击的情况,同样PRD算法也会避免玩家一直出现暴击的情况。

(2)洗牌算法

  • 说明
    例如音乐播放器中常见的“随机播放”,如果是真随机的话,有可能出现一首歌你永远都听不到,或者不停的播放同一首歌(极端情况)。所以播放器的做法是像洗牌一样把这些歌打乱顺序,然后按顺序播放这个乱序数组

()补充计算机产生随机数的方法:赝随机数算法(Pseudo-Random Number Generator,简称PRNG)

  • 说明
    由于计算机中只有确定的0和1,所以要产生3真正的随机数是不可能的,通过随机数算法得到的只是看起来像是,即如果输入随机数算法的值是一样的,那么得到的随机数也是一样的。

最后

以上就是阔达芹菜为你收集整理的游戏思考12:游戏的随机数考究(重点在目录的第三章)的全部内容,希望文章能够帮你解决游戏思考12:游戏的随机数考究(重点在目录的第三章)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(39)

评论列表共有 0 条评论

立即
投稿
返回
顶部