【Unity】导航系统Navigation

1 前言

        新版本的Unity导航系统。可以帮助我们轻松实现角色在地图上的导航移动效果。跟老版本相比感觉差不多,不过有些地方的改进的确不错。

2 Navigation的安装

        新版本的导航系统(Navigation)是以包的形式存在的,所以需要提前去安装(菜单栏-Window-PackageManager-搜索包Navigation)。如图:

安装完成后可从编辑器上层的菜单页面查看Navigation页面,如图:

Navigation是新版本的页面,Navigation(Obsolete)是老版本的页面。

3 简单的案例

3.1 地图烘焙

        在新场景中创建一个地面Plane,然后为其添加一个空父物体Map,再在父物体上添加一个NavMeshSurface组件。这里添加一个Map父物体是为了方便管理,没有强制要求子父级关系,比如NavMeshSurface组件添加到Plane上也可以。

之后先不管其各种参数含义,直接点击Bake按钮,即可看到地图被烘焙了,导航网格被显示出来。

同时这个时候我们可以看到在Assets资源中,有一个与场景同名的文件被创建出来,文件中还会有一个.asset资源文件NavMesh-Map,这个文件主要记录我们刚才烘焙所得的各种信息。

这个时候,若我们不想要这个烘焙效果了,可直接点击Map的NavMeshSurface组件上的Clear按钮,清除烘焙。

清除后地面的烘焙效果消失,且刚刚的NavMesh-Map文件也被删除了。至此,一个简单的地图烘焙操作就完成了。

3.2 角色控制

3.2.1 完善场景

        首先,创建一个Sphere,作为我们的玩家(Player),再创建一些Cube作为墙体。我们认为Cube是墙体,那么其应为地图的一部分,为了方便管理,我们将Cube设为Map的子物体。由于我们增加了墙体,所以地图有了新内容,需要我们重新烘焙一次,再次点击Bake即可。结果如图所示,可以看到墙体周围的地面已经是不可通过的区域了:

3.2.2 完善角色

        接下来给我们的Player增加一个Nav Mesh Agent组件,用来配合地图的烘焙,准备实现我们想要的效果。

同样,现在不用关心这些参数的含义,我们先以实现小案例为主。那么之后创建一个Player.cs脚本挂到Player身上,然后增加一些代码来实现Player的导航效果。

代码:

using UnityEngine;
using UnityEngine.AI;

public class Player : MonoBehaviour
{
    //定义NavMeshAgent组件对象
    private NavMeshAgent agent;

    private void Awake()
    {
        //获取NavMeshAgent组件对象
        agent = this.GetComponent<NavMeshAgent>();
    }

    private void Update()
    {
        //实现效果:让Player导航移动到鼠标左键点击的位置。
        //若按下鼠标左键
        if(Input.GetMouseButton(0))
        {
            //向鼠标所在位置发送射线
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            //射线碰撞信息(用于接收信息)
            RaycastHit hit;
            //检测射线ray的碰撞,并将碰撞信息返回到hit中
            if (Physics.Raycast(ray, out hit))
            {
                //将射线碰撞点设置为Player导航的目的地
                //NavMeshAgent组件的应用
                agent.SetDestination(hit.point);
            }
        }
    }
}

至此小案例就完成了,运行程序,点击地面就可以看到小球(Player)的导航移动了。

3.3 演示

4 组件参数

4.1 Nav Mesh Agent

此组件主要挂载到Player身上,即谁要导航移动,就挂到谁身上。另外,挂载NavMeshAgent的对象是不会被烘焙的。

4.1.1 Agent Type

        代理者类型,可以理解为一个组,只有在一个组内的Player、烘焙的导航网格等等才会相互有所影响。Humanoid是默认的预设,我们点开这个标签页。

可以看到选项里有一个设置选项,点击Open Agent Settings,可以打开设置面板。

