【unity进阶知识1】最详细的单例模式的设计和应用,继承和不继承MonoBehaviour的单例模式,及泛型单例基类的编写

文章目录

  • 前言
  • 一、不使用单例
  • 二、普通单例模式
    • 1、单例模式介绍
      • 实现步骤:
      • 单例模式分为饿汉式和懒汉式两种。
    • 2、不继承MonoBehaviour的单例模式
      • 2.1、基本实现
      • 2.2、防止外部实例化对象
      • 2.3、最终代码
    • 3、继承MonoBehaviour的单例模式
      • 3.1、基本实现
      • 3.2、自动创建和挂载单例脚本
      • 3.3、切换场景不销毁单例对象
      • 3.4、最终代码
  • 三、泛型单例基类
    • 1、不继承MonoBehaviour的单例模式基类
      • 1.1、基本实现
      • 1.2、防止外部实例化对象
      • 1.3、 多线程访问单例时会遇到问题
      • 1.3、最终代码
    • 2、继承MonoBehaviour的单例模式基类
      • 2.1、基本实现
      • 2.2、切换场景不销毁单例对象
      • 2.3、在OnDestroy方法中访问单例对象
      • 2.4、最终代码
  • 完结

前言

在游戏开发中,单例模式应该是我们最常见也是用的最多的设计模式了,但是你真的了解它吗?

本文通过实例分析,我们将阐述如何设计和应用单例模式,以提高代码的可维护性和复用性。无论是初学者还是经验丰富的开发者,这篇文章都将为你提供实用的技巧和深入的理解,帮助你在 Unity 项目中更有效地管理资源和对象。

一、不使用单例

为什么要是有单例?我们先来看看不使用单例的情况下如何访问不同类方法

新增TestModel ,新增Log测试方法

public class TestModel 
{public int money = 100;public int level = 2;public void Log(){Debug.Log($"打印金额:{money}");Debug.Log($"打印等级:{level}");}
}

调用Log方法

TestModel testModel = new TestModel();
testModel.Log();

运行效果,打印日志信息
在这里插入图片描述
可以发现,每次调用Log方法,我们都需要先实例化TestModel。如果我们还希望TestModel 数据应该保证整个游戏只有一份的,但是现在我们可以随意实例化多份TestModel 数据,这样我们就分不清哪个才是我们要的真正的数据
在这里插入图片描述
单例模式就可以很好的解决这个问题,而且访问的时候也可以非常方便的访问

二、普通单例模式

1、单例模式介绍

如果要让一个类只有唯一的一个对象,则可以使用单例模式来写。使用的时候用“类名.Instance.成员名”的形式来访问这个对象的成员。

实现步骤:

  • 1、把构造函数私有化,防止外部创建对象。
  • 2、提供一个属性给外部访问,这个属性就相当于是这个类唯一的对象。

单例模式分为饿汉式和懒汉式两种。

  • 1、饿汉式单例模式:
    在程序一开始的时候就创建了单例对象。但这样一来,这些对象就会在程序一开始时就存在于内存之中,占据着一定的内存。
  • 2、懒汉式单例模式:
    在用到单例对象的时候才会创建单例对象。

2、不继承MonoBehaviour的单例模式

2.1、基本实现

按前面的介绍编写代码

