一种解决A*Pathfindings使用RichAI寻路 跌落出导航网格的方法

发布时间 2023-11-26 13:22:29作者: 赭与秋

A*Pathfinding是Unity中一个比较常用的寻路插件,其主要功能是绘制导航图并让物体沿着导航图向目标移动,可结合多种方法进行寻路方式的扩展。

 

该插件付费的Pro版拥有一个通过投影方式获得场景中所有网格(mesh),在网格(mesh)标面自动生成导航网格的功能,称为RecastGraph,同时配合用于AI导航的组件RichAI,可实现比较高效率和操作简化的单位导航。

 

不过在使用过程中,当导航网格在复杂表面生成的时候,如图1所示,偶尔会产生单位穿过或者贴近需要爬升的区域时,导航发生误差导致单位跌落到导航网格或者世界之外。

这里提供一个临时解决这种问题的方法。

 

图1

 

先将目前Pro4.2.19版本官方文档地址放在该处

RecastGraph

https://arongranberg.com/astar/docs/recastgraph.html

RichAI

https://arongranberg.com/astar/docs/richai.html

 

首先,我们来了解它的大概工作原理。

RecastGraph的部分:使用RecastGraph对场景内的网格(mesh)或者时碰撞器(Collider)进行扫描时,将生成一个被简化后近似生成的体素集合,如图2,然后在该基础上进行导航网格的生成与绘制。

所以为什么当导航网格生成后,我们会看到如图1一样,导航网格嵌入地形的情况,因为经过简化后导航网格并不是贴合地形的,被简化后的边缘棱角将被直接连接在一起,而不是跟随所有的边缘,如图3。通过不断的缩小RecastGraph的扫描单位尺寸(CellSize)可以将生成的体素集合精细化,令其更贴切于地形,但是该项操作将大幅度影响导航网格的烘焙速度,以及增加寻路时的计算量。大部分时候需要保持一个合适的数值。

RecastGraph进行导航网格的生成是导航的前置部分。

图2

 

图3

RichAI部分:RichAI是专用于RecastGraph生成的导航网格的导航组件,官方说明该组件包含的方法已进行了针对性的规划和优化,所以插件提供的其他路线修饰算法对其并没有很大的作用。

 

我对这个插件并没有很深入的了解,所以这里仅对RichAI影响到当前讨论主题的部分进行猜测和说明。

使用RichAI时,可以对寻路单位的半径以及高度进行设定,但是与Unity自带的NavMeshAgent不同,RichAI并没有提供类似offset的选项用于调整寻路框体的中心点,RichAI的框体中心点总是与单位的Position属性相同,改变Radius变量将改变框体半径,改变Height时,框体仅在Y轴向正向进行高度的增减。导航时,导航框体下部将贴住依赖的网格,这意味着,如果想要让单位与地面的接近部位看起来符合直觉,我们需要采用将可视部分放入子物体并调整相对位置的等方法来进行调整,如图4。

 

图4(黄颜色线框为RichAI的框体轮廓线)

 

RichAI运用于寻路时,并不会让单位总是沿着导航网格移动,导航网格一般仅影响单位在X轴与Y轴的方位;

该组件使用一个Gravity的参数用于控制单位在Y向的重力感应(也可以自定义其他方向),同时该方法也控制单位在Y向受重力影响的位移。

具体方法为该组件将从导航框体向下放出射线及逆行垂直检测,以检测下方的物体网格,当单位受重力影响下坠时,如果射线检测到下方网格,则单位最多只会下坠到下方的网格之上,如图5。

 

图5

 

现在来到我们的正题,为什么当单位运行在复杂地形,尤其是高低差较大的情况下,容易发生单位穿过导航网格跌落到网格或世界之外的情况。

一般情况下,当单位遇到起伏地形时,单位的位置高度将随着垂直检测的结果进行调整,这个时候组件将是正常工作的,如图6。

图6

