群聚,就是多个非玩家角色一起行动,而不是个别行动。比如说:你看到的是一群大雁,它们的飞行是有一定的规律,而且你完全可以看出来它们会排成一字或者人字,不会是乱糟糟的飞行。那么,我们在游戏中,诸如巡逻小队之类的移动又是什么样的呢?
1987年,Craig Reynolds发表了一篇名为《Flocks,Herds and Schools: A Distributed Behavial Model》的论文,在这篇论文中,他提出了基本的群聚算法模型,甚至可以这样说,后续的一系列群聚算法都有这篇论文的身影。
那么我们来看看Craig Reynolds提到的boids——类鸟群的模拟群体。在这种类鸟群里面, 是没有领导核心,它们都是跟着群体在走,而这个群体似乎是自己有自己的想法。
初看起来,这种算法一定非常的复杂,但是,我们不得不说,真正的惊讶来自于其后的规则,总共描述这样的行为仅仅只有优雅而简单的三条规则。
凝聚:每个单位都往其临近单位的平均位置行动。
对齐:每个单位行动时,都要把自己对齐在其临近单位的平均方向上。
分隔:每个单位行动时,要避免撞上其临近单位。
为了分析我们这个群聚系统,我们需要理解一些基本的概念:
1, 单位的视野,我们以r为单位的视野半径,以θ为视野角度,落在这个圆弧内的所有物体都是可见的。
2, 避开规则(Avoidance rule),单位不会被彼此撞上。
3, 凝聚规则,单位离得太远就应该靠近一点,这个规则和避开规则合在一起,从而形成了群聚。
我们来看看一个实际的例子:
void DoUnitAI(int i)
{
int j;
int N;//临近单位数量
Vector Pave;//平均位置向量
Vector Vave;//平均速度向量
Vector Fs;//总净转向力
Vector Pfs;//Fs施加的位置
Vector d,u,v,w;
double m;
bool InView;
bool DoFlock = WideView||LimitedView||NarrowView;
int RadiusFactor;
//初始化
Fs.x = Fs.y = Fs.z = 0;
Pave.x = Pave.y = Pave.z = 0;
Vave.x = Vave.y = Vave.z = 0;
N = 0;
Pfs.x = 0;
Pfs.y = Units[i].fLength/2.0f;
…
…
}
这个函数里面还有关于临近单位相关信息处理:
for (j = 1; j < _MAX_NUM_UNITS; j++)
{
if (i != j)
{
InView = false;
d = Units[j].vPositon – Units[i].vPosition;
w = VRotate2D(-Units[i].fOrientation, d);
if (WideView)
{
InView = ((w.y > 0)
|| ((w.y < 0)
&& (fabs(w.x) > fabs(w.y) * _BACK_VIEW_ANGLE_FACOTOR)));
RadiusFactor = _WIDEVIEW_RADIUS_FACTOR;
}
if (LimitedView)
{
InView = (w.y > 0);
RadiusFactor = _LIMITEDVIEW_RADIUS_FACTOR;
}
if (NarrowView)
{
InView = (((w.y > 0) && (fabs(w.x) <
fabs(w.y) * _FRONT_VIEW_ANGLE_FACTOR)));
RadiusFactor = _NARROWVIEW_RADIUS_FACTOR;
}
if (InView)
{
if (d.Magnitude() <= (Units[i].fLength * RadiusFactor))
{
Pave += Units[j].vPosition;
Vave += Units[j].vVelocity;
N++;
}
}
…
…
}
}