☰
拆开 Unity 的 NavMeshData:一本“给 NPC 看的地图册“
2026/10/21 12:44:28 网站建设 项目流程

先打个比方

把 Unity 的导航系统想象成一家地图出版社:

  • 场景是一座真实的城市,有房子、桥、楼梯和悬崖;
  • 烘焙是出版社派测绘员把城市走一遍,画出"哪里能走";
  • NavMeshData就是印出来的那本地图册,专门给场景里的 NPC 用。

问题在于:这本地图册是用 Unity 自己的"密文"写的,只有 Unity 引擎能读懂。服务端拿到它,就像拿到一本外星文字写的书。

下面我们一页一页翻开这本书。


第一层:封面和版权页(能看懂)

打开NavMesh.asset(把序列化模式设为 Force Text),首先看到的是封面信息,都是明文:

NavMeshData:m_NavMeshBuildSettings:agentRadius:0.5# 这本地图是给"多胖"的人画的agentHeight:2# 给"多高"的人画的agentSlope:45# 最陡能爬多陡的坡agentClimb:0.75# 最高能跨多高的台阶cellSize:0.1667# 测绘员的尺子刻度有多细tileSize:256# 每一页覆盖多大的范围m_SourceBounds:...# 这本地图册覆盖城市的哪一片m_Position / m_Rotation# 这本地图摆在世界的什么位置m_NavMeshTiles:[...]# ← 正文,一页一页的地图

这就像地图册的版权页:

📘《XX 城步行地图(体型版)》
适用读者:身宽 1 米、身高 2 米、能跨 75 厘米台阶的人
测绘精度:约 17 厘米
共 N 页,每页约 43 米 × 43 米

一个有趣的细节:地图已经替你"让开了肩膀"

agentRadius: 0.5意味着测绘员画路的时候,已经把所有墙往里缩了 0.5 米。

真实的走廊: 地图上画的走廊: █ █ █ █ █ ← 3米宽 → █ █ ░░░░░░░░░ █ █ █ █ ← 2米宽 → █ █ █ █ ░░░░░░░░░ █ ↑ 两边各让出 0.5 米

所以在这张地图上,NPC 可以当作一个没有体积的点来走,绝对不会蹭到墙。这也是为什么胖子和瘦子要用不同的地图册:大体型坐骑的地图上,窄巷子直接就消失了。


第二层:正文,用密文写的地图页

m_NavMeshTiles里每一项就是一页地图,内容长这样:

-m_MeshData:4e41564d0700000001000000a3f2...(几万个字符)

一长串十六进制,这就是密文。Unity 从未公开它的格式,而且换个版本可能连"写法"都变了。

虽然看不懂原文,但 Unity 的导航系统源自开源的 Recast/Detour,所以我们大致知道这页纸上画了哪些东西。下面用一个小故事来认识它们。


🗺️ 小怪阿呆的回家之路

阿呆是一只哥布林,它被玩家打了一下,要从村口跑回山洞。它翻开地图册……

1. 地砖:多边形(Polygon)

阿呆看到的不是连续的地面,而是一块一块的地砖:

┌────┬──────┬────┐ │ A │ B │ C │ 每一块都是"凸多边形" ├────┼───┬──┴────┤ 最多 6 条边 │ D │ E │ F │ 站在同一块砖上, └────┴───┴────────┘ 两点之间一定能直线走到

凸这个特点很关键:同一块砖上随便两点,拉一根直线都不会出砖。所以阿呆只需要关心"从哪块砖走到哪块砖",砖内部怎么走根本不用想。

🧩 这就是 A* 搜索的基本单位:不是一步一步搜格子,而是一块一块搜地砖。

2. 砖与砖之间的门:邻接关系

每块砖都记录着:“我的第几条边通向哪块砖”。

A 的右边 → B A 的下边 → D A 的上边 → 无(墙!)

没有邻居的边就是墙或悬崖。阿呆要找路,本质就是在这张"砖块关系网"里找一条从起点砖到终点砖的通道。

3. 砖的颜色:区域(Area)

每块砖都涂了颜色:

颜色区域代价阿呆的感受
⬜ 白色Walkable1平地,好走
🟫 棕色泥地3能走,但很累
🟦 蓝色浅水5能不走就不走
🟥 红色岩浆不可通行绝不踩

注意:地图页上只写了颜色编号,没写"走一步多累"。那张价格表(区域代价)放在项目设置里,不在这本地图册中。后面会说到。

4. 地毯:细节网格(Detail Mesh)

为了让地砖数量少一些,测绘员会把起伏的地面压平成一块大砖。可是楼梯和小土坡怎么办?阿呆总不能飘在半空中。

于是每块砖上还铺了一层贴合地形的地毯:

地砖(简化后,是平的): ───────────── 地毯(真实起伏): ╱‾‾╲__╱‾‾‾╲__

