Unity中Vector3.MoveTowards正常但Vector2.MoveTowards失效的原因
我在Unity 2021.3的2D环境中搭建基于路点的敌人寻路系统,将EnemyPathing.cs脚本挂载到敌人预制体上。当使用Vector2.MoveTowards实现移动时,敌人卡在首个路点(waypointIndex[0])无法切换到下一个路点;但使用Vector3.MoveTowards时则能正常工作。核心代码如下:
public class EnemyPathing : MonoBehaviour { [SerializeField] List<Transform> waypoints; [SerializeField] float moveSpeed = 2f; int waypointIndex = 0; void Start() { transform.position = waypoints[waypointIndex].transform.position; } void Update() { Move(); } private void Move() { if (waypointIndex <= waypoints.Count - 1) { var targetPosition = waypoints[waypointIndex].transform.position; var movementThisFrame = moveSpeed * Time.deltaTime; // 问题代码 transform.position = Vector2.MoveTowards(transform.position, targetPosition, movementThisFrame); if (transform.position == targetPosition) { waypointIndex++; } } else { Destroy(gameObject); } } }
原因分析
1. Vector2与Vector3类型转换导致Z轴不一致
transform.position是Vector3类型,包含X/Y/Z三个轴的值。使用Vector2.MoveTowards时,会自动将输入的Vector3转换为Vector2(丢弃Z轴),计算后返回的Vector2再隐式转换回Vector3赋值给transform.position——此时Z轴会被强制重置为0。
如果你的路点Transform的Z轴值不为0(比如2D场景中为分层设置了Z轴偏移),那么transform.position的Z轴是0,而targetPosition的Z轴是路点的原始值,两者永远无法相等,导致waypointIndex无法递增,敌人卡在第一个路点。
而Vector3.MoveTowards会直接操作三个轴的数值,保持Z轴与当前物体一致,当路点Z轴和敌人初始Z轴相同时,移动后两者Z轴相等,更容易满足transform.position == targetPosition的判断。
2. 浮点数精度误差的隐形影响
即使路点Z轴为0,直接用==比较两个Vector3/Vector2也可能因为浮点数运算的精度问题导致判断失败。比如移动步长movementThisFrame可能让物体停在离目标极近但不完全相等的位置,这时==会返回false。Vector3.MoveTowards因为直接操作三个轴,可能在某些场景下刚好让位置完全匹配,而Vector2的转换可能放大了这种精度误差。
解决方案
方案一:转换为Vector2后再比较
将位置判断改为基于Vector2的比较,忽略Z轴的影响:
private void Move() { if (waypointIndex <= waypoints.Count - 1) { var targetPosition = waypoints[waypointIndex].transform.position; var movementThisFrame = moveSpeed * Time.deltaTime; transform.position = Vector2.MoveTowards(transform.position, targetPosition, movementThisFrame); // 转换为Vector2后比较,忽略Z轴 if ((Vector2)transform.position == (Vector2)targetPosition) { waypointIndex++; } } else { Destroy(gameObject); } }
方案二:使用距离阈值判断(推荐)
避免直接用==比较浮点数,而是判断当前位置与目标位置的距离是否小于一个极小的阈值(比如0.01f),彻底解决精度问题:
private void Move() { if (waypointIndex <= waypoints.Count - 1) { var targetPosition = waypoints[waypointIndex].transform.position; var movementThisFrame = moveSpeed * Time.deltaTime; transform.position = Vector2.MoveTowards(transform.position, targetPosition, movementThisFrame); // 用距离阈值替代直接相等判断 if (Vector2.Distance(transform.position, targetPosition) < 0.01f) { waypointIndex++; } } else { Destroy(gameObject); } }
方案三:统一处理Z轴
如果需要保留Z轴的层级设置,可以手动将目标位置的Z轴设置为与当前物体一致,再使用Vector2.MoveTowards:
private void Move() { if (waypointIndex <= waypoints.Count - 1) { var targetPos = waypoints[waypointIndex].transform.position; // 保持Z轴与当前物体一致 var targetPosition2D = new Vector2(targetPos.x, targetPos.y); var currentPos2D = new Vector2(transform.position.x, transform.position.y); var newPos2D = Vector2.MoveTowards(currentPos2D, targetPosition2D, moveSpeed * Time.deltaTime); // 重新赋值时保留原Z轴 transform.position = new Vector3(newPos2D.x, newPos2D.y, transform.position.z); if (Vector2.Distance(currentPos2D, targetPosition2D) < 0.01f) { waypointIndex++; } } else { Destroy(gameObject); } }
内容的提问来源于stack exchange,提问作者Shaker

