RecastDemo 实战教程:从场景模型到可用的导航网格系统
【免费下载链接】recastnavigationIndustry-standard navigation-mesh toolset for games项目地址: https://gitcode.com/GitHub_Trending/re/recastnavigation
RecastDemo 是 RecastNavigation 开源项目自带的交互式示例程序:加载一份场景网格、配置好智能体参数,它就能构建出可供路径查找的导航网格(NavMesh),并把构建、查询、可视化的完整链路跑给你看。本文面向刚接触导航系统的新手和普通游戏开发学习者,按“先跑通、再看内部、最后优化”的顺序,讲清楚如何搭建 RecastDemo、如何读懂构建流程与关键参数、如何在单网格和瓦片网格之间做选型,以及如何用调试视图验证导航质量、定位性能问题。
先说明阅读方式:第 2 节带你把程序跑起来并走通一次“构建—查询”闭环;第 3 到第 4 节解释内部机制和选型;第 5、6 节讲验证与优化;第 7 节汇总边界情况。所有示例都基于仓库内的真实模块,例如 Recast 核心、Detour 运行时、DetourCrowd、DetourTileCache 和 DebugUtils。
导航系统常见的三类卡点,RecastDemo 都给了现成答案
在接触 RecastDemo 之前,先明确它解决什么问题。手工导航(waypoint、路径网络)和自动生成的导航网格,在实际项目里通常会在三个地方卡住:
- 场景改了导航要重画:手工连点的方式无法跟随关卡迭代,改一处墙就要重连一片点。Recast 的解法是把导航网格当作“场景的派生物”:输入几何体变化后重新构建即可,导航数据与关卡模型保持一致。
- 大场景装不下、也更新不动:整张地图一次性烘焙,内存和构建时间都线性上涨,改一个角落要全图重烘。瓦片网格(Tile Mesh)+ DetourTileCache 支持按瓦片重建和按需加载,把开销限制在局部。
- 构建结果“看不见”:导航质量是算法内部决定的,肉眼不可见。RecastDemo 内置了从体素、区域、轮廓到最终 NavMesh 的全阶段调试视图,任何一条断掉的路径都可以逐阶段倒查。
这三个卡点对应了 RecastDemo 里最核心的三个 Sample:Sample_SoloMesh(整图单网格)、Sample_TileMesh(瓦片网格)、Sample_TempObstacles(运行期临时障碍,基于 TileCache)。理解这三个入口,基本就理解了整套工具的使用边界。
如何跑通 RecastDemo 最小闭环
1. 获取代码与依赖
仓库本身对 Recast 和 Detour 没有第三方依赖(C++98 编译器即可),但 RecastDemo 演示程序依赖 SDL2(用于窗口和输入)。获取源码:
git clone https://gitcode.com/gh_mirrors/re/recastnavigation然后按操作系统安装 SDL2:Windows 把开发库解压到RecastDemo/Contrib,macOS 把SDL2.framework放到RecastDemo/Bin,Linux 按发行版说明安装即可。完整步骤见 Docs/_2_BuildingAndIntegrating.md。
2. 生成构建文件并编译
项目同时提供 premake5 和 CMake 两套脚本(仓库根目录有CMakeLists.txt)。以 Linux + premake 为例:
cd RecastDemo premake5 gmake2 cd Build/gmake2 && makeWindows 用premake5 vs2022,macOS 用premake5 xcode4。编译完成后运行RecastDemo/Bin下的可执行文件。
3. 第一次构建:Solo Mesh 走通“输入—构建—查询”
启动后界面分三栏,右侧是参数面板,左侧是工具面板,中间是 3D 场景。最小闭环只需四步:
- 在右侧 Sample 下拉框选Solo Mesh,Input Mesh 保持默认(仓库自带 RecastDemo/Bin/Meshes/ 下的
dungeon.obj等测试网格); - 右侧参数保持默认即可:Cell Size 0.30、Cell Height 0.20、Agent Height 2.0、Agent Radius 0.6;
- 点Build,场景上会覆盖一层导航多边形,顶部出现 Build Time 耗时;
- 在左侧 Tools 里勾选Test Navmesh,模式选 Pathfind Follow,然后在场景里点击起点和终点,路径、路径经过的多边形、平滑后的直线路径会同时画出来。
如果只是想复现固定结果而不是手动点,程序支持加载测试用例,如 RecastDemo/Bin/TestCases/ 下的nav_mesh_test.txt,里面以文本形式记录了网格、起点终点和期望路径,适合做回归验证。
到这一步,你就有了一个可交互的导航系统:输入几何、NavMesh 构建、路径查询、可视化验证全部跑通。后面所有章节都是在这个闭环上做“看进去”和“改起来”。
场景如何变成导航网格:六个阶段与关键参数
构建管线总览
单网格的构建逻辑集中在 RecastDemo/Source/Sample_SoloMesh.cpp 的handleBuild()中,按顺序执行六个阶段:
用白话说:先把三角形“铺”进一张格子纸(栅格化),把站不住人的格子删掉(过滤),让可走区域收缩到角色半径内(腐蚀),把连续的可走格子切成一块块简单区域(区域划分),沿区域边缘描边并简化(轮廓),再切分成多边形(多边形化),最后交给 Detour 变成可以findPath的运行时数据。构建阶段到此结束,之后的一切都属于查询阶段。
关键参数与白话含义
右侧面板的参数一一对应rcConfig。调整它们之前先理解各自的作用:
| 参数(GUI 名称) | 默认值 | 白话含义 | 调整方向 |
|---|---|---|---|
| Cell Size | 0.30 | 地面上一格的实际尺寸(世界单位),决定导航精度上限 | 大场景放大到 0.5 以上,可显著减少格数 |
| Cell Height | 0.20 | 一格的竖直厚度,决定高度细节精度 | 场景高差小就放大,省内存 |
| Agent Height | 2.0 | 角色头顶到脚底需要的净空 | 有矮门就调低,否则矮门区域会被整体挖掉 |
| Agent Radius | 0.6 | 角色水平半径,用于收缩可走区域 | 与角色碰撞体半径一致 |
| Max Climb | 0.9 | 单级台阶能迈过的最大高度 | 按角色设定,调高可走碎台阶 |
| Max Slope | 45° | 超过该坡度视为不可走 | 角色不能爬陡坡就调低 |
| Min/Merged Region Size | 8 / 20 | 区域面积阈值,控制区域切得碎不碎 | 区域太碎就调大 Merge 值 |
| Max Edge Len | 12 | 轮廓单条边允许的最大格数 | 调大可减少多边形数量 |
| Max Edge Error | 1.3 | 轮廓简化允许的最大误差(格数) | 调大边缘更直,但会贴地偏差 |
| Verts Per Poly | 6 | 单个导航多边形顶点上限 | 不要超过 Detour 的上限 6 |
构建入口的初始化只做一次“换算”,之后全是管线调用:
m_cfg.cs = m_cellSize; m_cfg.ch = m_cellHeight; m_cfg.walkableSlopeAngle = m_agentMaxSlope; m_cfg.walkableHeight = (int)ceilf(m_agentHeight / m_cfg.ch); m_cfg.walkableClimb = (int)floorf(m_agentMaxClimb / m_cfg.ch); m_cfg.walkableRadius = (int)ceilf(m_agentRadius / m_cfg.cs);注意这些整数值是“格数”而不是世界单位——这是调试时最容易踩的坑:改了 Agent Radius 却没看到变化,往往是ceilf向上取整后格数没变。
区域划分的三种算法怎么选
rcBuildRegions*有三种实现,源码注释里写得很直白:
- Watershed(分水岭):默认,切出来的多边形最均匀漂亮,适合开阔地形,但最慢;
- Monotone:最快,保证无孔洞无重叠,代价是会产生细长多边形,路径偶尔绕路,适合追求构建速度的场景;
- Layer:速度居中,没有 watershed 的边角问题,注释建议用于“中等或中小尺寸瓦片的瓦片网格”。
单网格和瓦片网格怎么选:按项目阶段决策
两种构建方式的核心差异
Sample_SoloMesh把整个场景一次栅格化,产出一个dtNavMesh;Sample_TileMesh把世界按固定尺寸切成瓦片,每个瓦片独立走一遍上面的管线,拼成同一个dtNavMesh的不同 tile。对比:
| 维度 | Solo Mesh(单网格) | Tile Mesh(瓦片网格) |
|---|---|---|
| 构建入口 | 一次 Build 构建整图 | 点 Create All 全量构建,或点击单个瓦片重建 |
| 局部改动代价 | 全图重烘 | 只重建命中的瓦片(LMB 重建、Shift+LMB 清除) |
| 运行期能力 | 查询 + 人群模拟 | 额外支持流式加载、局部重烘、分层寻路 |
| 构建范围裁剪 | 无,输入三角形全量进入 | 用 chunky mesh 只取与瓦片重叠的三角形 |
| 接入复杂度 | 低,一条管线 | 高,需管理瓦片坐标、边界与缓存策略 |
| 建议场景 | 室内关卡、原型验证、小型静态地图 | 开放世界、大型地编、需要运行期改动的关卡 |
瓦片边界:为什么每个瓦片要多画一圈
瓦片网格最容易被忽视的细节是borderSize。构建某个瓦片时,它的栅格化包围盒会向外多扩一圈:
m_cfg.tileSize = (int)m_tileSize; m_cfg.borderSize = m_cfg.walkableRadius + 3; // 额外留 3 格余量 m_cfg.width = m_cfg.tileSize + m_cfg.borderSize*2; m_cfg.bmin[0] -= m_cfg.borderSize * m_cfg.cs; // 包围盒外扩 m_cfg.bmax[0] += m_cfg.borderSize * m_cfg.cs;这圈“多画出来的区域”本身不生成多边形,作用是让贴边障碍物在腐蚀(rcErodeWalkableArea)时正确影响边界附近的地形,保证相邻瓦片在交界处生成的多边形严丝合缝。如果把瓦片查询几何的范围只取瓦片本体,边界处的可走区域就会和邻居对不上,表现为路径在瓦片接缝处断开——这是瓦片方案最常见的 bug。
选型判断清单
按下面四条依次判断,命中前两条选单网格,否则上瓦片:
- 场景可走区域的格子总数(宽×高,Cell Size 0.3 时)是否明显小于 4096×4096?是则单网格内存压力可控;
- 关卡内容确定后是否还会频繁改动局部地形?频繁则必须瓦片;
- 是否需要运行期增删障碍并自动更新导航?需要则用瓦片 + DetourTileCache 做局部重烘;
- 是否需要只加载玩家附近的导航数据(流式)?需要则只有瓦片方案支持。
另外一点工程事实:单网格的 Save/Load 按钮(序列化为二进制再dtNavMesh::init)在瓦片方案下同样可用,两种方案产出的都是标准 Detour 数据,后期从单网格切到瓦片不需要重写查询层代码。
用调试视图验证导航质量:看到什么、查什么、怎么改
调试视图与构建阶段的对应关系
左侧 Debug 面板的 Draw 列表(见Sample_SoloMesh::handleDebugMode)把每个中间产物都画了出来,排查时按阶段倒查:
| Draw 模式 | 对应阶段 | 用来确认什么 |
|---|---|---|
| Input Mesh | 输入几何 | 原始三角形是否按预期进入构建 |
| Navmesh | 最终结果 | 多边形覆盖范围、离网格连接(off-mesh connection) |
| Voxels / Walkable Voxels | 栅格化/过滤 | 哪些格子被判可走,坡度与净空过滤是否过度 |
| Compact / Compact Distance | 紧凑高度场 | 腐蚀后的可走范围;距离场用于区域划分 |
| Compact Regions / Region Connections | 区域划分 | 区域是否切得合理,跨区域连接点在哪 |
| Raw Contours / Contours | 轮廓 | 边缘被简化成了什么样,断口出现在哪 |
| Poly Mesh / Poly Mesh Detail | 多边形化/细节 | 最终多边形切分;高度采样是否贴合地面 |
📌 提示:这些中间视图只有在勾选Keep Intermediate Results后才可用——构建完的默认流程会释放中间数据以省内存。
按症状倒查参数
把“你看到的现象”直接映射到“该查的阶段和该动的参数”:
| 现象 | 先查阶段 | 常见原因与调整 |
|---|---|---|
| A 到 B 找不到路径 | Compact Regions / Region Connections | 两端不在同一区域;检查中间是否有窄缝被腐蚀切断,考虑调小 Agent Radius 或检查 borderSize(瓦片场景) |
| 本可走的区域缺失 | Walkable Voxels | 坡度超 Max Slope;净空低于 Agent Height;台阶高于 Max Climb |
| 可走区域明显贴不住墙 | Compact Distance | 腐蚀过强,Agent Radius 相对 Cell Size 过大 |
| 边缘多边形锯齿多、数量爆炸 | Contours / Poly Mesh | 调大 Max Edge Len、Max Edge Error,或减少输入三角形 |
| 角色悬空或陷地 | Poly Mesh Detail | 细节采样太疏,检查 detailSampleDist 与 Max Error |
| 瓦片接缝处路径断开 | 瓦片边界 | 几何查询范围没有包含 border 外扩区域,或 borderSize 小于 walkableRadius |
路径类问题还可用 Tester 工具交叉验证:在 RecastDemo/Source/NavMeshTesterTool.cpp 中,Pathfind Follow 画出多边形路径,Pathfind Straight 画出平滑后的直线段(内部调用findPath+findStraightPath),Raycast 验证两点间是否被导航面遮挡,Distance to Wall 显示到最近墙壁的距离,Find Polys in Circle/Shape 验证区域查询。核心查询链路很短:
m_navQuery->findPath(m_startRef, m_endRef, m_spos, m_epos, &m_filter, m_polys, &m_npolys, MAX_POLYS); m_navQuery->findStraightPath(m_spos, epos, m_polys, m_npolys, flags, out, &nout, MAX_SMOOTH);如果findPath本身失败,问题一定在构建端;如果findPath成功但findStraightPath的折线异常,问题多半在多边形形状(过于细长或破碎)。这个二分法比盲目调参有效得多。
导航网格性能优化:从构建耗时到路径查询
监控指标:先看哪里
RecastDemo 里已经埋好了四个层次的观测点:
| 指标 | 在哪里看 | 说明 |
|---|---|---|
| 总构建时间 | 面板顶部 Build Time 标签 | 单次构建的毫秒数,回归对比的基准 |
| 分阶段耗时 | Show Log(由duLogBuildTimes输出) | 对应rcContext的rcTimerLabel枚举,如RC_TIMER_RASTERIZE_TRIANGLES、RC_TIMER_BUILD_CONTOURS、RC_TIMER_BUILD_POLYMESH,直接定位最贵的一步 |
| 多边形/顶点数 | Show Log 的 “Polymesh: N vertices M polygons” | 决定运行时内存与查询代价的数据量指标 |
| 运行期人群开销 | CrowdTool 里的实时曲线(ValueHistory) | 每帧dtCrowd::update的耗时与采样数历史 |
优化方向与验证方式
按“改动一个变量、对比一次 Build Time”的节奏做,优先级从高到低:
- 先砍输入:把场景里不进导航的装饰几何剔除,或降低 LOD。栅格化耗时与三角形数量近似线性,这一步收益最大且无质量损失。瓦片方案下,
Sample_TileMesh::buildTileMesh用rcGetChunksOverlappingRect只取与瓦片重叠的 chunk,本质上就是同一思路的空间裁剪。 - 放粗格子:Cell Size 从 0.3 提到 0.6,格数减半、面积上接近四分之一,构建时间和内存同步下降。代价是台阶和窄门的判定精度变粗,需在第 5 节的视图里复核边界。
- 换分区算法:对构建速度敏感的场景把 Partition 从 Watershed 换成 Monotone;瓦片网格可试 Layer。对比指标用分阶段耗时里的 regions 相关计时项。
- 控制多边形数量:调大 Max Edge Len 与 Max Edge Error,观察 polymesh 多边形数下降幅度与轮廓贴合度的平衡。
- 运行期路径开销:Crowd 场景下对比开关路径拓扑/可见性优化前后的
ValueHistory曲线;查询次数多的场景检查是否重复构造dtQueryFilter。
✅ 验证方式统一为:固定同一输入网格,只改一个参数,记录 Build Time、分阶段耗时和多边形数三项,三项都记录在案后再做下一个改动。只改参数不留记录,无法区分“变快”还是“变差”。
另外两个编译期开关值得知道:超大世界可定义DT_POLYREF64使用 64 位多边形引用;发布构建可定义RC_DISABLE_ASSERTS关掉断言开销。两者均在 Docs/_2_BuildingAndIntegrating.md 中有说明。
常见问题、适用边界与扩展方向
- 运行期摆放一个箱子挡路,单网格能做吗?不能自动做。单网格的运行期手段只有两种:用 Convex Volume 工具在查询时屏蔽多边形,或用 NavMeshPruneTool 手动剪掉多边形。需要“放下箱子导航自动更新”就切到瓦片 + DetourTileCache 的局部重烘路径(对应
Sample_TempObstacles)。 - Recast 有官方 C API 或其他语言绑定吗?官方不提供稳定的 C API,设计深度依赖 C++ 特性;其他语言绑定均为社区项目。集成到自己的工程时推荐源码集成:按需要把 Recast/(构建期)、Detour/(运行期)等目录加入工程即可,构建工具与运行时可以只引用各自需要的模块。
- 中间数据一直占着内存怎么办?不勾 Keep Intermediate Results 时构建完会自动释放体素场、紧凑高度场和轮廓集,只保留最终数据;调试期再打开。
- 导航面比场景“瘦”一圈,是不是 bug?不是,是
rcErodeWalkableArea按 Agent Radius 收缩的结果,保证角色圆心始终在可走区域内。角色半径设定错了(过大)才会出现不该有的收缩。 - 瓦片方案值得多复杂吗?判断标准还是第 4 节的四条清单:局部更新、流式加载、大尺寸,三者都不占就不用。
扩展方向上,RecastDemo 本身就是模板库:自定义构建流程可对照 Sample_SoloMesh.cpp 的管线顺序;自定义调试后端可实现duDebugDraw接口(DebugUtils/Include/),把调试视图接到自己的引擎里;多智能体移动、避障直接复用 DetourCrowd,RecastDemo 的 CrowdTool 演示了完整的接入方式。
小结
RecastDemo 提供的是一条完整的验证路径:用 Solo Mesh 十分钟跑通“输入几何 → 导航网格 → 路径查询”的最小闭环;用六个构建阶段的参数表和调试视图,把“路径为什么断”变成可定位、可复现的问题;用单网格与瓦片网格的选型清单,把架构决策变成几条可勾选的条件;最后用 Build Time、分阶段耗时和多边形数三个指标,让每一次参数改动都有对比依据。建议的实际上手顺序:先用默认参数构建dungeon.obj走通 Tester 工具,再逐项调参观察调试视图变化,等场景规模或动态需求上来时,再切换到 Tile Mesh 验证局部重建。
【免费下载链接】recastnavigationIndustry-standard navigation-mesh toolset for games项目地址: https://gitcode.com/GitHub_Trending/re/recastnavigation
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考