关于随机数生成算法,这是所有其它随机生成算法必须用到的基础。
        在游戏中自定义一个随机生成函数是很有必要的,比如要做一个联网的扑克游戏,开始发牌的时候并不用把所有的牌都一张张发给玩家,只需要发送一个用于生成随机数的种子,玩家就可以知道完整的发牌顺序。这里有个前提,在种子相同时每个玩家生成的随机数序列必须是一样的,如果使用系统api自带的随机函数很难保证这一点,自定义随机函数就没问题了。

        再比如游戏中的粒子系统,粒子运动看似杂乱无章,但是只要给的随机种子一致并且更新步长也一致,两个粒子系统中的粒子是可以做到完全同步的,这就为联网游戏中同步粒子动画提供了一个思路。

        再如随机生成场景类游戏,联网的各玩家只需要一个随机种子就可以同步整个场景。

关于具体随机数生成算法,网上介绍很多,这里摘抄两版:

一种,快速但不是很精确:   

static unsigned int rand_seed = 0;
    void   SrandFast(unsigned int seed)
    {
        rand_seed = seed;
    }
    unsigned int RandFast()
    {
        //return rand();
        //1 线性同余方法(LCG) Xn+1 ≡ (a* Xn + c )% m 弊端是相邻点之间的相关性  将相邻3个数作为坐标绘制点,会聚集到n个明显的平面
        rand_seed = (rand_seed * 16807) % ((1 << 31) - 1);
        return rand_seed%RandMax;
    }

  另一种,精确但不是很快速:        

#define LOWER_MASK            0x7fffffff
    #define M                     397
    #define MATRIX_A              0x9908b0df
    #define N                     624
    #define TEMPERING_MASK_B      0x9d2c5680
    #define TEMPERING_MASK_C      0xefc60000
    #define TEMPERING_SHIFT_L(y)  (y >> 18)
    #define TEMPERING_SHIFT_S(y)  (y << 7)
    #define TEMPERING_SHIFT_T(y)  (y << 15)
    #define TEMPERING_SHIFT_U(y)  (y >> 11)
    #define UPPER_MASK            0x80000000

    static int              k = 1;
    static unsigned long    mag01[2] = {0x0, MATRIX_A};
    static unsigned long    ptgfsr[N]={0};
    static bool init = true;
    void   Srand(unsigned int seed)
    {
        if (seed == 0)
        {
            seed = 1;
        }
        ptgfsr[0] = seed;
        for(k = 1; k < N; k++)
        {
            ptgfsr[k] = 69069 * ptgfsr[k - 1];
        }
        k = 1;
        init = false;
    }

    unsigned int Rand()
    {
        if (init)
        {
            Srand(1);
            init = false;
        }
        //马特赛特旋转法
        int                    kk;
        unsigned long        y;
        if(k == N)
        {
            for(kk = 0; kk < N - M; kk++)
            {
                y = (ptgfsr[kk] & UPPER_MASK) | (ptgfsr[kk + 1] & LOWER_MASK);
                ptgfsr[kk] = ptgfsr[kk + M] ^ (y >> 1) ^ mag01[y & 0x1];
            }
            for(; kk < N - 1; kk++)
            {
                y = (ptgfsr[kk] & UPPER_MASK) | (ptgfsr[kk + 1] & LOWER_MASK);
                ptgfsr[kk] = ptgfsr[kk + (M - N)] ^ (y >> 1) ^ mag01[y & 0x1];
            }
            y = (ptgfsr[N - 1] & UPPER_MASK) | (ptgfsr[0] & LOWER_MASK);
            ptgfsr[N - 1] = ptgfsr[M - 1] ^ (y >> 1) ^ mag01[y & 0x1];
            k = 0;
        }
        y = ptgfsr[k++];
        y ^= TEMPERING_SHIFT_U(y);
        y ^= TEMPERING_SHIFT_S(y) & TEMPERING_MASK_B;
        y ^= TEMPERING_SHIFT_T(y) & TEMPERING_MASK_C;
        y ^= TEMPERING_SHIFT_L(y);
        return y%RAND_MAX;
    }

其它衍生随机数生成:其中列举了一些易错点

    随机生成方向,对于2D的很简单,随机一个角度然后sin cos即可。
    对于3D的就不简单了, 首先想到的方法一,在立方体内随机取点然后归一化,结果是不准确的,仔细观察立方体八个角上概率会偏大,当然如果不介意些许误差也是可以用的。  
 

