使用NavMesh为AI代理实施路径查找器

图片

沿着道路控制交通


有时,我们需要AI角色沿着大致定义或精确定义的路径漫游游戏世界。 例如,在赛车游戏中,AI对手必须沿着道路行驶,而在RTS中,单位必须能够移动到所需的点,沿着地形移动并考虑彼此的位置。

为了显得聪明,AI代理必须能够确定他们在做什么,如果他们无法达到期望的点,那么他们必须能够绘制最有效的路线并在障碍物出现时改变其路线。

避免障碍是一种简单的行为,可以使AI实体达到目标点。 重要的是要注意,本文中实现的行为是针对人群模拟之类的行为的,其中每个代理的主要目标是避免其他代理并达到目标。 它们不能确定最有效和最短的路径。

技术要求


需要在装有Windows 7 SP1,+,8、10或Mac OS X 10.9+的系统上安装Unity 2017。 本文中的代码在Windows XP和Vista上不起作用,并且Windows和OS X的服务器版本尚未经过测试。

这篇文章的代码文件可以在GitHub找到

要了解实际的代码,请观看此视频

导航网格


让我们了解如何使用内置的Unity导航网格生成器,它可以大大简化AI代理路径的搜索。 在Unity 5.x的早期阶段,NavMesh功能对所有用户(包括具有个人版许可的用户)可用,尽管在早期它仅适用于Unity Pro。 在2017.1发行版之前,系统已进行了更新以提供基于组件的工作流程,但是由于它需要一个额外的可下载程序包(在撰写本文时仅在预览版本中可用),因此我们将遵循基于场景的标准工作流程。 不用担心,这两种方法的概念是相似的,并且当完成的实现最终到达2017.x时,就不会有重大变化。

GitHub上了解有关NavMesh组件系统的更多信息。

现在,我们将探索该系统可以为我们提供的所有可能性。 要搜索AI路径,必须以特定格式显示场景。 在2D地图上,使用A *算法使用二维网格(数组)搜索路径。 AI代理需要知道障碍物在哪里,尤其是静态障碍物。 处理动态移动的对象之间的碰撞是另一个通常称为转向行为的问题。 Unity具有一个用于生成NavMesh的内置工具,该工具在便于AI代理找到通往目标的最佳路径的上下文中表示场景。 首先,打开一个演示项目并转到NavMesh场景。

学习卡


打开NavMesh演示场景后,它应如下图所示:


障碍物和斜坡场景

这将是我们用来解释和测试NavMesh系统功能的沙箱。 总体方案类似于RTS(实时策略)类型的游戏。 我们开一辆蓝色的坦克。 单击不同的点,以便坦克向他们移动。 黄色指示器是水箱的当前目标。

导航静态


首先,您需要说您应该将在NavMesh中烘焙的场景中的所有几何图形标记为Navigation Static 。 您可能在例如Unity照明贴图系统中就已经看到了。 要使游戏对象静态化非常简单,只需选中“ 静态”框以获取其所有属性(导航,照明,剔除,批处理等),或使用下拉列表指定属性。 该复选框位于所选对象的检查器的右上角。


属性导航静态

可以为不同的对象单独完成此操作,或者,如果您具有内置的游戏对象层次结构,请将参数应用于父对象,然后Unity将提供将其应用于所有子对象的功能。

烘焙导航网格


对于整个场景,将使用“ 导航”窗口应用导航网格导航选项。 可以通过转到窗口 |打开此窗口。 导览 像任何其他窗口一样,它可以断开以自由移动或固定。 在我们的屏幕截图中,它显示为层次结构旁边的停靠选项卡,但是您可以将此窗口放在任何方便的位置。

打开窗口,您将看到各个选项卡。 它看起来像这样:


导航窗口

在我们的情况下,上一个屏幕截图显示了“ 烘焙”选项卡,但是在您的编辑器中,默认情况下可以选择任何选项卡。

让我们看看从左到右的每个选项卡。 让我们从“ 代理”选项卡开始,它看起来像屏幕截图所示:


代理商标签

如果您正在处理另一个项目,则可能会发现某些设置与我们为屏幕快照中显示的示例项目设置的设置不同。 该选项卡顶部有一个列表,您可以在其中单击+按钮添加新类型的代理。 您可以通过选择其他代理并单击-按钮来删除它们。 该窗口清楚地显示了更改设置时的各种设置。 让我们看看每个设置的作用:

  • 名称:在“业务代表类型”下拉列表中显示的业务代表类型名称。
  • 半径:您可以将其视为代理商的“个人空间”。 座席将尝试根据此值避免与其他座席过于紧密的接触,因为它是为了避免使用。
  • 高度:您可能会猜到,此设置设置用于垂直回避(例如,在物体下方通过时)的代理的高度。
  • 台阶高度:此值确定座席可以攀登的高度。
  • 最大坡度:正如我们将在下一节中看到的那样,该值确定了代理可以爬升的最大角度。 使用此参数,可以使座席无法访问地图的陡坡。