然而向下射线检测是有时间间隔的,当单位移动与垂直检测不同步时,可能发生在垂直检测的间隔中,检测框中心点水平移动越过了地形的网格(Mesh),导致在下一次进行垂直检测的时候会发生向下检测不到任何网格(Mesh)的情况,如图7所示,这个时候单位将失去用于保持高度位置的下方参照,造成我们遇到的单位跌落到网格或世界之外的情况。

这也解释了为什么导航时候单位是否跌出网格看起来是一个概率性的事件,因为垂直检测的时间间隔是比较小的,所以跌落的事件仅在遭遇上述情况的情况下发生。

 

图7

 

看到这里,我们可以得到两个显而易见的解决办法,那就是缩短垂直检测的时间间隔,或者降低单位在水平方向上移动的速度。

前者会造成系统性能上一定的负担增加,同时来说即便缩短检测间隔,但间隔仍然是客观存在的,无法预测间隔要缩至多短才能解决问题。

对于后者来说,我们知道系统对于单位和地形的位置判定是比较严格的,即便是以非常低的速度运行,但系统也只是在垂直检测那一帧进行检测,移动不论多慢都是逐帧运行的,只要在帧内垂直方向的点上检测失败,无论运行有多慢也都无济于事。所以该方法可能要以比较低的速度移动,才能在一定程度上解决问题。

 

在这里提供一个思路,即是改变垂直检测射线的发射点,通过提高发射点垂直方向的位置,以确保在所有的检测间隔中,都不会丢失下部的参照物。如图8所示。

 

图8

代码示例:

        Vector3 fixRayPosition = Vector3.zero;

        //获得单位目前位置

        fixRayPosition = transform.position;

        //调整y向高度,用于从这个调整后的高度向下发射射线

        fixRayPosition.y = fixRayPosition.y+20;

        //发射射线

        RaycastHit hit;

        if (Physics.Raycast(fixRayPosition, Vector3.down, out hit, Mathf.Infinity, 1 << LayerMask.NameToLayer("Ground")))

        {

            //获取地面的高度

            float groundHeight = hit.point.y;

            // 返回一个地面高度加上距地距离的位置

            return groundHeight + 5;

        }

 

该代码将从一个高于单位20个单位距离的位置向下发出射线,获得接触到的网格(Mesh)的接触点位置坐标,然后用这个接触点坐标加上一个调整离地距离的数值用于返回。

该方法在不增加额外性能开销的情况下,避免了绝大部分因为垂直检测失败导致的单位跌落情况,同时可在该检测之前或者之后添加重力之类的方法丰富操作。

这也是官方论坛回答中为什么建议将RichAI的Height调大,因为检测射线从检测框体中心发射,调整Height也能达到类似的提高射线发出位置的效果,但因为射线总是从框体中心发射,这意味着在达到相同效果的要求下,Height必须调整到两倍数值以进行匹配。为避免Height过大造成的未知问题,我没有选择这个方法。

 

但是这个方法调整时必须注意调整值与环境的搭配,如果在射线发射范围内另有一层网格(Mesh)的存在,会导致另一种检测失误,导致错误的位置调整,如图9,所以调整值在场景有上方遮蔽物的时候必须进行相应考虑。

 

 

图9

 

最后,总结一下思路。

在A*Pathfinding这个插件中:

1、使用RecastGraph生成导航图,并使用RichAI进行导航时,将会偶然发生移动单位穿越跌落到环境外的情况发生。

2、该问题发生于单位在两次垂直方向检测的间隔时,单位水平移动导致检测发出点越过环境物体,导致垂直检测失败造成跌落。

3、通过提高检测发出点的位置,可以确保单位穿越环境物体时,检测点总是高于环境物体或者需要需要检测的位置。

4、该方法需要确认调整位置后的检测点与单位之间不要有预期外的遮蔽物,避免检测获得错误的数值。

5、该方法适用于各方向、以及除外本插件以外类似使用场景的检测,可在各类实施中前置或者后置以丰富操作。