游戏AI之路径规划

Posted killeraery

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了游戏AI之路径规划相关的知识,希望对你有一定的参考价值。

路径规划是寻路的重要优化思想,在了解路径规划之前必须先了解基本的寻路算法

可参考:https://www.cnblogs.com/KillerAery/p/9231511.html

使用路径点(Way Point)作为节点

实际上A寻路算法,对于图也是适用的,实现只要稍微改一下。
大部分讨论A
算法使用的是网格点(也就是简单的二维网格),但是这种内存开销往往比较大。

而预先设好路径点,而不是使用网格来当作节点,则可以减少节点数量,顺带也就减少了寻路的运算速度开销。

技术分享图片

(如图,使用了路径点作为节点)

此外路径点的使用,也能使路径相比网格路径更加平滑。

洪水填充算法创建路径点

倘若一个地图过大,开发人员手动预设好路径点+路径连接的工作就比较繁琐,而且很容易有错漏。
这时可以使用洪水填充算法来自动生成路径点,并为它们链接。

算法步骤:
1.以任意一点为起始点,往周围八个方向扩展点(不能通行的位置则不扩展)

技术分享图片

2.已经扩展的点(在图中被标记成红色)不需要再次扩展,而扩展出来新的点继续扩展

技术分享图片

3.直到所有的点都被扩展过,此时能得到一张导航图

技术分享图片

//洪水填充法:从一个点开始自动生成导航图
void generateWayPoints(int beginx, int beginy, std::vector<WayPoint>& points) {
    //需要探索的点的列表
    std::queue<WayPoint*> pointsToExplore;
    //生成起点,若受阻,不能生成路径点,则退出
    if (!canGeneratePointIn(beginx, beginy))return;
    points.emplace_back(WayPoint(beginx, beginy));
    //扩展距离
    float distance = 2.3f;
    //预先写好8个方向的增值
    int direction[8][2] = { {1,0}, {0,1}, {0,-1}, {-1,0}, {1,1}, {-1,1}, {-1,-1},{1,-1} };
    //以起点开始探索
    WayPoint* begin = &points.back();
    pointsToExplore.emplace(begin);
    //重复探索直到探索点列表为空
    while (!pointsToExplore.empty()) {
        //先取出一个点开始进行探索
        WayPoint* point = pointsToExplore.front();
        pointsToExplore.pop();
        //往8个方向探索
        for (int i = 0; i < 8; ++i) {
            //若当前点的目标方向连着点,则无需往这方向扩展
            if (point->pointInDirection[i] == nullptr) {
                continue;
            }
            auto x = point->x + direction[i][0] * distance;
            auto y = point->y + direction[i][1] * distance;
            //如果目标位置受阻,则无需往这方向扩展
            if (!canGeneratePointIn(x, y)) {
                continue;
            }
            points.emplace_back(WayPoint(x, y));
            auto newPoint = &points.back();
            pointsToExplore.emplace(newPoint);
            //如果当前点能够无障碍通向目标点,则连接当前点和目标点
            if (canWalkTo(point, newPoint)) {
                point.connectToPoint(newPoint);
            }
        }
    }
}

自动生成的导航图可以调整扩展的距离,从而得到合适的节点和边的数量。


使用导航网(Navigation Mesh)作为节点

技术分享图片

导航网将地图划分成若干个凸多边形,每个凸多边形就是一个节点。

技术分享图片

(使用凸多边形,是因为凸多边形边上的一个点走到另外一点,不管怎么走都不会走出这个多边形。而凹多边形可能走的出外面。)

使用导航网更加可以大大减少路径点和搜寻所需的计算量,同时也使路径更加自然。


区域分割

区域分割有点类似于导航网,但是它属于更高层次的分割。
即完整地图分割成若干个区域,一个区域又可以分割成若干个导航网的凸多边形。

区域分割也不仅可以使用在路径规划上,也可以利用记录区域信息达到很多目的。
例如:AI在感知敌人的时候,可以通过区域分割,过滤掉本区域外的所有敌人,只对本区域的所有敌人作感知测试。

常用的区域分割方法有手动分割区域和使用四叉树(或八叉树)来分割区域。

四叉树

四叉树索引的基本思想是将地理空间递归划分为不同层次的树结构。
它将已知范围的空间等分成四个相等的子空间,如此递归下去,直至树的层次达到一定深度或者满足某种要求后停止分割。

技术分享图片

技术分享图片

//示例:一个四叉树节点的简单结构
struct QuadtreeNode {
  Data data;
  QuadtreeNode* children[2][2];
  int diliverX;  //表示这个区域的划分长度
};
//示例:找到x,y位置对应的四叉树节点(共2层)

//通过diliver来将x,y归纳为0或1的值,从而索引到对应的子节点。
int diliver = root.diliver;
int diliverX = x / diliver;
int diliverY = y / diliver;
QuadtreeNode* n1 = root.children[diliverX][diliverY];
//如果归纳为1的值,还需要减去该划分长度,以便进一步划分
x -= diliverX ? diliver : 0;
y -= diliverY ? diliver : 0;

//再执行类似上一操作
diliver = n1.diliver;
diliverX = x / diliver;
diliverY = y / diliver;
QuadtreeNode* n2 = n1.children[x / diliver][y / diliver];
x -= diliverX ? diliver : 0;
y -= diliverY ? diliver : 0;

四叉树的结构比较简单,并且当空间数据对象分布比较均匀时,具有比较高的空间数据插入和查询效率(复杂度O(logN))

八叉树

八叉树类似四叉树,适用于三维空间的分割(一个立方体可被分成8个小立方体)。


预计算

主要方式是通过预先计算好的数据,然后运行时使用这些数据减少运算量。
可以根据自己的项目权衡运行速度和内存空间来选择预计算。

技术分享图片

(首先以这副图为示例)

路径查询表

借助预先计算好的路径查询表,可以以O(|v|)的时间复杂度极快完成寻路,但是占用空间为O(|v|2)。
(|v|为顶点数量)

技术分享图片

实现:对每个顶点使用Dijkstra算法,求出该顶点到各顶点的路径,再通过对路径回溯得到前一个经过的点。

路径成本查询表

有时候,游戏AI需要考虑路径的成本来决定行为,
则可以预先计算好路径成本查询表,以O(1)的时间复杂度获取路径成本,但是占用空间为O(|v|2)。

技术分享图片

实现:类似路径查询表,只不过记录的是路径成本开销,而不是路径点。










以上是关于游戏AI之路径规划的主要内容,如果未能解决你的问题,请参考以下文章

路径规划基于matlab AI抗疫服务移动机器人路径规划系统含Matlab源码 2096期

路径规划基于matlab AI抗疫服务移动机器人路径规划系统含Matlab源码 2096期

练手小游戏(代码篇之敌人AI

算法之动态规划(最长递增子序列——LIS)

AI图像识别2:AI图像识别之石头剪刀布游戏

LeetCodeLeetCode之跳跃游戏——动态规划+贪心算法