recastnavigation是一个开源项目,该项目在github上对自己的说明是Navigation-mesh Toolset for Games。其中主要包含了3d寻路相关的库。起初关注这个项目是在大学的时候,当时和几个同学一起开发一款3d游戏,当时就有在游戏服务器实现3d寻路的想法,后来受到当时的指导老师李华旸老师的指导开始关注recastnavigation。刚接触这个项目的时候就被这个项目吸引了,经过初步了解,如果将这个项目应用于游戏服务器中,可以很完美的解决服务器3d寻路的问题。(后续recastnavigation简写为recast)
随后经过了一到两个月的研究,大概了解了其中的工具能够应用的场景,recastnavigation其中核心模块包含了3d寻路,控制agent(可理解为游戏中的人物)行走、移动,动态添加阻挡,动态改变地形。当然看到这里如果是做游戏客户端的同学,可能会说这有什么,这些在客户端不是很容易实现的么。也许你说的对,在客户端来说确实很好实现这些东西,很多现代的游戏引擎早已包含这些工具集。(补充一点
recast其实就是unity3d引擎自带的navigation寻路模块的前生,这一点我曾经在Google Group(需要翻墙)里和recast的作者memononen确认过)然而我们要做的是在服务器上应用这些技术,在服务器上却不是那么轻松的实现。对于这个技术的应用的经过,其中经过了很多波折(),所以只到这个技术的应用直到今天(距离我接触recast已经过去了快4年了)才真正落地应用于我现在公司的游戏项目的服务器中。下面来说明这个技术的应用的细节,也许下面的内容才是很多人 真正关心的东西(后面的内容真的有点西东)。
recast在GitHub上的地址:https://github.com/recastnavigation/recastnavigation.git (补充说明,获取、编译、运行等在该项目有清晰的说明), 编译运行看看效果。
recast提供的demo有三种模式可选,根据我的研究使用其中的Sample_TempObstacles是最佳实现(相较于其他该模式该模式允许预处理地图、动态添加阻挡,真正的游戏场景中的地图生成寻路网格(navmesh)非常耗时,服务器对于长时间阻塞加载是很头痛的(虽说对于现代c++程序员可以轻松的应用多线程来解决这个问题,但是使用预处理能减少消耗,何乐而不为呢)。
看完效果之后,也许在你心里应该有个大概的概念了,然后我们来看在服务器上怎么应用。首先说明一下,为什么我说recast能够轻松的在服务器上应用呢,recast是用c++实现的,目前很多游戏公司都是使用c++作为游戏服务器开发的主流语言,可以很容易嵌入。如果这个时候有杠精跑来说他们使用Go、Python来进行游戏服务器的开发,其实c++的程序很好嵌入到Go或者Python一起使用的,这个我们在Python上已经有过实践,我可以很负责任的说没问题,根据我的了解在Go上也是一样,虽然我没有亲自实践,但是我看过别人的实践,一样没有问题。后续我会专门写一篇文章关于c++程序或者c++库怎么在Python、Go这些语言开发的应用程序上应用。
首先我们需要能够识别的游戏地图文件,以unity为客户端为例,其中有两种方法能够导出unity中的地图供recast使用。
1. 导出unity中使用navigation寻路组件bake完生成好的导航网格(navmesh)-导出生成recast可识别的obj文件
2. 导出unity中原始地图obj文件。(坑:地形terrain没有网格,如果要先从地形生成地形的网格 然后导出所有网格生成obj)
我们分别介绍一下这两种方式,然后再来分析优缺点。第一种方式是直接导出unity里面生成好的navmesh,导出脚本ExportNavMesh
首先我们在unity中bake生成navmesh unity中生成的navmesh如图所示:
使用我们的导出脚本后,会生成一个obj文件
然后我们把这个obj文件放入recast生成navmesh看看效果
我们可以看到至此我们就可以使用recast嵌入服务器来进行寻路了,而你要做的事情剔除recast的图形界面相关的代码,使用其提供的api自己动手封装相关的功能调用api,然后集成在服务器c++程序调用即可,或者是先将recast编译成一个独立的库文件,然后将库集成至你的服务器中。
注意的事项:
从视觉上看,recast中的地图与unity中的原始地图不是完全一致的,但是和unity中的navmesh是一致的(不影响寻路结果计算)
使用recast生成寻路网格时应该调整build参数至合适的值,保证地图生成的寻路网格效果与unity中生成的基本一致。
建议使用recast先生成寻路网格,然后保存为tilecache
在正式的服务器程序中使用load tilecache的方式来加载,使用预处理生成的tilecache可以节省大量的载入时间
然后我们再来看看另一种导出地图的方式,直接导出地图场景生成一个obj文件,这样导出的文件在视觉上和unity中的原始地图是一致的。
注意事项:
unity中默认使用左手坐标,然而recast中默认使用右手坐标(如不清楚自行查阅相关资料),所以一定注意坐标系的问题,否则由于坐标系不同引发的问题而又难以发现这个问题会让你开始怀疑人生(对的,我曾经苦苦纠结这个坐标系问题导致我开始怀疑人生)在使用导出的navmesh时记得检查一下坐标,在recast中随便取一个点查看一下对应的坐标和unity中是否一致即可,如若不一致翻转x坐标即可 pos.x = -pos.x (切记)。
这种方式适合地图地形相对不那么大的情况,当地图太大时问题不在于这种方式,而是在于如果使用的是地形terrain文件,terrain没有mesh所以要先生成mesh,地形太大目前好像没有合适的工具能够导出原始的terrain文件,terrain越大导出的网格mesh与原始terrain差距就越大,经过我们在项目中的实践测试,这种差距是不能接受的。
最后要说明的是我这篇文章只展示了recast寻路这一个功能,其中还包含了其他丰富的工具集能够为我们在游戏开发中提供助益,其中包括生成随机点(我这里说到的包括后面说到的随机点都是指地图上的可达点-这一点非常重要)以某个点为中心生成随机点,判断距离墙边的距离,动态添加障碍物,添加两点之间的连通性(静态需要rebuild
耗时 但是可转化为动态),自动寻路(有助于在游戏服务器中AI的实现)等等。
后续如有发现遗漏的觉得非常重要的内容会再进行补充,希望这篇文章对你能有帮助,也希望对文中内容有不同看法的人能够与我交流,文中若有错误的地方也希望大家积极指出以便于我快速更正不要误导其他人。
待续... ...
导出脚本ExportNavMesh链接下载不了文件的,另外把unity的navmesh导出成obj后需要在RecastDemo里build吗?再build一次的话生成的navmesh就和unity的不一样了,要怎么直接使用obj生成recast的dtNavMesh对象?
从unity里面导出的navmesh如果再使用recast来build一次是会和unity生成的不一样,根据目前的经验,比较合适的做法是让美术制作一份单独的寻路网格数据(贴合游戏场景地形的一个模型 obj格式) unity客户端和服务器都使用这个寻路网格生成navmesh 生成的结果基本是一致的。