Unity小框架之单例模式基类

**单例模式(Singleton Pattern)**是一种常用的创建型设计模式,其核心目标是确保一个类只有一个实例,并提供一个全局访问点。它常用于需要控制资源访问、共享配置或管理全局状态的场景(如数据库连接池、日志管理器、应用配置等)。

单例模式的核心思想

  1. 私有构造函数 :防止外部通过 new 创建多个实例。
  2. 静态私有实例:类内部持有唯一的实例。
  3. 全局访问方法 :提供一个静态方法(如 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");
}

注意事项

  1. 手动挂载与自动创建的冲突

    • 如果手动在场景中挂载脚本,需确保只有一个实例。
    • 优化后的代码会优先使用手动挂载的实例。
  2. 跨场景行为

    • 若需某个单例仅在特定场景存在,移除 DontDestroyOnLoad
相关推荐
波波0073 小时前
C# 15 重磅新特性: 带标签 break 与 continue:重新定义嵌套循环控制流
服务器·前端·c#
wujian83117 小时前
怎么用千问生成word文档:从「格式崩」到「一键过」,AI导出鸭打通最后半厘米
人工智能·ai·c#·word·豆包·deepseek·ai导出鸭
zlinear数据采集卡8 小时前
D223上位机C#开发实战:从帧解析到波形显示的完整实现
开发语言·arm开发·嵌入式硬件·fpga开发·开源·c#
唐青枫9 小时前
别把 ImmutableArray 当成只读 List:C#.NET 不可变数组深入详解
c#·.net
_ZHOURUI_H_11 小时前
Unity TCP底层极限拆解:粘包、半包、CRC、序列号、双缓冲到底怎么一起工作的?
网络·网络协议·tcp/ip·游戏·unity·游戏引擎·游戏开发
zlinear数据采集卡12 小时前
D223 ADC数据处理流水线:从原始采样值到工程单位的完整转换链
开发语言·arm开发·嵌入式硬件·fpga开发·开源·c#
KindSuper_liu15 小时前
Unity 游戏网络基础:从 TCP、UDP 到 HTTP、TLS 与 Best HTTP
网络·游戏·unity
花落人散处15 小时前
CDiskClean 开发历程 —— 文件清理_目录树解析实现方案参考
c#
离陌在学C#16 小时前
C# 泛型:从基础到高级应用
开发语言·c#