解释HPA*与分层寻路
解读
国内Unity面试中,一旦项目涉及大地图、RTS、SLG、开放世界或数字孪生仿真,主程必问“你怎么做寻路”。HPA*(Hierarchical Path-Finding A*)是官方推荐的大场景解决方案,也是Unity NavMesh 分层API背后的理论依据。面试官想确认三件事:
- 你知道A*在10 km²以上地图或动态障碍密集场景会爆内存、CPU;
- 你能把地图抽象成多级图(Cluster Graph),用分层搜索把O(n²)降到O(n·log n);
- 你能在Unity里落地:NavMeshSurface分区、NavMeshLink搭桥、或者自写HPA*+DOTS,并处理动态障碍、帧率、GC。
知识点
- A*瓶颈:节点数=格子数×高度层,2000×2000地图=4 M节点,单帧50 ms+。
- HPA*核心:
- Clustering:把地图均匀切为mxm簇(Unity里一簇=一个NavMeshBuildSource),簇内建Entrance(Portal)。
- Abstract Graph:簇→抽象节点,Entrance→边,边权=簇内A*最短距离。
- 两级搜索:
①高层A在抽象图找簇序列(毫秒级);
②低层对每个簇**局部A精修**,只展开当前簇节点,惰性加载。
- 动态更新:障碍只影响所在簇,局部重新Voxelize + 抽象边权重更新,复杂度O(m²)而非O(N)。
- Unity实现:
- 官方NavMeshBuildSourceGroup按簇烘焙,运行时NavMeshBuilder.UpdateNavMeshData单簇刷新;
- 自写框架用JobSystem + NativeHashMap<ClusterID, PortalNode>,Burst编译后移动端<1 ms。
- 内存与GC:抽象图节点用Struct + unmanaged memory,路径结果用NativeList<PathNode>,零GC。
- 与流式场景结合:Addressables按WorldChunk加载,簇ID与ChunkID1:1映射,卸载时同步释放抽象节点。
答案
HPA是一种**分层A算法**,把大地图先切分为固定大小的簇(Cluster),在簇与簇之间预先计算出入口(Portal),形成一张高层抽象图。寻路时分两步:
- 在抽象图上做一次低维度A*,得到簇级别的宏观路径,时间复杂度O(E·log V),V=簇数,瞬间完成;
- agent每进入下一簇时,才在该簇内部做一次局部A*,把宏观边展开成实际坐标,惰性展开保证内存和CPU只消耗当前可见区域。
在Unity中,我通常把NavMeshSurface按地形块切分,每块对应一个簇,烘焙时把NavMeshLink放在簇边界作为Portal;运行时若动态障碍出现,只重新烘焙受影响簇,然后调用NavMeshBuilder.UpdateNavMeshData增量更新,移动端帧率稳定在60 fps。若项目使用DOTS,我会用IJobParallelFor + NativeQueue实现Burst加速的HPA*,抽象图缓存到NativeMultiHashMap,一次高层搜索耗时**<0.3 ms**,内存占用比传统A*降低**90%**以上。
拓展思考
- 分层粒度权衡:簇太大→抽象图精度低,簇太小→Portal爆炸;我一般在移动端取64×64 m,PC/主机128×128 m,用Profiler抓帧,把簇内节点数控制在1 k以内。
- 与NavMeshQuery结合:Unity 2022的NavMeshQuery.RayCast可替代簇内A*,做字符串拉直(String Pulling),进一步把局部路径节点从数百降到个位数。
- 动态障碍预测:在数字孪生场景,AGV路线会提前2 s广播障碍信息,我利用HPA*的局部更新特性,把障碍影响区域标记为脏簇,提前异步线程刷新,实现零卡顿绕行。
- 多层建筑扩展:把楼层当成Z轴簇,Portal在楼梯口,抽象图变成3D Graph,一套代码同时解决室外大地图 + 室内多层。