地砖负责寻路(够用就行),地毯负责贴地(告诉阿呆脚该踩在多高的位置)。

5. 目录索引:BV 树

阿呆被打懵了,第一个问题是:“我现在站在哪块砖上?”

如果一块一块去比对,几万块砖要翻到天亮。所以每页地图都附带一个目录:

整页 ├─ 左半边 │ ├─ 左上角 → 砖 A, B, D │ └─ 左下角 → 砖 E, G └─ 右半边 └─ ...

像查字典一样,几次就能定位到具体的砖。这就是 BV 树(包围体层次树)。

6. 跳板和滑梯:Off-Mesh Link

山洞门口有一个 2 米高的小平台,阿呆可以直接跳下去,但跳不上来。

这种"不连着的路"用一根单向箭头表示:

平台上 ●───跳下───▶● 地面 (单向,不能反着走)

烘焙时勾了 “Drop Height” 或 “Jump Distance”,Unity 会自动画这些箭头,存在地图册里。

🧭阿呆的最终路线:用目录查到自己在砖 A → 沿着砖之间的门一路找到山洞所在的砖 K(避开蓝色和红色)→ 中途跳下平台 → 沿着地毯贴地跑回家。
这正是服务端寻路要复现的全过程。


第三层:不在书里的东西(最容易漏!)

阿呆的地图册很完整,但有几样东西根本不在这本书里:

东西比喻实际存在哪
手工放的NavMeshLink/OffMeshLink策划后来贴在城市里的便利贴:“这里有梯子”场景里的组件
区域代价一张单独的价目表:“泥地 3 块钱一步”项目设置 NavMeshAreas
Agent 体型列表读者登记表:“我们有胖子版和瘦子版”项目设置 Agent Types
NavMeshObstacle挖洞运行时临时摆的路障只在游戏运行时生效
多个 NavMeshSurface 的拼接好几本地图册,各自摆在不同位置每个 Surface 一份数据 + 自己的变换

⚠️ 导出工具如果只导出网格,服务端的怪物就会发现:“梯子去哪了?”“怎么泥地和平地一样好走?”


那服务端怎么办?请 Unity 帮忙"复印"

既然看不懂密文,就让会读的人帮忙。Unity 提供了一台复印机:

NavMeshTriangulationtri=NavMesh.CalculateTriangulation();

它会把当前所有地图页翻译成通用格式交给你:一堆顶点、一堆三角形、每个三角形的颜色。

但这台复印机有几个毛病:

毛病 1:只会印三角形

原来的六边形地砖被剪成了好几个三角形,"哪几个三角形本来属于同一块砖"的信息丢了。

原来: ⬡ 复印后: ◸◹◺◿(碎成 4 个三角形)

不影响正确性,只是砖变多了,A* 要多搜一些。可以自己再拼回去。

毛病 2:同一个点印了好几次

两块砖共用的角,可能被印成两个位置几乎一样、但编号不同的点。如果不合并,程序会认为这两块砖"不挨着",阿呆就过不去了。

→ 解决:焊接顶点,靠得足够近的点合成一个。

毛病 3:拼接处对不齐(T 形接缝)

两页地图的交界处,常常出现这种情况:

┌───────────┐ │ X │ 上面那块砖的底边是一整条 ├─────●─────┤ 下面两块砖在中间有个点 ● │ Y │ Z │ 上下的边"不是同一条边" └─────┴─────┘ → 程序认为 X 和 Y、Z 都不相邻!

就像两张拼图看起来贴在一起,其实卡扣没扣上。

→ 解决:把上面那条长边也在 ● 处切开,让卡扣对齐。

毛病 4:便利贴不复印

手工放的 Link、价目表都不会出现在复印件里。

→ 解决:单独遍历场景组件、读取项目设置,补抄进服务端文件。


一图总结

┌──────────── NavMeshData(地图册)────────────┐ │ 📕 封面:适用体型、精度、范围、位置 ← 明文,能读 │ 📄 正文(密文,每页一个 Tile): ← 格式未公开 │ 🧱 地砖 多边形 │ │ 🚪 门 邻接关系 │ │ 🎨 颜色 区域编号 │ │ 🧶 地毯 细节高度 │ │ 📑 目录 BV 树 │ │ ↘️ 跳板 自动生成的跌落/跳跃连接 │ └──────────────────────────────────────────────┘ 书外:📌 便利贴(手放的 Link) 💰 价目表(区域代价) 📋 读者登记(Agent 类型) 🚧 临时路障(Obstacle) 服务端路线: 复印(CalculateTriangulation) → 焊点、扣好拼图(修接缝)、可选拼回大砖 → 补抄便利贴和价目表 → 装订成服务端自己能读的 .nav 地图册

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询