前言:在Unity中,单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点。比如我们常用的游戏管理器GameManager,我们只希望游戏中只存在一个,这个时候就可以使用单例模式,来确保只有一个游戏管理器。
单例模式可以分为懒汉式和饿汉式。
- 其中懒汉式顾名思义,就是很懒,只有真正用到它的时候,才会实例化出来。一般的写法大致可以分为线程不安全的经典写法,和线程安全的写法。我这里只介绍线程安全的方法,所谓线程的安全的写法,就是避免在多线程环境下创建多个实例出来,多个实例就破坏了单例原则。
下面是单例代码
using UnityEngine;
using System;/// <summary>/// 泛型单例基类,支持继承并实现单例模式/// </summary>/// <typeparam name="T">继承自 MonoBehaviour 的类</typeparam>public class SingletonMono<T> : MonoBehaviour where T : MonoBehaviour{// 定义一个静态的 Lazy<T> 对象,用于线程安全地初始化单例private static Lazy<T> _instanceLazy = new Lazy<T>(() =>{// 尝试在场景中查找已存在的 T 类型的实例T instance = FindObjectOfType<T>();if (instance == null){// 如果找不到实例,创建一个新的 GameObject,并添加 T 类型的组件GameObject singletonObject = new GameObject(typeof(T).Name + "_Singleton");instance = singletonObject.AddComponent<T>();// 确保这个 GameObject 在场景切换时不会被销毁DontDestroyOnLoad(singletonObject);}return instance;});// 获取单例实例private static T _instance => _instanceLazy.Value;// 标记单例是否已初始化private static bool _isInitialized = false;// 提供对单例实例的访问public static T Instance{get{if (!_isInitialized){// 如果单例尚未初始化,强制初始化_instanceLazy.Value;_isInitialized = true;}return _instance;}}// 在对象被初始化时调用protected virtual void Awake(){if (_instance == null){// 如果单例实例为空,重新初始化_instanceLazy = new Lazy<T>(() =>{GameObject singletonObject = new GameObject(typeof(T).Name + "_Singleton");T instance = singletonObject.AddComponent<T>();DontDestroyOnLoad(singletonObject);return instance;});_instanceLazy.Value;_isInitialized = true;}else if (_instance != this){// 如果当前对象不是单例实例,销毁它Destroy(gameObject);}}// 在对象被销毁时调用protected virtual void OnDestroy(){if (_instance == this){// 如果当前对象是单例实例,重置单例引用_instanceLazy = null;_isInitialized = false;}}}
使用示例
假设我们有一个游戏管理器类,我们希望它在整个游戏中只有一个实例
using UnityEngine;public class GameManager : SingletonMono<GameManager>{// 游戏管理器的具体逻辑private int score = 0;private int lives = 3;public void AddScore(int points){score += points;Debug.Log("Score: " + score);}public void LoseLife(){lives--;Debug.Log("Lives: " + lives);}}
在其他脚本中,你可以通过以下方式访问 GameManager 的实例:
using UnityEngine;public class PlayerController : MonoBehaviour
{void Update(){if (Input.GetKeyDown(KeyCode.Space)){// 访问 GameManager 的单例实例并调用方法GameManager.Instance.AddScore(10);}if (Input.GetKeyDown(KeyCode.Escape)){GameManager.Instance.LoseLife();}}
}
- 饿汉式单例在类加载时就初始化实例,确保实例在程序启动时就可用。它适用于需要在程序启动时就初始化的场景,并且在单线程环境下性能较高。然而,它不支持延迟初始化,因此在资源有限或对象创建成本较高的场景中可能不太适用。
饿汉式单例实现
using UnityEngine;public class SingletonMono<T> : MonoBehaviour where T : MonoBehaviour
{// 静态实例在类加载时就初始化private static T _instance;// 在类加载时初始化实例static SingletonMono(){GameObject singletonObject = new GameObject(typeof(T).Name + "_Singleton");_instance = singletonObject.AddComponent<T>();DontDestroyOnLoad(singletonObject);}// 提供对单例实例的访问public static T Instance => _instance;// 在对象被初始化时调用protected virtual void Awake(){if (_instance != this){// 如果当前对象不是单例实例,销毁它Destroy(gameObject);}}// 在对象被销毁时调用protected virtual void OnDestroy(){// 饿汉式单例通常不需要重置引用,因为实例在类加载时就已创建}
}
举个栗子
using UnityEngine;public class GameManager : SingletonMono<GameManager>
{private int score = 0;private int lives = 3;public void AddScore(int points){score += points;Debug.Log("Score: " + score);}public void LoseLife(){lives--;Debug.Log("Lives: " + lives);}
}
使用单例实例
using UnityEngine;public class PlayerController : MonoBehaviour
{void Update(){if (Input.GetKeyDown(KeyCode.Space)){GameManager.Instance.AddScore(10);}if (Input.GetKeyDown(KeyCode.Escape)){GameManager.Instance.LoseLife();}}
}