Unity NavMeshAgent沿Spline Path巡逻卡顿问题求助
我正在实现Unity中NavMeshAgent沿Spline Container路径巡逻的功能,AI处于WhilePatrolling状态时沿路径移动会出现卡顿(停顿一秒后继续前进),但追踪玩家时运行正常。以下是AI核心代码:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; using UnityEngine.Splines; namespace MeenyAI.AIRuntime.Core { public enum AIState { StartSearching, WhileSearching, StartPatrolling, WhilePatrolling, Following, StartWaiting, WhileWaiting } public class EnemyAICore : MonoBehaviour { public Transform Target { get; private set; } public bool HasTarget { get; private set; } = false; private AIState currentState = AIState.StartPatrolling; private bool isTargetPointSet; [SerializeField] private NavMeshAgent agent; [field: Header("AI Settings")] [SerializeField] private LayerMask targetMask; [SerializeField] private LayerMask obstacleMask; [field: SerializeField] public float seeRadius { get; private set; } = 25f; [field: SerializeField] public float seeFOV { get; private set; } = 220f; [field: SerializeField] public float attackRadius { get; private set; } = 5f; [field: SerializeField] public float attackFOV { get; private set; } = 90f; [SerializeField] private float lostTargetTime = 1f; [SerializeField] private float searchTime = 5f; [SerializeField] private float hideBehindDoorTime = 2f; [SerializeField] private float hideBehindCornerTime = 3f; [SerializeField] private SplineContainer patrolPathContainer; private SplinePath patrolPath; private float patrolTime = 0f; [Header("AI Loop")] public bool loopEnabled = true; public float loopWaitTime = 0.2f; private AIRoom lastRoom; private void Start() { InitializePatrolPath(); StartCoroutine(AILoop()); } private void InitializePatrolPath() { var pathTransform = patrolPathContainer.transform.localToWorldMatrix; List<SplineSlice<Spline>> splineSlices = new List<SplineSlice<Spline>>(); foreach (Spline spline in patrolPathContainer.Splines) { splineSlices.Add(new SplineSlice<Spline>(spline, new SplineRange(0, GetSize(spline.Knots)), pathTransform)); } patrolPath = new SplinePath(splineSlices); } private IEnumerator AILoop() { WaitForSeconds loopWait = new WaitForSeconds(loopWaitTime); while (loopEnabled) { GetTarget(); Debug.Log($"Distance to target: " + agent.remainingDistance); switch (currentState) { case AIState.StartPatrolling: StartPatrollingBehavior(); break; case AIState.Following: FollowingBehavior(); break; case AIState.StartSearching: lastRoom = AIPlayerInformations.Instance.PlayerCurrentRoom; StartSearchingBehavior(); break; case AIState.StartWaiting: TrollBehavior(); break; } yield return loopWait; } } private void StartPatrollingBehavior() { currentState = AIState.WhilePatrolling; StartCoroutine(WhilePatrollingBehavior()); } private IEnumerator WhilePatrollingBehavior() { while (currentState == AIState.WhilePatrolling) { Vector3 position = patrolPath.EvaluatePosition(patrolTime); FollowTarget(position); while (!isTargetPointSet) { if (agent.remainingDistance < 0.1) isTargetPointSet = true; yield return new WaitForSeconds(0f); } isTargetPointSet = false; patrolTime += (float)(0.005f * Time.deltaTime); if (patrolTime > 1f) patrolTime = 0f; } } private void FollowingBehavior() { if (Target != null) FollowTarget(Target.position); } private void StartSearchingBehavior() { currentState = AIState.WhileSearching; SearchRoom(); } private void TrollBehavior() { currentState = AIState.WhileWaiting; StartCoroutine(TrollCycle()); } private void GetTarget() { Collider[] foundColliders = Physics.OverlapSphere(transform.position, seeRadius, targetMask); if (foundColliders.Length != 0) { Transform target = foundColliders[0].transform; Vector3 direction = (target.position - transform.position).normalized; if (Vector3.Angle(transform.forward, direction) < (seeFOV / 2)) { float distance = Vector3.Distance(transform.position, target.position); if (!Physics.Raycast(transform.position, direction, distance, obstacleMask)) { HasTarget = true; Target = target; currentState = AIState.Following; } else CantFindTarget(); } else CantFindTarget(); } } private void FollowTarget(Vector3 position) { agent.SetDestination(position); } private void CantFindTarget() { if (currentState == AIState.Following) currentState = AIState.StartSearching; LoseTarget(); } private void LoseTarget() { HasTarget = false; Target = null; } private void SearchRoom() { StartCoroutine(SearchTimerBehavior()); StartCoroutine(SearchSubBehavior()); } private IEnumerator TrollCycle() { WaitForSeconds waitForDoor = new WaitForSeconds(hideBehindDoorTime); WaitForSeconds waitForCorner = new WaitForSeconds(hideBehindCornerTime); WaitForSeconds waitUpdate = new WaitForSeconds(loopWaitTime); GoToRandomPointsInRoom(lastRoom.Doors); isTargetPointSet = false; while (!isTargetPointSet) { if (agent.remainingDistance < 0.1) isTargetPointSet = true; yield return waitUpdate; } yield return waitForDoor; isTargetPointSet = false; GoToRandomPointsInRoom(lastRoom.Corners); while (!isTargetPointSet) { if (agent.remainingDistance < 0.1) isTargetPointSet = true; yield return waitUpdate; } yield return waitForCorner; if (currentState == AIState.WhileWaiting) currentState = AIState.StartPatrolling; } private IEnumerator SearchTimerBehavior() { WaitForSeconds wait = new WaitForSeconds(searchTime); yield return wait; if (currentState == AIState.WhileSearching) currentState = AIState.StartWaiting; } private IEnumerator SearchSubBehavior() { WaitForSeconds waitUpdate = new WaitForSeconds(loopWaitTime); isTargetPointSet = true; while (currentState == AIState.WhileSearching) { if (isTargetPointSet) GoToRandomPointsInRoom(lastRoom.Points); isTargetPointSet = (agent.remainingDistance < 0.1); yield return waitUpdate; } } private void GoToRandomPointsInRoom(Transform[] array) { int randomIndex = Random.Range(0, array.Length); Transform point = array[randomIndex]; FollowTarget(point.position); } private static int GetSize(IEnumerable toCount) { int count = 0; foreach (var part in toCount) { count += 1; } return count; } } }
问题原因分析
- 巡逻逻辑的等待机制:
WhilePatrollingBehavior中设置完目标点后,会进入循环等待agent.remainingDistance < 0.1才会更新下一个点,这种“到达再走下一步”的逻辑会导致AI移动出现明显停顿,尤其是NavMeshAgent的路径计算存在微小延迟时,等待时间会被放大。 - 时间增量不合理:
patrolTime += (float)(0.005f * Time.deltaTime)的增量极小,导致每次更新的路径点间距过近,加上必须到达当前点才会推进时间,进一步加剧了卡顿感。
修复方案
1. 修改巡逻协程逻辑
去掉等待到达点的循环,改为持续基于时间推进patrolTime并更新目标点,让NavMeshAgent平滑跟随路径:
[SerializeField] private float patrolSpeed = 0.1f; // 控制沿路径移动的速度,0-1区间内值越大移动越快 private IEnumerator WhilePatrollingBehavior() { while (currentState == AIState.WhilePatrolling) { // 基于时间持续推进路径进度 patrolTime += Time.deltaTime * patrolSpeed; if (patrolTime > 1f) patrolTime = 0f; // 获取当前路径点并设置目标 Vector3 position = patrolPath.EvaluatePosition(patrolTime); FollowTarget(position); // 等待一帧保证平滑更新 yield return null; } }
2. 优化路径初始化方法
GetSize方法可以直接利用集合的Count属性优化,避免不必要的遍历:
private void InitializePatrolPath() { var pathTransform = patrolPathContainer.transform.localToWorldMatrix; List<SplineSlice<Spline>> splineSlices = new List<SplineSlice<Spline>>(); foreach (Spline spline in patrolPathContainer.Splines) { // 直接使用spline.Knots.Count替代自定义GetSize方法 splineSlices.Add(new SplineSlice<Spline>(spline, new SplineRange(0, spline.Knots.Count), pathTransform)); } patrolPath = new SplinePath(splineSlices); }
内容的提问来源于stack exchange,提问作者LagowiecDev
相关产品推荐
相关产品推荐

