集群算法介绍

demo视频1
demo视频2
集群,也叫做群聚,大概的意思就是模拟一大群行为相近的单位的行动。比较常见的有一群鱼在列队游行等。

我也做了个demo,来大概模拟了这个集群的表现。这个群体里面有50个跟随者,还有一个领头者。跟随者一直跟随在领头者附近,相互之间会避开,遇到障碍会避开。如果不小心走远了,会回头继续跟随。
集群算法介绍_第1张图片

做集群的方法,一般有2种:

1、平均行动向量的算法

2、势函数算法

先大概说说原理,后面再说说我选择了哪种方法来做,还有具体的做法。

第一种方法,规定每个单位有一定的视野范围,每个单位的移动方向,都是它自己视野范围内所能看到的所有角色的平均移动向量。

第二种方法,是模拟物理学中分子之间力的关系(势能),当两个分子离得远了,它们之间会产生引力,距离越远引力越大。当两个分子离得太近,它们之间将会产生斥力,距离越近,斥力越大。通过势函数对这种力的模拟之后,每个单位之间会保持着一定的距离,并互相吸引地移动。

先来说第二个方法,使用势函数来实现集群,实现起来比较简单,只需要写个两重的循环,算出每个单位之间的距离,然后通过势函数公式算出引力或者斥力的大小就行了。但这样做有一些缺点:

首先,由于群体之间的凝聚是靠分子引力来做到的,所以整个群体都需要逐个单位之间算距离,然后算引力大小。这样如果在群体的单位数量很多的时候,效率会非常低。

然后,引力和斥力的相互转换是往返式的,一般的表现将会是两个单位物体先由于引力快速的聚拢在一起,然后到了太近的时候,又产生斥力,两个单位物体又往反方向运动。这样的表现比较像一群愤怒的蜜蜂在互相追逐,而不像一群鱼在悠闲的游动。

所以我选择了第一种方法。平均移动向量,当单位的视野距离和视野角度合适时,每个单位只会看到前面一个扇形范围的其他单位,然后算出看到的单位移动方向的平均值。比如一个单位看到前面有5个单位, 它们的平均移动向量方向都是往前的,那么这个单位也往前开始移动。如果前面的单位开始转向,它也会跟着转向。这种行为看起来会比较自然,比较接近现实中的一个队列中排队移动。

接下来说一下实现。角色的类就不重复说了,可以参考一下上一篇《游戏角色AI的实现方法讨论》。

我们继续写一个GroupRole的类来继承ActionRole类,然后写一个GroupAction类继承BaseAction。那么我们已经有了让角色移动的基本方法,也有了通过Action来不停改变角色移动方向的基本方法。我们就可以专心的实现GroupAction。

为了能有群组的基本信息,我们需要创建一个GroupData的类,来存储当前群组里面的所有成员,还有领头者。再存储多一点信息,比如场景内的阻挡点的位置,还有群组成员的视野距离和视野角度。

接下来我们需要实现的主要算法有:

1.群组成员的扇形范围视野里面看到的对象计算。这个其实比较简单,我们可以先算距离,把某个成员一定距离范围内其他群组成员列表找出来,然后通过其他成员的坐标减去当前成员的坐标得出向量,再通过这个向量和当前成员的面朝向的向量计算出夹角,再和当前成员的视野角度做比较,排除角度外的对象,得出的就是留在扇形范围内的其他群组成员。我们把这个计算的方法命名为GetRolesInView。

2.GetRolesInView返回的角色列表,我暂时称之为附近的成员,把它们的当前移动向量全部加起来。我们把这个向量的变量叫做curDir。

3.为了能让成员之间不重叠,我们需要进一步的对附近的成员和当前成员的距离做一个计算。如果它们之间的距离小于一定值,那么就需要改变curDir的方法,好让当前的成员远离靠得太近的附近成员。实际的算法就是当距离小于一定值后,当前成员位置减去附近成员的位置,取标准向量,然后乘以一个系数,最后和curDir相加。

4.为了能让成员避开障碍点,我们需要对比当前成员和附近阻挡点的坐标,如果离得太近了,那么也需要生成一个反方向的向量,和curDir相加,改变角色移动的方向以避开障碍。

