Unity中用泛型实现单例

引言

在游戏开发中,单例模式是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点。在Unity开发中,单例模式尤为重要,因为它可以帮助我们管理游戏中的全局状态、资源和服务。

本文将介绍如何在Unity中使用泛型来实现单例模式,这种方法不仅简洁高效,而且可以减少重复代码,提高代码的可维护性。

单例模式基础

在深入泛型实现之前,让我们先回顾一下传统的单例模式实现:

public class GameManager
{
    private static GameManager _instance;
    
    public static GameManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new GameManager();
            }
            return _instance;
        }
    }
    
    private GameManager() { }
    
    // 其他方法和属性
}

这种实现方式的问题是,对于每个需要单例的类,我们都需要编写类似的代码,这导致了大量的重复工作。

使用泛型实现单例

通过泛型,我们可以创建一个可复用的单例基类,让所有需要单例功能的类继承这个基类即可。

基本泛型单例

public class Singleton<T> where T : class, new()
{
    private static T _instance;
    
    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new T();
            }
            return _instance;
        }
    }
}

使用这个泛型单例类,我们可以这样定义一个单例:

public class GameManager : Singleton<GameManager>
{
    // 游戏管理器的具体实现
}

然后,在代码中可以这样访问:

GameManager.Instance.SomeMethod();

Unity中的MonoBehaviour泛型单例

在Unity中,许多管理器类需要继承自MonoBehaviour,以便能够使用Unity的生命周期方法和组件系统。因此,我们需要一个专门为MonoBehaviour设计的泛型单例基类:

using UnityEngine;

public abstract class MonoBehaviourSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T _instance;
    private static object _lock = new object();
    private static bool _applicationIsQuitting = false;
    
    public static T Instance
    {
        get
        {
            if (_applicationIsQuitting)
            {
                Debug.LogWarning($"[Singleton] Instance '{typeof(T)}' already destroyed on application quit. Won't create again - returning null.");
                return null;
            }
            
            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = (T)FindObjectOfType(typeof(T));
                    
                    if (FindObjectsOfType(typeof(T)).Length > 1)
                    {
                        Debug.LogError($"[Singleton] Something went wrong - there should never be more than 1 singleton! Reopening the scene might fix it.");
                        return _instance;
                    }
                    
                    if (_instance == null)
                    {
                        GameObject singleton = new GameObject();
                        _instance = singleton.AddComponent<T>();
                        singleton.name = $"(singleton) {typeof(T)}";
                        
                        DontDestroyOnLoad(singleton);
                        
                        Debug.Log($"[Singleton] An instance of {typeof(T)} is needed in the scene, so '{singleton}' was created with DontDestroyOnLoad.");
                    }
                    else
                    {
                        Debug.Log($"[Singleton] Using instance already created: {_instance.gameObject.name}");
                    }
                }
                
                return _instance;
            }
        }
    }
    
    protected virtual void Awake()
    {
        if (_instance == null)
        {
            _instance = this as T;
            DontDestroyOnLoad(gameObject);
        }
        else if (_instance != this)
        {
            Destroy(gameObject);
        }
    }
    
    protected virtual void OnApplicationQuit()
    {
        _applicationIsQuitting = true;
    }
}

使用这个基类,我们可以轻松创建MonoBehaviour单例:

public class AudioManager : MonoBehaviourSingleton<AudioManager>
{
    // 音频管理器的具体实现
    
    public void PlaySound(string soundName)
    {
        Debug.Log($"Playing sound: {soundName}");
        // 播放声音的具体实现
    }
}

然后在任何脚本中使用:

AudioManager.Instance.PlaySound("explosion");

懒加载单例

有时我们希望单例只在第一次被访问时才创建,这就是懒加载模式:

using UnityEngine;

public abstract class LazySingleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T _instance;
    
    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<T>();
                
                if (_instance == null)
                {
                    GameObject obj = new GameObject();
                    obj.name = typeof(T).Name;
                    _instance = obj.AddComponent<T>();
                }
            }
            
            return _instance;
        }
    }
}

