Unity3D新版NavMesh系统功能初步探索

作者:沈琰
前言
相信不少朋友使用过Unity3Dd的NavMesh系统为自己的游戏添加导航寻路功能。
但是老版本的NavMesh功能虽然简单易用,但是个人在实际使用的时候经常会感觉到臃肿和不方便。
这里对老版的NavMesh的使用方法就不再赘述了,我们来看看新版本的NavMesh系统有哪些变化。
1.新版的NavMesh系统
新版的NavMesh并没有随着Unity的更新添加进引擎功能中,它作为一个开源工程放在了GitHub上。
与之一同还有一些Unity官方的示例场景展示其功能与用法,我们可以通过下载获取这个工程(要求Unity3D的版本在5.6以上)。
下载地址:
https://link.zhihu.com/?target=https%3A//github.com/Unity-Technologies/NavMeshComponents
下载下来的工程里主要就是4个组件脚本:

分别简单介绍一下这几个组件能用来干嘛:
1.NavMeshSurface:
新版NavMesh系统最核心的组件。
老版的NavMesh在烘焙时,首先需把场景内的地形物体设置为Static,烘焙时也只能整体烘
焙,十分的不灵活。
新版的NavMeshSurface则将烘焙组件化了,其可以挂载在场景上的任何游戏物体上来定义
哪些物体可以生成NavMesh(有了该组件后可以不需要设置物体Navigition static的静态属
性,以前的静态属性只适用于editor模式,不适用于实时bake的特性),一个物体上可以有多
个NavMeshSurface。
2.NavMeshLink:
这个组件可以连接2个NavMesh区域,创建一条可通行的通道。
老版的OffMeshLink在使用时需要添加两个地形游戏物体的Transform,然后在烘焙时自动
生成可通行的通道,同样的不灵活。
在实际开发中可能有需要自定义Link的起始点与终点,让创建的路径处于自己的控制,而
NavMeshLink刚好可以满足这一需求。
使用时需要注意的是可以使用多个 NavMeshLink连接多个NavMeshSurface,但是Link和
Suface都必须有相同的AgentType。如果一个物体上有多个NavMeshSurface,则多个
Surface叠在一起会不清楚开始点或结束点连接放在哪个Surface上。
3.NavMeshModifier:
此组件允许对一些要用来被NavMeshSurface组件烘焙的物体进行微调,组件的作用效果
包括其下的所有子物体。
个人感觉其功能是对NavMeshSurface的一个补足,比如地形需要设置排除agents的特
定障碍。
4.NavMeshModifierVolume:
NavMeshModifierVolume与NavMeshModifier差不多,区别是前者作用于对体积盒包
围的所有物体(或物体的部分),后者只作用于自身及其子物体。
2. 制作动态的路径控制
空谈无益, 下面以一个实际效果为例, 来看下新版NavMesh能实现什么功能。

有时候开发时可能有这么一种需求,游戏运行导航时路径能动态控制或会被动态生成的障碍物所影响。
这在老版的NavMesh系统中后者可以使用NavMesh Obstacle组件实现,前者恐怕难以实现,而现在很轻松就可以做到。
制作步骤:
新建一个场景,用Plane来创建一块地板,其中中间部分等分成100个小的Plane。

然后在每个Plane的父节点上挂上NavMeshSurface,中间100等分的小Plane上额外挂上NavMeshModifier。
然后在脚本中拿到所有小Plane的NavMeshModifier,设计个函数通过控制NavMeshModifier
的Areas在中间生成一条cost较低的路径,agent在导航时会自动选择cost低的路径。

最后用一个cube当做生成器生成挂载了agent的capsule来观察路径的改变。

每次当路径更新时,使用NavMeshSurface.BuildNavMesh()函数重新烘培,最后得到的效果如下:

可以看到效果有一些不太理想,路径更新时会有明显卡顿。其原因是每次调用BuildNavMesh()
函数时会有烘培时间,地图越大则时间越长。
不过没关系,有改进的方法。
这里需要用到异步刷新的方法来规避这个问题,借用一下官方工程的两个脚本

使用方法为:LocalNavMeshBuilder的脚本的范围框内的所有挂载了NavMeshSourceTag的物体会异步刷新其导航网格。

把LocalNavMeshBuilder挂载在场景中整个Plane的父节点上或者再创建一个空物体,调整范围框的大小覆盖要烘培的Plane。

准备工作基本完成,最后还有一点,NavMeshSourceTag脚本内对物体的NavMesh更新时并未考虑NavMeshModifier。没关系,我们可以自己修改一下:

修改NavMeshSourceTag的脚本
然后就可以去掉NavMeshSurface组件,由LocalNavMeshBuilder来进行动态烘培。
3. 测试方法

如此就实现了一个简单的路径控制功能。可以把它用在一些简单的游戏的群体寻路逻辑中,例如MOBA游戏中小兵的路径控制。

同理当场景中有障碍物或者是新生成的路径,也能实时烘培加入导航计算之中。
其实官方示例里就已经有了另外一个很好的应用例子:

没有路,自己造
4. 立体导航
前面说到新版的NavMeshLink有可以自定义起始点和终点的特性,由此带来了一个问题:
两个导航片并不在一个水平面上时,连接两者导航时会发生什么情况?
答案是导航的方向会以导航网格自身的坐标系为基准,简而言之就是立体导航成为可能。

我们来做个更为明显的演示。
首先用6个同样大小的Plane拼成一个正方形然后用NavMeshSurface烘培,用link连接正方形的
每条边,此处注意把Link的宽度设置成与正方形边长相等或者略微超出。


写一个脚本挂载到angent上让其移动到鼠标点击的位置去,然后运行效果如下:

不止如此,由于NavMeshLink组件本身的灵活性,换句话说进入link和离开link的点是可以自定
义的,由此还能实现一些很有趣的小功能。
再创建一个新的场景,这次为了让agent的移动轨迹显眼一点,在上面添加一个TrialRenderer组
件,然后用NavMeshLink连接两个导航网格,为了效果可以适当让其间距长一些。

可以看到,按照正常情况,agent在通过link时是匀速运动通过的。然后我们现在想让agent通
过link时是""瞬移"过去的该如何实现?
用NavMeshAgent里封装好的函数就能实现这一功能:
void Update (){//判断agent是否处于NavMeshLink上
if (_agent.isOnOffMeshLink)
{
//立即完成当前link
_agent.CompleteOffMeshLink();
}}

因为link的入口和出口都是可控的 可以很方便的做出类似于传送门的效果,可以加上一些粒子效
果和音效让表现力提升不少。

结束
以上只是新版NavMesh功能实现的一小部分,可以看到确实是比老版更加的方便和灵活,这
篇文章也是为了起一个抛砖引玉的效果,大家可以充分发挥自己的想象力运用到自己的项目中。
以上工程上传于GitHub。篇幅所限,大部分细节并未给出代码,有兴趣的同学可以自行下载。
下载地址: https://github.com/tank1018702/unity_001/tree/master/NavMesh_Test
最后想系统学习游戏开发的童鞋,欢迎访问 http://levelpp.com/
游戏开发搅基QQ群:869551769
微信公众号:皮皮关