继承MonoBehaviour的单例

本文通过对比正常单例和继承MonoBehaviour的单例,详细解释了两种实现方式的区别及运行原理。并针对构造函数调用时机等常见问题进行了探讨。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以懒汉式为例:

  • 正常单例

public class GameCtrl
{
    #region 正常单例
    private GameCtrl()
    {
        Debug.Log("GameCtrl");
    }
    private static GameCtrl m_Instance = null;
    public static GameCtrl Instance
    {
        get
        {
            Debug.Log("get");
            if (m_Instance == null)
            {
                m_Instance = new GameCtrl();
            }
            return m_Instance;
        }
    }
    #endregion
}

点击事件:

    private void OnBtnClick()
    {
        Debug.Log("OnBtnClick");
        GameCtrl.Instance.PopHintMsg();
    }
  1. 运行unity打印结果:空
  2. 第一次点击按钮打印结果:
    第一次点击按钮打印结果
  3. 第二次点击按钮打印结果:
    第二次点击按钮打印结果

总结:构造函数在第一次get之后调用一次,所以第一次用的时候new,后面实例已经存在直接返回

  • 继承MonoBehaviour的单例

public class GameCtrl : MonoBehaviour
{
    #region 继承MonoBehaviour的单例
    private GameCtrl()
    {
        m_Instance = this;
        Debug.Log("GameCtrl");
    }
    private static GameCtrl m_Instance = null;
    public static GameCtrl Instance
    {
        get
        {
            Debug.Log("get");
            return m_Instance;
        }
    }
    #endregion
    // -------------分割线,以下为辅助阅读代码,与单例没关系-----------------
    private void Awake()
    {
        Debug.Log("Awake");
    }
    public void PopHintMsg()
    {
    }
}
  1. 运行unity打印结果:
    运行unity打印结果
  2. 第一次点击按钮打印结果:
    第一次点击按钮打印结果
  3. 第二次点击按钮打印结果:同第一次
    第一次点击按钮打印结果
    注意:与普通单例的构造函数有所区别,构造函数在运行游戏时立刻调用,每次用的时候实例都已经存在了

疑问:

  1. 正常单例情况下断点,发现每次get时Instance就已经实例化出来过了,而断点又进不去构造函数是为何?(采用打印的方法是正常的、符合预期的)
  2. 为什么运行unity就会执行构造方法?而且还执行了2次,并且停止游戏时也会运行一次构造方法(图上没贴出来),不知为何
  3. 为什么采用和正常单例相似的方式,在属性中判断是否为空,空了new出来不行?(new之后仍然为空)而要在构造函数中(Awake或者Start中)=this
    猜测: MonoBehaviour将new方法复写了,m_Instance = this最好写在Awake中因为最先且只调用一次
