Unity 专用的懒汉式单例(增强版)

单例模式的基本概念

单例模式的核心思想是“类的实例唯一性”,即无论程序运行到哪里,类的实例都是全局唯一的,且可以通过类自身提供的静态方法或属性获得实例。

单例模式的优缺点

优点

1.全局访问:单例模式提供了一个全局访问点,可以在整个项目中的任何地方访问这个类的实例,而不需要创建多个实例或进行复杂的依赖注入。

2.节省资源:确保只存在一个实例,可以避免重复创建对象,特别是一些资源密集型的类(如游戏管理器、音频管理器等各类管理器)。

3.简单且方便:由于全局访问的特点,单例可以很方便地在不同的场景和脚本中使用。

缺点

1.全局状态管理不当:如果不小心使用,单例容易造成全局状态的混乱,使得代码的维护和测试变得困难。

2.生命周期复杂:在 Unity 中,需要处理对象的生命周期,特别是在场景切换时要小心对象的重复创建或销毁。

unity中的使用

在 Unity 中,单例模式是一种常用的设计模式,同样也是用于确保一个类在应用程序中只存在一个实例,并提供全局访问点。单例模式在Unity开发中尤为常见,特别是当需要在多个场景中共享某个管理器(如游戏管理器、音频管理器、UI管理器等)时。

先了解一下单例模式的概念以及优缺点,再跟着我学习Unity 专用的懒汉式单例(增强版)~~

Unity 中单例模式的实现

以下单例模式是介绍的继承MonoBehaviour懒汉式单例没有考虑线程安全等其他问题。

话不多说,先展示完整代码

public abstract class MonoAbstractSingleton<T> : MonoBehaviour where T : Component
{
    private static T s_Instance;
    private static bool isSearching = false;

    public static T Instance
    {
        get
        {
            if (s_Instance == null && !isSearching)
            {
                isSearching = true;
                s_Instance = FindAnyObjectByType<T>();

                if (s_Instance == null)
                {
                    GameObject obj = new GameObject();
                    obj.name = typeof(T).Name;
                    s_Instance = obj.AddComponent<T>();

                    DontDestroyOnLoad(obj);
                }

                isSearching = false;
            }

            return s_Instance;
        }
    }

    protected virtual void Awake()
    {
        if (s_Instance == null)
        {
            s_Instance = this as T;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

 代码详细解读(一看就会)

public abstract class MonoAbstractSingleton<T> : MonoBehaviour where T : Component

public abstract calss MonoAbstractSingleton<T>这段代码是声明了一个抽象类。意味着该类不能被实例化,只能被继承。使用泛型<T>则是允许这个类被用于不同的组件类型。 

这后面接的MonoBehaviour在c#中的意思是继承这个MonoBehaviour类,这样写就使得这个单例可以附加到 Unity的GameObject上,并利用 Unity 的生命周期方法(如 AwakeStart等)。

而后面的where T:Component的意思是泛型约束。确保T必须是Unity的component类或者其他的子类,这是因为单例模式通常用于管理MonoBehaviour或其他组件

private static T s_Instance;

这段代码的意思是声明一个私有的静态变量,用于存储单例的实例。使用 static 关键字确保这个变量在类的所有实例之间共享,并且在整个程序生命周期内只有一个副本。

private static bool isSearching = false;

在多次调用单例的情况下,重复进行 FindAnyObjectByType<T>() 查找,会影响性能。通过引入标志位来减少无谓的查找操作,即使用bool值进行判断。

 public static T Instance
    {
        get
        {
            if (s_Instance == null && !isSearching)
            {
                isSearching = true;
                s_Instance = FindAnyObjectByType<T>();

                if (s_Instance == null)
                {
                    GameObject obj = new GameObject();
                    obj.name = typeof(T).Name;
                    s_Instance = obj.AddComponent<T>();

                    DontDestroyOnLoad(obj);
                }

                isSearching = false;
            }

            return s_Instance;
        }
    }

public static T Instance 提供一个公共的静态访问点(属性不了解的去看看c#语法),让其他脚本可以方便地访问单例实例。

get 访问器

if(s_Instance==null&&!isSearching)语句是检查单例实例是否已经被初始化。如果未初始化,则进行初始化。

isSearching=true 这段代码标识已经进行了一次查找,则不需要后续的查找了,避免在这个过程中再次触发 Instance 调用。

s_Instance = FindAnyObjectByType<T>() 是Unity 2023 引入的方法,用于查找场景中任何类型为 T的对象。这一步尝试在场景中查找已经存在的单例实例。

if(s_Instance==null)的意思是如果在场景中找不到现有的实例,则创建一个新的GameObject 并附加组件 T

GameObject obj=new GameObject()是表示创建一个新的空GameObject。

obj.name=typeof(T).Name 将新 GameObject 的名称设置为组件 T 的类型名,方便在层级视图中识别。

s_Instance =obj.Addcomponent<T>() 在新的GameObect上添加组件 T,并将其赋值给s_Instance,完成单例的初始化。

DontDestroyOnload(obj) 表示在场景切换时,不会因为重新加载场景而销毁单例实例,使得单例模式在整个过程中都始终存在。

isSearching = false 重置标识符,以便下次再进行查找。

protected virtual void Awake()
    {
        if (s_Instance == null)
        {
            s_Instance = this as T;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

protected virtual void Awake() 定义一个受保护且可重写的 Awake 方法,允许子类根据需要覆盖和扩展行为。

if(s_Instance==null)

{

    s_Instance = this as T;

   DontDestroyOnload(gameobject)

}

如果 s_Instance 为空,说明这是单例的第一个实例。将当前对象(this)强制转换为 T 并赋值给 s_Instance,完成单例实例的注册。

else

如果 s_Instance 不为空,说明已经存在一个单例实例。此时,当前对象不是单例,应被销毁以确保只有一个实例存在。

上述单例的使用方法

public class GameManager : MonoAbstractSingleton<GameManager>
{
    // 定义 GameManager 类的特定逻辑
    public int playerScore;

    public void AddScore(int score)
    {
        playerScore += score;
        Debug.Log("New Score: " + playerScore);
    }

    // 可以重写 MonoAbstractSingleton 中的 Awake 方法以实现自定义初始化逻辑
    protected override void Awake()
    {
        base.Awake(); // 调用基类的 Awake 确保单例逻辑生效
        // 其他初始化代码
        //todo
    }
}

使用很简单。创建一个类,去掉类后的MonoBehaviour,改为MonoAstractSingleton<写入你的类的名称> 。

注意:如果要在awake里面进行一些初始化的逻辑,记得重写 MonoAbstractSingleton 中的 Awake 方法来实现自定义初始化逻辑。protected override void Awake()里面也别忘了写:base.Awake();以确保单例的逻辑生效

具体使用场景

public class Player : MonoBehaviour
{
    void Start()
    {
        // 通过单例访问 GameManager,并调用它的方法
        GameManager.Instance.AddScore(10); // 增加 10 分
    }
}

通过输入你的类名点点点出来你的方法就可以啦!~~~~~~~~~~~~刹郭

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值