从零构建Unity NavMesh:烘焙、代理与动态寻路实战

张开发
2026/4/19 10:31:35 15 分钟阅读

分享文章

从零构建Unity NavMesh:烘焙、代理与动态寻路实战
1. 从零开始理解Unity NavMesh如果你玩过RPG或者策略游戏一定对NPC自动寻路的功能不陌生。想象一下当你在游戏中点击某个位置角色会自动绕过障碍物走到目的地——这就是导航寻路系统的魔力。Unity内置的NavMesh系统正是实现这种功能的利器。我第一次接触NavMesh是在开发一个塔防游戏时。当时手动编写寻路算法搞得焦头烂额直到发现了这个神器。它就像给游戏世界铺了一层隐形的地毯AI角色只能在这块地毯上行走。这块地毯是通过烘焙Bake生成的导航网格本质上是一张记录了可行走区域的地图。与A*等传统寻路算法不同NavMesh最大的优势是性能。所有复杂的路径计算都在编辑阶段完成运行时只需要简单的查询。实测下来在移动设备上同时运行20个寻路Agent也能保持60帧。下面我们就从创建一个简单场景开始一步步揭开它的神秘面纱。2. 场景搭建与导航网格烘焙2.1 搭建基础场景打开Unity新建一个3D项目我们先做个简易的迷宫创建平面Plane作为地面添加几个立方体Cube作为障碍物调整立方体位置形成通道这里有个新手常踩的坑尺寸问题。默认的1x1x1立方体在导航中显得太小建议将地面缩放为10倍立方体缩放为2x2x2。可以按这个比例布置你的迷宫// 建议场景尺寸参考 Ground: Position(0,0,0), Scale(10,1,10) Obstacles: Scale(2,2,2)2.2 标记静态障碍物选中所有障碍物和地面在Inspector右上角找到Static下拉菜单。这里要特别注意必须同时勾选Navigation Static和普通Static。我遇到过只勾Navigation Static导致烘焙失败的情况。提示可以全选所有物体后按CtrlShiftN快速添加Navigation Static标记2.3 导航网格烘焙参数详解打开Window AI Navigation面板重点看Bake页签的这些参数Agent Radius相当于角色的体型决定能通过多窄的通道Agent Height影响角色能通过的低矮空间Max Slope可攀爬的最大坡度默认45度Step Height可跨越的台阶高度第一次使用时建议先保持默认值点击Bake按钮。你会看到场景中出现蓝色半透明网格——这就是烘焙好的导航区域。如果发现某些应该通行的区域没有网格可以适当减小Agent Radius。3. 创建导航代理角色3.1 添加NavMeshAgent组件新建一个圆柱体Cylinder作为我们的AI角色。选中它后点击Add Component搜索NavMeshAgent。关键参数解析Speed: 5 // 移动速度米/秒 Angular Speed: 120 // 转向速度度/秒 Acceleration: 8 // 加速度 Stopping Distance: 0.5 // 在距离目标多远时停止3.2 代理与障碍物的交互在场景中尝试移动你的角色会发现它能自动避开障碍物。这得益于NavMeshAgent的自动避障功能。你可以通过修改Avoidance Priority0-99来控制避让优先级数值越高的代理越容易被其他代理避让。实测发现一个小技巧当多个代理相互阻挡时适当降低Speed和Acceleration可以显著减少卡顿现象。比如设置Speed3Acceleration5会让移动看起来更自然。4. 实现动态目标寻路4.1 基础寻路脚本创建一个新C#脚本NavAgentControllerusing UnityEngine; using UnityEngine.AI; public class NavAgentController : MonoBehaviour { [SerializeField] NavMeshAgent agent; [SerializeField] Transform target; void Update() { if(target ! null) agent.SetDestination(target.position); } }把脚本挂到代理角色上然后将NavMeshAgent组件和目标Transform拖拽到对应字段。运行游戏移动目标物体角色就会自动追踪了。4.2 高级寻路技巧路径状态检测是实际项目中的必备技能。修改脚本增加以下功能void Update() { if(agent.pathPending) return; // 路径计算中 if(agent.remainingDistance agent.stoppingDistance) { if(!agent.hasPath || agent.velocity.sqrMagnitude 0f) { // 到达目的地后的逻辑 Debug.Log(到达目标); } } }这段代码解决了两个常见问题避免每帧重复计算路径精确判断到达状态考虑停止距离和惯性5. 动态障碍物处理5.1 添加NavMeshObstacle对于会移动的障碍物比如推箱子的箱子需要添加NavMeshObstacle组件。关键设置Shape选择Box或Carve根据物体形状Carve勾选后会在移动时实时更新导航网格Move Threshold位移超过此值才重新计算5.2 性能优化建议动态障碍物虽然方便但过度使用会导致性能下降。我的经验法则是静态物体永远用Navigation Static偶尔移动的物体用NavMeshObstacleCarve频繁移动的物体改用物理碰撞Agent避让曾经在一个项目中我把50个动态障碍物都开了Carve结果帧率直接掉到20。后来改用分层处理性能立刻回到60帧。6. 常见问题排查6.1 烘焙问题问题烘焙后没有蓝色网格显示解决确认所有障碍物都标记了Navigation Static检查Navigation窗口的Display页签是否勾选Show NavMesh尝试增大Bake面板的Agent Radius6.2 寻路问题问题Agent卡在角落不动解决减小Agent的Radius和Height检查目标点是否在导航网格上用Debug.DrawLine可视化路径增加NavMeshAgent的Obstacle Avoidance Quality6.3 动态障碍物问题问题角色穿过应该避开的障碍物解决确认NavMeshObstacle的Carve已启用检查障碍物Layer是否被Agent的Avoidance Mask包含适当减小Obstacle的Carve Only Stationary阈值7. 实战案例巡逻AI实现结合上面知识我们实现一个简单的巡逻系统public class PatrolAI : MonoBehaviour { public Transform[] waypoints; private int currentWaypoint 0; private NavMeshAgent agent; void Start() { agent GetComponentNavMeshAgent(); GoToNextPoint(); } void GoToNextPoint() { if(waypoints.Length 0) return; agent.SetDestination(waypoints[currentWaypoint].position); currentWaypoint (currentWaypoint 1) % waypoints.Length; } void Update() { if(agent.remainingDistance 0.5f !agent.pathPending) { GoToNextPoint(); } } }这个脚本可以让AI在预设的路径点间循环移动。在实际项目中我通常会加上随机停留时间和路径点随机偏移让移动看起来更自然。

更多文章