Unity销毁GameObject后仍可访问问题及检测方法咨询
问题描述
我有两个Unity GameObject:monster(怪物)和hunter(猎人),各自挂载一个脚本。从怪物脚本中调用猎人脚本的函数销毁猎人对象,但猎人对象并未被置空。猎人实体已从屏幕消失,却仍能访问该对象及其成员。
HunterScript 代码
public class HunterScript : MovingEntityScript { // 最终继承自 MonoBehaviour int hp = 2; // 其他代码 public void TakeDamage () { if (--hp == 0) Destroy(gameObject); } }
MonsterScript 代码
public class MonsterScript: MovingEntityScript { // 最终继承自 MonoBehaviour GameObject target; // 启动时设置为唯一的 Hunter 实例 // 其他代码 public void Attack () { target.GetComponent<HunterScript>().TakeDamage(); Debug.Log(target == null || target.Equals(null)); // 即使猎人 hp == 0,也打印 False } }
怪物和猎人都大量使用协程,包括猎人销毁时也有协程。请问为何会出现这种情况?如何检测实体是否已真正被销毁?
原因分析
- Destroy是延迟执行的:Unity的
Destroy()不会立刻销毁对象,只是给它标记为「待销毁」,实际销毁操作要等当前帧Update循环结束、渲染开始前,或者下一帧才会执行。所以调用Destroy()的同一帧里,target仍然指向原对象实例,不会立刻变为null。 - C#对象与Unity引擎实体分离:
GameObject是C#层的包装类,背后对应Unity引擎底层的实体。调用Destroy()后,引擎实体被标记销毁,但C#的对象实例不会马上被垃圾回收,所以你仍能访问它的成员变量,直到GC清理掉这个C#对象。 - 协程延长对象生命周期:如果猎人销毁时还有未完成的协程,这些协程会继续执行到结束(除非手动停止),这会拖长C#对象的生命周期,导致它更晚被回收,自然就能一直访问到。
正确检测对象是否销毁的方法
- 延迟一帧用
target == null判断:Unity重载了GameObject的==运算符,会同时检查引擎实体是否被销毁,但必须等Destroy()执行后的下一帧,这个判断才会返回true。可以用协程实现延迟:public void Attack () { target.GetComponent<HunterScript>().TakeDamage(); StartCoroutine(CheckTargetStatusNextFrame()); } IEnumerator CheckTargetStatusNextFrame() { yield return null; Debug.Log(target == null); // 此处会打印true } - 监听
OnDestroy事件主动置空:在猎人脚本中添加销毁事件,让怪物脚本订阅后主动把target置空:
猎人脚本补充代码:
怪物脚本初始化时订阅:public event Action OnHunterDestroyed; void OnDestroy() { OnHunterDestroyed?.Invoke(); }void Start() { var hunterComp = target.GetComponent<HunterScript>(); hunterComp.OnHunterDestroyed += () => target = null; } - 用
TryGetComponent前置检查:访问组件前先尝试获取,失败则说明对象已销毁或组件不存在:public void Attack () { if (target.TryGetComponent(out HunterScript hunter)) { hunter.TakeDamage(); } else { Debug.Log("猎人已销毁"); } } - 检查
activeInHierarchy属性:对象销毁后activeInHierarchy会返回false,但要注意如果只是用SetActive(false)禁用对象,该值也会是false,需要结合场景逻辑区分。
内容的提问来源于stack exchange,提问作者An Assembler
相关产品推荐
相关产品推荐

