写点什么

同事看了我的代码惊呼:居然是这么在 Unity 中用单例的

  • 2022 年 7 月 25 日
  • 本文字数:3626 字

    阅读完需:约 12 分钟

同事看了我的代码惊呼:居然是这么在Unity中用单例的

推荐阅读

一、前言

今天,同事问我:“在 Unity 中怎么用单例呀”


然后我就把我写的代码甩过去了。


同事:“哦,原来是这么用的,你来给我演示一下吧”

二、单例模式介绍

首先要了解,单例模式是一种常用的软件设计模式,定义是单例对象的类只能允许一个实例存在,在许多时候整个系统只需要拥有一个全局对象,有利于协调系统整体的行为。


比如,服务器程序,将配置信息存放到一个文件中,然后使用一个单例对象进行读取,其他服务进程中的其他对象再通过这个单例去获取这些配置信息,简化了配置管理。


单例模式的结构图如下图所示:



那么,单例模式在 Unity 中又有那些应用场景呢:


1、在使用 Unity 开发项目的时候,经常会遇到需要一个管理类来管理一些全局的变量和方法,比如 GameManager 用于记录各种需要在整个游戏中用到数据。


2、一些需要从外部文档读取的数据,在其他脚本对象也需要用到的使用,也可以使用单例对象进行读取,其他对象再通过这个单例对象去获取数据。


假设有以下需求:


1、整个项目中有且只有一个 DataManager 单例对象 2、在 DataManager 单例对象中,需要去读取文档的数据,并且保存下载 3、在切换场景的时候 DataManager 对象不能被销毁 4、在不同的脚本中可以读取到 DataManager 单例对象读取的值


下面我们就来一步步实现单例对象下的 DataManager。

三、实现单例模式的 DataManager

3-1、定义单例对象

我们定义一个 DataManager 对象,继承于 MonoBehaviour,具体代码如下:


using System.Collections;using System.Collections.Generic;using UnityEngine;
public class DataManager : MonoBehaviour{ public static DataManager Instance;
private void Awake() { Instance = this; DontDestroyOnLoad(gameObject); }}
复制代码


1、使用静态的 DataManager 属性 Instance 保证了它可以通过类进行访问,而不是通过实例化访问 2、继承 MonoBehaviour 类的实例是由 Unity 进行创建,不能通过构造函数创建 3、在 Awake 函数里面对 Instance 进行赋值,保证了这个属性可以第一时间初始化 4、使用 DontDestroyOnLoad 可以使这个挂载脚本的游戏对象在切换场景中也不会被销毁 5、DontDestroyOnLoad 的参数使用(gameObject),这样切换场景中游戏对象不会被销毁,使用 this 只能保证当前脚本不会被销毁,但是对象销毁了,这个脚本也没有了。


创建完这个对象,在场景中也新建一个 DataManager 对象,将这个脚本拖到这个对象上:


3-2、单例对象去读取数据保存下来

代码如下:


