单例模式的基本概念
单例模式的核心思想是“类的实例唯一性”,即无论程序运行到哪里,类的实例都是全局唯一的,且可以通过类自身提供的静态方法或属性获得实例。
单例模式的优缺点
优点
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 的生命周期方法(如 Awake、Start
等)。
而后面的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 分
}
}
通过输入你的类名点点点出来你的方法就可以啦!~~~~~~~~~~~~刹郭!