当前位置:   article > 正文

Unity的C#编程教程_63_单一实例 Singleton 详解及应用练习_unity singleton

unity singleton

Singleton Design Pattern

  • 游戏设计模式之一:单一实例模式
    • 全局都可以访问的 class,该 class 仅存在一个
    • 比如我们的 Manager class:Game Manager,Item Manager,Player Manager,UI Manager,Spawn Manager等
    • 我们一般不用通过 GetComponent 访问,而是直接进行访问
    • 使用 singleton,确保这个 class 仅有一个

创建 Game Manager 脚本:

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

public class GameManager : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

创建一个 Player,挂载同名脚本:

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

public class Player : MonoBehaviour
{

    private GameManager _gm;

    // Start is called before the first frame update
    void Start()
    {
        _gm = GameObject.Find("Game Manager").GetComponent<GameManager>();
        // 传统方法找到对应的 GameManager 脚本,需要先找到对应的游戏对象,然后获取游戏对象下面挂载的脚本组件
    }

    // Update is called once per frame
    void Update()
    {

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

改成设计成 singleton:

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

public class GameManager : MonoBehaviour
{
    private static GameManager _instance;
    // 生成一个静态实例,确保了仅有一个实例

    public static GameManager Instance
    {
        get
        {
            if (_instance == null)
            {
                Debug.Log("Game Manager is null");
            }

            return _instance;
        }
    }
    // 用于别的脚本与 GameManager 交互
    // 这个 property 仅有一个 get 方法,确保了别的脚本只能对其只读
    // 读取的就是 _instance 实例对象

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private void Awake() // 加载脚本实例的时候进行调用
    {
        _instance = this; // 赋值为该游戏对象
    }

    public void Test()
    {
        Debug.Log("Testing");
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

在 Player 中调用:

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

public class Player : MonoBehaviour
{

    // private GameManager _gm;

    // Start is called before the first frame update
    void Start()
    {
        // _gm = GameObject.Find("Game Manager").GetComponent<GameManager>();
        // 传统方法找到对应的 GameManager 脚本,需要先找到对应的游戏对象,然后获取游戏对象下面挂载的脚本组件

        GameManager.Instance.Test();
        // 使用了 singleton 后就可以直接调用了
    }

    // Update is called once per frame
    void Update()
    {

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

使用 singleton 的时候,我们可以让所有的游戏对象(非静态)访问 Manager 的脚本进行交互,这也是仅有的和 Manager class 交互的方式,Manager classes 之间也可以交互,但是 Manager 脚本不会访问其他的游戏对象进行交互,所以这种交互是单向的。

Singleton UI Manager

  • 使用 singleton 方式创建 UI Manager

创建一个空的游戏对象,命名为 UI Manager,挂载同名脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // 需要引入 UI 的库

public class UIManager : MonoBehaviour
{
    private static UIManager _instance;
    // static 确保只有一个,所有游戏对象都访问这个

    public static UIManager Instance // 这里的 property 也是 static
    {
        get
        {
            if (_instance == null) // 检测脚本有没有挂载到游戏对象上
            {
                Debug.Log("The UIManager instance is null");
            }

            return _instance;
        }
        // 这设定 property,外部脚本只读访问 _instance
    }

    private void Awake()
    {
        _instance = this; // 对实例进行赋值为该游戏对象
    }

    public void ShowScore(int score)
    {
        Debug.Log("The score is: " + score);
        GameManager.Instance.Test();
        // Manager classes 之间也可以相互访问
        // 但是 Manager class 不访问别的游戏对象
        // 仅由别的游戏对象访问 Manager class
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

UI Manager 脚本负责管理游戏中的所有 UI 游戏对象,整个游戏中只会有一个 UI Manager,所以可以使用 singleton 使得访问更简便。

还可以在 Manager classes 之间进行交互:

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

public class GameManager : MonoBehaviour
{
    private static GameManager _instance;
    // 生成一个静态实例,确保了仅有一个实例

    public static GameManager Instance
    {
        get
        {
            if (_instance == null)
            {
                Debug.Log("Game Manager is null");
            }
            return _instance;
        }
    }

    private void Awake() // 加载脚本实例的时候进行调用
    {
        _instance = this; // 赋值为该游戏对象
    }

    public void Test()
    {
        Debug.Log("Testing");
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

Player 可以简便地访问 Manager class:

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

public class Player : MonoBehaviour
{

    // Start is called before the first frame update
    void Start()
    {
        UIManager.Instance.ShowScore(77);
        // 使用了 singleton 后就可以直接调用了
    }

    // Update is called once per frame
    void Update()
    {

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

Challenge: Singleton Spawn Manager

  • 任务说明:
    • 创建一个 Spawn Manager
    • 将其设计为 singleton
    • 创建一个 SpawnEnemy 方法,然后在 Player 脚本中进行尝试调用

创建游戏对象 Spawn Manager 并挂载同名脚本:

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

public class SpawnManager : MonoBehaviour
{
    private static SpawnManager _instance;

    public static SpawnManager Instance
    {
        get
        {
            if (_instance == null)
            {
                Debug.Log("The Spawn Manager instance is null");
            }

            return _instance;
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private void Awake()
    {
        _instance = this;
    }

    public void SpawnEnemy()
    {
        Debug.Log("Spawn one Enemy");
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

在 Player 中调用:

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

public class Player : MonoBehaviour
{

    // Start is called before the first frame update
    void Start()
    {
        SpawnManager.Instance.SpawnEnemy();
        // 使用了 singleton 后就可以直接调用了
    }

    // Update is called once per frame
    void Update()
    {

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

Singleton: Lazy Instantiation

1. Lazy Instantiation

  • 最优的做法是,在运行应用之前声明 Manager
    • 比如我们应该建立对应的 class,还有对应的游戏对象,挂载对应脚本
    • 然后还需要检验 instance 是不是存在

但是我们也可以先不创建游戏对象,自然创建的脚本也不用挂载了:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // 需要引入 UI 的库

public class UIManager : MonoBehaviour
{
    private static UIManager _instance;
    // static 确保只有一个,所有游戏对象都访问这个

    public static UIManager Instance // 这里的 property 也是 static
    {
        get
        {
            if (_instance == null) // 检测脚本有没有挂载到游戏对象上
            {
                GameObject obj = new GameObject("UI Manager");
                // 新建一个游戏对象叫做 UI Manager
                obj.AddComponent<UIManager>();
                // 为该游戏对象增加组件:该脚本
            }

            return _instance;
        }
        // 这设定 property,外部脚本只读访问 _instance
    }

    private void Awake()
    {
        _instance = this; // 对实例进行赋值为该游戏对象
    }

    public void ShowScore(int score)
    {
        Debug.Log("The score is: " + score);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

我们可以把原有的 UI Manager 删除,然后 Player 中照样可以调用:

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

public class Player : MonoBehaviour
{

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            UIManager.Instance.ShowScore(88);
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

运行以后会自动创建游戏对象 UI Manager,停止运行后该游戏对象会被删除。

使用 singleton 的两大优势:

  1. global access conmunication
  2. lazily instantiate an object

2. Downfall of Lazy Instantiation

  • 使用 lazy instantiation 的缺点
    • 比如我们使用 Spawn Manager
    • 我们会通过这个脚本去生成 Enemy,这需要我们首先由 Enemy 的 prefab,然后在 inspector 中拖拽赋值
    • 如果一开始不建立 Spawn Manager 游戏对象,那就没法拖拽赋值了
    • 需要我们知道 prefab 存在的文件夹,然后通过代码进行赋值,比较麻烦

MonoSingleton

  • 把任何 manager class 简单转化为 singleton

之前我们制作 singleton 的时候,每一个 manager class 都需要进行初始化设定以及实例赋值,假设我们有 50 个,那就要进行 50 次类似的操作。

即然需要重复做,那我们就可以想办法进行模块化操作。

建立一个 singleton 的模版,脚本命名为 MonoSingleton:

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

public  abstract class MonoSingleton<T> : MonoBehaviour where T: MonoSingleton<T>
    // 设定为抽象类,这样就不能挂载到游戏对象上,仅作为模版使用
    // <T> 设定了一个 generic type,即不同的类型都可以使用该模版,多态性
    // T 继承的应用模版的时候所输入的类型
    // 这里这么做的原因在于,我们不但要继承 MonoSingleton,还需要知道具体的类型

{
    private static T _instance;

    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                Debug.LogError(typeof(T).ToString()+ " has no instance");
                // 显示该类没有实例化,即没有赋值游戏对象(游戏对象挂载脚本)
            }

            return _instance;
        }
    }

    private void Awake()
    {
        _instance = this as T;
        // 或者 _instance = (T)this;
        Int();
        // 一旦加载实例,即进指定的行初始化
        // 如果没有被重写,那不执行任何操作
    }

    public virtual void Int() // 设定一个初始化方法
    {
        // 虚方法,可以选择重写,也可以不重写
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

我们尝试把 Player 的脚本来使用一下这个模版:

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

public class Player : MonoSingleton<Player> // 继承自模版,类型为 Player
{

    public override void Int()
    {
        base.Int(); // 调用默认的初始化方法,可去除
        Debug.Log("Player initialized!");

    }

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

另外尝试创建一个 Level Manager,挂载同名脚本:

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

public class LevelManager : MonoSingleton<LevelManager>
{
    public override void Int()
    {
        Debug.Log("Level Manager initialized");
    }

    public void SetLevel()
    {
        Debug.Log("Set a level");
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

通过 Player 尝试直接调用:

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

public class Player : MonoSingleton<Player>
{

    public override void Int()
    {
        Debug.Log("Player initialized!");
        LevelManager.Instance.SetLevel(); // 可以直接调用
    }

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/140740
推荐阅读
相关标签
  

闽ICP备14008679号