MonoSingleton——Unity中的单例模式

本文针对Unity中的单例模式提出了一种优化方案,避免了全场景搜索带来的性能损耗,并通过Mutex确保实例唯一性。介绍了如何利用Awake()方法保证运行时单例特性,同时提供了包含初始化和逆初始化过程的抽象基类。

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

Unity中有很多特别的类需要以单例模式呈现,比如全局的UI管理类,各种缓存池,以及新手导航类等等。而Unity中,因为所有继承自Monobehaviour的脚本在实现的时候都是单线程的,所以像网上流传的一些C#的实现方式就显得不那么的实用了。

很多国内的公司所使用的MonoSingleton都是有问题的,比如像Easytouch中关于单例是这样实现中有这样一段代码。

public static T instance
{
    get
    {
        if (m_Instance == null)
        {
            m_Instance = GameObject.FindObjectOfType(typeof(T)) as T;//1这里耗费性能,有风险
            if (m_Instance == null)//2
            {
                m_Instance = new GameObject("Singleton of " + typeof(T).ToString(), typeof(T)).GetComponent<T>();
                m_Instance.Init();
            }
        }
        return m_Instance;
    }
}

那么我标注的两处就是代码当中不正确的地方。2处这是明显的套用了多线程的单例实现方式,而实际上,在单线程模式当中这个判断并没有意义。而1中,直接对全场景进行搜索的过程其本身就很浪费性能。那么正确的实现方式是什么呢?

首先,我们需要一个全局变量,比如,先建立一个全局类Global

public abstract class Global : MonoBehaviour
{
    public static HashSet<string> Singleton=new HashSet<string>();
}

每次建立都将类名存进HashSet当中,那么上面那段代码就可以改成这样。

public static T instance
{
    get
    {
        if (m_Instance == null)
        {
            var name = "Singleton of " + typeof(T).ToString();
            var flag = Global.Singleton.Contains(name);
            if (!flag)
            {
                m_Instance = new GameObject(name, typeof(T)).GetComponent<T>();
                m_Instance.Init();
                Global.Singleton.Add(name);
            }
        }
        return m_Instance;
    }
}
可能您要说了,我已经有了一个全局类了,那么难道还要再填一个东西?我只想直接用,用没有更简便的方法。您要说更好,不一定,但是更简便,确实有的。我们这里可以用上互斥类Mutex的类,那么上面那段代码就可以改成下面这样:

public static T instance
{
    get
    {
        if (m_Instance == null)//注意,此处在实际中只执行一次。
        {
            bool createdNew;
            var name = "Singleton of " + typeof(T).ToString();
            Mutex mutex = new Mutex(false, name, out createdNew);
            if (createdNew)
            {
                m_Instance = new GameObject(name, typeof(T)).GetComponent<T>();
                m_Instance.Init();
            }
        }
        return m_Instance;
    }
}

但这只是说,我如果在其他地方操作这个单例,而这个单例还必须新建一个游戏物体,还必须挂在上面,挂在游戏物体上就至少要有一个transform组件。那么我可不可以直接挂在物体上,那该怎么办?如果我挂多了该怎么办?

有办法,这里我们利用Awake()方法

private void Awake()
{
    if (m_Instance == null)
    {
        bool createdNew;
        var name = "Singleton of " + typeof(T).ToString();
        Mutex mutex = new Mutex(false, name, out createdNew);
        if (createdNew)
        {
            m_Instance = this as T;
            m_Instance.Init();
        }
    }
    else
    {
        Destroy(this);
    }


这样就可以保证运行时的单例了。那么完整的MonoSingleton还需要一些细节。比如在我的单例基类中,我设计成抽象类,提供了两个抽象函数,分别是初始化和逆初始化。之所以这么做就是为了在我们继承MonoSingleton的时候想一想,是不是必须要把这个类做成单例的,它一定是有单例的必要所以才是单例的,而不是将单例当静态使用。

using UnityEngine;
using System.Collections;
using System.Threading;
/// <summary>
/// 单例基类,提供两个抽象函数Init 和 DisInit 初始化和逆初始化过程。
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class MonoSingleton<T> : MonoBehaviour
where T : MonoSingleton<T>
{

    private static T m_Instance = null;
    private static string name;
    private static Mutex mutex;
    public static T instance
    {
        get
        {
            if (m_Instance == null)
            {
                if ( IsSingle())
                {
                    m_Instance = new GameObject(name, typeof(T)).GetComponent<T>();
                    m_Instance.Init();
                }
            }
            return m_Instance;
        }
    }

    private static bool IsSingle()
    {
        bool createdNew;
        name = "Singleton of " + typeof(T).ToString();
        mutex = new Mutex(false, name, out createdNew);
        return createdNew;
    }

    private void Awake()
    {
        if (m_Instance == null)
        {
            if (IsSingle())
            {
                m_Instance = this as T;
                m_Instance.Init();
            }
        }
        else
        {
            Destroy(this);
        }
    }

    protected abstract void Init();
    protected abstract void DisInit();
    private void OnDestory()
    {
        if (m_Instance!=null)
        {
            mutex.ReleaseMutex();
            DisInit();
            m_Instance = null;
        }
    }
    private void OnApplicationQuit()
    {
        mutex.ReleaseMutex();
    }
}


### 如何在 Unity 中使用 C# 实现 MonoBehaviour 单例模式的最佳实践 #### 使用 `MonoBehaviour` 创建安全可靠的单例模式 为了确保单例的安全性和可靠性,在 Unity 中实现 `MonoBehaviour` 类的单例模式时,可以采取如下措施: 定义一个继承自 `MonoBehaviour` 的类,并声明一个静态只读字段用于存储唯一的实例对象。为了避免多次初始化带来的冲突,可以在 `Awake()` 方法中进行必要的检查和赋值操作。 ```csharp using UnityEngine; public class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour { private static T _instance; /// <summary> /// 获取当前场景中的唯一实例. /// </summary> public static T Instance { get { if (_instance == null) { _instance = FindObjectOfType<T>(); if (_instance == null) { GameObject singletonObject = new GameObject(); _instance = singletonObject.AddComponent<T>(); singletonObject.name = typeof(T).ToString() + " (Singleton)"; DontDestroyOnLoad(singletonObject); } } return _instance; } } protected virtual void Awake() { if (_instance != null && _instance != this as T) { Destroy(gameObject); // 销毁重复的对象 } else { _instance = this as T; DontDestroyOnLoad(this.gameObject); // 防止切换场景时丢失 } } } ``` 此代码片段展示了如何创建一个通用的泛型单例基类 `MonoSingleton<T>`[^1]。该类利用了C# 泛型特性来支持不同类型的具体实现。同时,通过重写 `Awake()` 函数实现了对多个相同类型的组件实例的有效管理,从而保证了整个应用程序生命周期内只有一个活动实例存在[^3]。 此外,考虑到 Unity 引擎对于 `MonoBehaviour` 组件的独特处理机制——即其实例化过程不由开发者直接控制而是由引擎负责完成,因此这里并没有采用传统的私有构造器方式去阻止外界随意创建新实例的行为[^2]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值