using System.Collections;using System.Collections.Generic;using System.IO;using UnityEngine;
public class DataManager : MonoBehaviour{ public static DataManager Instance;
string m_JsonContent;//临时文档数据 RootData m_JsonData;//临时接收JSON解析数据
//数据保存到这个List里面,其他脚本就可以调用到了 public List<StationAllInfo> m_StationsAllInfo = new List<StationAllInfo>();
private void Awake() { Instance = this; DontDestroyOnLoad(gameObject); }
private void Start() { ReadJSONData(); }
void ReadJSONData() { string pathstations = Application.streamingAssetsPath + "/地铁站.json"; using (StreamReader SR = new StreamReader(pathstations)) { m_JsonContent = SR.ReadToEnd(); Debug.Log(m_JsonContent); SR.Close(); SR.Dispose(); //保存JSON数据 m_JsonData = JsonUtility.FromJson<RootData>(m_JsonContent); } for (int i = 0; i < m_JsonData.GisJosnDatas.Count; i++) { if (m_JsonData.GisJosnDatas[i].properties.LINE_ID != "")//剔除无用的信息,保存有用的信息 { StationAllInfo item = new StationAllInfo(); item.X = m_JsonData.GisJosnDatas[i].properties.X; item.Y = m_JsonData.GisJosnDatas[i].properties.Y; item.STACODE = m_JsonData.GisJosnDatas[i].properties.STACODE; item.S_NAME = m_JsonData.GisJosnDatas[i].properties.S_NAME; item.LINE_ID = m_JsonData.GisJosnDatas[i].properties.LINE_ID; //05 换成 5 (比如05号线 换成5号线) if (m_JsonData.GisJosnDatas[i].properties.LINE_ID.Substring(0, 1) == "0") { item.LINE_NAME = m_JsonData.GisJosnDatas[i].properties.LINE_ID.Substring(1, 1) + "号线"; } else { switch (m_JsonData.GisJosnDatas[i].properties.LINE_ID)//对字母缩写的站名进行处理 { case "fs": item.LINE_NAME = "房山线"; break; case "bt": item.LINE_NAME = "八通线"; break; case "yz": item.LINE_NAME = "亦庄线"; break; case "cp": item.LINE_NAME = "昌平线"; break; case "jc": item.LINE_NAME = "机场线"; break; default: item.LINE_NAME = m_JsonData.GisJosnDatas[i].properties.LINE_ID + "号线"; break; } } item.STYPE = m_JsonData.GisJosnDatas[i].properties.STYPE; m_StationsAllInfo.Add(item); } } }}
复制代码

3-3、在其他脚本中使用数据

我们新建一个 UseData.cs 脚本去使用数据:


using UnityEngine;
public class UseData : MonoBehaviour{ private void Update() { //点击键盘W 显示数据 if (Input.GetKeyDown(KeyCode.W)) { ShowData(); } }
private void ShowData() { for (int i = 0; i < DataManager.Instance.m_StationsAllInfo.Count; i++) { Debug.Log(DataManager.Instance.m_StationsAllInfo[i].X + " " +DataManager.Instance.m_StationsAllInfo[i].Y + " " + DataManager.Instance.m_StationsAllInfo[i].STACODE + " " + DataManager.Instance.m_StationsAllInfo[i].S_NAME + " " + DataManager.Instance.m_StationsAllInfo[i].LINE_ID + " " + DataManager.Instance.m_StationsAllInfo[i].LINE_NAME + " " + DataManager.Instance.m_StationsAllInfo[i].STYPE + " "); } }}
复制代码


将这个脚本拖到场景中的对象上:



运行程序,敲击键盘 W:



所有的值,都从单例对象 DataManager 的 InStance 的 m_StationsAllInfo 数组中读取出来了。

3-4、切换场景读取数据

我们新建两个场景:Index 和 Next,然后 Index 场景中的对象上挂载脚本 ChangeScenes.cs 脚本,代码如下:


using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.SceneManagement;
public class ChangeScenes : MonoBehaviour{ // Start is called before the first frame update void Start() { }
// Update is called once per frame void Update() { if (Input.GetKeyDown(KeyCode.Q)) { SceneManager.LoadScene(1); } }}
复制代码


也就是切换场景,然后在 Index 场景中新建一个 DataManager 对象,将 DataManager 脚本拖上去。


然后 Next 场景中随便找一个对象挂载 UseData 脚本,场景中不需要创建 DataManager 对象


然后将两个场景加到 Build Setting 中:



运行程序:


切换到 Next 场景,DataManager 对象也存在:



敲击键盘 W:



所有的值,都从上一个场景中的 DataManager 对象的挂载脚本的单例对象 DataManager.cs 的 InStance 的 m_StationsAllInfo 数组中读取出来了。

四、后言

这是 Unity 使用单例的简单应用,最主要的几个知识点是;


1、对象切换场景不销毁


2、单例对象的静态属性


3、数据的保存


4、其他脚本用单例对象的数据的方法


结束,好好学习加油吧!

发布于: 1 小时前阅读数: 8
用户头像

Unity3D软件工程师 2019.10.31 加入

Unity3D软件工程师,专注于虚拟仿真开发和VR开发。

评论

发布
暂无评论
同事看了我的代码惊呼:居然是这么在Unity中用单例的_游戏开发_恬静的小魔龙_InfoQ写作社区