You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity MonoBehaviour单例OnDestroy执行顺序差异问题咨询

生命周期执行顺序差异的根本原因

Unity对DontDestroyOnLoad标记对象的销毁顺序,由对象的初始归属直接决定:

  • 场景内预先挂载的Counter,是在场景加载时先被创建为当前场景对象,Awake中调用DontDestroyOnLoad才移入全局持久场景。引擎退出或卸载场景时,会优先处理当前场景原生的所有对象(包括场景中挂载的Observable),再处理从该场景移入持久场景的对象,因此Observable会比Counter更早触发OnDisable、OnDestroy回调。
  • 懒加载创建的Counter,是代码动态生成GameObject后直接调用DontDestroyOnLoad,从创建初始就完全归属全局持久场景。引擎退出时会先清空全局持久场景的所有对象,再处理当前激活场景内的普通对象,因此Counter会比场景原生的Observable更早执行销毁回调。

你观察到的Some objects were not cleaned up when closing the scene报错,本质是销毁顺序反转后,Observable在OnDestroy中访问单例实例时,原Counter已经被销毁、静态_instance引用为空,触发懒创建逻辑在退出阶段生成了无法被正常清理的新对象导致的。

可落地的修复方案

  • 给单例基类增加退出状态拦截,从根源禁止退出阶段创建新实例
    在基类中增加应用退出标记,在退出流程中将标记置为true,instance访问逻辑优先判断该标记,退出阶段直接返回null,不执行创建逻辑;同时注意只有非退出阶段的对象销毁,才需要清空静态实例引用,避免误触发创建。核心修改代码如下:
    public class SingletonMonoBehaviour<T> : MonoBehaviour where T: SingletonMonoBehaviour<T>
    {
        private static T _instance;
        private static bool _isApplicationQuitting = false;
    
        public static T instance { 
            get { 
                if (_isApplicationQuitting) return null;
                if (_instance == null) {
                    var go = new GameObject(typeof(T).Name);
                    _instance = go.AddComponent<T>();
                    DontDestroyOnLoad(_instance);
                }
                return _instance;
            } 
        }
          
        protected virtual void Awake()
        {
            if (_instance != null && _instance != this)
            {
                Destroy(this.gameObject);
            } else {
                _instance = (T)this;
                DontDestroyOnLoad(this);
            }
        }
    
        protected virtual void OnApplicationQuit()
        {
            _isApplicationQuitting = true;
        }
    
        private void OnDisable() {
            Debug.Log("OnDisable " + this.name);
        }
    
        private void OnDestroy() {
            if (!_isApplicationQuitting)
            {
                _instance = null;
            }
            Debug.Log("OnDestroy " + this.name);
        }
    }
    
  • 统一单例创建路径
    如果需要固定全局对象的销毁顺序,可以在项目的启动初始化场景(首个加载的场景)中主动访问所有全局单例的instance属性,或直接在启动场景预先挂载所有单例组件,保证所有单例的创建来源一致,避免因归属不同导致的销毁顺序差异。
  • 优化事件绑定逻辑
    全局单例的事件注销尽量放在OnDisable阶段执行,访问单例实例前增加空判断,不要依赖销毁顺序保证逻辑正确性。

内容的提问来源于stack exchange,提问作者Евгений Латынин

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 10:03:22