最近在游戲開發中要做尋路。首選果斷就是Unity3D自帶的尋路啦。方便穩定,基本功能都能滿足。我們的需求也不復雜,就是一個英雄在不同的地圖中探索。但是介於一個比較惡心的問題,果斷放棄了它。所以,說A* Pathfinding Project之前,讓我先吐槽幾百字……
這個問題就是NavMesh不能動態地加載。Unity3D中每一個場景的NavMesh,如果有的話,都會對應一個名叫NavMesh.asset的文件。一一對應。假如你使用Resources.Load()方法載入它,會發現它其實就是一個NavMesh類的實例。好方便啊~~。但是Unity3D卻沒有提供任何方法動態替換一個場景的NavMesh。NavMesh類都是靜態方法哦~。你就是載入進來,也拿它沒轍,哦吼吼~。那好吧,接受這個殘酷的現實。但哥不嫌麻煩,咱每一張地圖都建一個場景,對應一個NavMesh,可以了吧?理論上可以。但實際不行。介於場景眾多,你在操作它們的時候(移動、重命名等等),你不知什么時候,場景和它的NavMesh的關聯關系就會丟失。總之,再次給Unity3D的做了一半的功能跪了。
通過百度、谷歌,以及各種機緣巧合,知道了有A* Pathfinding Project這個尋路插件。看它高大上的官網(
http://arongranberg.com/astar/),介紹的各種功能也是很強大的。A*,NavMesh和好多其它種哥也不懂的尋路算法都實現。 而且可以Save和Load預先做好的NavMesh和設置。於是哥決定嘗試一下……

具體的使用,這里就不詳細說了。看它自帶的ExampleScenes以及官方教程(
http://arongranberg.com/astar/docs/index.php)就可以了。下面說一些遇到的坑,希望對遇到的朋友有幫助。
1.如果你想要使用代碼來Scan,AstarPath.active.Scan()或者ScanLoop()是不靠譜的。它們只有在AstarPath這個組件被選中時才管用。建議使用AstarPathEditor.MenuScan()。這個是位於Edit菜單內的功能選項。是可以在編輯器內調用的。
2.建議使用RecastGraph替代NavMeshGraph。兩者尋路效果上沒有太大差別,只是NavMesh的生成方式不一樣。NavMeshGraph需要你手動指定一個Mesh。而RecastGraph是根據圖層,和擺在場景里的Mesh生成的NavMesh。甚至可以在運行時生成。相比指定一個Mesh要靈活多了。而且NavMeshGraph的UI代碼有問題,不能拖拽,必須從整個工程里面選。。。
3.AIPath這個很常用的腳本是有BUG的。雖然作者自己也說了吧。但沒想到問題那么明顯。作者為了達到最大程度地兼容,讓AIPath在GameObject上,依次去找CharacterController,Rigidbody,最后是Transform,來控制移動。如果使用Rigidbody的話,會使用AddForce()方法移動。但是,卻沒有判斷isKinematic這個屬性。如果為true,AddForce()肯定沒用啊。所以,自己加上個判斷唄……。
1 public virtual void Update () { 2 3 if (!canMove) { return; } 4 5 Vector3 dir = CalculateVelocity (GetFeetPosition()); 6 //Rotate towards targetDirection (filled in by CalculateVelocity) 7 RotateTowards (targetDirection); 8 9 if (rvoController != null) { 10 rvoController.Move (dir); 11 } else 12 if (navController != null) { 13 #if FALSE 14 navController.SimpleMove (GetFeetPosition(),dir); 15 #endif 16 } else if (controller != null) { 17 controller.SimpleMove (dir); 18 } else if (rigid != null && !rigid.isKinematic) { 19 rigid.AddForce (dir); 20 } else { 21 transform.Translate (dir*Time.deltaTime, Space.World); 22 } 23 }
4.目前(3.5.1版本)A* Pathfinding Project還不支持躲避(Local Avoidance)。在之前版本,躲避是使用RVO這個插件實現的。但貌似由於版權問題,RVO被從A* Pathfinding Project中剝離了。如果非要實現躲避或動態阻擋這類功能,可以使用NavmeshCut這個組件。它只支持RecastGraph。原理就是實時地更新NavMesh,從中挖一個洞。要實現這個效果,場景中還必須掛一個TileHandlerHelper的組件。它會定時更新NavMesh。不過,如果阻擋物過多,或者更新頻率過於頻繁,肯定是不靠譜的。畢竟要修改Mesh也是有一定開銷的。那建議就選用別的尋路插件咯……。
5.另外,TileHandlerHelper會提前對graph進行判空檢查。假如,你的RecastGraph是運行時加載的,那這里肯定會報空指針。這是因為在AstarData類的DeserializeGraphsPart()方法里,對數據反序列化后,沒有更新AstarData.recastGraph這個字段。在DeserializeGraphsPart()最后手動調用一下UpdateShorcuts()就好了。
1 public void DeserializeGraphsPart (Pathfinding.Serialization.AstarSerializer sr) { 2 ClearGraphs (); 3 graphs = sr.DeserializeGraphs (); 4 if ( graphs != null ) for ( int i = 0; i<graphs.Length;i++ ) if ( graphs[i] != null ) graphs[i].graphIndex = (uint)i; 5 6 userConnections = sr.DeserializeUserConnections(); 7 //sr.DeserializeNodes(); 8 sr.DeserializeExtraInfo(); 9 sr.PostDeserialization(); 10 11 UpdateShortcuts(); 12 }
不論怎樣,作為一個第三方的尋路插件,A* Pathfinding Project功能已經很給力了,而且貌似還是一個人搞的(orz...)。所以有點小BUG也無可厚非了,就當鍛煉自己Debug的能力了。最后,還是給A* Pathfinding Project點32個贊~~