接下来,我们有“ 区域”选项卡,看起来像此屏幕截图中所示:


如您在屏幕快照中所见,Unity提供了几种无法更改的区域: WalkableNot WalkableJump 。 除了命名和创建新区域之外,您还可以为这些区域分配移动它们的成本。

区域用于两个目的:使代理可访问或不可访问区域,以及将区域标记为差旅费用不那么理想。 例如,您可以开发一个RPG,使恶魔敌人无法进入标记为“圣地”的区域。 您还可以将地图的某些区域标记为“泥潭”或“沼泽”,由于移动成本高昂,代理商将避免使用这些区域。

第三个“ 烘烤”选项卡可能是最重要的。 它允许您为场景创建NavMesh本身。 您应该已经熟悉一些选项。 烘焙选项卡如下所示:


烘烤标签

此选项卡上的代理大小选项确定代理如何与环境交互,而“ 代理”选项卡上的选项控制与其他代理和移动对象的交互。 但是它们控制相同的参数,因此我们将跳过它们。 “放下高度”和“ 跳跃距离”控制代理可以“跳转”到NavMesh中与代理当前所在位置不直接相关的部分的距离。 我们将在下面更详细地考虑这一点,因此,如果不确定,则仍然无法研究这些参数。

此外,有些高级选项通常默认情况下是隐藏的。 要扩展这些选项,只需单击“ 高级”标题旁边的下拉三角形。 手动体素大小可以认为是“质量”设置。 尺寸越小,越多的细节将存储在网格中。 最小区域面积用于跳过低于所选阈值的烘烤平台或表面。 烘焙网格时,“ 高度网格”为我们提供了更详细的垂直数据。 例如,使用此选项可以在爬楼梯时保持座席的正确位置。

清除”按钮将删除场景的所有NavMesh数据,而“ 烘焙”按钮将为场景创建一个网格。 烘焙过程非常快。 只要选择了一个窗口,就可以使用场景窗口中的“ 烘焙”按钮观察NavMesh的生成。 让我们单击“ 烘焙”按钮以查看结果。 在我们的示例场景中,我们最终得到类似于以下屏幕截图的内容:


蓝色区域显示NavMesh。 下面我们将返回到此。 同时,让我们进入最后一个选项卡-Object ,如下所示:


上一个屏幕快照中显示的三个按钮( 全部网格渲染器地形 )用作场景过滤器。 当在层次结构中包含许多对象的复杂场景中工作时,它们很有用。 选择一个选项可从层次结构中过滤掉相应的类型,从而使选择它们变得容易。 您可以使用按钮浏览场景,以搜索要标记为导航静态的对象。

使用导航网格代理


现在,我们已经使用NavMesh设置了场景,我们需要一种让代理使用此信息的方法。 对我们来说幸运的是,Unity有一个Nav Mesh Agent组件,您可以将其拖动到角色上。 在我们的示例场景中,有一个名为Tank的游戏对象,组件已经连接到该对象。 查看层次结构,您将看到类似以下内容:


这里有很多参数,我们不会考虑所有内容,因为它们很清楚,并且可以在Unity官方文档中找到描述。 但是,我们将提到主要内容:

  • 座席类型 :还记得导航窗口中的“ 座席”选项卡吗? 可以在此处选择可分配代理类型。
  • 自动遍历离网链接 :此选项允许代理自动使用离网链接功能,我们将在下面进行讨论。
  • 区域遮罩 :您可以在此处选择在“ 导航”窗口的“ 区域”选项卡中配置的区域

仅此而已。 该组件为我们完成了90%的艰苦工作:铺路,避免障碍等。 您唯一需要做的就是将目标点转移到业务代表。 让我们看一下这个问题。

目标点设定


设置AI代理后,我们需要一种方法来告诉他要去哪里。 在我们的示例项目中,有一个名为Target.cs的脚本可以完全执行此任务。

这是一个简单的类,可完成三件事:

  • 将光束从相机“射”到世界上的鼠标位置
  • 更新标记位置
  • 更新所有NavMesh代理的目标属性。

代码很简单。 整个课程如下:

