Unity的C#编程教程_63_单一实例 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()
    {
        
    }

}

创建一个 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()
    {

    }
}

改成设计成 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");
    }

}

在 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()
    {

    }
}

使用 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
    }
}

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");
    }

}

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()
    {

    }
}

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");
    }
}

在 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()
    {

    }
}

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);
    }
}

我们可以把原有的 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);
        }
    }
}

运行以后会自动创建游戏对象 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()
    {
        
    }
}

我们尝试把 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()
    {

    }
}

另外尝试创建一个 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");
    }
}

通过 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
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity中,预制件和实例化是非常常见的概念。预制件是一种可重复使用的对象,可以创建多个实例。而实例化则是在场景中创建一个新的对象实例,该实例可以被修改、移动和删除,而不会影响预制件或其他实例。 下面是一个简单的示例,演示如何在Unity中添加对象实例: 1. 首先,打开Unity编辑器并创建一个新场景。 2. 在场景中创建一个新的空对象,此空对象将用作容器来保存实例化的对象。 3. 然后,创建一个新的预制件。您可以在项目视图中右键单击,选择“Create”->“Prefab”,然后将其命名为“Cube”。 4. 将预制件拖动到场景中的容器对象中,这将创建一个预制件的实例。 5. 在场景中选择容器对象,然后在Hierarchy视图中右键单击并选择“Create Empty”,这将创建另一个空对象。 6. 您现在可以将此新对象命名为“SpawnPoint”,以便于识别。 7. 接下来,打开脚本编辑器并创建一个新的C#脚本。将以下代码添加到脚本中: ``` using UnityEngine; public class SpawnObject : MonoBehaviour { public GameObject objectToSpawn; public Transform spawnPoint; void Start() { Instantiate(objectToSpawn, spawnPoint.position, spawnPoint.rotation); } } ``` 8. 在场景中选择SpawnPoint对象,然后将SpawnObject脚本添加到该对象上。 9. 在SpawnObject脚本组件中,将objectToSpawn属性设置为Cube预制件,并将spawnPoint属性设置为SpawnPoint对象的Transform组件。 10. 您现在可以运行场景,并查看Cube预制件的实例化。您可以尝试通过更改SpawnPoint对象的位置来更改实例化的位置,并且可以通过更改Cube预制件的属性来更改实例化的外观。 希望这个示例能够帮助您了解Unity中的预制件和实例化的基本概念,以及如何将它们用于创建对象实例

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值