Unity中如何通过引用向队列添加Waypoint且避免意外修改?
首先得明确核心问题:因为Waypoint是继承自MonoBehaviour的引用类型,正常情况下你把CurrentWaypoint指向的实例存入队列后,后续修改CurrentWaypoint指向新节点时,队列里的旧引用应该还是指向原来的实例——除非你的代码逻辑里每次入队的都是同一个实例引用。针对你遇到的队列全是同一引用的问题,咱们可以用下面几种方法来规避:
1. 存储Waypoint的唯一标识符而非实例引用
给每个Waypoint类添加一个唯一ID字段,直接用Unity自带的实例ID就很方便,每个MonoBehaviour实例的ID都是全局唯一的:
public class Waypoint : MonoBehaviour { // 直接用Unity生成的实例ID作为唯一标识 public int UniqueId => GetInstanceID(); }
然后把队列改成存储int类型的ID:
Queue<int> visitedWaypointIds = new Queue<int>(); // AI抵达当前Waypoint时,将ID加入队列 visitedWaypointIds.Enqueue(currentWaypoint.UniqueId); // 检查下一个节点是否已访问过 if (!visitedWaypointIds.Contains(nextWaypoint.UniqueId)) { currentWaypoint = nextWaypoint; }
这种方法完全避开了引用类型的坑,因为存的是值类型的ID,不会出现引用被意外修改的情况,检查效率也很高。
2. 修正代码逻辑,确保入队的是当前实例引用
如果你还是想直接存Waypoint实例,那要确保每次入队的是当前指向的具体实例,而不是后续会被重新赋值的变量本身。正确的代码逻辑应该是这样:
Queue<Waypoint> visitedWaypoints = new Queue<Waypoint>(); Waypoint currentWaypoint; // 第一步:先把当前抵达的Waypoint实例加入队列 visitedWaypoints.Enqueue(currentWaypoint); // 第二步:再修改currentWaypoint指向新的下一个节点 currentWaypoint = GetNextTargetWaypoint();
这样队列里存入的是旧节点的实例引用,后续修改currentWaypoint指向新节点时,队列里的旧引用不会受到任何影响。如果你的队列还是全是同一引用,那大概率是GetNextTargetWaypoint()方法有问题,每次都返回同一个实例,这时候就要检查寻路逻辑了。
3. 用HashSet代替队列(仅需去重检查时更高效)
如果你的需求只是防止AI返回已访问过的节点,不需要记录访问顺序,那用HashSet<Waypoint>会比队列高效得多——它的Contains方法是O(1)时间复杂度,而队列是O(n):
HashSet<Waypoint> visitedWaypoints = new HashSet<Waypoint>(); // 抵达节点时添加到集合 visitedWaypoints.Add(currentWaypoint); // 检查下一个节点是否已访问 if (!visitedWaypoints.Contains(nextWaypoint)) { currentWaypoint = nextWaypoint; }
HashSet会自动过滤重复的实例引用,同一个Waypoint只能被添加一次,完美解决重复问题。
额外注意事项
- 如果直接存实例引用,要注意Waypoint对象被销毁的情况(比如场景切换),检查时先判断
waypoint != null,避免空引用错误。 - 若需要记录访问顺序但又要去重,也可以用
Queue搭配HashSet,用HashSet做快速检查,用Queue记录顺序。
内容的提问来源于stack exchange,提问作者WDUK

