单例模式(Singleton Pattern)是一种常用的创建型设计模式,其核心目标是确保一个类只有一个实例,并提供一个全局访问点。它常用于需要控制资源访问、共享配置或管理全局状态的场景(如数据库连接池、日志管理器、应用配置等)。
单例模式的核心思想
- 私有构造函数:防止外部通过
new
创建多个实例。 - 静态私有实例:类内部持有唯一的实例。
- 全局访问方法:提供一个静态方法(如
getInstance()
)获取唯一实例。
下面来介绍一下在C#和unity中实现的单例模式基类,你某些需要进行单例模式化的脚本,就可以继承这个基类然后就实现了自己的单例化,那你就可以在其他地方进行使用了。
一、最基本的单例基类
代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;//单例模式基类模块//1.C#泛型的知识
//2.设计模式中 单例模式的知识
public class BaseManager <T> where T : new()
{//单例模式private static T instance;public static T GetInstance(){if (instance == null){instance = new T();}return instance;}
}
使用方法:
例如下面这个脚本,我们创建了一个NewBehaviourScript的脚本,然后直接继承单例模式基类,如果其他地方需要调用,就直接使用就行
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class NewBehaviourScript : BaseManager<NewBehaviourScript>
{ void Start(){Debug.Log(NewBehaviourScript.GetInstance());}
}
再来一个示例:
// 子类继承 BaseManager,并满足 new() 约束
public class GameManager : BaseManager<GameManager>
{// 必须有一个公共无参构造函数public GameManager() {Debug.Log("GameManager Created");}public void Init(){Debug.Log("GameManager Initialized");}
}// 使用方式
void Start()
{
//可以在你项目中的任意一个地方进行使用GameManager manager = GameManager.GetInstance();manager.Init();// 问题:外部仍然可以 new GameManager(),破坏单例!GameManager another = new GameManager(); // 这是允许的 但是你自己选择可以不实现 后面我们还有保护措施 使得外部不能实例化
}
二、继承了Mono的单例模式基类
继承了Mono那么我们就可以使用Unity的生命周期函数了
代码:
public class SingletonMono<T> : MonoBehaviour where T : MonoBehaviour
{private static T _instance;// 使用属性替代 GetInstance(),更符合 C# 习惯public static T Instance{get{// 如果实例不存在,尝试查找或创建if (_instance == null){_instance = FindObjectOfType<T>();// 如果场景中没有,自动创建一个新的 GameObjectif (_instance == null){GameObject obj = new GameObject(typeof(T).Name);_instance = obj.AddComponent<T>();}}return _instance;}}protected virtual void Awake(){// 如果实例已存在且不是当前对象,销毁自身if (_instance != null && _instance != this){Destroy(gameObject);return;}// 初始化实例_instance = this as T;// 按需设置跨场景保留DontDestroyOnLoad(gameObject); }
}
还有个简单的版本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;//C#泛型的知识
//设计模式中 单例模式的知识//继承了MonoBehaviour的 单例模式对象 需要我们自己保证它的唯一性
public class SingletonMono<T> : MonoBehaviour where T : MonoBehaviour
{private static T instance;public static T GetInstance(){//继承了MonoBehaviour的类,不能直接new//只能通过拖动到对象上 或者通过加脚本的api AddComponent//U3d内部会帮助我们直接实例化return instance;}protected virtual void Awake(){instance = this as T;}
}
请注意:继承了这个单例模式基类的话,是不能够自己去new的你只能拖拽到物体身上。
示例:
这样改进是为了让我们在没有继承Mono的时候,仍然能使用生命周期函数
public class AudioManager : SingletonMono<AudioManager>
{public void PlaySound(string clipName){Debug.Log("Playing: " + clipName);}
}// 使用方式
void Start()
{AudioManager.Instance.PlaySound("BackgroundMusic");
}
示例:
using UnityEngine;// 继承 SingletonMono,并指定自身为泛型类型 T
public class SoundManager : SingletonMono<SoundManager>
{// 自定义音频方法public void PlaySound(string clipName){Debug.Log("播放音效: " + clipName);}// 初始化音频资源(在 Awake 中调用)protected override void Awake(){base.Awake(); // 调用基类的 Awake 方法,确保单例赋值Debug.Log("SoundManager 初始化完成");}
}
创建这样一个空物体,挂在脚本后,其他的类里面才能使用
使用:
public class PlayerController : MonoBehaviour
{private void Start(){// 获取 SoundManager 实例并调用方法SoundManager.Instance.PlaySound("跳跃音效");}private void Update(){// 直接通过 Instance 属性访问if (Input.GetKeyDown(KeyCode.Space)){SoundManager.Instance.PlaySound("射击音效");}}
}
三、继承了mono并且已经自己实例化的
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SingletonAutoMono<T> : MonoBehaviour where T : MonoBehaviour
{private static T instance;public static T GetInstance(){if (instance == null){GameObject obj = new GameObject();//设置对象的名字为脚本名字obj.name = typeof(T).ToString();//让这个单例模式对象过场景不移除//因为 单例模式对象 往往是存在于整个程序生命周期中的DontDestroyOnLoad(obj);instance = obj.AddComponent<T>();}return instance;}}
使用示例:
在继承了这个类的脚本里面直接使用内部的函数即可
public class NetworkManager : SingletonAutoMono<NetworkManager>
{public void Connect(string serverIP){Debug.Log($"连接到服务器: {serverIP}");}protected override void Awake(){base.Awake(); // 调用基类 Awake 确保单例初始化Debug.Log("网络管理器已初始化");}
}// 使用方式
void Start()
{NetworkManager.Instance.Connect("127.0.0.1");
}
注意事项
-
手动挂载与自动创建的冲突:
- 如果手动在场景中挂载脚本,需确保只有一个实例。
- 优化后的代码会优先使用手动挂载的实例。
-
跨场景行为:
- 若需某个单例仅在特定场景存在,移除
DontDestroyOnLoad
。
- 若需某个单例仅在特定场景存在,移除