Skip to main content
摘要:本文档详细阐述 Project Vampirefall 中敌人如何利用 Unity NavMesh 系统进行寻路,重点解析异常状态 (CC) 下的寻路控制,以及动态阻挡 (塔防) 的实现细节。

1. 核心组件架构 (Core Components)

在 Unity 中,寻路不仅仅是 SetDestination。对于复杂的 ARPG + TD 游戏,我们需要精细控制 NavMeshAgent

1.1 Agent 配置的最佳实践

  • Acceleration (加速度): 设为极高 (如 60-100)。防止怪物起步“滑步”或转弯“漂移”。我们需要响应灵敏的移动。
  • Angular Speed (转速): 设为 360 或更高。怪物应该能瞬间转身,而不是像坦克一样慢慢转。
  • Auto Braking (自动刹车):
    • 近战怪: True。防止冲过头。
    • 远程怪: False。它们需要平滑地移动到射程边缘并停止。
  • Avoidance Priority (避让优先级):
    • Boss/Elite: 0-20 (优先级高,推着别人走)。
    • Minion: 50-99 (优先级低,会被挤开)。
    • 技巧: 动态调整优先级。当怪物进入攻击状态时,提高优先级,防止被路过的队友挤歪。

2. 状态控制与寻路交互 (CC & Pathfinding)

这是 ARPG 开发中最容易出 Bug 的地方:如何让 NavMeshAgent 正确响应“冰冻”、“击退”等物理效果?

❄️ 2.1 减速 (Slow)

  • 实现: 直接修改 agent.speed
  • 叠加规则:
    • 建议使用 乘法叠加FinalSpeed = BaseSpeed * (1 - SlowA) * (1 - SlowB)
    • 设置下限:Mathf.Max(FinalSpeed, 0.5f),防止速度减为 0 导致动画播放异常。

🧊 2.2 定身/冰冻 (Root / Freeze)

  • 错误做法: agent.enabled = false。这会导致怪物失去碰撞体积,或者瞬间重置路径。
  • 正确做法: agent.isStopped = true
    • 保留路径数据 (Path Pending),只是暂停移动。
    • 同时禁用 Animator 的移动参数,防止原地太空步。
  • 恢复: agent.isStopped = false

💫 2.3 眩晕 (Stun)

  • 区别: 定身时怪物还能攻击/转头,眩晕时完全瘫痪。
  • 实现:
    1. agent.isStopped = true
    2. agent.updateRotation = false (禁止转头)。
    3. 逻辑层禁用 AI 状态机 (FSM.Pause())。

🥊 2.4 击退 (Knockback) —— 最难点

NavMeshAgentRigidbody 是死对头。Agent 试图吸附在地面,Rigidbody 试图飞出去。
  • 标准流程:
    1. Disable Agent: agent.enabled = false (必须彻底关掉,否则它会强制把坐标拉回 NavMesh 上)。
    2. Add Force: rb.isKinematic = false; rb.AddForce(ExplosionForce).
    3. Wait: 等待击退时间(如 0.5秒)或等待速度接近 0。
    4. Re-enable Agent:
      • rb.isKinematic = true.
      • agent.Warp(transform.position) (关键!告诉 Agent 我瞬移到了新位置).
      • agent.enabled = true.
      • agent.SetDestination(Target) (重新寻路).

3. 实用案例大全 (Practical Use Cases)

⚔️ Case A: 到达攻击距离 (Reaching Attack Range)

当怪物进入射程时,如何优雅地停下来攻击,而不是推着玩家走?
  • 核心逻辑: 不要依赖 StoppingDistance,自己手动控制。
  • 流程:
    1. Check: 每帧检测 Distance(Self, Target) <= AttackRange
    2. Stop: 满足条件时,立即 agent.isStopped = true
    3. Rotate: 攻击时手动调用 transform.LookAt(Target) (或者插值旋转),保证朝向正确。
    4. Resume: 如果目标跑出射程 Distance > AttackRange * 1.2 (迟滞阈值),则 agent.isStopped = false 继续追击。

体积修正 (Size Adjustment)