using UnityEngine; using UnityEngine.AI; public class Target : MonoBehaviour { private NavMeshAgent[] navAgents; public Transform targetMarker; private void Start () { navAgents = FindObjectsOfType(typeof(NavMeshAgent)) as NavMeshAgent[]; } private void UpdateTargets ( Vector3 targetPosition ) { foreach(NavMeshAgent agent in navAgents) { agent.destination = targetPosition; } } private void Update () { if(GetInput()) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hitInfo; if (Physics.Raycast(ray.origin, ray.direction, out hitInfo)) { Vector3 targetPosition = hitInfo.point; UpdateTargets(targetPosition); targetMarker.position = targetPosition; } } } private bool GetInput() { if (Input.GetMouseButtonDown(0)) { return true; } return false; } private void OnDrawGizmos() { Debug.DrawLine(targetMarker.position, targetMarker.position + Vector3.up * 5, Color.red); } } 

这里发生以下动作:在Start方法中,我们使用FindObjectsOfType()方法初始化navAgents数组。

UpdateTargets()方法通过我们的navAgents数组,并在给定的Vector3中设置它们的目标点。 这是代码的关键。 您可以使用任何机制来获取目标点,并使代理移动到该目标点,只需设置NavMeshAgent.destination字段即可; 代理会做剩下的事情。

在我们的示例中,单击是用来移动的,因此当玩家单击鼠标时,我们会沿着鼠标光标的方向将摄影机发出的光线释放到世界中,如果光线与物体相交,则将碰撞点分配给新的targetPosition代理。 我们还会相应地调整目标标记,以轻松可视化游戏中的目的地。

要测试操作,您需要根据上一节的说明烘焙NavMesh,然后启动“播放”模式并选择地图上的任何区域。 如果单击多次,您会看到代理无法到达某些区域-红色立方体的顶部,顶部平台和屏幕底部的平台。

红色立方体太高。 对于我们的“ 最大坡度”设置而言,通往最高平台的坡度太陡了,代理商无法攀登。 以下屏幕截图显示了最大坡度设置如何影响NavMesh:


NavMesh最大坡度= 45

如果将“ 最大坡度”值更改为51 ,然后再次单击“ 烘焙”按钮再次烘焙NavMesh,结果将如下所示:


NavMesh最大坡度= 51

如您所见,我们可以自定义关卡设计,通过更改单个参数,无法访问整个区域。 例如,当您有需要绳索,梯子或电梯才能爬升的平台或壁架时,这可能会很有用。 或者也许是一项特殊技能,例如攀爬能力?

应用脱离网状链接


您可能会注意到我们的场景有两个休息时间。 我们的特工可以进入第一个,但屏幕底部的那个太远了。 这些计算并不完全是任意的。 离网链接实质上是在不相关的NavMesh段之间的空间上建立桥梁。 这些链接可以在编辑器中看到:


具有连接线的蓝色圆圈是连接。

Unity可以通过两种方式生成这些链接。 我们已经考虑过的第一个。 还记得导航窗口“ 烘焙”选项卡中的“ 跳跃距离”值吗? 烘焙NavMesh时,Unity自动使用该值生成这些链接。 尝试将测试场景中的值更改为5,然后再次烘焙。 看到-平台现在已连接? 这是因为网格现在在新的指定阈值内。

再次将值更改为2并烘烤。 现在让我们来看第二种方式。 创建将用于连接两个平台的球体。 大致按照屏幕快照所示放置它们:


您已经知道发生了什么,但是让我们分析允许他们进行连接的过程。 在我们的例子中,我在右端称球,在左端称球。 您很快就会明白原因。 接下来,我将Off Mesh Link组件添加到了右侧的平台上(相对于之前的屏幕截图)。 您会注意到该组件具有开始结束字段。 您可能会猜到,我们会将先前创建的球体拖到相应的插槽中- 起始字段中的起始球体和终止字段中的终止球体。 检查器将如下所示:


给定正值时,将考虑“ 成本改写”的值。 当使用这种关系时,它会应用成本因素,而不是通向目标的更具成本效益的途径。

Bi定向如果为true,则允许代理在两个方向上移动。 要创建单向流量的链接,可以禁用此值。 根据名称使用激活值。 如果为false,则代理将忽略此关联。 您可以打开和关闭它来创建游戏场景,例如,玩家必须按下开关才能激活连接。

为了启用这种关系,不需要重新烘焙。 查看您的NavMesh,您将看到它与屏幕截图中的样子完全一样:


如您所见,较小的间隙仍然会自动连接,现在我们有了两个球体之间的“ 离网链接”组件生成的新连接。 启动播放模式,然后单击远端平台。 如预期的那样,代理现在可以导航到断开连接的平台:


在游戏级别上,您可能需要更改这些参数才能获得所需的结果,但是这些功能的组合为您提供了方便的现成工具。 您可以使用NavMesh功能快速创建一个简单的游戏。

本教程是Ray Barrera,Aung Sithu Kyaw和Thet Naing Swe撰写的Unity 2017游戏AI编程-第三版的一部分。

Source: https://habr.com/ru/post/zh-CN414453/


All Articles