单例的生命周期管理

在Unity中,场景加载和卸载会影响单例的生命周期。有两种常见的处理方式:

  1. 永久单例:使用DontDestroyOnLoad确保单例在场景切换时不被销毁。
  2. 场景单例:单例只在当前场景有效,场景切换时会被销毁。

以下是一个支持这两种模式的泛型单例实现:

using UnityEngine;

public abstract class PersistentSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T _instance;
    
    [SerializeField]
    private bool _isPersistent = true;
    
    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<T>();
                
                if (_instance == null)
                {
                    GameObject obj = new GameObject();
                    obj.name = typeof(T).Name;
                    _instance = obj.AddComponent<T>();
                }
            }
            
            return _instance;
        }
    }
    
    protected virtual void Awake()
    {
        if (_instance == null)
        {
            _instance = this as T;
            
            if (_isPersistent)
            {
                DontDestroyOnLoad(gameObject);
            }
        }
        else if (_instance != this)
        {
            Destroy(gameObject);
        }
        
        OnAwake();
    }
    
    protected virtual void OnAwake() { }
}

线程安全的单例

在多线程环境下,我们需要确保单例的线程安全:

public class ThreadSafeSingleton<T> where T : class, new()
{
    private static T _instance;
    private static readonly object _lock = new object();
    
    public static T Instance
    {
        get
        {
            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = new T();
                }
                return _instance;
            }
        }
    }
}

单例的最佳实践

使用泛型单例时,请记住以下最佳实践:

  1. 谨慎使用单例:单例虽然方便,但过度使用会导致代码耦合度高,难以测试。
  2. 考虑依赖注入:在适当的情况下,考虑使用依赖注入代替单例。
  3. 初始化顺序:注意单例之间的依赖关系,确保它们按正确的顺序初始化。
  4. 内存管理:确保单例在不再需要时能够被正确清理,避免内存泄漏。
  5. 线程安全:在多线程环境中,确保单例的线程安全。

示例:游戏管理系统

下面是一个使用泛型单例实现的游戏管理系统示例:

// 游戏管理器
public class GameManager : MonoBehaviourSingleton<GameManager>
{
    public GameState CurrentState { get; private set; }
    
    public void ChangeState(GameState newState)
    {
        CurrentState = newState;
        Debug.Log($"Game state changed to: {newState}");
    }
}

// 音频管理器
public class AudioManager : MonoBehaviourSingleton<AudioManager>
{
    public void PlayMusic(string trackName)
    {
        Debug.Log($"Playing music track: {trackName}");
    }
    
    public void PlaySFX(string sfxName)
    {
        Debug.Log($"Playing sound effect: {sfxName}");
    }
}

// 数据管理器
public class DataManager : MonoBehaviourSingleton<DataManager>
{
    public void SaveGame()
    {
        Debug.Log("Saving game data...");
    }
    
    public void LoadGame()
    {
        Debug.Log("Loading game data...");
    }
}

// 使用示例
public class GameController : MonoBehaviour
{
    void Start()
    {
        // 访问各个单例
        GameManager.Instance.ChangeState(GameState.MainMenu);
        AudioManager.Instance.PlayMusic("MainTheme");
        DataManager.Instance.LoadGame();
    }
    
    void OnGameOver()
    {
        GameManager.Instance.ChangeState(GameState.GameOver);
        AudioManager.Instance.PlaySFX("GameOver");
        DataManager.Instance.SaveGame();
    }
}

public enum GameState
{
    MainMenu,
    Playing,
    Paused,
    GameOver
}

结论

泛型单例模式在Unity开发中非常有用,它可以帮助我们减少重复代码,提高代码的可维护性。通过本文介绍的方法,你可以根据自己的需求选择合适的泛型单例实现,并在游戏开发中灵活运用。

