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的靜態(tài)屬
性,以前的靜態(tài)屬性只適用于editor模式,不適用于實時bake的特性),一個物體上可以有多
個NavMeshSurface。
2.NavMeshLink:
這個組件可以連接2個NavMesh區(qū)域,創(chuàng)建一條可通行的通道。
老版的OffMeshLink在使用時需要添加兩個地形游戲物體的Transform,然后在烘焙時自動
生成可通行的通道,同樣的不靈活。
在實際開發(fā)中可能有需要自定義Link的起始點與終點,讓創(chuàng)建的路徑處于自己的控制,而
NavMeshLink剛好可以滿足這一需求。
使用時需要注意的是可以使用多個 NavMeshLink連接多個NavMeshSurface,但是Link和
Suface都必須有相同的AgentType。如果一個物體上有多個NavMeshSurface,則多個
Surface疊在一起會不清楚開始點或結束點連接放在哪個Surface上。
3.NavMeshModifier:
此組件允許對一些要用來被NavMeshSurface組件烘焙的物體進行微調,組件的作用效果
包括其下的所有子物體。
個人感覺其功能是對NavMeshSurface的一個補足,比如地形需要設置排除agents的特
定障礙。
4.NavMeshModifierVolume:
NavMeshModifierVolume與NavMeshModifier差不多,區(qū)別是前者作用于對體積盒包
圍的所有物體(或物體的部分),后者只作用于自身及其子物體。
2. 制作動態(tài)的路徑控制
空談無益, 下面以一個實際效果為例, 來看下新版NavMesh能實現什么功能。

有時候開發(fā)時可能有這么一種需求,游戲運行導航時路徑能動態(tài)控制或會被動態(tài)生成的障礙物所影響。
這在老版的NavMesh系統中后者可以使用NavMesh Obstacle組件實現,前者恐怕難以實現,而現在很輕松就可以做到。
制作步驟:
新建一個場景,用Plane來創(chuàng)建一塊地板,其中中間部分等分成100個小的Plane。

然后在每個Plane的父節(jié)點上掛上NavMeshSurface,中間100等分的小Plane上額外掛上NavMeshModifier。
然后在腳本中拿到所有小Plane的NavMeshModifier,設計個函數通過控制NavMeshModifier
的Areas在中間生成一條cost較低的路徑,agent在導航時會自動選擇cost低的路徑。

最后用一個cube當做生成器生成掛載了agent的capsule來觀察路徑的改變。

每次當路徑更新時,使用NavMeshSurface.BuildNavMesh()函數重新烘培,最后得到的效果如下:

可以看到效果有一些不太理想,路徑更新時會有明顯卡頓。其原因是每次調用BuildNavMesh()
函數時會有烘培時間,地圖越大則時間越長。
不過沒關系,有改進的方法。
這里需要用到異步刷新的方法來規(guī)避這個問題,借用一下官方工程的兩個腳本

使用方法為:LocalNavMeshBuilder的腳本的范圍框內的所有掛載了NavMeshSourceTag的物體會異步刷新其導航網格。

把LocalNavMeshBuilder掛載在場景中整個Plane的父節(jié)點上或者再創(chuàng)建一個空物體,調整范圍框的大小覆蓋要烘培的Plane。

準備工作基本完成,最后還有一點,NavMeshSourceTag腳本內對物體的NavMesh更新時并未考慮NavMeshModifier。沒關系,我們可以自己修改一下:

修改NavMeshSourceTag的腳本
然后就可以去掉NavMeshSurface組件,由LocalNavMeshBuilder來進行動態(tài)烘培。
3. 測試方法

如此就實現了一個簡單的路徑控制功能??梢园阉迷谝恍┖唵蔚挠螒虻娜后w尋路邏輯中,例如MOBA游戲中小兵的路徑控制。

同理當場景中有障礙物或者是新生成的路徑,也能實時烘培加入導航計算之中。
其實官方示例里就已經有了另外一個很好的應用例子:

沒有路,自己造
4. 立體導航
前面說到新版的NavMeshLink有可以自定義起始點和終點的特性,由此帶來了一個問題:
兩個導航片并不在一個水平面上時,連接兩者導航時會發(fā)生什么情況?
答案是導航的方向會以導航網格自身的坐標系為基準,簡而言之就是立體導航成為可能。

我們來做個更為明顯的演示。
首先用6個同樣大小的Plane拼成一個正方形然后用NavMeshSurface烘培,用link連接正方形的
每條邊,此處注意把Link的寬度設置成與正方形邊長相等或者略微超出。


寫一個腳本掛載到angent上讓其移動到鼠標點擊的位置去,然后運行效果如下:

不止如此,由于NavMeshLink組件本身的靈活性,換句話說進入link和離開link的點是可以自定
義的,由此還能實現一些很有趣的小功能。
再創(chuàng)建一個新的場景,這次為了讓agent的移動軌跡顯眼一點,在上面添加一個TrialRenderer組
件,然后用NavMeshLink連接兩個導航網格,為了效果可以適當讓其間距長一些。

可以看到,按照正常情況,agent在通過link時是勻速運動通過的。然后我們現在想讓agent通
過link時是""瞬移"過去的該如何實現?
用NavMeshAgent里封裝好的函數就能實現這一功能:
void Update (){//判斷agent是否處于NavMeshLink上
if (_agent.isOnOffMeshLink)
{
//立即完成當前l(fā)ink
_agent.CompleteOffMeshLink();
}}

因為link的入口和出口都是可控的 可以很方便的做出類似于傳送門的效果,可以加上一些粒子效
果和音效讓表現力提升不少。

結束
以上只是新版NavMesh功能實現的一小部分,可以看到確實是比老版更加的方便和靈活,這
篇文章也是為了起一個拋磚引玉的效果,大家可以充分發(fā)揮自己的想象力運用到自己的項目中。
以上工程上傳于GitHub。篇幅所限,大部分細節(jié)并未給出代碼,有興趣的同學可以自行下載。
下載地址: https://github.com/tank1018702/unity_001/tree/master/NavMesh_Test
最后想系統學習游戲開發(fā)的童鞋,歡迎訪問 http://levelpp.com/
游戲開發(fā)攪基QQ群:869551769
微信公眾號:皮皮關