游戏思考17:寻路引擎recast和detour学习一(跨平台编译库及相关寻路接口解析)

一、windows安装编译recastnavigation1.5.1

1)下载源码,我下的是recastnavigation - 1.5.1
传送门
2)下载依赖库SDL,我下的是SDL2-2.0.22

SDL2-devel-2.0.22-VC.zip (Visual C++ 32/64-bit)

传送门

3)移动解压的SDL文件并改名字
sdl文件夹SDL2-2.0.22解压到/recastnavigation-master-1.5.1/RecastDemo/Contrib,修改目录名为SDL

4)下载premake5.exe。解压到/RecastDemo

5)在/RecastDemo目录打开cmd界面
执行premake5 vs2017(本机的vs版本)
在这里插入图片描述
6)进入/RecastDemo/Build/vs2015,打开recastnavigation.sln,执行编译生成解决方案。
在这里插入图片描述

7)进入/RecastDemo/Bin,打开RecastDemo.exe即可享用
在这里插入图片描述
在这里插入图片描述

二、linux编译recastnavigation1.5.1

1)安装sdl的最新版本到linux下面

./configure
sudo make && sudo make install

2)将sdl的头文件放到/usr/include/下面的SDL文件夹(新建的)
3)将sdl的.a静态库放到/usr/lib下面
4)如果没安装opengl的相关库需要安装下

sudo yum list mesa*
sudo yum install mesa*

5)将recastDemo下面的源文件加上SDL头文件前衔
在这里插入图片描述
6)若提示关于编译安装提示No package ** found时,可能需配置pkg-config

$ vim ~/.bash_profile 
 
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig                        
export PKG_CONFIG_PATH

source ~/.bash_profile   //生效配置

7)下载premake5的二进制文件,上面有链接,放到RecastDemo里面

./premake5  gmake
cd Build/gmake/
sudo make

8)linux下编译debug和release库,选择不同编译选项就好
在这里插入图片描述

三、背景知识

Recast & Detour是一个开源的寻路引擎,其遵循zlib协议

1)分类

Recast:主要功能是将场景网格模型生成用于寻路的网格模型(Navigation Mesh)。所生成的寻路网格模型当然要比原模型简单很多,这也提高了实时寻路算法的效率。
Detour:主要功能是利用上一步所生成的Navigation Mesh进行寻路,其包含了多种寻路算法,根据不同的路径光滑程度与寻路时间效率的要求可做不同的选择。

2)navmesh定义

A navigation mesh, or navmesh, is an abstract data structure used in artificial intelligence applications to aid agents in path-finding through large spaces. Meshes that do not map to static obstacles in the environment that they model, offer the additional advantage that agents with access to the mesh will not consider these obstacles in path-finding, reducing computational effort and making collision detection between agents and static obstacles moot. Meshes are typically implemented as graphs, opening their use to a large number of algorithms defined on these structures.

  • 使用范围
    One of the most common uses of a navigation mesh is in video games, to describe the paths that a computer-controlled character can follow. It is usually represented as a volume or brush that is processed and computed during level compilation.

3)编译包含的库

Recast & Detour本身是个独立的程序库,其源码所带的Demo用到了SDL库。SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用C语言写成。SDL提供了数种控制图像、声音、输出入的函数,让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS X等)的应用软件。目前SDL多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。SDL内置了调用OpenGL的函数。SDL的编译又需要Direct X。

4)问题

(1)客户端生成的navmesh,和服务器生成的navmesh在坐标系上不一致:
  • 原因:

其实客户端生成的navmesh是带有自身坐标系和相对坐标的。之前之所以有那种困惑,是因为服务器用的是recastDemo中生成的navmesh,客户端生成了自己的,根本就是两种世界,两种坐标,当然不一致了!!

  • 解决方法

1)法一:使用了KBEngine修改过的CritterAI,它导出来的navmesh文件,在KBEngine中是能直接使用的。然后把我们服务器中解析navmesh的格式,改成和KBEngine一致就可以了
2)法二:CritterAI的底层是C++写的dll,通过unity导入进工程,然后生成navmesh的,只要符合unity使用dll的规则,我们其实也可以使用recast的源码,然后改成unity能够使用的dll来导出navmesh(导出unity中的地图供recast使用
3)法三:
从unity里面导出的navmesh如果再使用recast来build一次是会和unity生成的不一样,根据目前的经验,比较合适的做法是让美术制作一份单独的寻路网格数据(贴合游戏场景地形的一个模型 obj格式) unity客户端和服务器都使用这个寻路网格生成navmesh 生成的结果基本是一致的。

  • 法一实例代码