我们可以修改Humanoid的相关参数,也可以通过+、-按钮可以添加新的Agent Type,去自定义相关参数。

  • Name:创建的Agent Tpye名称。
  • Radius:Player中心与墙壁、窗台的距离 / 烘焙区域大小。比如地图上的一个Cube,若这个调大,那么烘焙出来的不可通过区域就会变大,看图,分别设置Radius为0.5和1。

  • Height:Player的高度 / Player可以达到的空间有多低。知道车辆限高杆不?太高就过不去,低点就过去了。

  • Step Height:Player爬台阶(障碍物)的最大高度 / 能够烘焙的阶梯的最大高度。设置一定高度即可烘焙到阶梯,如图。另外应满足Step Height<Height,换言之,你Step Height设置的再高,只要Height比你低,应用高度就是Height。

  • Max Slope:Player爬斜坡的最大角度 / 能够烘焙的斜坡的最大角度。如图。

PS:Agent Type匹配问题。

        说明下自定义Agent Type的使用,若我们新增了Agent Types并进行替换,那么Player的NavMeshAgent组件和地图上的NavMeshSurface组件上的Agent Type都要替换掉,保持一致,且地图还要重新烘焙下。如图:

NavMeshAgent需要有一个或多个与之对应的NavMeshSurface才行,即相同的Agent Type。我们也可以创建多个Player和Map,然后为它们分组,赋予不同的Agent Type,那么他们之间是不会相互影响的。如图,想要复现的话注意我修改的Collect Objects参数,让地图仅烘焙自己及其子级对象,这个在后面会讲到。

Generated Links

        生成链接。这里指跳跃链接。

  • Drop Height:最大下落高度,即从高处往下跳的最大高度。
  • Jump Distance:最大跳跃距离,即从一点跳向另一点的最大距离。

以上两个参数基本是一起使用的,但具体怎么用呢?首先找到我们的Map身上的NavMeshSurface组件,将参数Generate Links勾选了,勾选后便可以在烘焙时自动借助以上两个参数生成跳跃链接。如图:

4.1.2 Base Offset

        偏差值,用来设置偏差的,那么是谁的偏差?首先,我们来观察下上面小案例Scene窗口中的Player。

可以看到小球的外侧有一个圆柱体将其包裹其中,这个圆柱体才是我们导航系统中真正操作的对象。换句话说我是在给小球导航吗?我是在给这个圆柱体导航,小球只是附带物罢了。那么我们的这个偏差值即表示“圆柱体”与“小球”的高度偏差,注意是高度。

PS:圆柱体本来是有些陷到地面的,我这里把Player整体拉上来了一些,不过不影响,无论是陷到地下,还是浮空,还是跑出地图范围了,只要距离不太远,一旦运行都会落到烘焙的地面上或是拉到地图边缘。

那么演示操作一下:

