Unity所有GameObject销毁后执行CleanUp逻辑的可靠方案
Unity 最晚执行清理逻辑组件实现方案
问题背景
现有一套初始化系统,通过给组件添加[DefaultExecutionOrder(-100)]特性,可保证其Awake()逻辑早于所有其他游戏对象执行。需要实现对称的反向逻辑:在场景内所有游戏对象全部销毁完成后,触发指定清理函数。
已尝试的失效方案如下:
- 接力传递方案:在主组件
OnDestroy()中遍历根场景对象,给存活对象挂载清理组件接力触发检查,实际运行判断逻辑不准,清理时机错误 - 异步轮询方案:将
OnDestroy()设为异步方法,间隔100ms检测存活对象,退出播放模式后循环无法终止,会误检测编辑模式对象 - OnDestroy实例化方案:在主组件
OnDestroy()中实例化挂载清理组件的对象,Unity引擎禁止在销毁生命周期内执行新对象实例化操作 - 析构函数方案:在组件C#析构函数中写清理逻辑,析构函数调用时机由GC决定,存在早于
Awake()执行的异常情况 - ISubsystem方案:尝试使用Unity引擎
ISubsystem接口实现,缺乏清晰业务层使用文档,接入成本过高
约束条件:
- 不允许使用全局工厂模式实例化管控对象
OnApplicationQuit()逻辑仅作为最后兜底选项,优先使用OnDestroy()链路实现- 要求同一个组件既能最早执行初始化,又能最晚执行清理
实现原理
利用Unity引擎原生生命周期和销毁顺序规则实现,无额外侵入性:
Unity销毁对象的固定顺序为:
- 先销毁所有激活普通场景内的GameObject,同节点上组件的
OnDestroy()触发顺序和DefaultExecutionOrder值负相关:值越小越先触发,值越大越晚触发 - 所有普通场景对象完全销毁后,才会开始销毁标记了
DontDestroyOnLoad的永久存活场景对象
只要提前在初始化阶段创建一个存放在永久场景、设置了最高执行顺序的隐藏锚点对象,它的OnDestroy()就会是全场景最后一批触发的生命周期函数,此时所有业务对象已经完成销毁,刚好满足清理时机要求。
具体实现步骤
- 给核心初始化组件设置
[DefaultExecutionOrder(-32000)](Unity支持的最低执行顺序值,比之前的-100更早,保证绝对最先执行Awake) - 在核心组件的
Awake()最开头创建隐藏的清理锚点对象,不需要外部预制体或全局工厂,代码内直接创建即可:- 锚点对象添加
HideFlags.HideAndDontSave标记,不会显示在层级面板、不会被保存到场景资源、不会被用户误操作 - 给锚点挂载清理触发组件,给该组件设置
[DefaultExecutionOrder(32000)](Unity支持的最高执行顺序值,保证同批对象里最晚触发OnDestroy) - 调用
DontDestroyOnLoad()把锚点移到永久存活场景
- 锚点对象添加
- 在清理触发组件的
OnDestroy()中调用核心系统的最终清理逻辑即可。
代码示例
核心系统组件:
using UnityEngine; // 最低执行顺序,保证Awake全局最先运行 [DefaultExecutionOrder(-32000)] public class CoreSystem : MonoBehaviour { public static CoreSystem Instance { get; private set; } private void Awake() { if (Instance != null) { Destroy(gameObject); return; } Instance = this; // 初始化阶段提前创建清理锚点,禁止在OnDestroy阶段创建 CreateCleanupAnchor(); // 原有业务初始化逻辑 InitAllSystems(); } private void CreateCleanupAnchor() { GameObject anchor = new GameObject("InternalCleanupAnchor"); // 隐藏对象,不写入场景文件,不显示在层级面板 anchor.hideFlags = HideFlags.HideAndDontSave; // 挂载清理触发器 anchor.AddComponent<FinalCleanupTrigger>(); // 移到永久存活场景,最后才会被销毁 DontDestroyOnLoad(anchor); } private void InitAllSystems() { // 这里写你的原有初始化逻辑 } /// <summary> /// 所有对象销毁完成后触发的最终清理逻辑 /// </summary> public void ExecuteFinalCleanup() { // 这里写你需要的清理逻辑 Debug.Log("All scene objects destroyed, final cleanup executed."); } }
清理触发器组件:
using UnityEngine; // 最高执行顺序,保证同销毁批次内OnDestroy全局最晚触发 [DefaultExecutionOrder(32000)] public class FinalCleanupTrigger : MonoBehaviour { private void OnDestroy() { // 触发时所有普通场景对象已经完成销毁 CoreSystem.Instance?.ExecuteFinalCleanup(); } }
避坑说明
- 之前“接力棒”方案失效的核心原因:Unity销毁对象是分帧分批执行的,同一销毁帧内大量对象处于“正在销毁但判空仍返回true”的中间状态,无法准确判断是否所有对象都完成销毁
- 不要使用异步轮询方案:Unity退出播放模式后域重载不会等待异步任务执行完成,会出现逻辑跑飞、误操作编辑模式资源的问题
- 不要在任何
OnDestroy()生命周期内执行新对象实例化操作,Unity引擎会直接拦截该操作 - C#析构函数由GC调度,和Unity引擎生命周期完全脱钩,绝对不能用来承载引擎对象相关的清理逻辑
内容的提问来源于stack exchange,提问作者IC_
相关产品推荐
相关产品推荐

