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
相关推荐
天人合一peng3 小时前
unity 生成标记根据背景色标记变色
unity·游戏引擎
xiaogutou11214 小时前
2026年历史课件PPT模板选购指南:教师备课效率与精度的平衡方案
开发语言·c#
Eiceblue7 小时前
使用 C# 将 Excel 转换为 Markdown 表格(含批量转换示例)
开发语言·c#·excel
天人合一peng7 小时前
unity 生成标记根据背景色变色为明显的颜色
unity·游戏引擎
魔士于安7 小时前
Unity 超市总动员 超市收银台 超市货架 超市购物手推车 超市常见商品
游戏·unity·游戏引擎·贴图·模型
CandyU27 小时前
Unity —— 数据持久化
unity·游戏引擎
zh路西法7 小时前
【Unity实现Oneshot胶卷显形】游戏窗口化与Win32API的使用
游戏·unity·游戏引擎
迪捷软件8 小时前
显控系统虚拟仿真的工程化路径
游戏引擎·cocos2d
不会编程的懒洋洋9 小时前
WPF XAML+布局+控件
xml·开发语言·c#·视觉检测·wpf·机器视觉·视图
唐青枫9 小时前
别再层层传参了!C#.NET AsyncLocal 异步上下文透传实战
c#·.net