记住,单例模式虽然强大,但应该谨慎使用。在适当的场景下使用单例,可以使你的代码更加清晰、高效,但过度使用则可能导致代码难以测试和维护。

希望本文对你在Unity中实现泛型单例有所帮助!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/981114.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

我代表中国受邀在亚马逊云科技全球云计算大会re:Invent中技术演讲

大家好我是小李哥&#xff0c;本名叫李少奕&#xff0c;目前在一家金融行业公司担任首席云计算工程师。去年5月很荣幸在全球千万名开发者中被选为了全球亚马逊云科技认证技术专家&#xff08;AWS Hero&#xff09;&#xff0c;是近10年来大陆地区仅有的第9名大陆专家。同时作为…

C++蓝桥杯基础篇(七)

片头 嗨~小伙伴们&#xff0c;大家好&#xff01;今天我们来一起学习蓝桥杯基础篇&#xff08;七&#xff09;&#xff0c;学习相关字符串的知识&#xff0c;准备好了吗&#xff1f;咱们开始咯&#xff01; 一、字符与整数的联系——ASCII码 每个常用字符都对应一个-128~127的…

显式 GC 的使用:留与去,如何选择?

目录 一、什么是显式 GC&#xff1f; &#xff08;一&#xff09; 垃圾回收的基本原理 &#xff08;二&#xff09;显式 GC 方法和行为 1. System.gc() 方法 2. 显式 GC 的行为 &#xff08;三&#xff09;显式 GC 的使用场景与风险 1. JVM 如何处理显式 GC 2. 显式 GC…

2025.03.03(第一天)

1、常见的高危端口号有哪些&#xff0c;并涉及到哪些攻击方式 端口号服务常见攻击方式21FTP匿名登录、文件上传漏洞22SSH暴力破解、密钥泄露、中间人攻击53DNSDNS劫持、DNS缓存投毒、DDoS放大攻击80/443HTTP/HTTPSSQL注入1433MSSQL暴力破解、SQL注入、远程代码执行3306MySQLSQ…

MySQL数据库基本概念

目录 什么是数据库 从软件角度出发 从网络角度出发 MySQL数据库的client端和sever端进程 mysql的client端进程连接sever端进程 mysql配置文件 MySql存储引擎 MySQL的sql语句的分类 数据库 库的操作 创建数据库 不同校验规则对查询的数据的影响 不区分大小写 区…

确保移动设备上机器学习的安全性:挑战与最佳实践

随着企业不断推出更智能、个性化且响应迅速的体验&#xff0c;AI处理能力在移动设备中的普及&#xff0c;促使了机器学习&#xff08;ML&#xff09;本地集成的应用和SDK的快速发展。2024年谷歌I/O大会报告中强调了这一趋势&#xff0c;谷歌鼓励开发者在移动应用中使用本地机器…

【Mac】2025-MacOS系统下常用的开发环境配置

早期版本的一个环境搭建参考 1、brew Mac自带终端运行&#xff1a; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" Installation successful!成功后运行三行命令后更新环境&#xff08;xxx是mac的username&a…

