[U3D Learning Note] Unity C# Survival Guide (19) -- Singletons

啊啊啊啊啊又到了设计模式时间,前面讲delegate和event时就提到了observer pattern观察者模式,这回要讲的是Singletons单例模式

Singleton Design Pattern

  • The singleton is a software engineer concept where u have global access to a class, and that class only exists once. [Example: Manager Classes, like UI Manager, Spawn Manager]
    Use singleton to avoid the use of GetComponent, because you’re already going to have a reference to the Manager and a direct communication path.
    在前面,我们通常是先通过查找gameManger在的实体然后获取他的脚本,从而使用gameManger的东西。但是gameManger在整个游戏中只会有一个,我们可以用单例来做这件事情。
    下面是单例的代码示例
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace ChapterSingleton{

    public class GameManager : MonoBehaviour
    {
        // a private variable to declare the instance of the class  -- has to be static
        private static GameManager _instance; 

        // a static, public property to be able to access and get the private variable _instance
        public static GameManager Instance
        {
            get{
                // return the instance
                // handle error
                if(_instance == null){
                    Debug.LogError("game manager is null");
                }
                return _instance;
            }
            // only get because we don't want anyone to be able to change it
        } // for other classes to communicate with

        //assign the instance
        private void Awake() {
            _instance = this;
        }

        public void DisplayName(){
            Debug.Log("mzk");
        }
    }
}

其他物体这样获取单例中的信息

namespace ChapterSingleton{
    public class Player : MonoBehaviour
    {
        // Start is called before the first frame update
        void Start()
        {
            GameManager.Instance.DisplayName();
        }
        // Update is called once per frame
        void Update()
        {            
        }
    }
}

(说真的 单例我看得晕乎乎的 可能前面把property相关看得太简单了?(是需要反复观看的地方

然后这边有涉及到函数Awake(),这个是在Start()之前运行的

Singleton UI Manager

这节是创一个UI Manager, 模仿前面,创一个private变量再创一个public static property
注意:单例之间是可以互相调用的,但是不能调用非单例

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace ChapterSingleton{
    public class UIManager : MonoBehaviour
    {
        private static UIManager _instance;
        public static UIManager Instance
        {
            get{
                if(_instance == null){
                    Debug.LogError("ui manager is null");
                }
                return _instance;
            }
        }

        private void Awake() {
            _instance = this;
        }

        public void UpdateScore(int score){
            Debug.Log("score: " + score);
            Debug.Log("Notifying the game manager");
            // manager class can talk to each other
            // but the manager class should never talk to an obj that's not singleton
            GameManager.Instance.DisplayName();
        }
    }
}

Lazy Instantiation(延迟初始化)

在前面我们知道,我们必须在程序运行前先声明好这些manager。像在前面我们会先做个判断(if(_instance == null))这些实例是否存在然后才能返回结果。在前面我们就只是扔个报错提示说这个实例不存在啊,我们也可以如果不存在的话就创造一个实例。
比如前面的那个UI Manager我们可以改成这样,当判断为true时就生成一个装这个脚本的gameobj,然后当我们后面比如说按个空格啊获取这个UIManager的信息时才初始化这个UIManager,这个就是简单的延迟初始化

		public static UIManager Instance
        {
            get{
                if(_instance == null){
                    // Debug.LogError("ui manager is null");
                    GameObject go = new GameObject("UI Manager");
                    go.AddComponent<UIManager>();
                }
                return _instance;
            }
        }

详情的话还是要看链接的文档,配合线程相关食用更香

Downfall of Lazy Instantiation

这边讲了一下lazy instantiation的弊端吧。就假设我们的SpawnManager要进行怪物的生成,那就需要接入怪物的预制件。在一般情况下我们就是在inspector上面直接把预制件拖进去什么的。但是由于我们的SpawnManager是在程序运行过程中生成的,它不会自动给你接入这些预制件,就那个地方会显示None。这就需要我们写个脚本让它能够动态的引入这个预制件。这是一个需要注意的坑。

MonoSingleton

  • MonoSingleton: a ability to turn any manager class easily into a singleton and use it to act as a singleton in initialized values.
    从前面的部分我们可以发现,这些Manager Class的格式都是一样的,都是先弄一个private variable去声明这个实例,然后做一个public static property,最后在Awake()函数里面分配这个实例。Manager Class一多的话,就可以意识到我们一直在写一个重复的部分,一个两个还好,但万一我们要做十几个二十几个Manager Class呢。这时候我们就要采用MonoSingleton的方法,它就像是一个模板,或者我们叫它所有单例的通用模板(generic template)。MonoSingleton是一个抽象类,忘记抽象类是什么的话看这里

看看代码(啊好头疼怎么那么难啊
这里用了通用类型T,在这边T是继承MonoSingleton的类。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace ChapterSingleton{

    public class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>
    {
        private static T _instance;
        public static T Instance
        {
            get{
                if(_instance == null){
                    Debug.LogError(typeof(T).ToString() + " is NULL");
                }
                return _instance;
            }
        }
        private void Awake() {
            _instance = this as T;
        }

    }
}

那怎么把一个类变成单例呢 嗯就这样

    public class ShopManager : MonoSingleton<ShopManager>
    {      
    }

我们可以再拓展一下 比如说有些通用函数我们需要在子类重写的 我们也可以先在MonoSingleton种弄个虚函数 像这样

namespace ChapterSingleton{

    public class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>
    {
        private static T _instance;
        public static T Instance
        {
            get{
                if(_instance == null){
                    Debug.LogError(typeof(T).ToString() + " is NULL");
                }
                return _instance;
            }
        }
        private void Awake() {
            _instance = this as T;
            Init();
        }

        public virtual void Init()
        {
            //optional to override 
        }
    }
}

我们在创建单例时就可以直接重写了

namespace ChapterSingleton
{
    public class ShopManager : MonoSingleton<ShopManager>
    {
        public override void Init()
        {
            base.Init();
            Debug.Log("Shop sys initialized");
        }
    }
}

这章是需要反复观看的

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值