bool NavMeshLoader::load_cai(const char* path) {
        FILE* fp = fopen(path, "rb");
        if (!fp) return false;

        // Read header.
        NavMeshSetHeader_CAI header;
        size_t readLen = fread(&header, sizeof(NavMeshSetHeader_CAI), 1, fp);
        if (readLen != 1)
        {
            fclose(fp);
            return false;
        }

        if (header.version != NAVMESHSET_VERSION)
        {
            fclose(fp);
            return false;
        }

        dtNavMesh* mesh = dtAllocNavMesh();
        if (!mesh)
        {
            fclose(fp);
            return false;
        }
        dtStatus status = mesh->init(&header.params);
        if (dtStatusFailed(status))
        {
            fclose(fp);
            return false;
        }

        // Read tiles.
        for (int i = 0; i < header.tileCount; ++i)
        {
            NavMeshTileHeader tileHeader;
            readLen = fread(&tileHeader, sizeof(tileHeader), 1, fp);
            if (readLen != 1)
            {
                fclose(fp);
                return false;
            }

            if (!tileHeader.tileRef || !tileHeader.dataSize)
                break;

            unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
            if (!data) break;
            memset(data, 0, tileHeader.dataSize);
            readLen = fread(data, tileHeader.dataSize, 1, fp);
            if (readLen != 1)
            {
                fclose(fp);
                return false;
            }

            mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0);
        }

        fclose(fp);
        m_navMesh = mesh;
        m_navQuery = dtAllocNavMeshQuery();

        status = m_navQuery->init(m_navMesh, 2048);
        if (dtStatusFailed(status))
        {
            return false;
        }

        return true;
    }

法二:导出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寻路网格,然后保存为tilecache
    在这里插入图片描述
    ③在正式的服务器程序中使用load tilecache的方式来加载,使用预处理生成的tilecache可以节省大量的载入时间

  • 第二种方法:直接导出地图场景生成一个obj文件,这样导出的文件在视觉上和unity中的原始地图是一致的。
    在这里插入图片描述

  • 注意事项
    unity中默认使用左手坐标,然而recast中默认使用右手坐标(如不清楚自行查阅相关资料),所以一定注意坐标系的问题,否则由于坐标系不同引发的问题而又难以发现这个问题会让你开始怀疑人生(在使用导出的navmesh时记得检查一下坐标,在recast中随便取一个点查看一下对应的坐标和unity中是否一致即可,如若不一致翻转x坐标即可 pos.x = -pos.x (切记)。)
    ②这种方式适合地图地形相对不那么大的情况,当地图太大时问题不在于这种方式,而是在于如果使用的是地形terrain文件terrain没有mesh所以要先生成mesh,地形太大目前好像没有合适的工具能够导出原始的terrain文件,terrain越大导出的网格mesh与原始terrain差距就越大,经过我们在项目中的实践测试,这种差距是不能接受的。

四、接口解析

1、初始化Recast引擎——recast_init
2、加载地图——recast_loadmap(int id, const char* path),id为地图的id,因为我们某些游戏中可能会有多个地图寻路实例,例如Moba游戏,每一场游戏中的地图寻路都是独立的,需要id来区分,path就是寻路数据的完整路径(包含文件名),这个寻路数据我们可以通过RecastDemo来得到
3、寻路——recast_findpath(int id, const float* spos, const float* epos),寻路的结果其实只是返回从起点到终点之间所有经过的凸多边形的序号,id为地图id,spos为起始点,epos为中点,我们可以把它们理解为C#中的Vector3
4、计算实际路径——recast_smooth(int id, float step_size, float slop),计算平滑路径,其实是根据findpath得到的【从起点到终点所经过的凸多边形的序号】,得到真正的路径(三维坐标),所以这一步是不可缺少的
5、得到凸多边形id序列——recast_getpathpoly(int id),得到pathfind以后,从起点到终点所经过的所有凸多边形id的序列
6、得到寻路路径坐标序列——recast_getpathsmooth(int id),得到smooth以后,路线的三维坐标序列
7、释放地图——recast_freemap(int id),游戏结束后记得释放地图寻路数据资源嗷
8、释放Recast引擎——recast_fini(),如果我们在客户端使用,游戏流程结束要使用这个释放Recast引擎

五、navigation寻路C#简单性能测试

  • 思路
    以这张地图为例,进行随机寻路(包括合法点和非法点)
    在这里插入图片描述

  • 代码

         public static void BenchmarkSample()
         {
             BenchmarkHelper.Profile("寻路100000次", BenchmarkRecast, 100000);
         }private static void BenchmarkRecast()
         {
             if (RecastInterface.FindPath(100,
                 new System.Numerics.Vector3(-RandomHelper.RandomNumber(2, 50) - RandomHelper.RandFloat(),
                     RandomHelper.RandomNumber(-1, 5) + RandomHelper.RandFloat(), RandomHelper.RandomNumber(3, 20) + RandomHelper.RandFloat()),
                 new System.Numerics.Vector3(-RandomHelper.RandomNumber(2, 50) - RandomHelper.RandFloat(),
                     RandomHelper.RandomNumber(-1, 5) + RandomHelper.RandFloat(), RandomHelper.RandomNumber(3, 20) + RandomHelper.RandFloat())))
             {
                 RecastInterface.Smooth(100, 2f, 0.5f);
                 {
                     int smoothCount = 0;
                     float[] smooths = RecastInterface.GetPathSmooth(100, out smoothCount);
                     List<Vector3> results = new List<Vector3>();
                     for (int i = 0; i < smoothCount; ++i)
                     {
                         Vector3 node = new Vector3(smooths[i * 3], smooths[i * 3 + 1], smooths[i * 3 + 2]);
                         results.Add(node);
                     }
                 }
             }
         }
  • 得到的结果是
    在这里插入图片描述
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值