最近在游戏开发中要做寻路。首选果断就是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和设置。于是哥决定尝试一下……
 
A* Pathfinding Project
 
具体的使用,这里就不详细说了。看它自带的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个赞~~