4、动态障碍物:NavMesh Obstacle组件、动态切割NavMesh、性能优化
好,咱们接着聊寻路系统里一个很头疼的问题——动态障碍物。
你想想看,游戏世界里不可能全是静止不动的墙和柱子。门会开、箱子会被推走、甚至会有从天而降的巨石。如果寻路网格(NavMesh)是死的,那AI角色就会傻乎乎地往墙上撞。我早期做项目时就吃过这个亏,一个NPC愣是冲着刚关上的门走,穿模穿得那叫一个尴尬。
所以,这一章咱们就专门解决这个问题。我会把NavMesh Obstacle组件、动态切割技术,还有性能优化的坑,一次性给你讲透。
4.1 NavMesh Obstacle 组件:轻量级的“挡路牌”
Unity提供了一个现成的组件,叫 NavMesh Obstacle。说白了,它就是个“挡路牌”。
你把它挂在一个动态物体上(比如一个滚动的油桶),AI角色就会自动绕开它。不需要重新烘焙整个地图,性能开销很小。
核心原理: Obstacle组件不会修改NavMesh本身,它只是在运行时告诉寻路系统:“这里暂时不能走”。AI会通过“避障”算法绕开它,而不是重新寻路。
怎么用?很简单。给物体添加组件,然后配置几个关键参数:
| 参数 | 作用 | 我的建议 |
|---|---|---|
| Shape | 形状(Box/Capsule) | 根据物体外形选,别偷懒 |
| Size | 碰撞体大小 | 稍微比模型大一圈,留点余量 |
| Carve | 是否切割NavMesh | 静态物体关掉,动态物体看情况 |
| Move Threshold | 移动阈值 | 默认0.1,别改太小,否则频繁触发 |
小技巧: 如果物体只是原地旋转(比如风扇),把Carve关掉。只开Obstacle就够了,性能更好。我曾经见过一个项目,所有风扇都开了Carve,结果每帧都在切割网格,帧率直接掉到20。
4.2 动态切割 NavMesh:真正的“地形改造”
Obstacle组件虽然好用,但它有个局限——它只是“绕开”,不是“挖掉”。
什么意思?比如一扇吊桥升起来了,AI应该认为那里是悬崖,而不是绕到桥头再跳过去。这时候,就需要动态切割了。
动态切割,就是运行时真的把NavMesh挖掉一块。Unity里通过开启Obstacle组件的 Carve 属性来实现。
// 开启切割的代码示例
using UnityEngine;
using UnityEngine.AI;
public class DynamicObstacle : MonoBehaviour
{
private NavMeshObstacle obstacle;
void Start()
{
obstacle = GetComponent<NavMeshObstacle>();
// 开启切割
obstacle.carving = true;
// 设置切割形状为盒子
obstacle.shape = NavMeshObstacleShape.Box;
// 设置大小,比实际模型略大
obstacle.size = new Vector3(2f, 3f, 2f);
}
// 当物体移动时,切割区域会自动更新
void Update()
{
// 比如让障碍物左右移动
transform.position = new Vector3(Mathf.Sin(Time.time) * 5f, 0, 0);
}
}
嗯,这里要注意:切割是有代价的。每次切割,Unity都要重新计算受影响区域的寻路数据。如果场景里同时有十几个动态切割物体,性能就会明显下降。
避坑指南: 我曾经在一个塔防项目里,给每个可建造的炮塔都开了Carve。结果20个炮塔一建起来,游戏直接卡成PPT。后来我改成只在炮塔建造和拆除时触发切割,平时用Obstacle模式,问题就解决了。
4.3 性能优化:别让动态障碍物拖垮你的帧率
动态障碍物用得好是神器,用不好就是性能杀手。我总结了几条实战经验,你直接拿去用:
4.3.1 减少切割频率
不是所有移动都需要切割。只有改变寻路拓扑结构时才需要。比如:
- 门打开/关闭时 → 需要切割
- 箱子被推动时 → 不需要切割,用Obstacle就行
- 角色死亡倒下 → 不需要切割,尸体用Obstacle
4.3.2 使用 Move Threshold
这个参数很多人忽略。它控制物体移动多少距离后才重新计算切割。默认0.1米,如果你的物体移动很频繁(比如巡逻的无人机),建议改成0.5或1.0。
// 优化后的配置
obstacle.carvingMoveThreshold = 0.5f; // 移动超过0.5米才重新切割
4.3.3 分层管理障碍物
我习惯把障碍物分成三类:
| 类型 | 示例 | 推荐方案 |
|---|---|---|
| 静态障碍物 | 墙壁、柱子 | 直接烘焙进NavMesh |
| 半动态障碍物 | 门、升降平台 | 用Carve,只在状态变化时触发 |
| 全动态障碍物 | 移动的敌人、滚石 | 只用Obstacle,不开Carve |
4.3.4 限制同时切割的数量
如果场景里确实需要多个动态切割,可以写一个管理器,控制同时切割的物体数量。比如:
public class ObstacleManager : MonoBehaviour
{
public int maxCarvingObstacles = 5;
private List<NavMeshObstacle> activeCarvers = new List<NavMeshObstacle>();
public void RequestCarve(NavMeshObstacle obstacle)
{
if (activeCarvers.Count >= maxCarvingObstacles)
{
// 关闭最久没用的那个
activeCarvers[0].carving = false;
activeCarvers.RemoveAt(0);
}
obstacle.carving = true;
activeCarvers.Add(obstacle);
}
}
个人经验: 我做过一个吃鸡类游戏,毒圈边缘会有随机掉落的巨石。一开始所有巨石都开Carve,帧率直接崩了。后来改成只对距离玩家最近的3块巨石开Carve,其他用Obstacle,帧率瞬间回到60。你想想看,玩家根本注意不到远处的石头有没有切割,对吧?
4.4 总结与最佳实践
好,咱们把这一章的核心点捋一捋:
- NavMesh Obstacle 是轻量级方案,适合频繁移动的物体
- 动态切割(Carve) 是重量级方案,适合改变地形的物体
- 性能优化 的关键是:减少切割频率、合理设置阈值、分层管理
最后送你一句话:能用Obstacle解决的,就别用Carve。 这是我在无数次掉帧后悟出来的道理。下一章咱们会讲如何让AI角色“聪明地”绕过这些障碍物,而不是傻傻地原地转圈。到时候见!