本文档详细阐述 Project Vampirefall 中敌人如何利用 Unity NavMesh 系统进行寻路,重点解析异常状态 (CC) 下的寻路控制,以及动态阻挡 (塔防) 的实现细节。
在 Unity 中,寻路不仅仅是 SetDestination。对于复杂的 ARPG + TD 游戏,我们需要精细控制 NavMeshAgent。
True。防止冲过头。False。它们需要平滑地移动到射程边缘并停止。这是 ARPG 开发中最容易出 Bug 的地方:如何让 NavMeshAgent 正确响应“冰冻”、“击退”等物理效果?
agent.speed。FinalSpeed = BaseSpeed * (1 - SlowA) * (1 - SlowB)。Mathf.Max(FinalSpeed, 0.5f),防止速度减为 0 导致动画播放异常。agent.enabled = false。这会导致怪物失去碰撞体积,或者瞬间重置路径。agent.isStopped = true。
Animator 的移动参数,防止原地太空步。agent.isStopped = false。agent.isStopped = true。agent.updateRotation = false (禁止转头)。FSM.Pause())。NavMeshAgent 和 Rigidbody 是死对头。Agent 试图吸附在地面,Rigidbody 试图飞出去。
agent.enabled = false (必须彻底关掉,否则它会强制把坐标拉回 NavMesh 上)。rb.isKinematic = false; rb.AddForce(ExplosionForce).rb.isKinematic = true.agent.Warp(transform.position) (关键!告诉 Agent 我瞬移到了新位置).agent.enabled = true.agent.SetDestination(Target) (重新寻路).当怪物进入射程时,如何优雅地停下来攻击,而不是推着玩家走?
StoppingDistance,自己手动控制。Distance(Self, Target) <= AttackRange。agent.isStopped = true。transform.LookAt(Target) (或者插值旋转),保证朝向正确。Distance > AttackRange * 1.2 (迟滞阈值),则 agent.isStopped = false 继续追击。在塔防中,塔通常有很大的碰撞体积(如 Radius = 2.0m),而小怪只有 Radius = 0.5m。直接计算 Vector3.Distance (中心点距离) 会导致小怪贴着塔站,或者打不到塔。
EffectiveRange = AttackRange + TargetRadius + SelfRadius。
AttackAnimation 的关键帧 (Impact Frame) 触发时,检测 Distance <= EffectiveRange 即可判定伤害。这是最稳定、手感最好的做法。在塔防模式下,玩家放塔不能把怪物的路彻底堵死 (Maze Blocking Rule)。
NavMeshObstacle。Carve = true (必须勾选,否则怪物会穿过去或被挤开,而不是绕路)。NavMesh.CalculatePath(SpawnPoint, NexusPoint, AreaMask, pathResult)。pathResult.status == NavMeshPathStatus.PathComplete。Partial 或 Invalid,说明路被堵死了,禁止玩家建造。让怪物像蜘蛛一样翻越城墙,或者跳过沟壑。
OffMeshLink 组件。agent.isOnOffMeshLink 变为 true。agent.autoTraverseOffMeshLink = false (禁止自动瞬移)。Coroutine 平滑移动 Agent 的 transform 到 Link 的另一端。agent.CompleteOffMeshLink(),交还控制权。弓箭手不仅要追玩家,还要保持距离。
Dir = (SelfPos - TargetPos).normalized.FleePos = SelfPos + Dir * 5.0f.NavMesh.SamplePosition(FleePos, out hit, 2.0f, AreaMask).SetDestination(hit.position)。几百个僵尸挤在一起,互相推挤导致卡顿。
NavMesh.CalculatePath 算出一口路径。transform.Translate。public class NavMeshMovement : MonoBehaviour
{
[SerializeField] private NavMeshAgent _agent;
[SerializeField] private Rigidbody _rb;
// 协程:处理击退
public IEnumerator ApplyKnockback(Vector3 force, float duration)
{
// 1. 切断 NavMesh 连接
_agent.isStopped = true; // 先停逻辑
_agent.enabled = false; // 再关组件 (重要顺序)
_rb.isKinematic = false; // 开启物理
// 2. 施加力
_rb.AddForce(force, ForceMode.Impulse);
// 3. 等待物理模拟
yield return new WaitForSeconds(duration);
// 4. 恢复 NavMesh
_rb.velocity = Vector3.zero;
_rb.isKinematic = true;
// 寻找最近的有效地面,防止卡在墙里
if (NavMesh.SamplePosition(transform.position, out NavMeshHit hit, 2.0f, NavMesh.AllAreas))
{
_agent.Warp(hit.position); // 瞬移回网格
_agent.enabled = true;
_agent.isStopped = false;
}
else
{
// 异常处理:被击飞出地图了,直接处死
GetComponent<Health>().Kill();
}
}
}
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 怪物原地转圈 | 目标点在脚下,但因为 StoppingDistance 没停住。 | 增大 StoppingDistance 或检测 agent.remainingDistance < Threshold 手动 Stop。 |
| 怪物穿墙 | 速度太快,或 NavMeshObstacle 没有开 Carve。 |
开启 Carve;对于极快单位,改用 Raycast 检测前方障碍。 |
| 浮空/陷入地下 | Agent 的 BaseOffset 设置不对,或模型原点不在脚底。 |
调整 BaseOffset;确保美术模型的 Pivot 在脚底中心。 |
| 性能暴跌 | 每一帧都对 100 个怪调用 SetDestination。 |
必须限制频率!使用协程每 0.2s 更新一次路径,或仅当目标移动超过 1米时更新。 |