计算机毕业设计SpringBoot+Vue.js美食推荐系统商城(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

【Linux网络#14】:数据链路层(以太网 局域网通信 ARP协议 ARP 欺骗 DDos 攻击)

&#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;Linux—登神长阶 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f49e; &#x1f49e; &#x1f49e; 生活总是不会一帆风顺&#x…

001-码云操作

码云操作 一、配置公钥1.官网地址1.进入 git bash2.查看生成的公钥3.设置到 Gitee4.测试 二、初始化一个项目1.新建仓库 一、配置公钥 方便后续提交代码不用填写密码 1.官网地址 官网地址&#xff1a;https://gitee.com/Git码云教程&#xff1a;https://gitee.com/help/arti…

Android 获取jks的SHA1值:java.io.IOException: Invalid keystore format

命令生成 keytool -list -v -keystore 全路径.jks -alias 别名 -storepass 密码 -keypass 密码 1、遇到 的问题&#xff1a; 通过快捷键 ‘win r’ 启动的小黑框运行上面的命令会出现下面这个错误keytool 错误: java.io.IOException: Invalid keystore format 2、解决问题 …

项目准备(flask+pyhon+MachineLearning)- 1

目录 这是一篇学习笔记 1. 搭建项目 2.前期准备工作 3.创建用户(user)模板 这是一篇学习笔记 目的&#xff1a;用flask快速实现发布有关机器学习的项目&#xff0c;掌握flask框架&#xff0c;机器学习模型的存储和调用。 1. 搭建项目 使用pycharm创建项目&#xff0c;fl…

DeepSeek开源周Day5: 3FS存储系统与AI数据处理新标杆

项目地址&#xff1a; GitHub - deepseek-ai/3FS: A high-performance distributed file system designed to address the challenges of AI training and inference workloads.GitHub - deepseek-ai/smallpond: A lightweight data processing framework built on DuckDB and…

火语言RPA--PDF提取图片

【组件功能】&#xff1a;提取PDF文档指定位置图片 配置预览 配置说明 文件路径 支持T或# 默认FLOW输入项 待提取图片的PDF文件的完整路径。 提取位置 全部、指定页、指定范围3种位置供选择。 PDF文件密码 支持T或# 打开PDF文件的密码。 页码 支持T或# 提取指定页的页…

专业便捷PDF软件,即开即用

PDF文件因其小巧的体积、便捷的分享与存储方式&#xff0c;以及卓越的安全性&#xff0c;已成为学习、企业及各类机构中不可或缺的文件格式。无论是在学术研究、课程资料、商业报告还是合同文件中&#xff0c;PDF都能有效保持原有的格式和布局&#xff0c;确保内容在不同设备和…

LLVM - 编译器前端 - 学习将源文件转换为抽象语法树(二)

一:处理消息 在一个庞大的软件(比如编译器)中,我们不希望将消息字符串分散在各个地方。如果需要修改消息内容或将其翻译成另一种语言,最好将它们集中存放在一个地方!目前缺少的是对消息的集中定义。下面我们看看来如何实现它。 一种简单的方法是,每条消息都有一个 ID(一…

windows安装vue

1、下载nodejs安装包 https://nodejs.cn/download/ 2、安装node 中途记得可以自己改安装路径&#xff0c;其他都是下一步 3、安装完成后检查 node -v &#xff1a;查看nodejs的版本 npm -v &#xff1a;查看npm的版本 4、修改npm默认安装目录与缓存日志目录的位置 在nodejs目…

PyCharm接入本地部署DeepSeek 实现AI编程!【支持windows与linux】

今天尝试在pycharm上接入了本地部署的deepseek&#xff0c;实现了AI编程&#xff0c;体验还是很棒的。下面详细叙述整个安装过程。 本次搭建的框架组合是 DeepSeek-r1:1.5b/7b Pycharm专业版或者社区版 Proxy AI&#xff08;CodeGPT&#xff09; 首先了解不同版本的deepsee…

注意力机制详解笔记 Attention is all I donot understand!

注意力机制好奇了太久&#xff0c;QKV知道是什么但是一直没搞懂为什么&#xff0c;这段时间终于眼一闭心一横摁头看了一天视频&#xff0c;3B1B大佬太强了&#xff01;基于GPT看了三个视频&#xff0c;基本讲的toy model&#xff0c;没有讲“硬核”的如何训练和码代码&#xff…

腾讯云对象存储服务(COS)

腾讯云对象存储服务&#xff08;COS&#xff09; 安全、可扩展、低成本的云存储解决方案 腾讯云 对象存储服务&#xff08;COS&#xff0c;Cloud Object Storage&#xff09; 是一种高可靠、高性能、可扩展的云存储服务&#xff0c;专为海量非结构化数据&#xff08;如图片、…