在塔防中,塔通常有很大的碰撞体积(如 Radius = 2.0m),而小怪只有 Radius = 0.5m。直接计算 Vector3.Distance (中心点距离) 会导致小怪贴着塔站,或者打不到塔。
  • 公式修正: EffectiveRange = AttackRange + TargetRadius + SelfRadius
    • Center-to-Center: 默认距离计算是中心对中心。
    • Edge-to-Edge: 我们希望的是“边缘对边缘”。
    • 通过加上双方半径,我们算出的是“为了让武器碰到对方边缘,两个中心点需要的最大距离”。
  • 近战判定:
    • 对于近战攻击,不需要真实的物理碰撞(武器碰到塔)。
    • Hitbox Lag: 依赖物理碰撞会导致因为动画延迟、帧率波动而造成的判定丢失。
    • 最佳实践: 只要在 AttackAnimation 的关键帧 (Impact Frame) 触发时,检测 Distance <= EffectiveRange 即可判定伤害。这是最稳定、手感最好的做法。

🧱 Case B: 动态阻挡与堵路检测 (Dynamic Blocking)

在塔防模式下,玩家放塔不能把怪物的路彻底堵死 (Maze Blocking Rule)。
  • 组件: 塔预制体上挂 NavMeshObstacle
  • 设置: Carve = true (必须勾选,否则怪物会穿过去或被挤开,而不是绕路)。
  • 放置前检测算法:
    1. 虚拟放置一个 Obstacle (不显示,只参与计算)。
    2. 调用 NavMesh.CalculatePath(SpawnPoint, NexusPoint, AreaMask, pathResult)
    3. 检查 pathResult.status == NavMeshPathStatus.PathComplete
    4. 如果是 PartialInvalid,说明路被堵死了,禁止玩家建造。
让怪物像蜘蛛一样翻越城墙,或者跳过沟壑。
  • 场景: 僵尸从地面跳上 2楼平台攻击玩家。
  • 实现:
    1. 在地图关键连接点放置 OffMeshLink 组件。
    2. 当 Agent 走到 Link 入口时,agent.isOnOffMeshLink 变为 true
    3. 接管控制:
      • agent.autoTraverseOffMeshLink = false (禁止自动瞬移)。
      • 播放“跳跃”动画。
      • 使用 Coroutine 平滑移动 Agent 的 transform 到 Link 的另一端。
      • 到达后,agent.CompleteOffMeshLink(),交还控制权。

🎭 Case D: 游击/风筝 (Kiting)

弓箭手不仅要追玩家,还要保持距离。
  • 笨办法: 只要距离 < 5米,就向后跑。容易卡墙角。
  • NavMesh 办法 (SamplePosition):
    1. 计算反向向量: Dir = (SelfPos - TargetPos).normalized.
    2. 目标点: FleePos = SelfPos + Dir * 5.0f.
    3. 采样有效性: NavMesh.SamplePosition(FleePos, out hit, 2.0f, AreaMask).
    4. 如果采样成功,SetDestination(hit.position)
    5. 如果采样失败(背后是墙),则向侧面寻找向量(叉乘)重试。

🐜 Case E: 拥挤处理 (Crowd Separation)

几百个僵尸挤在一起,互相推挤导致卡顿。
  • RVO (Reciprocal Velocity Obstacles): Unity 内置的避障。
    • 缺点: 消耗 CPU,且容易让怪物看起来像醉汉乱晃。
  • 优化方案:
    1. 降低 RVO Quality: 在 Project Settings 里把 Agent 的避障质量调低。
    2. 关闭部分 RVO: 只有前排怪物开启 Obstacle Avoidance,后排怪物关闭(反正它们只要跟着前排走)。
    3. 流场 (Flow Field) 替代: 如果同屏超过 200 个单位,放弃 NavMeshAgent
      • 使用 NavMesh.CalculatePath 算出一口路径。
      • 所有怪物只根据路径上的“向量场”移动 transform.Translate
      • 仅在攻击最后阶段启用 Agent 进行精确定位。

4. 代码片段:安全的击退处理


5. 常见坑点速查 (Troubleshooting)