void   RandDir(vec3& dir)
     {
         //不准确   加上判断球内就准确了
         dir = vec3(RandRange(-1.0f,1.0f),RandRange(-1.0f,1.0f),RandRange(-1.0f,1.0f));
         dir.Normalize();
     }
     又想到了方法二:先将向量a[0,0,1]绕y轴随机旋转0~360度,然后再绕x轴旋转0~360度。这个方法貌似正确,其实错的更离谱,仔细观察会发现靠近x轴正负1位置概率偏大。
     //float theta1 = RandRange(0.0f,TWOPI);
     //float theta2 = RandRange(0.0f,TWOPI);
     //float len = RandRange(-1.0,1.0);
     //float radLevel = cosf(theta1)*len;      //水平半径
     //float ex = radLevel * cosf(theta2);
     //float ey = sinf(theta1)*len;
     //float ez = radLevel * sinf(theta2);
     主要出错在第二步,因为正确做法需要的是绕新frame下的x轴旋转,算法变得复杂。关于下面公式是由自己写的小工具自动推导出来的,工具源码有空再贴出来。
     void   RandDir(vec3& dir)
     {
         float A = RandRange(0.0f,TWOPI);
         float B = RandRange(0.0f,TWOPI);
         float cosA = cos(A);
         float cosB = cos(B);
         float sinA = sin(A);
         float sinB = sin(B);
         dir.x =(((1-cosB)*cosA*cosA+cosB)*sinA-(1-cosB)*sinA*cosA*cosA); 
         dir.y = -(sinA*sinB*sinA+cosA*sinB*cosA)                       ; 
         dir.z =(((1-cosB)*sinA*sinA+cosB)*cosA-(1-cosB)*sinA*cosA*sinA); 
     }
     
     随机生成球内一个点:第一步先用上面的方法随机取一个方向,第二步在半径内随机取一个长度开三次方后付给方向,开三次方是必须的,否则靠近球心处概率偏大,当然如果正好需要这种边缘弱化的分布也可以不开方。(对于2D一般是开二次方)
     vec3   RandomPosInSphere(float rad)
     {
         vec3 pos = RandDir();
         pos *= pow(randRange(0.0f,rad),1.0f/3); 
         return pos;
     }
     
     
     随机生成在三角面上一个点的位置:
     void RandomPosOnTrigon(const vec3& pos0,const vec3& pos1,const vec3& pos2,vec3& res)
     {
         //错误的算法一:直接插值不是均匀分布,靠近重心密度更大
         //vec3 rat;
         //rat[0] = Rand()%1000;
         //rat[1] = Rand()%1000;
         //rat[2] = Rand()%1000;
         //float sum = (rat[0]+rat[1]+rat[2]);
         //if (sum==0)
         //{
         //    res = pos0;
         //  return;
         //}
         //sum = 1.0f/sum;
         //rat *= sum;
         //pos0 = pos0*rat[0] + pos1*rat[1] + pos2*rat[2];        //错误的算法二:先插值一条边,再用得到的点和对顶点插值,不是均匀分布,靠近对顶点密度更大
        //正确的算法一:先插值一条边,再用得到的点和对顶点插值,插值时比率要开方, 因为微分法面积随距离平方而变大
         //此方法可以方便的扩展到3维, 取三棱锥内的随机点:先取底面随机点,再和最上面的顶点根据距离开三次方插值
         float rat1 = (Rand()%1000)/999.0f;//0~1
         //插值pos0, pos1
         res = pos0*rat1 + pos1*(1-rat1);
         float rat2 = sqrt((Rand()%1000)/999.0f);//sqrt (0~1)
         //插值posMid, pos2
         res = res*rat2 + pos2*(1-rat2);        正确的算法二:取平行四边形内的随机点,如果不在三角形内则旋转对称
         //float rat1 = (Rand()%1000)/999.0f;//0~1
         //float rat2 = (Rand()%1000)/999.0f;
         //if (rat1+rat2>1)
         //{
         //    //不在三角形内 旋转对称
         //    rat1 = 1-rat1;
         //    rat2 = 1-rat2;
         //}
         //res = pos0 + (pos1-pos0)*rat1 + (pos2-pos0)*rat2;
     }    
     有时可能需要的不是平均分布随机函数而是高斯分布:
     //正太分布随机函数:平均值0, 标准偏差1。 68.26%的概率在[-1,1]内
     float  RandGauss( )
     {
         static float U, V;
         static int phase = 0;
         float Z;        if(phase == 0)
         {
             U = Rand() / (RandMax + 1.0);
             V = Rand() / (RandMax + 1.0);
             Z = sqrt(-2.0 * log(U))* sin(2.0 * _PI * V);
         }
         else
         {
             Z = sqrt(-2.0 * log(U)) * cos(2.0 * _PI * V);
         }
         phase = 1 - phase;
         return Z;
     }    //正太分布随机函数:平均值m, 标准偏差sd 
     float  RandGauss(float m,float sd)
     {
         float X = m + sd* RandGauss();
         return X;
     }完