AI Navigation
导航系统允许你创建能够在游戏世界中智能移动的角色。这些角色使用从场景几何体自动创建的导航网格。动态障碍物允许你在运行时改变角色的导航,而离网格链接让你构建特定的动作,比如开门或跳过间隙或从边缘跳下。本节详细描述了Unity的导航和寻路系统。
下表描述了AI Navigation包文档的主要主题。
| 主题 | 描述 |
|---|---|
| What's new | 查看AI Navigation包最新版本中的更改。 |
| Upgrade | 将你的项目转换为使用新的导航系统。 |
| Navigation System | 理解使用Unity中AI Navigation所需的关键概念。 |
| Navigation Overview | 使用此包创建NavMeshes、代理、链接和障碍物。 |
| Navigation Interface | 了解此包中导航组件的接口。 |
| Samples | 了解此包中包含的示例项目。 |
| Glossary | 查看AI Navigation术语定义。 |
AI Navigation版本1.1.1中的新内容
这是一个新包版本。
AI Navigation包版本1.1.1与之前存在的功能相比的更改摘要。
此版本的主要更新包括:
添加
- NavMesh Surface的新选项,用于烘焙HeightMesh和自动生成链接。
- NavMesh Surface中的新选项,仅使用标记为NavMesh Modifiers的源对象。
- NavMesh Modifier中的新选项,也作用于子对象。
更新
- 仅当包安装时,导航窗口才可用。
- 从导航窗口中移除了Bake和Object选项卡,因为它们不再需要。但是,你仍然可以在导航(已弃用)窗口中找到它们。
- 导航静态和离网格链接生成选项不再在静态编辑器标志下拉菜单中可用。但是,你仍然可以在导航(已弃用)窗口的对象选项卡中找到它们。
- 改进了几个包含的示例。
要查看此版本的所有更改和更新,请参阅AI Navigation包更新日志。
升级项目以使用AI Navigation包
从Unity 2022.2开始,Unity中的导航和寻路由AI Navigation包处理。
如果你有在Unity早期版本中创建的导航功能的项目,AI Navigation包会自动安装并添加到你的项目中。你可以选择以下操作之一:
- 继续按原样使用你的项目
- 将项目转换为使用新包
移除旧的组件脚本
如果你的项目使用了由从Unity的NavMeshComponents GitHub仓库下载的脚本定义的NavMesh Surface、NavMesh Modifier、NavMesh Modifier Volume或NavMesh Link组件,那么在将AI Navigation包添加到项目之前,请移除这些脚本及其关联文件。如果不移除这些脚本,你可能会在控制台中遇到与这些组件相关的冲突和错误。新组件反映了与旧组件相同的行为,除了使用以下组件时:
- NavMesh Surface组件现在包含一个选项,仅在烘焙过程中使用具有NavMesh Modifier的对象。
- 你现在可以指定是否将NavMesh Modifier组件应用于层次结构中的子对象。
转换你的项目
如果你想使用新包,你需要转换你的项目。作为转换过程的一部分,NavMesh Updater进行以下更改:
- 之前烘焙并嵌入场景中的任何NavMesh现在从在新的GameObject上创建的NavMeshSurface组件引用。
- 任何标记为Navigation Static的对象现在具有带有适当设置的NavMeshModifier组件。
要转换你的项目,请执行以下操作:
- 在主菜单中,转到Window > AI > NavMesh Updater。
- 在NavMesh Updater窗口中,选择要转换的数据类型。
- 点击Initialize Converters以检测并显示你选择的数据类型。
- 选择要转换的数据。
- 点击Convert Assets完成转换。
创建新的代理类型
如果不同场景中烘焙的NavMeshes使用了不同的代理设置,那么你需要创建新的代理类型来匹配这些设置。
要创建代理类型,请执行以下操作:
- 在主菜单中,转到Window > AI > Navigation。
- 选择Agents。
- 创建新条目并指定相关设置。
分配新的代理类型
当你创建了新的代理类型后,你需要按如下方式分配它们:
- 将新创建的代理类型分配给为该场景创建的NavMeshSurface中的相应NavMeshSurfaces。
- 将代理类型分配给打算使用该NavMesh的NavMeshAgents。
要找到每个现有NavMesh使用的设置,请在Project窗口中选择NavMesh .asset文件。NavMesh设置将在Inspector中显示。
Unity中的导航系统
本节描述了使用Unity中AI Navigation所需的关键概念。它包含以下主题:
| 主题 | 描述 |
|---|---|
| Inner Workings of the Navigation System | 了解AI Navigation系统的不同元素如何协同工作。 |
接下来是Inner Workings of the Navigation System部分,翻译时要准确。
Inner Workings of the Navigation System
当你想要智能地移动游戏中的角色(或称为代理)时,你需要解决两个问题:如何推理关于目的地,然后如何移动到那里。这两个问题紧密耦合,但性质不同。推理关于目的地的问题是更全局和静态的,因为它考虑整个场景。移动到目的地是更局部和动态的,它只考虑移动的方向以及如何避免与其他移动代理的碰撞。
可行走区域
导航系统需要自己的数据来表示游戏场景中的可行走区域。可行走区域定义了场景中代理可以站立和移动的地方。在Unity中,代理被描述为圆柱体。可行走区域是通过测试代理可以站立的位置从场景几何体自动构建的。然后这些位置连接到位于场景几何体顶部的表面。这个表面称为导航网格(NavMesh)。
NavMesh将此表面存储为凸多边形。凸多边形是一种有用的表示,因为我们知道多边形内任意两点之间没有障碍物。除了多边形边界,我们还存储有关哪些多边形是彼此邻居的信息。这允许我们推理整个可行走区域。
寻找路径
要在场景中的两个位置之间找到路径,我们首先需要将起点和终点位置映射到它们最近的凸多边形。然后我们从起点开始搜索,访问所有邻居,直到到达目标凸多边形。跟踪访问的凸多边形允许我们找到从起点到终点的凸多边形序列,这将引导我们找到路径。找到路径的常见算法是A*(发音为"A star"),这是Unity使用的算法。
跟随路径
描述从起点到目标凸多边形的凸多边形序列称为走廊。代理将通过始终转向走廊的下一个可见角点来达到目的地。如果你有一个简单的游戏,其中只有一个代理在场景中移动,那么在一个 swoop 中找到走廊的所有角点并让角色沿连接角点的线段移动是没问题的。
当处理同时移动的多个代理时,它们在避免彼此时会偏离原始路径。尝试使用由线段组成的路径来纠正此类偏差很快变得困难和容易出错。
由于每个帧中代理的移动量很小,我们可以使用凸多边形的连通性来修复走廊,以防我们需要稍微绕道。然后我们快速找到下一个可见角点来转向。
避免障碍物
转向逻辑获取下一个角点的位置,并基于此计算出到达目的地所需的期望方向和速度(或速度)。使用期望速度移动代理可能导致与其他代理的碰撞。
障碍物避免选择一个新的速度,该速度在向期望方向移动和防止与其他代理和导航网格边缘的未来碰撞之间取得平衡。Unity使用互易速度障碍(RVO)来预测和防止碰撞。
移动代理
在转向和障碍物避免之后,最终速度被计算。在Unity中,代理使用简单的动态模型进行模拟,该模型还考虑加速度以允许更自然和流畅的移动。
在此阶段,你可以将模拟代理的速度提供给动画系统,使用root motion移动角色,或者让导航系统处理这一点。
一旦代理使用任一方法移动,模拟代理的位置将被移动并约束到NavMesh。这最后一步对于鲁棒的导航很重要。
全局和局部
要了解导航,最重要的事情之一是全局导航和局部导航之间的区别。
全局导航用于在世界范围内找到走廊。在世界范围内找到路径是一个成本高昂的操作,需要相当多的处理能力和内存。
描述路径的凸多边形线性列表是用于转向的灵活数据结构,并且可以根据代理的位置局部调整。局部导航试图弄清楚如何高效地向下一个角点移动而不与其他代理或移动对象碰撞。
障碍物的两种情况
许多导航应用需要其他类型的障碍物,而不仅仅是其他代理。这些可能是射击游戏中的箱子、桶或车辆。这些障碍物可以使用局部障碍物避免或全局寻路来处理。
当障碍物移动时,最好使用局部障碍物避免。这样代理可以预测性地避免障碍物。当障碍物静止时,并且可以被视为阻挡所有代理的路径,障碍物应该影响全局导航,即导航网格。
改变NavMesh称为雕刻。该过程检测障碍物的哪些部分接触NavMesh,并在NavMesh中雕刻孔洞。这是一个计算成本高昂的操作,这也是为什么移动障碍物应该使用碰撞避免来处理的原因。
关于NavMeshes上位置之间的快捷方式
NavMesh凸多边形之间的连接使用寻路系统内的链接来描述。有时有必要让代理导航到不可行走的区域,例如跳过栅栏或穿过关闭的门。这些情况需要知道动作的位置。
你可以使用OffMesh Link组件注释这些动作,该组件告诉寻路器存在通过指定链接的路线。你的代码可以稍后访问此链接,并在代理跟随路径时执行特殊动作。
关于NavMesh代理
NavMesh代理是一个GameObject,由一个直立圆柱体表示,其大小由Radius和Height属性指定。圆柱体随GameObject移动,但即使GameObject旋转也保持直立。圆柱体的形状用于检测和响应与其他代理和障碍物的碰撞。当GameObject的锚点不在圆柱体底部时,使用Base Offset属性指定高度差。
圆柱体的高度和半径在Navigation窗口和各个代理的NavMesh Agent组件属性中指定。
- Navigation窗口设置描述所有NavMesh Agents如何与静态世界几何体碰撞和避免。
- NavMesh Agent组件属性值描述代理如何与移动障碍物和其他代理碰撞。
通常你在两个地方设置代理的大小相同的值。但是,你可能给一个重士兵更大的半径,这样其他代理会在你的士兵周围留下更多空间。否则,你的士兵以与其他代理相同的方式避免环境。
关于NavMesh障碍物
NavMesh Obstacles可以在游戏过程中影响NavMesh Agent的导航,有两种方式:
阻碍
当Carve未启用时,NavMesh Obstacle的默认行为类似于Collider。NavMesh Agents尝试避免与NavMesh Obstacle碰撞,当接近时,它们与NavMesh Obstacle碰撞。障碍物避免行为非常基本,并且具有短半径。因此,NavMesh Agent可能无法在充满NavMesh Obstacles的环境中找到绕过路径。此模式最适合障碍物持续移动的情况(例如,车辆或玩家角色)。
雕刻
当Carve启用时,障碍物在静止时在NavMesh中雕刻一个孔洞。当移动时,障碍物是一个阻碍。当在NavMesh中雕刻一个孔洞时,寻路器能够导航NavMesh Agent绕过充满障碍物的区域,或者如果当前路径被障碍物阻挡,则找到另一条路线。对于通常阻挡导航但可被玩家或其他游戏事件(例如爆炸或桶)移动的NavMesh Obstacles,开启雕刻是一个好习惯。
移动NavMesh Obstacles的逻辑
Unity将NavMesh Obstacle视为当它移动的距离超过Carve > Move Threshold设置的距离时移动。但是,为了减少CPU开销,孔洞仅在必要时重新计算。此计算的结果在下一帧更新中可用。重新计算逻辑有两种选项:
- 仅当NavMesh Obstacle静止时雕刻
- 当NavMesh Obstacle移动时雕刻
仅当NavMesh Obstacle静止时雕刻
这是默认行为。要启用它,请选中NavMesh Obstacle组件的Carve Only Stationary复选框。在此模式下,当NavMesh Obstacle移动时,雕刻的孔洞被移除。当NavMesh Obstacle停止移动并已静止超过Carving Time To Stationary设置的时间时,它被视为静止,并且雕刻的孔洞被更新。当NavMesh Obstacle移动时,NavMesh Agents使用碰撞避免来避免它,但不规划绕过它的路径。
仅静止雕刻通常在性能方面是最佳选择,并且当与由物理控制的GameObject关联的NavMesh Obstacle时是一个很好的匹配。
当NavMesh Obstacle移动时雕刻
要启用此模式,请取消选中NavMesh Obstacle组件的Carve Only Stationary复选框。当未选中时,当障碍物移动的距离超过Carving Move Threshold设置的距离时,雕刻的孔洞被更新。此模式适用于大型、缓慢移动的障碍物(例如,坦克),它被步兵避免。
注意:当使用NavMesh查询方法时,你应该考虑到在改变NavMesh Obstacle和该变化对NavMesh的影响之间有一帧延迟。
导航区域和成本
导航区域定义在特定区域行走有多困难,成本较低的区域在路径查找期间将被优先选择。此外,每个NavMesh Agent都有一个Area Mask,可用于指定代理可以在哪些区域移动。
在上面的示例中,区域类型用于两个常见用例:
- 水区域通过分配更高的成本使其更难行走,以处理在浅水中行走较慢的场景。
- 门区域通过特定角色使其可访问,以创建人类可以通过门,但僵尸不能的场景。
区域类型可以分配给包含在NavMesh烘焙中的每个对象,此外,每个Off-Mesh Link具有指定区域类型的属性。
路径查找成本
简而言之,成本允许你控制寻路器在寻找路径时偏好哪些区域。例如,如果你将区域成本设置为3.0,那么穿过该区域的行程被认为是替代路线的三倍长。
要完全理解成本如何工作,让我们看看寻路器是如何工作的。
路径查找期间访问的节点和链接。
Unity使用A*在NavMesh上计算最短路径。它作用于连接节点的图。算法从路径起点的最近节点开始,并访问连接的节点,直到到达目标。
上面图片中的黄色点和线显示了节点和链接如何在NavMesh上放置,以及它们在A*期间被遍历的顺序。
在两个节点之间移动的成本取决于行进的距离和链接下凸多边形的区域类型成本,即distance * cost。在实践中,这意味着如果区域的成本是2.0,那么穿过此类凸多边形的距离将显示为两倍长。A*算法要求所有成本必须大于1.0。
成本对结果路径的影响可能难以调整,特别是对于较长的路径。处理成本的最佳方式是将它们视为提示。例如,如果你不想经常使用Off-Mesh Links,你可以增加它们的成本。但调整代理偏好走在人行道上的行为可能具有挑战性。
另一个你可以在某些级别上注意到的事情是寻路器并不总是选择非常短的路径。原因是节点放置。在开放区域旁边有微小障碍物的场景中,这会导致导航网格非常庞大和小型凸多边形。在这种情况下,大凸多边形上的节点可能被放置在任何地方,并且从寻路器的角度来看,它看起来像是一个绕道。
每个区域类型的成本可以在Areas选项卡中全局设置,或者你可以使用脚本覆盖每个代理的成本。
区域类型
区域类型在Navigation Window的Areas选项卡中指定。有29个自定义类型和3个内置类型:Walkable、Not Walkable和Jump。
- Walkable是一个通用区域类型,指定该区域可以行走。
- Not Walkable是一个通用区域类型,阻止导航。它适用于你想要将某个对象标记为障碍物,但不希望NavMesh在其上的情况。
- Jump是一个分配给所有自动生成的Off-Mesh Links的区域类型。
如果几个不同区域类型的对象重叠,结果名称区域类型通常将是具有最高索引的那个。有一个例外:Not Walkable始终优先。如果你需要阻止某个区域,这可能很有用。
区域掩码
每个代理都有一个Area Mask,描述它在导航时可以使用哪些区域。区域掩码可以在代理属性中设置,或者可以在运行时使用脚本操作位掩码。
区域掩码在你只想让特定类型的角色能够穿过某个区域时很有用。例如,在僵尸 evasion 游戏中,你可以将每扇门下的区域标记为Door区域类型,并从僵尸角色的Area Mask中取消选中Door区域。
附加资源
- Create a NavMesh - 创建NavMesh的工作流程。
- NavMeshAgent.areaMask - 设置代理areaMask的脚本API。
- NavMeshAgent.SetAreaCost() - 设置代理area成本的脚本API。
导航概述
本节提供有关如何为你的场景创建NavMeshes、NavMesh Agents、NavMesh Obstacles和Off-Mesh Links的详细信息。它包含以下主题:
- Create a NavMesh
- Create a NavMesh Agent
- Create a NavMesh Obstacle
- Create an Off-mesh Link
- Using NavMesh Agent with Other Components
- Advanced Navigation How-tos
创建NavMesh
你需要创建一个NavMesh来定义场景中角色可以智能导航的区域。
要创建NavMesh,请执行以下操作:
- 选择你要添加NavMesh的场景几何体。
- 在Inspector窗口中,点击Add Component。
- 选择Navigation > NavMesh Surface。
- 在NavMesh Surface组件中,指定必要的设置。有关可用设置的详细信息,请参阅NavMesh Surface组件。
- 完成后,点击Bake。
NavMesh在Navigation窗口打开且可见时生成,并在底层场景几何体上显示为蓝色叠加层。
创建NavMesh代理
一旦你为你的级别烘焙了NavMesh,是时候创建一个可以导航场景的角色了。我们将从一个圆柱体构建我们的原型代理,并设置其运动。这是使用NavMesh Agent组件和一个简单脚本完成的。
首先让我们创建角色:
- 创建一个圆柱体:GameObject > 3D Object > Cylinder。
- 默认圆柱体尺寸(高度2和半径0.5)适合人形代理,所以我们保持原样。
- 添加NavMesh Agent组件:Component > Navigation > NavMesh Agent。
现在你有一个简单的NavMesh Agent设置好,可以接收命令了!
当你开始使用NavMesh Agent进行实验时,你很可能需要根据你的角色大小和速度调整其尺寸。
NavMesh Agent组件处理角色的寻路和运动控制。在你的脚本中,导航可以简单到设置期望的目的地 - NavMesh Agent可以处理从那里开始的一切。
接下来,我们需要构建一个简单的脚本,允许你将角色发送到另一个GameObject指定的目的地,而Sphere将是目的地。
- 创建一个新的C#脚本(MoveTo.cs)并替换其内容为上面的脚本。
- 将MoveTo脚本分配给你刚刚创建的角色。
- 创建一个Sphere,这将是代理将移动到的目的地。
- 将Sphere从角色移动到靠近NavMesh表面的位置。
- 选择角色,找到MoveTo脚本,并将Sphere分配给Goal属性。
- 按 Play:你应该看到代理导航到Sphere的位置。
总结一下,在你的脚本中,你需要获取NavMesh Agent组件的引用,然后设置代理的目的地属性。Navigation How Tos将给你更多关于如何使用NavMesh Agent解决常见游戏场景的示例。
创建NavMesh障碍物
NavMesh Obstacle组件可用于描述代理在导航时应避免的障碍物。例如,代理在移动时应避免物理控制的物体,如箱子和桶。
我们将添加一个箱子来阻挡级别顶部的路径。
- 首先创建一个立方体来表示箱子:GameObject > 3D Object > Cube。
- 将立方体移动到平台顶部,立方体的默认大小适合箱子,所以保持原样。
- 向立方体添加NavMesh Obstacle组件。从Inspector中选择Add Component,然后选择Navigation > NavMesh Obstacle。
- 将障碍物的形状设置为box,形状将自动适应中心并调整大小以匹配渲染网格。
- 添加一个刚体到障碍物。从Inspector中选择Add Component,然后选择Physics > Rigidbody。
- 最后,从NavMesh Obstacle Inspector中打开Carve设置,以便代理知道找到绕过障碍物的路径。
现在我们有一个工作的箱子,它是物理控制的,并且AI知道在导航时如何避免它。
创建Off-Mesh Link
Off-Mesh Links用于创建穿过可行走导航网格表面的路径。例如,跳过沟渠或栅栏,或开门后穿过它,都可以描述为Off-Mesh Links。
我们将添加一个Off-Mesh Link组件来描述从上层平台到地面的跳跃。
- 首先创建两个圆柱体:GameObject > 3D Object > cylinder。
- 你可以将圆柱体缩放到(0.1, 0.5, 0.1)以便于使用。
- 将第一个圆柱体移动到上层平台的边缘,靠近NavMesh表面。
- 将第二个圆柱体放在地面上,靠近NavMesh,在链接应该着陆的位置。
- 选择第一个圆柱体并为其添加一个Off-Mesh Link组件。从Inspector中选择Add Component,然后选择Navigation > OffMesh Link。
- 在Start字段中分配第一个圆柱体,在End字段中分配第二个圆柱体。
现在你有一个功能的Off-Mesh Link设置好了!如果通过off-mesh link的路径比沿Navmesh行走的路径短,将使用off-mesh link。
你可以使用场景中的任何GameObject来持有Off-Mesh link组件,例如栅栏prefab可以包含off-mesh link组件。同样,你可以使用具有Transform作为起点和终点标记的任何GameObject。
NavMesh烘焙过程可以检测并自动创建常见的跳跃和下降链接。请查看Building Off-Mesh Links Automatically以获取更多详细信息。
细节
如果代理没有遍历OffMesh link,请确保两个端点都正确连接。正确连接的端点应该在访问点周围显示一个圆圈。
另一个常见原因是Navmesh Agent的Area Mask不包含OffMesh Link的区域。
使用NavMesh Agent与其他组件
你也可以将NavMesh Agent、NavMesh Obstacle和Off Mesh Link组件与其他Unity组件一起使用。以下是混合不同组件时的注意事项列表。
NavMesh Agent和Physics
- 你不需要为NavMesh Agents添加物理colliders来避免彼此
- 也就是说,导航系统模拟代理及其对障碍物和静态世界的反应。这里的静态世界是烘焙的NavMesh。
- 如果你想让NavMesh Agent推动物理对象或使用物理触发器:
- 添加Collider组件(如果不存在)
- 添加Rigidbody组件
- 启用kinematic(这是重要的!)
- Kinematic意味着刚体由除物理模拟之外的其他东西控制
- 如果NavMesh Agent和Rigidbody(非kinematic)同时处于活动状态,你会有竞争条件
- 两个组件可能试图在同一时间移动代理,导致未定义的行为
你可以使用NavMesh Agent来移动,例如玩家角色,而不使用物理
- 将玩家代理的避免优先级设置为小数字(高优先级),以允许玩家穿过人群
- 使用NavMeshAgent.velocity移动玩家代理,以便其他代理可以预测玩家移动以避免玩家。
NavMesh Agent和Animator
- NavMesh Agent和Animator与Root Motion可能导致竞争条件
- 两个组件试图在每个帧中移动transform
- 两种可能的解决方案
- 信息应该始终单向流动
- 要么代理移动角色,动画跟随
- 要么动画基于模拟结果移动角色
- 否则你将陷入难以调试的反馈循环
- 信息应该始终单向流动
- Animation follows agent
- 使用NavMeshAgent.velocity作为Animator的输入,大致匹配代理的运动到动画
- 稳健且易于实现,将在动画无法匹配速度时导致脚滑
- Agent follows animation
- 禁用NavMeshAgent.updatePosition和NavMeshAgent.updateRotation以将模拟与游戏对象位置分离
- 使用模拟代理的位置(NavMeshAgent.nextPosition)和动画根(Animator.rootPosition)之间的差异来计算动画的控制
- 请参阅Coupling Animation and Navigation以获取更多详细信息
NavMesh Agent和NavMesh Obstacle
- 不要混合使用!
- 启用两者将使代理尝试避免自己
- 如果雕刻也启用,代理会不断重新映射到雕刻孔洞的边缘,甚至更错误的行为
- 确保在任何给定时间只有一个处于活动状态
- 死亡状态,你可以关闭代理并打开障碍物以强制其他代理避免它
- 或者你可以使用优先级使某些代理被避免更多
NavMesh Obstacle和Physics
如果你想物理控制的物体影响NavMesh Agent的行为
- 向代理应该意识到的对象添加NavMesh Obstacle组件,这允许避免系统推理关于障碍物
- 如果游戏对象具有Rigidbody和NavMesh Obstacle,障碍物的速度从Rigidbody自动获取
- 这允许NavMesh Agents预测并避免移动障碍物
导航技巧
以下主题描述了一组技术和代码示例,以实现导航中的常见任务。与文档中的所有代码一样,你可以免费将这些示例用于任何目的,无需注明Unity。
| 主题 | 描述 |
|---|---|
| Tell a NavMeshAgent to Move to a Destination | 为NavMesh agent设置目的地。 |
| Move Agent to a Position Clicked by the Mouse | 使用鼠标点击为NavMesh agent设置目的地。 |
| Make an Agent Patrol Between a Set of Points | 为NavMesh agent设置巡逻点。 |
| Couple Animation and Navigation | 将动画集成到你的导航中。 |
告诉NavMeshAgent移动到目的地
你可以通过将NavMeshAgent.destination属性设置为你要代理移动到的点来告诉代理开始计算路径。一旦计算完成,代理将自动沿路径移动,直到到达目的地。下面的代码实现了一个简单的类,使用GameObject标记目标点,该点将在Start函数中分配给destination属性。注意,该脚本假设你已经在编辑器中添加并配置了NavMeshAgent组件。
// MoveDestination.cs
using UnityEngine;
using UnityEngine.AI;
public class MoveDestination : MonoBehaviour {
public Transform goal;
void Start () {
NavMeshAgent agent = GetComponent<NavMeshAgent>();
agent.destination = goal.position;
}
}
将代理移动到鼠标点击的位置
此脚本允许你通过在NavMesh上点击鼠标来选择目的地。点击的位置由射线确定(有关此技术的完整描述,请参阅Camera的Rays from the Camera页面)。由于GetComponent函数执行缓慢,脚本在Start函数中将结果存储在变量中,而不是在Update中重复调用。
// MoveToClickPoint.cs
using UnityEngine;
using UnityEngine.AI;
public class MoveToClickPoint : MonoBehaviour {
NavMeshAgent agent;
void Start() {
agent = GetComponent<NavMeshAgent>();
}
void Update() {
if (Input.GetMouseButtonDown(0)) {
RaycastHit hit;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 100)) {
agent.destination = hit.point;
}
}
}
}
让代理在一系列点之间巡逻
许多游戏具有在游戏区域自动巡逻的NPC。导航系统可用于实现此行为,但它比标准寻路涉及更多 - 仅仅使用两点之间的最短路径会形成一个有限且可预测的巡逻路线。你可以通过保持一组对NPC"有用"的关键点,并以某种序列访问它们来获得更令人信服的巡逻模式。例如,在迷宫中,你可能会将关键巡逻点放在 junctions 和 corners,以确保代理检查每个走廊。对于办公楼,关键点可能是各个办公室和其他房间。
关键巡逻点的理想顺序将取决于你希望NPC的行为方式。例如,机器人可能会以方法顺序访问这些点,而人类守卫可能会尝试通过更随机的模式抓住玩家。机器人的简单行为可以使用下面显示的代码实现。
巡逻点通过使用Transforms的公共数组提供给脚本。此数组可以从Inspector使用GameObjects标记点来分配。GotoNextPoint函数设置代理的目的地(这也启动其移动),然后选择数组中序列中的下一个点,该点将用于下一个stand。在Update函数中,代码循环遍历点,但你可以轻松修改这一点,例如通过使用Random Range随机选择数组索引。
在Update函数中,脚本检查代理与目的地的接近程度,使用remainingDistance属性。当此距离非常小时,调用GotoNextPoint开始巡逻的下一部分。
// Patrol.cs
using UnityEngine;
using UnityEngine.AI;
using System.Collections;
public class Patrol : MonoBehaviour {
public Transform[] points;
private int destPoint = 0;
private NavMeshAgent agent;
void Start () {
agent = GetComponent<NavMeshAgent>();
// 禁用自动制动允许连续移动
// (即,代理在接近目的地时不会减速)
agent.autoBraking = false;
GotoNextPoint();
}
void GotoNextPoint() {
// 如果没有设置点,则返回
if (points.Length == 0)
return;
// 设置代理前往当前选择的目的地。
agent.destination = points[destPoint].position;
// 选择数组中的下一个点作为目的地,
// 循环到开始(如果需要)
destPoint = (destPoint + 1) % points.Length;
}
void Update () {
// 当代理接近当前目的地时选择下一个目的地
if (!agent.pathPending && agent.remainingDistance < 0.5f)
GotoNextPoint();
}
}
耦合动画和导航
本文件的目标是指导你设置导航人类角色的移动,使用Unity的内置动画和导航系统以及自定义脚本来实现这一点。
我们假设你对Unity和Mecanim动画系统的基础知识有所了解。
一个示例项目可用 - 所以你不需要添加scripts或从头设置动画和动画控制器:
- NavigationAnimation_53.zip 适用于Unity 5.3+
创建动画控制器
为了获得响应迅速且多功能的动画控制器 - 覆盖不同方向的运动 - 我们需要一组在不同方向移动的动画。这有时称为strafer-set。
除了移动动画,我们还需要角色的站立动画。
我们通过在2D混合树中排列strafer-set来继续 - 选择混合类型:2D Simple Directional并将动画放置在Compute Positions > Velocity XZ。
对于混合控制,我们添加两个浮点参数velx和vely,并将它们分配给混合树。
在这里,我们将放置7个run动画 - 每个具有不同的速度。除了 forwards(+ left/right)和 backwards(+ left/right),我们还使用一个在原地跑的动画剪辑(在下面的2D混合图中居中突出显示)。这样做的原因是双重的,首先它在与其他动画混合时保持跑步的风格,其次动画防止混合时的脚滑。
然后我们在自己的节点(Idle)中添加idle动画剪辑。我们现在有两个离散的动画状态,我们用2个转换将它们耦合。
然后我们添加一个布尔控制参数move来控制移动和idle状态之间的切换。然后禁用转换的Has Exit Time属性。这允许转换在动画期间的任何时间触发。转换时间应设置为约0.10秒以获得响应式转换。
现在将新创建的动画控制器放在你要移动的角色上。
按播放并选择Hierarchy窗口中的角色。你现在可以在Animator窗口中手动控制动画值,并改变移动状态和速度。
下一步是创建其他控制动画参数的方法。
导航控制
在角色上放置一个NavMeshAgent组件,并调整半径、高度以匹配角色 - 另外更改speed属性以匹配动画混合树中的最大速度。
为角色所在的场景创建一个NavMesh。
接下来,我们需要告诉角色导航到哪里。这通常非常特定于应用程序。这里我们选择点击移动行为 - 角色移动到用户在屏幕上点击的世界中的点。
// ClickToMove.cs
using UnityEngine;
using UnityEngine.AI;
[RequireComponent (typeof (NavMeshAgent))]
public class ClickToMove : MonoBehaviour {
RaycastHit hitInfo = new RaycastHit();
NavMeshAgent agent;
void Start () {
agent = GetComponent<NavMeshAgent>();
}
void Update () {
if(Input.GetMouseButtonDown(0)) {
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hitInfo, 100)) {
agent.destination = hitInfo.point;
}
}
}
}
按播放 - 并在场景中点击 - 你会看到角色在场景中移动。但是 - 动画与移动不匹配。我们需要将代理的状态和速度信息传递给动画控制器。我们将添加另一个脚本。
// LocomotionSimpleAgent.cs
using UnityEngine;
using UnityEngine.AI;
[RequireComponent (typeof (NavMeshAgent))]
[RequireComponent (typeof (Animator))]
public class LocomotionSimpleAgent : MonoBehaviour {
Animator anim;
NavMeshAgent agent;
Vector2 smoothDeltaPosition = Vector2.zero;
Vector2 velocity = Vector2.zero;
void Start () {
anim = GetComponent<Animator> ();
agent = GetComponent<NavMeshAgent>();
// 不要自动更新位置
agent.updatePosition = false;
}
void Update () {
Vector3 worldDeltaPosition = agent.nextPosition - transform.position;
// 将'worldDeltaPosition'映射到局部空间
float dx = Vector3.Dot (transform.right, worldDeltaPosition);
float dy = Vector3.Dot (transform.forward, worldDeltaPosition);
Vector2 deltaPosition = new Vector2 (dx, dy);
// 低通滤波deltaMove
float smooth = Mathf.Min(1.0f, Time.deltaTime / 0.15f);
smoothDeltaPosition = Vector2.Lerp (smoothDeltaPosition, deltaPosition, smooth);
// 如果时间前进则更新速度
if (Time.deltaTime > 1e-5f)
velocity = smoothDeltaPosition / Time.deltaTime;
bool shouldMove = velocity.magnitude > 0.5f && agent.remainingDistance > agent.radius;
// 更新动画参数
anim.SetBool("move", shouldMove);
anim.SetFloat("velx", velocity.x);
anim.SetFloat("vely", velocity.y);
GetComponent<LookAt>().lookAtTargetPosition = agent.steeringTarget + transform.forward;
}
void OnAnimatorMove () {
transform.position = agent.nextPosition;
}
}
这个脚本值得解释一下。它放在角色上 - 该角色具有Animator和NavMeshAgent组件,以及上面的点击移动脚本。
首先,脚本告诉代理不要自动更新角色位置。我们在脚本中最后处理位置更新。方向由代理更新。
动画混合由读取代理速度控制。它被转换为相对速度(基于角色方向) - 然后平滑。转换后的水平速度分量然后传递给Animator,并且idle和moving之间的状态切换由速度(即速度大小)控制。
在OnAnimatorMove()回调中,我们更新角色的位置以匹配NavMeshAgent。
再次播放场景显示动画尽可能接近移动。
提高导航角色的质量
为了提高动画和导航角色的质量,我们将探讨几个选项。
注视
让角色注视和转向兴趣点是传达注意力和预期的重要方式。我们将使用动画系统的lookAt API。这需要另一个脚本。
// LookAt.cs
using UnityEngine;
using System.Collections;
[RequireComponent (typeof (Animator))]
public class LookAt : MonoBehaviour {
public Transform head = null;
public Vector3 lookAtTargetPosition;
public float lookAtCoolTime = 0.2f;
public float lookAtHeatTime = 0.2f;
public bool looking = true;
private Vector3 lookAtPosition;
private float lookAtWeight;
private Animator animator;
void Start () {
if (head == null)
{
Debug.LogError("No head transform - LookAt disabled");
enabled = false;
return;
}
animator = GetComponent<Animator>();
lookAtTargetPosition = head.position + transform.forward;
}
void OnAnimatorIK () {
lookAtTargetPosition.y = head.position.y;
float lookAtTargetWeight = looking ? 1.0f : 0.0f;
Vector3 curDir = lookAtTargetPosition - head.position;
Vector3 futDir = lookAtPosition - head.position;
lookAtPosition = head.position + curDir;
curDir = Vector3.RotateTowards(curDir, futDir, 6.28f*Time.deltaTime, float.PositiveInfinity);
lookAtPosition = head.position + curDir;
float blendTime = lookAtTargetWeight > lookAtWeight ? lookAtHeatTime : lookAtCoolTime;
lookAtWeight = Mathf.MoveTowards (lookAtWeight, lookAtTargetWeight, Time.deltaTime/blendTime);
animator.SetLookAtWeight (lookAtWeight, 0.2f, 0.5f, 0.7f, 0.5f);
animator.SetLookAtPosition (lookAtPosition);
}
}
将此脚本添加到角色并分配head属性到角色transform层次结构中的head transform。LookAt脚本没有导航控制的概念 - 所以要控制看哪里,我们回到LocomotionSimpleAgent.cs脚本并添加几行来控制注视。在Update()末尾添加:
LookAt lookAt = GetComponent<LookAt> ();
if (lookAt)
lookAt.lookAtTargetPosition = agent.steeringTarget + transform.forward;
这将告诉LookAt脚本将兴趣点设置为大约沿路径的下一个角点 - 或者如果没有角点 - 到路径的末尾。
尝试一下。
动画驱动的角色使用导航
角色迄今为止完全由代理的位置控制。这确保了与其他角色和障碍物的避免直接转换为角色位置。然而,如果动画没有覆盖提议的速度,这可能导致脚滑。在这里,我们将稍微放宽对角色的约束。基本上,我们将用动画质量换取避免质量。
将LocomotionSimpleAgent.cs脚本上的OnAnimatorMove()回调替换为以下行:
void OnAnimatorMove () {
// 使用导航表面高度基于动画移动更新位置
Vector3 position = anim.rootPosition;
position.y = agent.nextPosition.y;
transform.position = position;
}
尝试这个,你可能会注意到角色现在会从代理位置(绿色线框圆柱体)漂移。你可能需要限制角色动画漂移。这可以通过将代理拉向角色 - 或将角色拉向代理位置来完成。在LocomotionSimpleAgent.cs脚本的Update()方法末尾添加以下内容:
// 将角色拉向代理
if (worldDeltaPosition.magnitude > agent.radius)
transform.position = agent.nextPosition - 0.9f*worldDeltaPosition;
或者 - 如果你希望代理跟随角色。
// 将代理拉向角色
if (worldDeltaPosition.magnitude > agent.radius)
agent.nextPosition = transform.position + 0.9f*worldDeltaPosition;
什么效果最好很大程度上取决于特定的用例。
结论
我们已经设置了一个使用导航系统移动并相应动画的角色。调整混合时间、look-at权重等的数字可以改善外观 - 并且是进一步探索此设置的很好方式。
导航用户界面
导航用户界面由Navigation窗口、AI Navigation overlay、AI Navigation Editor Preferences和几个用于构建NavMesh的组件组成。NavMesh构建组件为你提供了额外的控制,允许你在运行时和Unity Editor中生成和使用NavMeshes。
| 主题 | 描述 |
|---|---|
| Navigation window | 定义游戏世界中代理和区域的类型。 |
| AI Navigation preferences | 自定义导航调试可视化。 |
| AI Navigation overlay | 显示导航调试可视化。 |
| NavMesh Agent component | 定义你想要导航游戏世界的角色。 |
| NavMesh Surface component | 为一种类型的Agent构建并启用NavMesh表面。 |
| NavMesh Modifier component | 调整GameObject在NavMesh烘焙时的行为。 |
| NavMesh Modifier Volume component | 基于体积控制NavMesh区域类型的生成。 |
| NavMesh Obstacle component | 定义NavMesh Agents在导航游戏世界时避免的移动障碍物。 |
| NavMesh Link component | 连接每种类型代理的NavMesh表面。 |
| OffMesh Link component | 创建NavMeshes之间无法由可行走表面表示的快捷方式。 |
导航窗口参考
使用Navigation窗口指定你在场景中使用的NavMesh代理和区域的类型。
要进入Navigation窗口,在主菜单中转到Window > AI > Navigation。
Agents选项卡
Agents选项卡包含允许你定义在场景中使用的代理类型的属性。
| 属性 | 描述 |
|---|---|
| Agent Types | 选择要修改的代理类型。点击"+"图标添加代理类型。点击"-"图标移除当前选中的代理类型。 |
| Name | 指定代理类型的名称。 |
| Radius | 定义代理中心可以靠近墙壁或边缘的距离。 |
| Height | 指定此类型代理在Unity单位中的高度。 |
| Step Height | 指定此类型代理可以爬升的最大步高。 |
| Max Slope | 指定代理可以爬升的斜坡的陡峭程度。在文本框中输入值,或拖动滑块调整值(以度为单位)。 |
生成的链接
下表描述了定义此代理类型生成链接限制的属性。
| 属性 | 描述 |
|---|---|
| Drop Height | 指定此代理类型可以跳下的最大高度。 |
| Jump Distance | 指定此代理类型的跳跃链接的最大距离。 |
Areas选项卡
Areas选项卡允许你指定在场景中使用的不同区域类型行走有多困难。有29个自定义区域类型和3个内置区域类型:
- Walkable是一个通用区域类型,指定该区域可以行走。
- Not Walkable是一个通用区域类型,阻止导航。它适用于你想要将某个对象标记为障碍物,但不希望NavMesh在其上的情况。
- Jump是一个分配给所有自动生成的OffMesh Links的区域类型。
下表描述了Areas选项卡上可用的属性。
AI Navigation首选项参考
使用AI Navigation首选项指定导航网格(NavMesh和HeightMesh)在场景视图中的显示方式。
AI Navigation首选项位于Preferences窗口中。要打开AI Navigation首选项,请执行以下操作:
- 在主菜单中,转到Edit > Preferences。
- 选择AI Navigation。
下表描述了AI Navigation首选项选项卡中可用的控件。
| 控件 | 描述 |
|---|---|
| Selected Surfaces Opacity | 指定当前选择层次结构中NavMesh Surface实例显示的网格(NavMesh和HeightMesh)的不透明度。 |
| Unselected Surfaces Opacity | 指定当前选择层次结构之外NavMesh Surface实例显示的网格(NavMesh和HeightMesh)的不透明度。 |
| Height Mesh Color | 设置用于显示HeightMesh的颜色。 |
| Reset to Defaults | 将所有NavMesh可视化设置参数重置为其默认值。 |
注意:NavMesh以Navigation窗口Areas选项卡中描述的颜色表示。该调色板无法修改。
AI Navigation overlay参考
AI Navigation overlay允许你控制NavMesh表面、代理和GameObjects在场景视图中的显示。你可以使用它来帮助你调试任何与AI Navigation和寻路相关的问题。
AI Navigation overlay默认停靠在Scene视图的右下角。
Surfaces
本节控制NavMesh Surface实例的显示方式。下表描述了overlay的Surfaces部分中可用的控件。
| 控件 | 描述 |
|---|---|
| Show Only Selected | 仅显示当前场景选择层次结构中的表面。你可以在Preferences窗口中设置选定和非选定表面的不透明度。有关更多详细信息,请参阅AI Navigation首选项。 |
| Show NavMesh | 显示相关表面的导航网格。用于显示此网格的颜色是区域类型定义的那些。 |
| Show HeightMesh | 显示相关表面的HeightMeshes(表面精确的高度信息)。 |
Agents
本节控制当前选中的NavMesh Agents的显示信息。下表描述了overlay的Agents部分中可用的控件。
| 控件 | 描述 |
|---|---|
| Show Path Polygons | 以较深的颜色显示代理路径中的NavMesh凸多边形部分。 |
| Show Path Query Nodes | 以黄色显示路径查找查询期间探索的路径查询节点。 |
| Show Neighbors | 显示碰撞避免邻居(动态障碍物)相对于代理的位置。 |
| Show Walls | 显示代理的碰撞避免墙(静态障碍物)。 |
| Show Avoidance | 显示碰撞避免过程中采样的不同位置。 |
Obstacles
本节控制当前选中的NavMesh Obstacles的显示信息。下表描述了overlay的Obstacles部分中可用的控件。
| 控件 | 描述 |
|---|---|
| Show Carve Hull | 显示用于雕刻NavMesh的凸形状。 |
NavMesh Agent组件参考
NavMesh Agent组件允许你创建角色(代理),它们在穿过场景时避免彼此。代理使用NavMesh在游戏空间中导航并避免彼此和其他移动障碍物。你可以使用NavMesh Agent的脚本API来处理寻路和空间推理。
要使用NavMesh Agent组件,请将其添加到GameObject:
- 选择代表你的代理的GameObject。
- 在Inspector中,点击Add Component。
- 选择Navigation > NavMesh Agent。
NavMesh Agent组件显示在Inspector窗口中。
你可以使用此组件创建NavMesh agents。有关详细信息,请参阅Create a NavMesh Agent。有关NavMesh agents的更多信息,请参阅About NavMesh agents。
下表描述了NavMesh agent组件中可用的属性。
| 属性 | 描述 |
|---|---|
| Agent type | 选择你要创建的代理类型。这允许代理沿为所选代理类型创建的任何NavMesh移动。 |
| Base offset | 指定碰撞圆柱体相对于transform pivot点的偏移量。 |
Steering
| 属性 | 描述 |
|---|---|
| Speed | 设置代理沿路径移动的最大速度(以Unity单位/秒)。 |
| Angular Speed | 设置代理的最大旋转速度(以度/秒)。 |
| Acceleration | 设置代理的最大加速度(以Unity单位/秒平方)。 |
| Stopping Distance | 指定代理可以到达其目的地的距离。代理在到达此接近目的地时停止。 |
| Auto Braking | 指定代理在接近目的地时是否减速。启用时,代理在接近目的地时减速。如果你希望代理在多个点之间平滑移动(例如,如果代理是守卫),请禁用此功能。 |
Obstacle Avoidance
| 属性 | 描述 |
|---|---|
| Radius | 指定从代理中心到计算与其他GameObject碰撞的距离。 |
| Height | 指定代理需要通过高于其头顶的障碍物所需的高度间隙。例如,门口或隧道的最小高度。 |
| Quality | 选择障碍物避免质量。如果你有大量代理,可以降低障碍物避免质量以减少性能成本。如果你将障碍物避免质量设置为无,则碰撞会解决,但其他代理和障碍物不会被主动避免。 |
| Priority | 指定代理避免彼此的行为。代理避免更高优先级的其他代理,并忽略更低优先级的其他代理。值应在0-99范围内,其中较低的数字表示更高的优先级。 |
Path Finding
| 属性 | 描述 |
|---|---|
| Auto Traverse OffMesh Link | 指定代理是否自动遍历OffMesh links。启用时,代理自动遍历OffMesh links。如果要在动画或特定方式下遍历OffMesh links,请禁用Auto Traverse OffMesh Link。 |
| Auto Repath | 指定当代理到达部分路径的末尾时它做什么。当没有到目的地的路径时,Unity生成到最接近目的地的可达位置的路径。如果启用此属性,当代理到达部分路径的末尾时,它会再次尝试找到到目的地的路径。 |
| Area Mask | 指定代理在寻找路径时考虑的area types。你可以选择多个选项。当你准备为NavMesh烘焙网格时,你可以设置每个网格的区域类型。例如,你可以用特殊区域类型标记楼梯,并限制某些代理类型使用楼梯。 |
NavMesh Surface
NavMesh Surface组件表示特定NavMesh Agent类型的可行走区域,并定义场景中应构建NavMesh的部分。
要使用NavMesh Surface组件,请导航到GameObject > AI > NavMesh Surface。这将创建一个带有附加的NavMesh Surface组件的空GameObject。一个场景可以包含多个NavMesh Surfaces。
你可以将NavMesh Surface组件添加到任何GameObject。当你想使用GameObject的层次结构来定义哪些GameObject有助于NavMesh时,这很有用。
参数
| 属性 | 描述 |
|---|---|
| Agent Type | 使用NavMesh Surface的NavMesh Agent类型。用于烘焙设置和在寻路期间将NavMesh Agent匹配到适当表面。 |
Default Area
| 属性 | 描述 |
|---|---|
| Walkable (this is the default option) | |
| Not Walkable | |
| Jump | 使用NavMesh Modifier组件更详细地修改区域类型。 |
Generate Links
如果启用此选项,烘焙过程中收集的对象将被考虑生成链接。有关更多信息,请参阅Links Generation部分。
Use Geometry
- Render Meshes - 使用来自Render Meshes和Terrains的几何体。
- Physics Colliders - 使用来自Colliders和Terrains的几何体。代理可以使用此选项比Render Meshes选项更接近环境物理边界的边缘。
NavMesh Data
(只读)定位存储NavMesh的资产文件。
Clear
使用当前设置烘焙NavMesh。
Bake
使用NavMesh Surface组件的主要设置对输入几何体进行广泛过滤。使用NavMesh Modifier组件微调Unity如何处理每个GameObject的输入几何体。
烘焙过程自动排除具有NavMesh Agent或NavMesh Obstacle的GameObject。它们是NavMesh的动态用户,因此不贡献于NavMesh构建。
Object collection
| 属性 | 描述 |
|---|---|
| Collect Objects | 定义用于烘焙的GameObject。 - All - 使用所有活动GameObject(这是默认选项)。 - Volume - 使用与边界体积重叠的所有活动GameObject。边界体积之外但在代理半径内的几何体被考虑用于烘焙。 - Children - 使用NavMesh Surface组件的所有子层次结构中的所有活动GameObject,以及组件所在的GameObject。 |
| Include Layers | 定义包含在烘焙过程中的层。除了Collect Objects,这还允许进一步排除特定GameObject从烘焙(例如,效果或动画角色)。这默认设置为Everything,但你可以切换选项(通过打勾)或单独关闭。 |
Advanced Settings
高级设置部分允许你自定义以下附加参数:
| 属性 | 描述 |
|---|---|
| Override Voxel Size | 控制Unity处理NavMesh烘焙输入几何体的准确性(这是速度和准确性之间的权衡)。选中复选框以启用。默认为未选中(禁用)。3 voxels per Agent radius(6 per diameter)允许捕获狭窄通道,如门,同时保持快速烘焙时间。对于大型开放区域,使用1或2 voxels per radius进行烘焙。紧密的室内点更适合较小的voxels,例如4到6 voxels per radius。超过8 voxels per radius通常不会提供太多额外好处。 |
| Override Tile Size | 为了使烘焙过程并行化和内存高效,场景被划分为用于烘焙的tiles。NavMesh上可见的白色线条是tile边界。默认的tile大小是256 voxels,这在内存使用和NavMesh碎片化之间提供了良好的权衡。要更改此默认值,请选中此复选框,并在Tile Size字段中输入你想要的voxels数量。较小的tiles,更碎片化的NavMesh。这有时会导致非最优路径。NavMesh雕刻也 operates on tiles。如果你有很多障碍物,你可以通过使tile大小更小(例如约64到128 voxels)来加快雕刻速度。如果你计划在运行时烘焙NavMesh,使用较小的tile大小以保持内存使用量。 |
| Minimum Region Area | 允许你从较大的NavMesh中剪掉小区域。构建NavMesh的过程不保留mesh的拉伸,因为mesh小于指定的tile大小。如果区域straddles一个tile边界,则该区域可能不会被移除,尽管Minimum Region Area参数。NavMesh在tiles的网格中并行构建。如果tile边界周围的tiles不可访问,则该区域在构建过程的某个阶段被移除。 |
| Build Height Mesh | 启用创建额外的数据,用于更准确地确定NavMesh上任何位置的高度。Height Mesh组件在Unity 2022.2.0f1之后可用。 |
NavMesh Modifier组件参考
使用NavMesh Modifier组件在运行时调整GameObject的行为。NavMesh Modifier组件仅在生成过程中影响NavMesh。这意味着NavMesh在烘焙时更新以反映对NavMesh Modifier组件的任何更改。
要使用NavMesh Modifier组件,请将其添加到GameObject:
- 在Inspector中选择Add Component,然后选择Navigation > NavMesh Modifier。
NavMesh Modifier组件显示在Inspector窗口中。
NavMesh Modifier组件也可以层次结构地影响NavMesh生成。这意味着附加组件的GameObject及其所有子对象都会受到影响。此外,你可以在层次结构中更下方放置另一个NavMesh Modifier以覆盖更高级别的NavMesh Modifier。
要层次结构地应用NavMesh Modifier,请选择Apply To Children属性。
注意:NavMesh Modifier组件替换了你可以从Navigation窗口Objects选项卡启用的旧Navigation Static设置,以及GameObject上的Navigation Static标志。NavMesh Modifier组件在运行时可用,而Navigation Static标志仅在编辑器中可用。
下表描述了NavMesh Modifier组件中可用的属性。
| 属性 | 描述 |
|---|---|
| Mode | 指定是否考虑或忽略受影响的GameObject(s)。 |
| Add or Modify Object | 在构建NavMesh时考虑受影响的GameObject(s)。 |
| Remove Object | 在为指定代理类型构建NavMesh时忽略受影响的GameObject(s)。 |
| Affected Agents | 指定NavMesh Modifier影响的代理。例如,你可以选择让某些障碍物被特定代理忽略。 |
| All | 修改所有代理的行为。 |
| None | 从修改的行为中排除所有代理。 |
| Apply to Children | 将此配置应用于GameObject的子层次结构。要覆盖此组件在层次结构中更下方的影响,添加另一个NavMesh Modifier组件。 |
| Override Area | 更改受影响GameObject(s)的区域类型。 |
| Area Type | 如果你想更改区域类型,请选中复选框,然后在Area Type下拉菜单中选择新的区域类型。 |
| Force the NavMesh bake process to either include or ignore the affected GameObject(s) when you generate links. | |
| Override Generate Links | 指定是否在生成链接时包含或忽略受影响的GameObject(s)。 |
| Generate Links | 要在NavMesh烘焙过程中包含GameObject(s),请选中此复选框。要忽略GameObject(s)在NavMesh烘焙过程中生成链接,请清除此复选框。 |
NavMesh Modifier Volume组件参考
使用NavMesh Modifier Volume组件更改任何NavMeshes在定义区域内的区域类型。可用的属性允许你定义受影响的区域并指定你想要的区域类型更改。但是,修饰符体积仅影响为所选代理类型新构建的NavMeshes。它对场景中已存在的NavMeshes或不受影响的代理类型没有影响。
你需要将NavMesh Modifier Volume组件添加到GameObject。虽然你可以将NavMesh Modifier Volume组件添加到任何场景,但通常将其添加到与你要影响的NavMesh关联的GameObject。
要向GameObject添加NavMesh Modifier Volume组件,请执行以下操作:
- 选择你要使用的GameObject。
- 在Inspector中,选择Add Component > Navigation > NavMesh Modifier Volume。
NavMesh Modifier Volume组件显示在Inspector窗口中。
要更改整个GameObject的区域类型,请使用NavMesh Modifier组件。
NavMesh Modifier Volume在当你需要将区域类型分配给你的NavMesh的一部分,而该部分可能不表示为单独的几何体时很有用。例如,你可以使用NavMesh Modifier Volume使你的NavMesh的一部分不可行走或更难穿过。NavMesh Modifier Volume始终将其区域类型分配给与NavMesh Modifier对象重叠的NavMesh,即使区域类型具有较低的索引。当多个体积重叠时,具有最高索引值的区域类型优先。这些规则的例外是分配给任何重叠组件的内置Not Walkable区域类型始终是最重要的。
NavMesh Modifier Volume影响NavMesh生成过程。因此,NavMesh会更新以反映对NavMesh Modifier Volumes的任何更改。
下表描述了NavMesh Modifier Volume组件中可用的属性。
| 属性 | 描述 |
|---|---|
| Edit Volume | 切换在Scene视图中编辑体积大小的能力。要修改体积大小,请点击Edit Volume按钮。一个带有手柄的线框表示体积,在Scene视图中显示。拖动手柄以修改体积的大小。 |
| Size | 指定NavMesh Modifier Volume的尺寸,由XYZ测量定义。 |
| Center | 指定NavMesh Modifier Volume相对于GameObject中心的中心,由XYZ坐标定义。 |
| Area Type | 选择NavMesh Modifier Volume应用于定义区域内NavMeshes的区域类型。可用选项包括Navigation窗口Areas选项卡中定义的所有具有成本的区域类型。 |
| Open Area Settings | 打开Navigation窗口的Areas选项卡以定义新的区域类型或修改现有的区域类型。 |
| Affected Agents | 选择NavMesh Modifier Volume更改适用的代理类型。例如,你可以使选定的NavMesh Modifier Volume成为仅对某些代理类型的危险区域。可用选项包括Navigation窗口中定义的所有代理类型,无论是现在还是将来。 |
| All | 将更改应用于所有定义的代理类型。 |
| None | 不将更改应用于任何定义的代理类型。 |
| Defined area types | 将更改应用于选定的代理类型。你可以选择多个代理类型。 |
NavMesh Obstacle组件参考
NavMesh Obstacle组件允许你定义NavMesh Agents在导航世界时应避免的障碍物(例如,桶或箱子),这些障碍物由物理系统控制。它允许你定义
AI Navigation
导航系统允许你创建能够在游戏世界中智能移动的角色。这些角色使用从场景几何体自动创建的导航网格。动态障碍物允许你在运行时改变角色的导航,而离网格链接让你构建特定的动作,比如开门或跳过间隙或从边缘跳下。本节详细描述了Unity的导航和寻路系统。
下表描述了AI Navigation包文档的主要主题。
主题
描述
What's new
查看AI Navigation包最新版本中的更改。
Upgrade
将你的项目转换为使用新的导航系统。
Navigation System
理解使用Unity中AI Navigation所需的关键概念。
Navigation Overview
使用此包创建NavMeshes、代理、链接和障碍物。
Navigation Interface
了解此包中导航组件的接口。
Samples
了解此包中包含的示例项目。
Glossary
查看AI Navigation术语定义。
What's new in AI Navigation version 1.1.1
这是一个新包版本。
AI Navigation包版本1.1.1与之前存在的功能相比的更改摘要。
此版本的主要更新包括:
Added
• NavMesh Surface的新选项,用于烘焙HeightMesh和自动生成链接。
• NavMesh Surface中的新选项,仅使用标记为NavMesh Modifiers的源对象。
• NavMesh Modifier中的新选项,也作用于子对象。
Updated
• 仅当包安装时,导航窗口才可用。
• 从导航窗口中移除了Bake和Object选项卡,因为它们不再需要。但是,你仍然可以在导航(已弃用)窗口中找到它们。
• 导航静态和离网格链接生成选项不再在静态编辑器标志下拉菜单中可用。但是,你仍然可以在导航(已弃用)窗口的对象选项卡中找到它们。
• 改进了几个包含的示例。
要查看此版本的所有更改和更新,请参阅AI Navigation包更新日志。
Upgrade projects for use with AI Navigation package
从Unity 2022.2开始,Unity中的导航和寻路由AI Navigation包处理。
如果你有在Unity早期版本中创建的导航功能的项目,AI Navigation包会自动安装并添加到你的项目中。你可以选择以下操作之一:
• 继续按原样使用你的项目
• 将项目转换为使用新包
Remove old component scripts
如果你的项目使用了由从Unity的NavMeshComponents GitHub仓库下载的脚本定义的NavMesh Surface、NavMesh Modifier、NavMesh Modifier Volume或NavMesh Link组件,那么在将AI Navigation包添加到项目之前,请移除这些脚本及其关联文件。如果不移除这些脚本,你可能会在控制台中遇到与这些组件相关的冲突和错误。新组件反映了与旧组件相同的行为,除了使用以下组件时:
• NavMesh Surface组件现在包含一个选项,仅在烘焙过程中使用具有NavMesh Modifier的对象。
• 你现在可以指定是否将NavMesh Modifier组件应用于层次结构中的子对象。
Convert your project
如果你想使用新包,你需要转换你的项目。作为转换过程的一部分,NavMesh Updater进行以下更改:
• 之前烘焙并嵌入场景中的任何NavMesh现在从在新的GameObject上创建的NavMeshSurface组件引用。
• 任何标记为Navigation Static的对象现在具有带有适当设置的NavMeshModifier组件。
要转换你的项目,请执行以下操作:
-
在主菜单中,转到Window > AI > NavMesh Updater。
-
在NavMesh Updater窗口中,选择要转换的数据类型。
-
点击Initialize Converters以检测并显示你选择的数据类型。
-
选择要转换的数据。
-
点击Convert Assets完成转换。
Create new agent types
如果不同场景中烘焙的NavMeshes使用了不同的代理设置,那么你需要创建新的代理类型来匹配这些设置。
要创建代理类型,请执行以下操作:
-
在主菜单中,转到Window > AI > Navigation。
-
选择Agents。
-
创建新条目并指定相关设置。
Assign new agent types
当你创建了新的代理类型后,你需要按如下方式分配它们:
• 将新创建的代理类型分配给为该场景创建的NavMeshSurface中的相应NavMeshSurfaces。
• 将代理类型分配给打算使用该NavMesh的NavMeshAgents。
要找到每个现有NavMesh使用的设置,请在Project窗口中选择NavMesh .asset文件。NavMesh设置将在Inspector中显示。
Navigation System in Unity
本节描述了使用Unity中AI Navigation所需的关键概念。它包含以下主题:
主题
描述
Inner Workings of the Navigation System
了解AI Navigation系统的不同元素如何协同工作。
Inner Workings of the Navigation System
当你想要智能地移动游戏中的角色(或称为代理)时,你需要解决两个问题:如何推理关于目的地,然后如何移动到那里。这两个问题紧密耦合,但性质不同。推理关于目的地的问题是更全局和静态的,因为它考虑整个场景。移动到目的地是更局部和动态的,它只考虑移动的方向以及如何避免与其他移动代理的碰撞。
Walkable Areas
导航系统需要自己的数据来表示游戏场景中的可行走区域。可行走区域定义了场景中代理可以站立和移动的地方。在Unity中,代理被描述为圆柱体。可行走区域是通过测试代理可以站立的位置从场景几何体自动构建的。然后这些位置连接到位于场景几何体顶部的表面。这个表面称为导航网格(NavMesh)。
NavMesh将此表面存储为凸多边形。凸多边形是一种有用的表示,因为我们知道多边形内任意两点之间没有障碍物。除了多边形边界,我们还存储有关哪些多边形是彼此邻居的信息。这允许我们推理整个可行走区域。
Finding Paths
要在场景中的两个位置之间找到路径,我们首先需要将起点和终点位置映射到它们最近的凸多边形。然后我们从起点开始搜索,访问所有邻居,直到到达目标凸多边形。跟踪访问的凸多边形允许我们找到从起点到终点的凸多边形序列,这将引导我们找到路径。找到路径的常见算法是A*(发音为"A star"),这是Unity使用的算法。
Following the Path
描述从起点到目标凸多边形的凸多边形序列称为走廊。代理将通过始终转向走廊的下一个可见角点来达到目的地。如果你有一个简单的游戏,其中只有一个代理在场景中移动,那么在一个 swoop 中找到走廊的所有角点并让角色沿连接角点的线段移动是没问题的。
当处理同时移动的多个代理时,它们在避免彼此时会偏离原始路径。尝试使用由线段组成的路径来纠正此类偏差很快变得困难和容易出错。
由于每个帧中代理的移动量很小,我们可以使用凸多边形的连通性来修复走廊,以防我们需要稍微绕道。然后我们快速找到下一个可见角点来转向。
Avoiding Obstacles
转向逻辑获取下一个角点的位置,并基于此计算出到达目的地所需的期望方向和速度(或速度)。使用期望速度移动代理可能导致与其他代理的碰撞。
障碍物避免选择一个新的速度,该速度在向期望方向移动和防止与其他代理和导航网格边缘的未来碰撞之间取得平衡。Unity使用互易速度障碍(RVO)来预测和防止碰撞。
Moving the Agent
在转向和障碍物避免之后,最终速度被计算。在Unity中,代理使用简单的动态模型进行模拟,该模型还考虑加速度以允许更自然和流畅的移动。
在此阶段,你可以将模拟代理的速度提供给动画系统,使用root motion移动角色,或者让导航系统处理这一点。
一旦代理使用任一方法移动,模拟代理的位置将被移动并约束到NavMesh。这最后一步对于鲁棒的导航很重要。
Global and Local
要了解导航,最重要的事情之一是全局导航和局部导航之间的区别。
全局导航用于在世界范围内找到走廊。在世界范围内找到路径是一个成本高昂的操作,需要相当多的处理能力和内存。
描述路径的凸多边形线性列表是用于转向的灵活数据结构,并且可以根据代理的位置局部调整。局部导航试图弄清楚如何高效地向下一个角点移动而不与其他代理或移动对象碰撞。
Two Cases for Obstacles
许多导航应用需要其他类型的障碍物,而不仅仅是其他代理。这些可能是射击游戏中的箱子、桶或车辆。这些障碍物可以使用局部障碍物避免或全局寻路来处理。
当障碍物移动时,最好使用局部障碍物避免。这样代理可以预测性地避免障碍物。当障碍物静止时,并且可以被视为阻挡所有代理的路径,障碍物应该影响全局导航,即导航网格。
改变NavMesh称为雕刻。该过程检测障碍物的哪些部分接触NavMesh,并在NavMesh中雕刻孔洞。这是一个计算成本高昂的操作,这也是为什么移动障碍物应该使用碰撞避免来处理的原因。
About shortcuts between positions on NavMeshes
NavMesh凸多边形之间的连接使用寻路系统内的链接来描述。有时有必要让代理导航到不可行走的区域,例如跳过栅栏或穿过关闭的门。这些情况需要知道动作的位置。
你可以使用OffMesh Link组件注释这些动作,该组件告诉寻路器存在通过指定链接的路线。你的代码可以稍后访问此链接,并在代理跟随路径时执行特殊动作。
About NavMesh agents
NavMesh代理是一个GameObject,由一个直立圆柱体表示,其大小由Radius和Height属性指定。圆柱体随GameObject移动,但即使GameObject旋转也保持直立。圆柱体的形状用于检测和响应与其他代理和障碍物的碰撞。当GameObject的锚点不在圆柱体底部时,使用Base Offset属性指定高度差。
圆柱体的高度和半径在Navigation窗口和各个代理的NavMesh Agent组件属性中指定。
• Navigation窗口设置描述所有NavMesh Agents如何与静态世界几何体碰撞和避免。
• NavMesh Agent组件属性值描述代理如何与移动障碍物和其他代理碰撞。
通常你在两个地方设置代理的大小相同的值。但是,你可能给一个重士兵更大的半径,这样其他代理会在你的士兵周围留下更多空间。否则,你的士兵以与其他代理相同的方式避免环境。
About NavMesh Obstacles
NavMesh Obstacles可以在游戏过程中影响NavMesh Agent的导航,有两种方式:
Obstructing
当Carve未启用时,NavMesh Obstacle的默认行为类似于Collider。NavMesh Agents尝试避免与NavMesh Obstacle碰撞,当接近时,它们与NavMesh Obstacle碰撞。障碍物避免行为非常基本,并且具有短半径。因此,NavMesh Agent可能无法在充满NavMesh Obstacles的环境中找到绕过路径。此模式最适合障碍物持续移动的情况(例如,车辆或玩家角色)。
Carving
当Carve启用时,障碍物在静止时在NavMesh中雕刻一个孔洞。当移动时,障碍物是一个阻碍。当在NavMesh中雕刻一个孔洞时,寻路器能够导航NavMesh Agent绕过充满障碍物的区域,或者如果当前路径被障碍物阻挡,则找到另一条路线。对于通常阻挡导航但可被玩家或其他游戏事件(例如爆炸或桶)移动的NavMesh Obstacles,开启雕刻是一个好习惯。
Logic for moving NavMesh Obstacles
Unity将NavMesh Obstacle视为当它移动的距离超过Carve > Move Threshold设置的距离时移动。但是,为了减少CPU开销,孔洞仅在必要时重新计算。此计算的结果在下一帧更新中可用。重新计算逻辑有两种选项:
• 仅当NavMesh Obstacle静止时雕刻
• 当NavMesh Obstacle移动时雕刻
Only carve when the NavMesh Obstacle is stationary
这是默认行为。要启用它,请选中NavMesh Obstacle组件的Carve Only Stationary复选框。在此模式下,当NavMesh Obstacle移动时,雕刻的孔洞被移除。当NavMesh Obstacle停止移动并已静止超过Carving Time To Stationary设置的时间时,它被视为静止,并且雕刻的孔洞被更新。当NavMesh Obstacle移动时,NavMesh Agents使用碰撞避免来避免它,但不规划绕过它的路径。
仅静止雕刻通常在性能方面是最佳选择,并且当与由物理控制的GameObject关联的NavMesh Obstacle时是一个很好的匹配。
Carve when the NavMesh Obstacle has moved
要启用此模式,请取消选中NavMesh Obstacle组件的Carve Only Stationary复选框。当未选中时,当障碍物移动的距离超过Carving Move Threshold设置的距离时,雕刻的孔洞被更新。此模式适用于大型、缓慢移动的障碍物(例如,坦克),它被步兵避免。
注意:当使用NavMesh查询方法时,你应该考虑到在改变NavMesh Obstacle和该变化对NavMesh的影响之间有一帧延迟。
Navigation Areas and Costs
导航区域定义在特定区域行走有多困难,成本较低的区域在路径查找期间将被优先选择。此外,每个NavMesh Agent都有一个Area Mask,可用于指定代理可以在哪些区域移动。
在上面的示例中,区域类型用于两个常见用例:
• 水区域通过分配更高的成本使其更难行走,以处理在浅水中行走较慢的场景。
• 门区域通过特定角色使其可访问,以创建人类可以通过门,但僵尸不能的场景。
区域类型可以分配给包含在NavMesh烘焙中的每个对象,此外,每个Off-Mesh Link具有指定区域类型的属性。
Pathfinding Cost
简而言之,成本允许你控制寻路器在寻找路径时偏好哪些区域。例如,如果你将区域成本设置为3.0,那么穿过该区域的行程被认为是替代路线的三倍长。
要完全理解成本如何工作,让我们看看寻路器是如何工作的。
Nodes and links visited during pathfinding.
Unity使用A*在NavMesh上计算最短路径。它作用于连接节点的图。算法从路径起点的最近节点开始,并访问连接的节点,直到到达目标。
上面图片中的黄色点和线显示了节点和链接如何在NavMesh上放置,以及它们在A*期间被遍历的顺序。
在两个节点之间移动的成本取决于行进的距离和链接下凸多边形的区域类型成本,即distance * cost。在实践中,这意味着如果区域的成本是2.0,那么穿过此类凸多边形的距离将显示为两倍长。A*算法要求所有成本必须大于1.0。
成本对结果路径的影响可能难以调整,特别是对于较长的路径。处理成本的最佳方式是将它们视为提示。例如,如果你不想经常使用Off-Mesh Links,你可以增加它们的成本。但调整代理偏好走在人行道上的行为可能具有挑战性。
另一个你可以在某些级别上注意到的事情是寻路器并不总是选择非常短的路径。原因是节点放置。在开放区域旁边有微小障碍物的场景中,这会导致导航网格非常庞大和小型凸多边形。在这种情况下,大凸多边形上的节点可能被放置在任何地方,并且从寻路器的角度来看,它看起来像是一个绕道。
每个区域类型的成本可以在Areas选项卡中全局设置,或者你可以使用脚本覆盖每个代理的成本。
Area Types
区域类型在Navigation Window的Areas选项卡中指定。有29个自定义类型和3个内置类型:Walkable、Not Walkable和Jump。
• Walkable是一个通用区域类型,指定该区域可以行走。
• Not Walkable是一个通用区域类型,阻止导航。它适用于你想要将某个对象标记为障碍物,但不希望NavMesh在其上的情况。
• Jump是一个分配给所有自动生成的Off-Mesh Links的区域类型。
如果几个不同区域类型的对象重叠,结果名称区域类型通常将是具有最高索引的那个。有一个例外:Not Walkable始终优先。如果你需要阻止某个区域,这可能很有用。
Area Mask
每个代理都有一个Area Mask,描述它在导航时可以使用哪些区域。区域掩码可以在代理属性中设置,或者可以在运行时使用脚本操作位掩码。
区域掩码在你只想让特定类型的角色能够穿过某个区域时很有用。例如,在僵尸 evasion 游戏中,你可以将每扇门下的区域标记为Door区域类型,并从僵尸角色的Area Mask中取消选中Door区域。
Additional resources
• Create a NavMesh - 创建NavMesh的工作流程。
• NavMeshAgent.areaMask - 设置代理areaMask的脚本API。
• NavMeshAgent.SetAreaCost() - 设置代理area成本的脚本API。
Navigation Overview
本节提供有关如何为你的场景创建NavMeshes、NavMesh Agents、NavMesh Obstacles和Off-Mesh Links的详细信息。它包含以下主题:
• Create a NavMesh
• Create a NavMesh Agent
• Create a NavMesh Obstacle
• Create an Off-mesh Link
• Using NavMesh Agent with Other Components
• Advanced Navigation How-tos
Create a NavMesh
你需要创建一个NavMesh来定义场景中角色可以智能导航的区域。
要创建NavMesh,请执行以下操作:
-
选择你要添加NavMesh的场景几何体。
-
在Inspector窗口中,点击Add Component。
-
选择Navigation > NavMesh Surface。
-
在NavMesh Surface组件中,指定必要的设置。有关可用设置的详细信息,请参阅NavMesh Surface组件。
-
完成后,点击Bake。
NavMesh在Navigation窗口打开且可见时生成,并在底层场景几何体上显示为蓝色叠加层。
Creating a NavMesh Agent
一旦你为你的级别烘焙了NavMesh,是时候创建一个可以导航场景的角色了。我们将从一个圆柱体构建我们的原型代理,并设置其运动。这是使用NavMesh Agent组件和一个简单脚本完成的。
首先让我们创建角色:
-
创建一个圆柱体:GameObject > 3D Object > Cylinder。
-
默认圆柱体尺寸(高度2和半径0.5)适合人形代理,所以我们保持原样。
-
添加NavMesh Agent组件:Component > Navigation > NavMesh Agent。
现在你有一个简单的NavMesh Agent设置好,可以接收命令了!
当你开始使用NavMesh Agent进行实验时,你很可能需要根据你的角色大小和速度调整其尺寸。
NavMesh Agent组件处理角色的寻路和运动控制。在你的脚本中,导航可以简单到设置期望的目的地 - NavMesh Agent可以处理从那里开始的一切。
接下来,我们需要构建一个简单的脚本,允许你将角色发送到另一个GameObject指定的目的地,而Sphere将是目的地。
-
创建一个新的C#脚本(MoveTo.cs)并替换其内容为上面的脚本。
-
将MoveTo脚本分配给你刚刚创建的角色。
-
创建一个Sphere,这将是代理将移动到的目的地。
-
将Sphere从角色移动到靠近NavMesh表面的位置。
-
选择角色,找到MoveTo脚本,并将Sphere分配给Goal属性。
-
按 Play:你应该看到代理导航到Sphere的位置。
总结一下,在你的脚本中,你需要获取NavMesh Agent组件的引用,然后设置代理的目的地属性。Navigation How Tos将给你更多关于如何使用NavMesh Agent解决常见游戏场景的示例