阅读笔记-游戏开发中的人工智能-第4章-群聚

1. 基本群聚

    这一节介绍的算法,来自于Craig Reynolds在1987年发表的论文《Flocks,Herds and Schools:A Distributed Behavioral Model》。在论文中,他提出算法原型仿真整群的鸟、鱼或其他生物。
    这个算法的做出的行为非常类似水中的鱼群或成群的飞鸟。所有的“类鸟群”(指模拟的群体)可以同时朝同一方向移动,接着下一时刻,群聚形成的形体之尖端会转弯,而群体中其余的部分也会跟着转,形成的类鸟群在群体中传达转弯的行为,而形成波浪状。
    算法的三个规则:
    凝聚:每个单位都往其邻*单位的*均位置行动。
    对齐:每个单位行动时,都要把自己对齐在其邻*单位的*均方向上。
    分割:每个单位行动时,要避免撞上其邻*单位。
    单位视野
    两个参数决定每个单位的视野,半径R和角度θ。大的半径能让单位看到群体中的更多同伴,从而产生凝聚性更强的群体。也就是说,群体没有分裂成小群体的倾向。
    对于较小的视野,可能会产生类似一队蚂蚁行进的效果。比如:仿真一群战斗机,可能会使用大角度。仿真一直军队鬼鬼祟祟跟踪某人,可能会用小角度。 
               
                  图-单位的视野                                                                                             图-宽视野与窄视野

           
2. 群聚实例

2.1 行进模式
    这个实例考虑的是以物理机制为基础的范例,把每个单位视为刚体,通过在每个单位的前端施加转向力,来保证群聚的行进模式。每条规则都会影响施加的力,最终施加的力和方向是这些规则影响的综合。另外,需要考虑两件事:首先,要控制好每条规则贡献的转向力;其次,要调整行进模式,以确保每个单位都获得*衡。
    对于避开规则:为了让单位不会彼此撞上,且单位根据对齐和凝聚规则而靠在一起。当单位彼此间距离够宽时,避开规则的转向力贡献就要小一点;反之,避开规则的转向力贡献就要大一些。对于避开用的反向力,一般使用反函数就够用了,分隔距离越大,得出的避开用转向力越小;分隔距离越小,得出的避开用转向力越大。
    对于对齐规则:考虑当前单位当前方向,与其邻*单位间*均方向间的角度。如果该角度较小,我们只对其方向做小幅度调整,然而,如果角度较大,就需要较大的调整。为了完成这样的任务,可以把对齐用的转向力贡献,设定成和该单位方向及其邻*单位*均方向间的角度成正比。
2.2 邻*单位
    凝聚,对齐,分隔三个规则要起作用的前提是得到每个当前单位的邻*单位。邻*单位就是当前单位视野范围内的单位,需要从角度和距离两方面进行判断。
    首先根据角度检测,对于不同的视野检测方法是不同的(比如,宽视野,窄视野),下面的例子是对于宽广视野的检测
    ·计算d=目标单位位置-当前单位位置,w为d以目标单位反方向的坐标系中的向量坐标。
    ·如果w.y>0,则目标单位必然在视野范围内。
    ·如果w.y<0,则要看x,y坐标构成的线段斜率,是否在设定的视野区域之外,如果fabs(w.x)>fab(w.y)*_BACK_VIEW_ANGLE_FACTOR,则目标单位在视野范围内。其中的_BACK_VIEW_ANGLE_FACTOR就是视野角度系数。该系数等于1时,视野弧线的直线与x轴夹角是45度,该系数越大,两条线越接*x轴,不可见区域越大,反之,系数越小,两条线越接*y轴,不可见区域越小。
      
                                                                                  图-宽广视野
    此外,书中还给出了有限视野(实际上就是个半圆)和狭窄视野(实际上是一个扇形)的角度检测方法。在经过角度检测后,还要检测距离。如果向量d的数值小于当前单位的长度与视野半径的乘积,即可以认为目标单位与当前单位足够接*。这样经过角度检测和距离检测的单位,就是当前单位的邻*单位。
2.3 凝聚 

    凝聚考虑的是当前单位的速度向量v和相对位置向量u,其中u=邻*单位的*均位置-当前单位的位置。