4.1.3 Steering

        Player控制设置。

  • Speed:表示Player的移动速度,准确说是最大速度,因为开始是缓慢提速的。另外速度过高时可能会出现围绕目标点移动,没法准确停到目标点处。
  • Angular Speed:表示Player的旋转速度。
  • Acceleration:Player的加速度。
  • Stopping Distance:停止距离。在距离目标点一定距离时停止移动。
  • Auto Braking:Player靠近目标点附近时是否自动刹车。速度过大时可能会来回环绕目的点移动,这个可以防止这种情况。不过我测试发现感觉没什么用(

4.1.4 Obstacle Avoidance

        障碍物避让设置。参数所调整的便是我们上文所说的“圆柱体”,其还起到避障作用。

  • Radius:避让半径。圆柱体半径。从中心扩散半径。
  • Height:避让高度。圆柱体高度。往上提升高度。
  • Quality:避让质量。质量越高,避让越精细。
  • Priority:避让优先级。范围0-99,0为最高优先级,99位最低优先级。优先级高的Player可以撞退优先级低的Player,反之撞不动。比如抢占同一个目标点的情况,优先级高的就可以准确到达目标点位置,而优先级低的就会被优先级高的挡在外面。如图(开始时两个Player优先级相同都是50):

PS:同优先级Player间避让问题。

        若两个Player向同一目标点移动,则会出现避让(将上面的案例中的Player直接复制一个,就可以实现此效果,这时候也可以去试试上面的优先级测试)。那么目标点与两个同优先级的Player的位置关系是什么?答:是以圆柱体边缘为基点,平均靠近目标点。表述可能不太清晰,直接看下图吧,图中把一个Player的Radius增大了,然后两个Player都往目标点移动,从图中可以看到两个小球(Player)都没有到目标点的位置,基本上是两个小球的圆柱体在目标点处相切(虽然有些偏差)。

4.1.5 Path Finding

  • Auto Traverse Off Mesh Link:勾选后表示Agent可以自动的通过“跳跃链接”。取消勾选的话到达跳跃点边缘后会停止。对Off Mesh Link、NavMeshLink两个跳跃链接组件都有效。对依靠Agent Type-Generated Links-Drop Height和Jump Distance参数生成的跳跃链接也有效。
  • Auto Repath:勾选后表示当Agent的当前路径无效时,可以自动计算寻找新的路径前进。以“跳跃链接”为例,额.....算了,直接看动图吧。

需要注意,不要同时显示两条路径,这样即使隐藏另一条,即使没勾选AutoRepath,Player也会去找另一条;还要在Player达到边缘停止前完成路线1的隐藏和路线2的显示,否则即使勾选,也不会去找路线2。通过这些测试我们也大概知道这个RePath是个什么逻辑了。

  • Area Mask:能够行走的区域。Area这里是一个类似层级的东西,在后续NavMeshSurface组件会有介绍。只有在这里选中的区域才是Player可以行走的区域。

4.2 Nav Mesh Surface

此组件主要挂载到地图上,说是地图,实际上随便一个游戏物体基本都可以,一般是挂载到一个空物体上,然后子级放构建地图的游戏对象。

4.2.1 Agent Type

这个和Nav Mesh Agent的一样,就不说啦。

4.2.2 Default Area

        区域。一个类似于层级的东西。如下:

默认给我们提供了三个,可走、不可走、跳跃。当然我们也可以点击Open Area Settings自己来自定义。

又看到老朋友Agent了。在User里填写名字可以添加我们自己的区域。Cost表示消耗,可以理解为Player在面临多个区域时,选择走哪个区域的优先级判断。大家都是懒人,消耗越大的区域,自然不想走,这里也一样,Cost越大的区域优先级就越低,越小优先级越大。

4.2.3 Generate Links

        若勾选此选项,则被收集的对象会依赖于Agent Type中设置的Agent的Generated Links下的Drop Height参数和Jump Distance参数来生成“跳跃链接(Link)”。换人话就是,勾选后,在烘焙时,会借助上述两个参数自动生成“跳跃链接”。
        另外,此参数是可以借助NavMeshModifier重写覆盖的,等介绍到这个组件的时候再讲吧。
        再另外....生成的跳跃链接是属于默认提供的Jump Area的!

4.2.4 Use Geometry

        使用几何。即我们使用怎样的几何体来进行烘焙,有两个选项如下:

  • Render Meshes:渲染网格。
  • Physics Colliders:物理碰撞体。推荐这个吧,性能上好些。

4.2.5 Object Collection

        对象收集。被收集的对象在Bake时才能被烘焙到。

Collect Objects:收集对象的方式。

  • All Game Objects:收集所有游戏对象。
  • Volume:设置一个3D矩形区域,仅收集此区域内的对象,或是对象的一部分,比如地面的一部分,如图:

  • Current Object Hierachy:收集当前NavMeshSurface组件所挂在对象及其所有子物体。
  • NavMeshModifier Component Only:挂载了NavMeshModifier组件的对象才会被收集。

PS:需要注意,挂载NavMeshAgent或NavMeshObstacle组件的对象是不会被收集烘焙的。

Include Layers:收集对象时的层级筛选。在满足Collect Objects收集方式的前提下,对收集对象进行层级筛选,只有属于包含层级的对象才能被收集。

4.2.6 Advanced

        高级设置。

  • Override Voxel Size:是否重写(设置)Voxel Size。
  • Voxel Size:设置Voxel Size。

什么是Voxel Size?立体像素的大小。其影响着我们借助借助几何形状烘焙生成的导航网格的准确性。立体像素比较合适的大小是每个Agent Radius 2-4尺寸,立体像素尺寸越小,导航网格生成的时间越长。

  • Override Tile Size:是否重写(设置)Tile Size。
  • Tile Size:设置Tile Size。

什么是Tile Size?瓷砖尺寸(?),好吧就是区块之类的东西的尺寸。这个东西大概.....就是说我们重新烘焙时,变化的区域块,即变化是按块划分的,一块一块的变化,这个值越小的话就可以获得更细节变化,即效果更好,当然带来的缺点是会生成更多的数据存储起来。

  • Minimum Region Area:最小区域。借助这个参数,可剔除未连接的小型导航网格区域。表面积小于指定值的导航网格区域将被移除。注意,即使勾选了也可能无法删除某些区域,导航网格是以区块网格(就上面Tile)形式并行构建而成的,如果某个区域跨越区块边界,则不会移除该区域。这是因为,区域修剪发生在构建过程的一个阶段,周围的区块是不可访问的。
  • Build Height Mesh:为Nav Mesh Agent生成一个更为精确的高度网格。烘焙生成导航网格的时候可以发现楼梯实际上是以斜体形式形成导航网格的,这是因为导航网格是可行走空间的近似形状,会让一些特征扁平化,所以有时候我们导航的时候所移动的位置就会不准确,在这种情况下就可以勾选这个,去构建高度网格。注意,构建高度网格会在运行时占用内存与处理资源,并会增加烘焙生成导航网格的时间。

4.2.7 Nav Mesh Data

        就是烘焙后产生的数据文件,前面有提过的。Clear后就没了,Bake就有。

4.3 NavMeshLink

此组件是用来创建“跳跃链接”的,实现Player在不同地形间跳跃。一般是将其挂载到一个空物体上,就会形成一个桥梁一样的东西。如图:

4.3.1 Agent Type

和之前两个组件的一样,这里就不多介绍了。不过和之前一样,这里Agent Type的选择需要与Player、Map保持一致,否则不会生效。

4.3.2 Start Point&End Point

  • Start Point:起始点,即跳跃的起始位置。一般来说,只能从起始位置跳跃。
  • End Point:终点,即跳跃的终点位置。一般来说,从这边是无法跳跃的。
  • Swap:点击交换起始点与终点。
  • Align Transform:定位“跳跃链接”的锚点到中心。前面说了,此脚本一般是挂载到空物体上的,那么这个空物体就形成了一个桥梁,既然是一个对象,那就有自己的锚点,通过调整Start Point、End Point很容易让锚点偏移,这个按钮就是用来调整锚点到中心的。如图:

4.3.3 Width

        路径宽度。越宽,可走范围就越大。如图:

4.3.4 Cost Modifier

        Cost修改(覆盖/重写)。首先,前面说过Area这个东西,如同层级一样,是有优先级的,即Cost。这里,若为正值,则此“跳跃链接”的Cost为我们输入的值,否则使用其所属Area的Cost。

4.3.5 Auto Update Position

        官网解释:如果启用此属性,当端点移动时,网格外链接将重新连接到导航网格。如果禁用,即使移动了端点,链接也将保持在其起始位置。
        感觉这个是给老组件Off-Mesh Link用的,老组件在运行时若修改两个端点的位置,只有在勾选此项时,“跳跃链接”才会跟着端点调整。现在这个新Link组件,无论勾不勾上,都可以随时跟着端点调整,似乎用不到啊。

4.3.6 Bidirectional

        双向。即无论从起始点还是终点,都可以跳跃!

4.3.7 Area Type

        区域类型。就是设置Area的,前面都讲过了。

4.4 Nav Mesh Obstacle

导航网格障碍物组件。谁是障碍物就挂到谁身上。Player的移动是会避开障碍物的。另外,挂载NavMeshObstacle组件的对象是不会被烘焙的。

4.4.1 Shape

        障碍物几何体的形状。分为Box(方体)和Capsule(胶囊)两种。无论我们挂载的对象是什么样子,这个选项才是真正的起障碍物作用的几何形状,类似碰撞体。

4.4.2 Center

        几何形状的中心位置。

4.4.3 Size

        几何形状尺寸,在选择Box和Capsule时,可调参数是不同的,这个很简单就不说了。

4.4.4 Carve

        雕刻。勾选此选项后,导航网格障碍物会在导航网格中创建一个孔,即在烘焙的网格中挖出一个不可经过的区域。同时会出现三个参数,这三个参数是来调整导航网格障碍物移动时如何重新雕刻孔的。

  • Move Threshold:当导航网格障碍物的移动距离超过 Move Threshold 设置的值时,Unity 会将其视为移动状态。使用此属性可设置该阈值距离来更新移动的雕孔。
  • Time To Stationary:将障碍物视为静止状态所需等候的时间(以秒为单位)。
  • Carve Only Stationary:启用此属性后,只有在静止状态时才会雕刻障碍物。

        开启与关闭的区别图示:

更详细的官方解释:

障碍与雕刻

        导航网格障碍物可通过两种方式影响导航网格代理(即Player,官方文档称其为代理)在游戏中的导航:

  • 障碍

        未启用 Carve 时,导航网格障碍物的默认行为类似于碰撞体的行为。导航网格代理会尝试避免与导航网格障碍物的碰撞,当靠近时,它们会与导航网格障碍物碰撞。障碍躲避行为是非常基本的,具有一条短半径。因此,导航网格代理可能无法在导航网格障碍物很混乱的环境中找到方向。此模式最适合用于障碍物不断移动的情况(例如,车辆或玩家角色)。

  • 雕刻

        启用 Carve 时,障碍物处于静止状态时将在导航网格中雕刻一个孔。移动时,障碍物即为障碍物。在导航网格中雕刻一个孔后,寻路器 (pathfinder) 能够让导航网格代理绕过雕有障碍物的位置周围,或者如果当前路径被障碍物阻挡,则寻找另一条路线。对于通常会阻碍导航但可被玩家或其他游戏事件(如爆炸)移动的导航网格障碍物(例如板条箱或木桶),最好为其开启雕刻功能。

官方文档图片

(PS:若以障碍物方式,就会在这种情况下被卡住。说明还是通过在导航网格上雕刻来寻路更为智能一些。)

移动的导航网格障碍物的逻辑

        当导航网格障碍物的移动距离超过 Carve > Move Threshold 设置的值时,Unity 会将其视为移动状态。当导航网格障碍物移动时,雕刻的孔也会移动。但是,为了减少 CPU 开销,只在必要时才重新计算该孔。此计算的结果将在下一帧更新中可用。重新计算逻辑有两个选项:

  • 仅当导航网格障碍物静止时才进行雕刻

        这是默认行为。要启用此选项,请勾选导航网格障碍物组件的 Carve Only Stationary 复选框。在此模式下,当导航网格障碍物移动时,雕刻的孔将被移除。当导航网格障碍物已停止移动并且静止时间超过 Carving Time To Stationary 设置的值时,障碍物将被视为静止状态,并再次更新雕刻的孔。当导航网格障碍物为移动状态时,导航网格代理会使用碰撞躲避功能避开它,但不会在它周围规划路径。
        Carve Only Stationary 通常是性能方面的最佳选择,当与导航网格障碍物相关联的游戏对象由物理系统控制时,是很适合的选项。

  • 导航网格障碍物移动后进行雕刻

        要启用此模式,请取消勾选导航网格障碍物组件的 Carve Only Stationary 复选框。取消勾选此复选框的情况下,当障碍物移动的距离超过 Carving Move Threshold 设置的值时,雕刻的孔会更新。此模式适用于大型缓慢移动的障碍物(例如,步兵避开的坦克)。

        注意:使用导航网格查询方法时,应考虑到更改导航网格障碍物与该更改对导航网格生效之间存在一帧延迟。

4.5 Nav Mesh Modifier

导航网格修改器。可调整特定游戏对象在导航网格烘焙期间的行为方式。就好比是对我们的Map进行局部修改一样。修改器会影响挂载到的对象以及其子级对象。

        如下图,Map是所有地图的父物体,Other是原先的地图内容,这里我增加了两块地面,并给其增加了一份父级空对象ModifierTest,再其父级对象上挂载了一个Nav Mesh Modifier组件。这样以来我的地图Map中就有两块地面是被一个导航网格修改器可修改的。后续将演示使用。

4.5.1 Mode

        修改形式。分为如下两种:

  • Add or Modify object:增加或修改对象。表示我们的修改器可用来进行修改操作。
  • Remove object:移除对象。表示我们的修改器可用来进行移除操作。

那么,如何使用呢?

Add or Modify object

        当选择Add or Modify object时,下方会多出两个参数Override Area(重写区域)、Override Generate Links(重写跳跃链接生成)。这两个重写的内容本文之前都介绍过,分别勾选如下:

可以看到,勾选后,我们可以重新选择设置的Area,以及是否自动生成Links。

        首先演示跳跃链接的生成吧,要先把Map的NavMeshSurface组件上的自动生成给关掉,然后我们重写那两块地面可以生成。如下:

可以看到只有那两块地板有跳跃链接的生成。PS:虽然跳跃链接链接到了左侧的大地面,但设置是只有那两块修改器控制的地面能生成,所以Player能从两块地面上来到大地面,但不能从大地面移动到小地面上。

        然后演示所属Area。首先我们先关闭修改器的Override Area烘焙下,默认烘焙是Walkable,然后通过修改器将两个地面的Area修改为Jump,再烘焙。如下:

可以看到那两块地面颜色变化了,Area被重写了。

Remove object

        这个就很简单了。选择这个后就表示将修改器影响的对象完全剔除烘焙。

4.5.2 Affected Agents

        影响的Agents。就是Agent Type里的那个,比如NavMeshSurface设置的Type是A,修改器是B,那么修改器就无法起到作用,除非改为A才能其作用。演示:

4.5.3 Apply To Chilren

        是否应用到孩子。前面说了修改器影响的是挂载的对象及其子物体,这里就是决定是否影响子物体的。

4.5.4 子父级的Modifier

        子父级上都挂有Modifier组件时,他们的优先级关系是什么?答案是覆盖关系,子级覆盖父级。但要注意这里覆盖的是什么,覆盖的是修改器吗?不是,覆盖的是我们的游戏对象,即不是简单的修改器替换,而是要根据修改器的参数才能确定多层修改器最终的修改结果是什么。理解了这个后面就好理解了。
        给ModifierTest加一个空物体父级F,挂载一个Modifier组件,然后....看图吧,F和ModifierTest上的组件设置是这样。

那么烘焙结果是什么?答案是两地面不烘焙。那么若把ModifierTest的Apply To Children给去掉勾选呢?答案是两地面烘焙。看演示吧:

因为我们去掉勾选后,子级修改器不会作用于孩子,所以修改不到两个地面,但这个时候要注意修改不到不代表就是默认烘焙,我们上层还有一个修改器呢,子级修改不到两个地面,那么父级对两个地面修改就不会被子级修改给覆盖掉,所以结果是父级修改器的修改结果。

4.6 Nav Mesh Modifier Volume

导航网格修改器体积。用来使用一个Box标定区域的,即将某块区域设置为某个Area(类似层级那个)。后面有演示。

4.6.1 Size

        Box大小,也可点击Edit按钮直接编辑,与碰撞体类似。

4.6.2 Center

        Box中心位置。

4.6.3 Area Type

        要修改标识为的Area。演示:

注意Box的高度,要碰撞到烘焙的地面才行,最后我拉高Box后导致无法标识地面区域。

4.6.4 Affected Agents

        影响的Agent,即Agent Type设置的那个。

5 结束语

        明明没多少东西,一做笔记就花费了好长时间....总之还好,至少后面就不怕忘了。导航系统差不多也就这些内容了,那么这次就到这里结束吧。

猜你喜欢

转载自blog.csdn.net/Davidorzs/article/details/134602222