public class TestModel 
{private static TestModel instance;public static TestModel Instance { get { //保证对象的唯一性if (instance == null){instance = new TestModel();}return instance; } }public int money = 100;public int level = 2;public void Log(){Debug.Log($"打印金额:{money}");Debug.Log($"打印等级:{level}");}
}

调用Log方法

TestModel.Instance.Log();

运行效果,打印日志信息,和之前的一样
在这里插入图片描述
现在无论你如何访问,都是同一个实例

2.2、防止外部实例化对象

当然,你会发现目前还是可以通过之前非单例模式进行访问TestModel数据

TestModel testModel = new TestModel();

如果你想防止外部实例化对象,其实也很简单,只要定义私有的构造方法即可

private TestModel(){}

2.3、最终代码

public class TestModel 
{private static TestModel instance;public static TestModel Instance { get { //保证对象的唯一性if (instance == null){instance = new TestModel();}return instance; } }//定义私有的构造方法,防止外部实例化对象private TestModel(){}public int money = 100;public int level = 2;public void Log(){Debug.Log($"打印金额:{money}");Debug.Log($"打印等级:{level}");}
}

3、继承MonoBehaviour的单例模式

3.1、基本实现

继承MonoBehaviour的单例模式和前面类似,唯一的区别就是我们需要使用FindObjectOfType<T>() 来获取组件的引用,它是 Unity 中的一个方法,用于在场景中查找并返回类型为 T 的第一个实例。

public class TestUI : MonoBehaviour 
{//定义私有的构造方法,防止外部实例化对象private TestUI(){}private static TestUI instance;public static TestUI Instance { get { //保证对象的唯一性if (instance == null){instance = FindObjectOfType<TestUI>();}return instance; } }public void Log(){Debug.Log("打印日志:访问成功");}
}

调用

TestUI.Instance.Log();

直接执行肯定报空引用异常错误
在这里插入图片描述
因为我们继承了monobehavior的脚本,所以要要挂载到游戏场景身上才有用,记得挂载脚本
在这里插入图片描述

运行效果
在这里插入图片描述

3.2、自动创建和挂载单例脚本

如果每次访问单例我们都需要手动挂载脚本,那也太麻烦了,所以一般我们都通过代码自动创建和挂载对应脚本

GameObject go = new GameObject("TestUI");//创建游戏对象
instance = go.AddComponent<TestUI>();//挂载脚本到游戏对象

3.3、切换场景不销毁单例对象

即使我们在当前场景创建了单例对象,但是切换到一个新场景,必然会销毁之前的所有对象,这样就找不到之前创建的单例对象了

我们可以使用DontDestroyOnLoad(instance);,用于确保指定的游戏对象在加载新场景时不会被销毁。通常,当场景切换时,Unity 会销毁当前场景中的所有对象,但使用这个方法后,调用的对象(例如单例模式中的实例)将保持存在。
在这里插入图片描述

3.4、最终代码

public class TestUI : MonoBehaviour 
{//定义私有的构造方法,防止外部实例化对象private TestUI(){}private static TestUI instance;public static TestUI Instance { get { //保证对象的唯一性if (instance == null){instance = FindObjectOfType<TestUI>();if(instance == null){GameObject go = new GameObject("TestUI");//创建游戏对象instance = go.AddComponent<TestUI>();//挂载脚本到游戏对象}DontDestroyOnLoad(instance);}return instance; } }public void Log(){Debug.Log("打印日志:访问成功");}
}

三、泛型单例基类

一个游戏可能有很多个单例,如果每个单例都需要书写这么多代码,既麻烦又容易出错,我们可以选择定义泛型单例基类

1、不继承MonoBehaviour的单例模式基类

1.1、基本实现

我们没办法new泛型T,所以使用反射

/// <summary>
/// 不继承MonoBehaviour的泛型单例基类
/// </summary>
public class NonMonoSingleton<T> where T : NonMonoSingleton<T>
{private static T instance;public static T Instance{get{// 保证对象的唯一性if (instance == null){instance = Activator.CreateInstance(typeof(T), true) as T; // 使用反射创建实例}return instance;}}// 私有构造函数,防止外部实例化protected NonMonoSingleton() { }
}

使用,想要成为单例的类直接这个继承SingletonPatternBase泛型单例基类即可,就不需要重复写那么多代码了

public class TestModel : NonMonoSingleton<TestModel>
{// //定义私有的构造方法,防止外部实例化对象// private TestModel(){}// private static TestModel instance;// public static TestModel Instance { //     get { //         //保证对象的唯一性//         if (instance == null){//             instance = new TestModel();//         }//         return instance; //     } // }public int money = 100;public int level = 2;public void Log(){Debug.Log($"打印金额:{money}");Debug.Log($"打印等级:{level}");}
}

调用,调用和之前一样

TestModel.Instance.Log();

结果,正常打印
在这里插入图片描述

1.2、防止外部实例化对象

不过现在我们又可以通过实例化的方式直接进行访问TestModel数据

TestModel testModel = new TestModel();

我们可以和前面一样,定义私有的构造方法,防止外部实例化对象

private TestModel(){}

不过为了方便,我们通常都不这么做,因为这样每个类我们又要新增这段构造方法,完全没有必要。多人协作时,我们只需要内部沟通好,单例不要通过实例化访问即可。

1.3、 多线程访问单例时会遇到问题

我们可以使用lock线程锁和volatile关键字进行处理。
lock线程锁当多线程访问时,同一时刻仅允许一个线程访问。
volatile关键字修饰的字段,当多个线程都对它进行修改时,可以确保这个字段在任何时刻呈现的都是最新的值。

private static object locker = new object();
private volatile static T instance;

1.3、最终代码

using System;/// <summary>
/// 不继承MonoBehaviour的泛型单例基类
/// </summary>
public class NonMonoSingleton<T> where T : NonMonoSingleton<T>
{//线程锁。当多线程访问时,同一时刻仅允许一个线程访问private static object locker = new object();//volatile关键字修饰的字段,当多个线程都对它进行修改时,可以确保这个字段在任何时刻呈现的都是最新的值private volatile static T instance;public static T Instance{get{if (instance == null){lock (locker){// 保证对象的唯一性if (instance == null){instance = Activator.CreateInstance(typeof(T), true) as T; // 使用反射创建实例}}}return instance;}}// 私有构造函数,防止外部实例化protected NonMonoSingleton() { }
}

2、继承MonoBehaviour的单例模式基类

2.1、基本实现

using UnityEngine;/// <summary>
/// 继承MonoBehaviour的泛型单例基类
/// </summary>
public class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
{private static T instance;public static T Instance{get{if (instance == null){instance = FindObjectOfType<T>();if (instance == null){GameObject go = new GameObject(typeof(T).Name); // 创建游戏对象instance = go.AddComponent<T>(); // 挂载脚本}}return instance;}}// 构造方法私有化,防止外部 new 对象protected MonoSingleton() { }
}

使用

public class TestUI : MonoSingleton<TestUI> 
{// //定义私有的构造方法,防止外部实例化对象// private TestUI(){}// private static TestUI instance;// public static TestUI Instance { //     get { //         //保证对象的唯一性//         if (instance == null){//             instance = FindObjectOfType<TestUI>();//             if(instance == null){//                 GameObject go = new GameObject("TestUI");//创建游戏对象//                 instance = go.AddComponent<TestUI>();//挂载脚本到游戏对象//             }//             DontDestroyOnLoad(instance);//         }//         return instance; //     } // }public void Log(){Debug.Log("打印日志:访问成功");}
}

调用

TestUI.Instance.Log();

效果,正常访问
在这里插入图片描述

2.2、切换场景不销毁单例对象

和前面一样,同样加上DontDestroyOnLoad(instance);即可

2.3、在OnDestroy方法中访问单例对象

如果直接在在OnDestroy方法中访问单例对象

private void OnDestroy() {TestUI.Instance.Log();
}

每次运行结束时会报错:
Some objects were not cleaned up when closing the scene.(Did you spawn new GameObjects from OnDestroy?)

在这里插入图片描述
修改MonoSingleton基类,我们可以新增变量IsExisted 记录单例对象是否存在,在成功实例化时IsExisted= trueOnDestroyIsExisted=false

public static bool IsExisted { get; private set; } = false;

在OnDestroy调用时,先判断IsExisted是否为true

private void OnDestroy() {if(TestUI.IsExisted) TestUI.Instance.Log();
}

效果,开始运行执行一次,结束运行调用OnDestroy又执行一次,且无报错
在这里插入图片描述

2.4、最终代码

using UnityEngine;/// <summary>
/// 继承MonoBehaviour的泛型单例基类
/// </summary>
public class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
{//记录单例对象是否存在。用于防止在OnDestroy方法中访问单例对象报错public static bool IsExisted { get; private set; } = false;private static T instance;public static T Instance{get{if (instance == null){instance = FindObjectOfType<T>();if (instance == null){GameObject go = new GameObject(typeof(T).Name); // 创建游戏对象instance = go.AddComponent<T>(); // 挂载脚本}}DontDestroyOnLoad(instance);IsExisted = true;return instance;}}// 构造方法私有化,防止外部 new 对象protected MonoSingleton() { }private void OnDestroy() {IsExisted = false;}
}

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

CCRC-DSA数据安全评估师 :什么是产品安全架构?

产品安全架构是构筑其自身安全特征的核心组件和它们之间的相互关系。 对任何具体产品而言&#xff0c;安全性作为产品质量的一部分&#xff0c;并非独立存在&#xff0c;而是与性能、可扩展性、可维护性等其他质量属性并行&#xff0c;并可通过逻辑模块来表述。 本文将围绕安…

x-cmd pkg | hurl - 强力的 HTTP 请求测试工具,让 API 测试更加简洁和高效

目录 简介快速上手安装 hurl发送 HTTP 请求Hurl 文件格式 功能特点竞品和相关项目进一步探索 简介 hurl 是 [github.com/Orange-OpenSource] 用 Rust 开发的 HTTP 请求处理和测试工具&#xff0c;专注于简化 HTTP 请求的创建、执行以及自动化测试流程&#xff0c;能以简单的纯…

TypeScript:模块

一、前言 关于术语的一点说明: 请务必注意一点&#xff0c;TypeScript 1.5里术语名已经发生了变化。 “内部模块”现在称做“命名空间”。 “外部模块”现在则简称为“模块”&#xff0c;这是为了与 ECMAScript 2015里的术语保持一致&#xff0c;(也就是说 module X { 相当于现…

【C++】8.类和对象(6)

文章目录 5. 内部类6. 匿名对象7. 对象拷贝时的编译器优化 5. 内部类 如果一个类定义在另一个类的内部&#xff0c;这个内部类就叫做内部类。内部类是一个独立的类&#xff0c;跟定义在全局相比&#xff0c;他只是受外部类类域限制和访问限定符限制&#xff0c;所以外部类定义的…

QT菜单之快捷菜单设计

快捷菜单又称为上下文菜单&#xff0c;通常在用鼠标右击的时候弹出。创建快捷菜单的方法和创建菜单栏菜单类似。 效果图&#xff1a; 一、将MainWindow类对象的ContextMenuPolicy属性设置为customContextMenu。 打开mainWindow.ui&#xff0c;在属性视图上找到ContextMenuPoli…

What is the OpenAI Chat Completion API tools/functions property format?

题意&#xff1a;OpenAI 聊天完成 API 的工具/函数属性格式是什么 问题背景&#xff1a; Is there any clear documentation on the format of OpenAIs Chat Completion API tools/functions object format? I understand its JSON, but there appear to be underlying requi…

《线性代数》学渣笔记

文章目录 1 行列式1.1 克拉默法则1.2 基本性质1.3 余子式 M i j M_{ij} Mij​1.4 代数余子式 A i j ( − 1 ) i j ⋅ M i j A_{ij} (-1)^{ij} \cdot M_{ij} Aij​(−1)ij⋅Mij​1.5 具体型行列式计算&#xff08;化为基本型&#xff09;1.5.1 主对角线行列式&#xff1a;主…

数据结构实验二之线性表(下)

实验题5:实现循环双链表的各种基本运算的算法 题目描述 编写一个程序cdlinklist.cpp,实现循环双链表的各种基本运算和整体建表算法 (假设循环双链表的元素类型ElemType为int),并在此基础上设计一个程序exp2-5.cpp 完成以下功能。 (1)初始化循环双链表h。 (2)依次采用尾插法插入…

免费的 H5/PC 地图打卡 —— 功能代码及实现指南/功能代码已上传

在本文中&#xff0c;我们将通过天地图&#xff08;Tianditu&#xff09;实现一个简单的 H5/PC 版地图打卡功能。通过实时获取用户的位置&#xff0c;检测其与打卡点的距离&#xff0c;来决定是否可以完成打卡。代码已上传&#xff0c;本文将逐步介绍如何实现这一功能。 效果图…

EDI简化,两剂初免效果好

EDI简化&#xff0c;两剂初免效果好 大家好&#xff0c;疫苗是防控传染病的重要工具。但对于一些如HIV等病原体&#xff0c;有效疫苗的研发仍面临诸多挑战。在疫苗接种中&#xff0c;生发中心起着关键作用。近期研究表明——《Two-dose priming immunization amplifies humoral…

[数据集][目标检测]基于yolov5增强数据集算法mosaic来扩充自己的数据集自动生成增强图片和对应标注无需重新标注

【算法介绍】 YOLOv5最引人注目的增强技术之一是马赛克增强&#xff0c;它将四张不同的图像拼接成一张图像。 思路&#xff1a;首先&#xff0c;从数据集中随机选择四张图像&#xff0c;然后将它们缩放、随机裁剪&#xff0c;并按马赛克模式拼接在一起。这种方式允许模型看到…

为什么AI不会夺去软件工程师的工作?

▼ 自从AI大模型爆火以来&#xff0c;我每天的工作中&#xff0c;已经有大量的真实代码是通过AI完成的。人工智能辅助下的编程&#xff0c;确实大幅减轻了我的工作负担&#xff0c;大大提高了生产力。 大语言模型是如此成功&#xff0c;以至于无可避免地在开发者社区中引起了…

DesignMode__unity__抽象工厂模式在unity中的应用、用单例模式进行资源加载

目录 抽象工厂模式 思维导图 接口&#xff08;抽象类&#xff09; 工厂接口 抽象产品类 抽象武器接口 抽象人物接口 具体工厂和具体产品 具体工厂 &#xff08;1&#xff09;产品接口&#xff0c;生成具体人物 &#xff08;2&#xff09;武器接口&#xff0c;生成具体…

mapboxGL 离线部署或者说去除token最简单得方法

找到本项目中得node_modules包管理器中得mapbox-gl包 找打dist文件夹下得mapbox-gl-dev.js 相比于mapbox-gl.js得压缩文件 mapbox-gl-dev.js没有压缩&#xff0c;好修改&#xff0c;也无需要编译 在mapbox-gl-dev.js找到 this._authenticate()&#xff0c;注释或者去除即可 最…

【Proteus仿真】基于51单片机的简易电压表制作(可串口远程调控)

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;设计一个简易电压表&#xff1a; 采用3位LED数码管显示被测电压值&#xff1a;基本测量范围是 0-5V&#xff1b;测量误差为士0.02V。开机或复位后&#xff0c;在 LED 最…

三角型电动采光排烟天窗的高效排烟设计优势

三角型电动采光排烟天窗的排烟效果在多个方面均展现出了显著的优势&#xff0c;主要体现在以下几个方面。一、设计原理与结构特性 三角型电动采光排烟天窗采用三角形构造&#xff0c;这种设计在结构上具有显著的稳定性&#xff0c;能够抵御不同气候条件及风压的影响。同时减少了…

网站建设合同怎么写

网站建设合同成为企业与网站开发服务提供商之间不可或缺的法律文书。一份明晰而全面的网站建设合同不仅有助于规范双方权责&#xff0c;还能有效防范潜在的合同纠纷。以下是一份网站建设合同的范本&#xff0c;旨在提供参考。 一、合同双方信息 甲方&#xff08;委托方&#x…

QT| “无法粘贴窗口部件”错误以及customplot安装使用

“无法粘贴窗口部件”错误以及customplot “无法粘贴窗口部件”错误customplot下载添加到项目中使用QCustomPlot常用的代码 “无法粘贴窗口部件”错误 情景&#xff1a;使用QT设计界面&#xff0c;很多部分比较类似&#xff0c;可以复制另一个界面的ui&#xff0c;但是粘粘的时…

TS-AI:一种用于多模态个体化脑区划分的深度学习管道,并结合任务对比合成|文献速递-Transformer架构在医学影像分析中的应用

Title 题目 TS-AI: A deep learning pipeline for multimodal subject-specific parcellation with task contrasts synthesis TS-AI&#xff1a;一种用于多模态个体化脑区划分的深度学习管道&#xff0c;并结合任务对比合成 01 文献速递介绍 人类大脑在结构和功能组织上表…

武汉正向科技 格雷母线检测方式 :车检,地检

正向科技|格雷母线原理运用-车检&#xff0c;地检 地上检测方式 地址编码器和天线箱安装在移动站上&#xff0c;通过天线箱发射地址信号&#xff0c;地址解码器安装在固定站&#xff08;地面&#xff09;上&#xff0c;在固定站完成地址检测。 车上检测方式 地址编码器安装在…