if(DoFlock && (N>0)) { // DoFlock=true:启用凝聚规则,N>0:邻*单位数量大于零
   Pave = Pave / N; // 邻*单位的*均位置向量
   v = Units[i].vVelocity; // 当前单位的速度向量
   v.Normalize(); 
   u 
= Pave-Units[i].vPosition; // 邻*单位*均位置向量与当前单位向量的差值,相对位置向量
   u.Normalize();
   w.VRotate2D(
-Units[i].fOrientation, u);
   
if(w.x < 0) m = -1// 相对位置向量(即u)在当前单位的右边,需要右转当前单位
   if(w.x > 0) m = 1;  // 相对位置向量(即u)在当前单位的左边,需要左转当前单位
   if(fabs(v*u) < 1// 确保反余弦函数可以正常运行
     Fs.x += m * _STEERINGFORCE * acos(v * u) / pi; 
     
// Fs.x使用的坐标系是应该也是前面刚刚转化过的坐标系
     
// acos(v*u):计算相对位置向量与当前单位的速度向量之间的夹角,除以pi是为了把弧度数值转化为标量
}

2.4 对齐

    对齐考虑的是当前单位的速度向量v和邻*单位的*均速度向量u,其他部分与凝聚部分的代码都一样,很好理解。

if(DoFlock && (N>0)) {
    Vave 
= Vave / N;
    u 
= Vave; // 邻*单位的*均速度向量
    u.Normalize();
    v 
= Units[i].vVelocity;
    v.Normalize();
    w.VRotate2D(
-Units[i].fOrientation, u);
    
if(w.x < 0) m = -1;
    
if(w.x > 0) m = 1;
    
if(fabs(v*u) < 1)
         Fs.x 
+= m * _STEERINGFORCE * acos(v * u) / pi;
}

        
                                                      图-邻*单位的*均位置和方向
2.5 分隔
    前面的凝聚和对齐规则,都会尝试让单位相互靠*一点。使用的分别是(速度向量,位置向量)和(速度向量,速度向量)。
        
                                                                   图-分隔
    外层弧线是可见视野,内层弧线是最小分隔距离。
    前面凝聚和对齐只需要计算*均位置,*均速度就可以了,但是对于分隔,需要当前单位与邻*的每个单位分别比较,对于进入最小间隔的单位,算出一个力,加到Fs.x上去。
    部分代码如下:

if(InView) { // 如果在视野内
    if(d.Magnitude() <= Units[i].fLength * _SEPARATION_FACTOR) {
        
if(w.x < 0) m = 1// 这里是分隔,方向与凝聚和对齐规则正好相反
        if(w.x > 0) m = -1;
        Fs.x 
+= m * _STEERINGFOCE * (Units[i].fLength*_SEPARATION_FACTOR) / d.Magnitude(); // 分隔越小,力越大  
    }
}

3. 避开障碍物

    要做的就是提供某种机制给单位使用,让他们能看到前方的障碍物,再施加适当的转向力,是其避开路径中的障碍物。
    为了检测障碍物是否在某单位的路劲内,我们要借助机器人学,替我们的单位安装虚拟触角(feeler)。基本上,这些触角会处在单位的前方,如果触角碰到某种东西,就是那些单位要转向的时候了。模型的形式很多,比如可以装上三个触角,分别位于三个不同方向,不但能检测出是否有障碍物,而且检测该障碍物位于单位的那一侧。宽广的单位需要一个以上的触角,才能确保单位不会和障碍物碰撞。在3D游戏中,可以使用虚拟体积,以测定是否即将和某障碍物碰撞。总之,手段很多。
    这一节,给出了一个避开圆形障碍物的实例。具体代码忽略。总之,对当前单位做出的改变都会表现在Fs.x上面。
4. 跟随领头者 

    基本群聚算法的三条规则,似乎让群体在游戏世界中随处闲逛,如果在其中加入领头者,就能让群体的移动更有目的性,或者看起来比较有智能。比如:战争模拟游戏中,计算机控制一群飞机追击玩家。可以让其中的一架作为领头者,其他飞机采用基本群聚规则跟着领头者跑。在和玩家发生混战时,可以适时关闭群聚规则,让飞机分散进攻。另一个实例,仿真一直军队,指定其中某个单位为领头者,可以让他们成横队或者纵队,采用宽广视野或有限视野模式,使其他单位采取群聚行为。
   这一节给出的实例是通过某种规则确定领头者,这样即使领头者被玩家干掉了,或者由于某种原因脱离其群体,整个群体不会因此失去领导。
   一旦领头者确定了之后,就可以通过若干规则和技巧,让领头者做一些事情,比如,追逐某物,或者逃命。
   这一节给出的实例有点意思:把群体分成两类,一类单位的速度快一些,负责追击玩家,另一类速度慢一些,负责拦截玩家。两类分别找到各自的领头者,其他单位在自己的类别中,使用群聚算法。这样能够表现出,一组群体尾随在玩家之后,另一组群体从侧翼包抄的效果。
   具体领头者寻找的方法是通过计算每个单位窄视野范围内邻*单位的个数,如果要成为领头者,必然个数为0,如果有多个单位的窄视野内都没有单位,这一点书中没有交代,个人认为随机选取一个应该都可以吧。

posted @ 2011-05-19 09:36  xiaodongrush  阅读(1853)  评论(0编辑  收藏  举报