Unity中FindGameObjectsWithTag仍找到已销毁对象的问题及解决方法
Unity销毁对象后仍被FindGameObjectsWithTag找到导致MissingReferenceException的原因与解决办法
原因
- Unity的
Destroy()方法并非立即销毁对象,只是将其标记为待销毁状态,真正的销毁操作会延迟到当前帧的Update阶段结束后、LateUpdate之后的专属销毁阶段执行。因此同一帧内的其他Update方法调用FindGameObjectsWithTag时,依然会检索到这个已标记但未真正移除的对象。 - 被标记销毁的对象引用不会直接变为null,而是成为一个“无效引用”,当代码尝试访问该对象的属性、方法或组件时,就会触发
MissingReferenceException。
解决办法
1. 避免同一帧内的依赖冲突
- 调整逻辑执行时机:将销毁对象的逻辑移到
EarlyUpdate中执行,确保在其他Update方法执行前完成标记;或者让查找逻辑延迟到下一帧(比如用Invoke延迟0秒,或者在LateUpdate中执行查找)。 - 维护自定义对象列表:提前将需要管理的目标对象存入
List<GameObject>或HashSet<GameObject>,销毁时直接从列表中移除,后续业务逻辑直接遍历这个自定义列表,不再依赖FindGameObjectsWithTag这类全局查找接口。
2. 检查对象有效性
每次通过查找获取对象引用后,先做有效性校验,避免访问无效对象:
GameObject[] objects = GameObject.FindGameObjectsWithTag("TargetTag"); foreach (var obj in objects) { // 基础校验:Unity对已销毁对象重载了==运算符,会返回false if (obj == null) continue; // 更严谨的校验方式(避免自定义==重载干扰) if (Object.ReferenceEquals(obj, null)) continue; // 或者通过尝试获取组件间接判断 if (!obj.TryGetComponent<Transform>(out _)) continue; // 执行后续逻辑 }
3. 用对象池替代销毁
如果目标对象需要频繁创建和销毁,改用对象池模式:
- 不调用
Destroy(),而是将对象设置为非激活状态(obj.SetActive(false)),此时FindGameObjectsWithTag不会返回非激活对象。 - 需要复用对象时,从对象池中取出并重新激活(
SetActive(true)),既避免了销毁重建的性能开销,也从根源上消除了无效引用问题。
4. 自定义状态标记延迟销毁
给目标对象添加自定义状态字段,标记其是否待销毁:
public class TargetObject : MonoBehaviour { public bool isMarkedForDestroy = false; }
销毁前先标记状态,延迟一帧再执行销毁:
// 销毁逻辑 var target = GetComponent<TargetObject>(); target.isMarkedForDestroy = true; Destroy(target.gameObject, 0); // 延迟0秒会在下一帧执行销毁
其他方法查找后先检查状态:
foreach (var obj in GameObject.FindGameObjectsWithTag("TargetTag")) { var targetComp = obj.GetComponent<TargetObject>(); if (targetComp != null && targetComp.isMarkedForDestroy) continue; // 执行后续逻辑 }
内容的提问来源于stack exchange,提问作者Sho Boi
相关产品推荐
相关产品推荐

