如何动态加载NavMesh瓦片
解读
国内中大型项目(开放世界、吃鸡、SLG)普遍采用“大世界分块+动态加载”方案,NavMesh 数据量动辄几十兆,一次性烘焙会撑爆移动端内存。面试官问“动态加载 NavMesh 瓦片”,核心想确认三件事:
- 你是否知道 Unity 官方 NavMeshSurface 组件 + NavMeshBuildSource 的 Runtime 烘焙管线;
- 能否把烘焙后的 NavMeshData 按瓦片序列化、压缩、分包,并在玩家移动时异步加载、卸载;
- 是否掌握 NavMeshAddData/RemoveData 的线程安全与内存泄漏细节,以及不同平台对 NavMesh 大小限制(iOS < 16 MB/块)的坑。
回答时务必给出“离线烘焙→序列化→运行时异步加载→挂载到 NavMeshSurface→队列卸载”的完整闭环,并主动提到 Addressables 或自定义文件包(LZ4 压缩) 的落地经验,否则会被认为只停留在 Demo 级别。
知识点
- NavMeshSurface:Unity 2017.2+ 官方提供的运行时烘焙入口,支持多 Agent、多区域。
- NavMeshBuildSource:将场景中的 MeshRenderer/TerrainCollider 抽成构建源,可在子线程收集。
- NavMeshData 序列化:NavMeshSurface.BuildNavMesh() 返回的 NavMeshData 可转 byte[],用 NavMeshDataHeader.Write() 自定义二进制格式,存储到 StreamingAssets 或 Addressables。
- 异步加载:Unity 主线程调用 NavMesh.AddNavMeshData(data, position, rotation) 挂载;卸载时用 NavMesh.RemoveNavMeshData(handle),句柄必须缓存,否则重复 Add 会泄漏。
- 瓦片粒度:按 32×32 m 或 64×64 m 分块,兼顾内存(<8 MB/块)与 Overdraw;玩家周围 3×3 九宫格常驻,边缘采用 LRU 队列 卸载。
- 线程安全:NavMeshBuildSource 收集可在子线程,但 NavMesh.AddNavMeshData 必须在主线程;加载时用 Unity.Mathematics.float3 避免 GC。
- 平台限制:iOS 对 NavMeshData.chunkCount 有限制,单块三角形数 < 65535;Android 需关闭 Strip Engine Code 防止 NavMesh 符号被裁剪。
- 热更新兼容:把 *.bytes 放在 Addressables 的 LZ4 压缩包,通过 AsyncOperationHandle<byte[]>.Result 拿到数据后,用 NavMeshData.CreateAndSet 重建,全程不依赖场景,支持热更。
答案
“我们在××项目里把 4 km×4 km 的大世界切成 256 块 256×256 m 的瓦片,离线用 NavMeshSurface.BuildNavMesh() 烘焙,把返回的 NavMeshData 通过 BinaryWriter 写成自定义格式(头 32 byte 存版本、包围盒、三角形数,后续直接 NavMeshData.GetDataPtr 拷 byte[]),再打 Addressables 的 LZ4 包。
运行时按玩家九宫格预加载:子线程用 BoundingBox.IntersectRay 计算周围 3×3 索引,主线程通过 Addressables.LoadAssetAsync<TextAsset> 拿 byte[],反序列化后 NavMesh.AddNavMeshData(data, Vector3.zero, Quaternion.identity) 挂载,返回的 NavMeshDataInstance 句柄存进 Dictionary<int,NavMeshDataInstance> tileCache。
玩家移动超过 1.5 个瓦格时,触发 LRU 卸载:先 NavMesh.RemoveNavMeshData(tileCache[tileIndex]),再 Addressables.Release,确保 iOS 内存峰值 < 150 MB。
为了兼容热更,二进制文件后缀用 *.bytes,Addressables 打标签 navmesh_{版本号},热更时只下载差异包,玩家无感知。整个方案在 iPhone 7 上 60 FPS 跑通 100 人同屏,NavMesh 加载卡顿 < 20 ms。”
拓展思考
- 动态障碍与瓦片冲突:如果项目有 NavMeshObstacle 实时 carving,需要给瓦片加 “脏标记”,在卸载前把 carving 结果 bake 回离线数据,否则重新加载后障碍失效。
- LOD 级 NavMesh:远景瓦片可烘焙 低分辨率 NavMesh(三角形密度减半),用 NavMeshBuildSettings.voxelSize 乘以 2,内存再省 40%。
- 服务端验证:帧同步项目需在 战斗服 同样加载 NavMesh 瓦片,但服务端无 Unity,可用 recastnavigation 的 Detour 库读自定义二进制,保证 AOI 同步 与客户端一致。