单例
1、概述
单例模式是游戏开发中最常用的设计模式之一,用于确保某个类只有一个实例,并提供全局访问点。本框架提供了两种单例基类,分别适用于不同的使用场景。
2、MonoSingleton<T> - 基于MonoBehaviour的单例
设计目的
MonoSingleton<T>
专为需要挂载到游戏场景中的组件设计,适用于需要依赖Unity生命周期方法的单例类。
使用场景
需要访问Unity组件(如Transform、Renderer等)的单例
需要使用Unity生命周期方法(如Update、Start等)的单例
需要在Inspector中配置参数的单例
需要与其他GameObject进行交互的单例
实现特点
自动场景管理:如果实例不存在,会自动在场景中创建GameObject并挂载组件
防止重复创建:通过Awake方法检测并销毁重复实例
场景持久化:实例会随着场景加载而自动创建
线程安全:在Unity主线程中安全使用
MonoSingleton代码
using UnityEngine;
/// <summary>
/// 通用 MonoBehaviour 单例基类,适用于需要挂载到场景中的组件类型。
/// </summary>
/// <typeparam name="T">单例类型参数。</typeparam>
public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>
{
private static T _instance;
// 获取当前类型的单例实例。
public static T Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<T>();
if (_instance == null)
{
var obj = new GameObject(typeof(T).Name);
_instance = obj.AddComponent<T>();
}
}
return _instance;
}
}
// 初始化单例实例。
protected virtual void Awake()
{
if (_instance == null)
{
_instance = this as T;
}
else if (_instance != this)
{
Destroy(gameObject);
}
}
}
代码示例
public class GameManager : MonoSingleton<GameManager>
{
private int playerScore = 0;
protected override void Awake()
{
base.Awake(); // 必须调用基类Awake方法
// 初始化代码
}
public void AddScore(int points)
{
playerScore += points;
}
public int GetScore()
{
return playerScore;
}
}
// 使用示例
GameManager.Instance.AddScore(100);
3、Singleton<T> - 普通单例类
设计目的
Singleton<T>
适用于不需要依赖Unity引擎的纯C#类,提供更轻量级的单例实现。
使用场景
数据管理类(如配置管理器、存档管理器)
服务类(如网络服务、音频服务)
工具类(如本地化管理器、事件系统)
任何不需要Unity组件功能的单例类
实现特点
线程安全:使用Lazy<T>实现线程安全的延迟初始化
轻量级:不依赖Unity引擎,性能开销更小
严格单例:通过保护构造函数防止外部实例化
延迟加载:实例在第一次访问时才会创建
Singleton代码
using System;
/// <summary>
/// 通用单例基类,提供线程安全的实例访问方式,适用于非 MonoBehaviour 类型的类。
/// </summary>
/// <typeparam name="T">单例类型参数</typeparam>
public abstract class Singleton<T> where T : class, new()
{
private static readonly Lazy<T> _instance = new(() => new T());
// 获取当前类型的单例实例。
public static T Instance => _instance.Value;
// 受保护的构造函数,防止外部实例化。
protected Singleton() { }
}
代码示例
public class ConfigManager : Singleton<ConfigManager>
{
private Dictionary<string, string> configData;
protected ConfigManager()
{
// 初始化配置数据
configData = new Dictionary<string, string>();
LoadConfig();
}
private void LoadConfig()
{
// 加载配置文件的逻辑
}
public string GetConfigValue(string key)
{
return configData.ContainsKey(key) ? configData[key] : null;
}
}
// 使用示例
string value = ConfigManager.Instance.GetConfigValue("GameDifficulty");
4、使用建议
选择指南:
如果需要使用Unity组件或生命周期方法,选择
MonoSingleton<T>
如果是纯数据或逻辑类,选择
Singleton<T>
最佳实践:
避免在单例中保存大量数据,以免造成内存压力
单例类应该职责单一,遵循单一职责原则
考虑使用依赖注入来管理单例的生命周期
注意事项:
MonoSingleton<T>
在场景切换时会被销毁,需要手动处理持久化需求Singleton<T>
在整个应用程序生命周期内存在,需要注意内存管理两种单例都不适合用于需要频繁创建和销毁的对象
这两种单例实现为Unity游戏开发提供了灵活而强大的基础工具,开发者可以根据具体需求选择合适的单例类型。