**单例模式(Singleton Pattern)**是一种常用的创建型设计模式,其核心目标是确保一个类只有一个实例,并提供一个全局访问点。它常用于需要控制资源访问、共享配置或管理全局状态的场景(如数据库连接池、日志管理器、应用配置等)。
单例模式的核心思想
- 私有构造函数 :防止外部通过
new
创建多个实例。 - 静态私有实例:类内部持有唯一的实例。
- 全局访问方法 :提供一个静态方法(如
getInstance()
)获取唯一实例。
下面来介绍一下在C#和unity中实现的单例模式基类,你某些需要进行单例模式化的脚本,就可以继承这个基类然后就实现了自己的单例化,那你就可以在其他地方进行使用了。
一、最基本的单例基类
代码:
cs
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的脚本,然后直接继承单例模式基类,如果其他地方需要调用,就直接使用就行
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : BaseManager<NewBehaviourScript>
{
void Start()
{
Debug.Log(NewBehaviourScript.GetInstance());
}
}
再来一个示例:
cs
// 子类继承 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的生命周期函数了
代码:
cs
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>();
// 如果场景中没有,自动创建一个新的 GameObject
if (_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);
}
}
还有个简单的版本:
cs
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的时候,仍然能使用生命周期函数
cs
public class AudioManager : SingletonMono<AudioManager>
{
public void PlaySound(string clipName)
{
Debug.Log("Playing: " + clipName);
}
}
// 使用方式
void Start()
{
AudioManager.Instance.PlaySound("BackgroundMusic");
}
示例:
cs
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 初始化完成");
}
}

创建这样一个空物体,挂在脚本后,其他的类里面才能使用
使用:
cs
public class PlayerController : MonoBehaviour
{
private void Start()
{
// 获取 SoundManager 实例并调用方法
SoundManager.Instance.PlaySound("跳跃音效");
}
private void Update()
{
// 直接通过 Instance 属性访问
if (Input.GetKeyDown(KeyCode.Space))
{
SoundManager.Instance.PlaySound("射击音效");
}
}
}
三、继承了mono并且已经自己实例化的
cs
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;
}
}
使用示例:
在继承了这个类的脚本里面直接使用内部的函数即可
cs
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
。
- 若需某个单例仅在特定场景存在,移除