5.最后我们得出了当前角色应该往哪个位置移动才是合理的,curDir还需要取一次标准向量。这时候,当前成员就可以朝着curDir的方向旋转并移动了。

把以上的算法用代码实现之后,我们就可以运行来看结果了。实际运行后,发现2个问题:

1.当群组成员数量多起来之后,代码运行时的效率很低。

2.当领头者的速度较快,超出了群体的视野距离或者视野角度之后,群组成员就脱离了领头者自己朝着一个方向越走越远。

接下来解决这两个问题。

首先解决效率的问题。

造成效率低的原因,是GetRolesInView的计算比较复杂,而且需要遍历所有的成员,才能完全计算出每个成员的扇形范围内的附近成员。所以必须降低运行的次数。

从现实中去考虑,实际上每个成员的视野距离和范围都是有限的,假如我们可以只是查找某单位成员附近一定范围内的空间有没有其他成员和障碍,那样实际上就会减少很多计算。而且就算以后再增加成员和障碍,也只是每个成员增加检查有限范围内的空间而已。

为了达到检查某个空间内是否有角色和障碍,我把地图划分为了格子坐标,最简单的办法就是把角色的实际坐标向下取整,得出整数坐标x和y,复杂一点可以根据自定义格子的大小,用角色的实际坐标来整除。然后建议一个二维的字典,通过x和y存储某个格子坐标点上有没有角色。当角色移动之后,会给角色标记一个moveDirty的脏标记。然后在每一帧开始之前,先把已经设置了脏标记的角色改变一下存储的格子。

至于障碍,我们用另外一张二维表来存储,只存储有障碍的坐标点。然后障碍物通过中心点位置、长宽、旋转角度,计算出它包含覆盖的坐标点,然后把这些坐标点设置阻挡。

这样做了之后,我们就从判断某单个成员角色,变成了判断某个格子。如果有多个角色在同一个格子里面,也就只需要计算一遍。然后想获得某个格子附近有多少个格子有对象,也是非常简单的,因为只需要一个格子相邻的一圈或多圈格子就行了。最后,要计算角度也变得简单了,我们可以通过格子的二维坐标,计算二维的角度。

接下来需要解决群成员走散的问题。

从刚才运行的现象分析,走散的原因实际上是领头者超出了最前面一排群成员的视野范围,还有当某些群成员走远了之后,它自己不知道回头。

要解决这个问题就比较简单了,当GetRolesInView得到结果为空,或者当前单位离领头者的距离超出一定范围之后,就可以把curDir变为指向领头者的坐标。这样当某些单位前面已经没有其他成员,或者走远了,就会自动的往领头者靠拢。

当修改后,可以发现运行的效率已经显著的提高了,而且群组成员的行为看起来也更合理。 可以尝试着改变群组成员的视野距离和视野角度,会发现在不同参数的情况下,群聚的效果会有不一样的表现。我们可以选择一个合适的参数,比如有限的视野距离,让运算时查找范围更小,提高运行的效率,而保持表现在可接受的范围内。

最后,说一下为什么需要领头者。从理论上来说,其实群组可以没有领头者的。一般的做法是每个群组成员都设定同一个移动目标,当成员离移动目标比较远时,我们使用集群算法让大家都往一个大致趋势一样的方向移动。当成员到达了移动目标附近一定范围内后,可以切换另外一种行动模式,变成围攻之类。

假如地图比较复杂,需要用到寻路算法时,如果每个成员都单独的去计算这个移动路径趋势来走,单纯只是把周围的角色靠拢在一起或者避免重叠,那么其实计算的消耗是非常大的。所以如果我们有一个领头者,担任队长的角色,那么就可以针对队长来做寻路和一些行为判断的算法。当队长离目标比较远,队长命令大家用集群算法排好队往目标移动,移动的路线又队长控制。当距离目标很近之后,队长发布命令,让大家切换行动模式,变成围攻目标。然后其他群组的成员,就可以忽略寻路和ai考虑的算法,完全听从队长的指挥,这样可以减少很多计算量。

你可能感兴趣的:(寻路和智能,集群,群聚)