<think>我们正在讨论的是在Unity中需要继承MonoBehaviour实现模式的使用场景。根据之前的引用和问题,用户想了解在什么情况下需要选择继承MonoBehaviour模式,而不是普通的非MonoBehaviour。分析:在Unity中,继承MonoBehaviour模式通常用于需要与Unity引擎生命周期挂钩的类。如,需要用到Awake、Start、Update等回调,或者需要作为组件挂载在游戏对象上,或者需要使用协程(Coroutine)等Unity特定功能。使用场景:1.需要访问Unity组件的:比如一个全局的音效管理器,它可能需要AudioSource组件,并且需要随着游戏状态更新而更新(如暂停时停止音效)。2.需要每帧更新的:比如一个输入管理器,需要在Update中检测输入,并分发给其他系统。3.需要协程的:比如一个网络请求管理器,需要发起异步的Web请求,并使用协程等待结果。4.需要持久化且跨场景的:比如一个游戏状态管理器,它需要在整个游戏过程中存在,并且可能需要响应场景加载事件(如Unity的OnSceneLoaded事件)。5.需要在Inspector中配置数据的如一个全局配置,设计时在Inspector中设置参数,运行时作为供其他脚本访问。注意:继承MonoBehaviour模式通常需要处理跨场景不销毁(DontDestroyOnLoad)以及重复实的销毁(防止多个存在)。下面我们参考引用中的实现,并给出一个典型的继承MonoBehaviour模式实现,然后讨论其使用场景。实现示(结合引用[2]和引用[1]):```csharpusingUnityEngine;publicclassSingletonMono<T>:MonoBehaviourwhereT:MonoBehaviour{privatestaticT_instance;publicstaticTInstance{get{if(_instance==null){//在场景中查找是否已经存在该类型的实_instance=FindObjectOfType<T>();if(_instance==null){//创建一个新的GameObject来挂载组件GameObjectsingletonObject=newGameObject();_instance=singletonObject.AddComponent<T>();singletonObject.name=typeof(T).ToString()+"(Singleton)";//设置为跨场景不销毁DontDestroyOnLoad(singletonObject);}}return_instance;}}protectedvirtualvoidAwake(){//防止重复创建if(_instance!=null&&_instance!=this){Destroy(gameObject);}else{_instance=thisasT;//确保对象在加载新场景时不被销毁DontDestroyOnLoad(gameObject);}}}```使用场景详解:1.**全局游戏管理器(GameManager)**:-需要管理游戏状态(如开始、暂停、结束)-可能需要响应Unity生命周期事件(如Update、OnApplicationPause)-通常需要跨场景存在2.**音效管理器(AudioManager)**:-需要挂载AudioSource组件-可能需要实时控制音量、播放暂停等-使用协程处理音效的淡入淡出3.**输入管理器(InputManager)**:-每帧检测玩家输入-将输入事件分发给其他系统-可能需要处理不同平台(PC、移动设备)的输入差异4.**场景加载管理器(SceneLoader)**:-使用Unity的SceneManager加载场景-在加载过程中显示加载进度条(需要每帧更新进度)-使用协程处理异步加载5.**网络管理器(NetworkManager)**:-使用Unity的UnityWebRequest发送网络请求-需要协程处理异步网络操作-在Inspector中配置服务器地址等参数6.**对象池管理器(ObjectPoolManager)**:-管理游戏对象的生成和回收-需要访问GameObject和Transform等Unity组件-可能需要在场景中放置一个父物体来管理池中的对象与普通(非MonoBehaviour)的对比:|特性|继承MonoBehaviour|普通(非MonoBehaviour)||------|-------------------------|---------------------------||Unity生命周期|支持(Awake,Start,Update等)|不支持||协程|支持|不支持(需要借助MonoEvent)||Unity组件|可挂载组件|不可挂载组件||序列化|支持(Inspector可见)|不支持||跨场景|需手动设置DontDestroyOnLoad|自动跨场景(无对象)||内存占用|有GameObject开销|无额外开销|注意事项:-继承MonoBehaviour模式在Awake中需要处理重复实的问题,避免多个实同时存在。-由于Unity的生命周期,Awake可能会在非预期的时间被调用,因此的初始化时机需要特别注意。-在编辑器模式下,当停止播放时,对象会被销毁,但静态变量_instance可能不会重置,这可能导致下次运行出现问题。因此,有时需要在OnDestroy中重置_instance(但需谨慎,因为DontDestroyOnLoad的对象不会在场景切换时销毁,也不会在停止播放时销毁,除非特别处理)。总结:当需要与Unity引擎深度交互(如使用Unity的生命周期方法、协程、组件等)时,应该选择继承MonoBehaviour模式。否则,使用普通(非MonoBehaviour)更为轻量和高效。相关问题:1.继承MonoBehaviour模式在停止播放后_instance未重置怎么办?2.如何让继承MonoBehaviour模式在多个场景切换时不被重复创建?3.如何实现一个继承MonoBehaviour模式,同时允许在Inspector中配置参数?4.在继承MonoBehaviour模式中,如何安全地使用协程?5.继承MonoBehaviour模式和普通模式在性能上有什么差异?</think>### Unity中继承MonoBehaviour模式使用场景 在Unity开发中,**继承MonoBehaviour模式**主要用于需要深度集成Unity引擎功能的场景。以下是典型使用场景及技术解析: --- #### 核心使用场景 1. **需要Unity生命周期回调的场景** ```csharp public class AudioManager : SingletonMono<AudioManager> { void Update() { /* 每帧更新音频状态 */ } void OnApplicationPause() { /* 暂停时静音处理 */ } } ``` - 适用于需要`Awake/Start/Update/OnDestroy`等Unity原生生命周期方法的类 - 如:音效管理器、粒子系统控制器、实时输入检测 2. **需要挂载组件的场景** ```csharp public class UIManager : SingletonMono<UIManager> { [SerializeField] Canvas mainCanvas; // Inspector可配置 void Awake() { GetComponent<CanvasGroup>().alpha = 1; // 访问Unity组件 } } ``` - 需要Unity组件功能时(如`Transform/Rigidbody/Collider`) - 支持Inspector可视化配置参数 3. **需要协程(Coroutine)的场景** ```csharp public class SceneLoader : SingletonMono<SceneLoader> { public IEnumerator LoadSceneAsync(string sceneName) { AsyncOperation op = SceneManager.LoadSceneAsync(sceneName); while (!op.isDone) { yield return null; } } } ``` - 异步加载资源/场景 - 实现延时特效、动画序列等 4. **需要DontDestroyOnLoad的场景** ```csharp public class GameStateManager : SingletonMono<GameStateManager> { protected override void Awake() { base.Awake(); DontDestroyOnLoad(gameObject); // 跨场景持久化 } } ``` - 玩家数据管理 - 全局游戏状态跟踪 --- #### 技术实现要点(参考引用[1][2]) ```csharp public class SingletonMono<T> : MonoBehaviour where T : MonoBehaviour { private static T _instance; public static T Instance { get { if (_instance == null) { _instance = FindObjectOfType<T>(); if (_instance == null) { GameObject obj = new GameObject(); obj.name = typeof(T).Name; _instance = obj.AddComponent<T>(); DontDestroyOnLoad(obj); // 关键步骤[^1] } } return _instance; } } protected virtual void Awake() { if (_instance != null && _instance != this) { Destroy(gameObject); // 防止重复创建[^2] } else { _instance = this as T; } } } ``` --- #### 与非MonoBehaviour的对比 | 特性 | 继承MonoBehaviour | 普通 | |------|------------------------|----------| | **生命周期挂钩** | ✅ 完整Unity生命周期 | ❌ | | **组件访问** | ✅ 直接使用GetComponent | ❌ | | **协程支持** | ✅ StartCoroutine | ❌ | | **场景持久化** | ✅ DontDestroyOnLoad | 自动实现 | | **线程安全** | ❌ 需额外处理 | ✅ | | **内存开销** | 较高(含GameObject) | 极低 | --- #### 最佳实践建议 1. **初始化时机控制** ```csharp // 首次访问时自动初始化 void Start() { var manager = UIManager.Instance; } ``` 2. **防止重复实** 在`Awake`中实现实销毁逻辑(参考上述代码),避免场景切换时重复创建 3. **编辑器调试支持** ```csharp #if UNITY_EDITOR void OnValidate() { // 编辑器模式下验证配置 } #endif ``` 4. **资源释放处理** ```csharp void OnDestroy() { if (_instance == this) { // 释放持有的资源 } } ``` > 继承MonoBehaviour模式适用于约60%的Unity游戏系统,特别是需要深度集成引擎功能的模块[^3]。对于纯数据管理类,推荐使用更轻量的非MonoBehaviour。 --- #### 相关问题 1. 如何解决继承MonoBehaviour在场景切换时的重复创建问题? 2. 在继承MonoBehaviour中如何安全使用Unity协程? 3. 如何为继承MonoBehaviour实现线程安全访问? 4. 继承MonoBehaviour模式与ScriptableObject有何适用场景差异? 5. 多场景开发中如何管理继承MonoBehaviour依赖关系?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿斯提尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值