UFL1-2——继承MonoBehaviour的单例模式基类

继承MonoBehaviour​的单例模式对象,建议大家使用自动挂载式的方案,其潜在的安全问题相对较小,制定好规则后可以忽略不计

前置知识点

大体知识体系

  • C#相关知识点(C#四部曲)、Unity相关知识点(Unity四部曲)

细节知识点

  1. 上节课的相关细节知识点
  2. MonoBehaviour相关知识点(Unity四部曲之Unity入门中)

实现继承MonoBehaviour的单例模式基类的注意事项

继承MonoBehaviour​的类不可以new()​!!!

继承MonoBehaviour​的脚本一定得依附在GameObject上!!!

实现挂载式的单例模式基类

这种方式不建议大家使用,因为很容易被破坏单例模式的唯一性,这种单例模式基类存在以下的问题:

  1. 可能会手动挂载了多个脚本
  2. 切换场景回来时,由于场景放置了挂载脚本的对象,回到该场景时 又会有一个该单例模式对象
  3. 还可以通过代码动态的添加多个该脚本,也会破坏唯一性
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
using System;
using UnityEngine;

/// <summary>
/// 继承MonoBehaviour的单例模式基类,需要挂载才可以使用
/// </summary>
/// <typeparam name="T">继承该类的类</typeparam>
[Obsolete("这个单例类不安全!不建议使用!")]
public class SingletonMono<T> : MonoBehaviour where T : MonoBehaviour
{
private static T instance;

public static T Instance
{
get => instance;
}

/// <summary>
/// 在重写该Awake函数时切记保留base.Awake()!!!
/// </summary>
protected virtual void Awake()
{
instance = this as T;
}
}

使用方法

如果想要在管理器里初始化,只需要重写Awake​方法然后初始化即可,但是base.Awake()是不可删除的!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestMonoMgr : SingletonMono<TestMonoMgr>
{
int i;

protected override void Awake()
{
base.Awake();
i = 10;
}

public void TestLog()
{
print("TestMonoMgr!" + i);
}
}

使用前先将继承了SingletonMono<>​的TestMonoMgr​挂载到场景上的某个对象上:

image

1
2
3
4
5
6
7
8
9
10
using System;
using UnityEngine;

public class Main : MonoBehaviour
{
void Start()
{
TestMonoMgr.Instance.TestLog();
}
}

输出:image

自动挂载式的单例模式基类

推荐使用这种方式实现的继承Mono的单例模式基类,因为它无需手动挂载和动态添加,也无需关心多线程或者切换场景带来的问题

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
using UnityEngine;

/// <summary>
/// 继承MonoBehaviour的单例模式基类,第一次调用时会自动创建并挂载到一个空对象上
/// </summary>
/// <typeparam name="T">继承该类的类</typeparam>
public class SingletonAutoMono<T> : MonoBehaviour where T : MonoBehaviour
{
private static T instance;

public static T Instance
{
get
{
//当发现单例为空时,动态创建并挂载到一个空对象上
if (instance == null)
{
GameObject obj = new GameObject();
obj.name = $"Manager_{typeof(T)}";
instance = obj.AddComponent<T>();
DontDestroyOnLoad(obj); //让该管理器对象过场景时也不被移除
}
return instance;
}
}
}

使用方法

管理器示例:

1
2
3
4
5
6
7
public class TestMonoMgr2 : SingletonAutoMono<TestMonoMgr2>
{
public void TestLog()
{
print("我是TestMonoMgr2!");
}
}

使用管理器示例:

1
2
3
4
5
6
7
8
9
10
using System;
using UnityEngine;

public class Main : MonoBehaviour
{
void Start()
{
TestMonoMgr2.Instance.TestLog();
}
}

输出:image

同时可以看见与管理器对应的游戏对象被创建出来:image

对于潜在的安全问题

  1. 构造函数问题:继承MonoBehaviour的函数,不能new(),所以不用担心公共构造函数

    继承MonoBehaviour​的函数,不能new()​,所以不用担心公共构造函数

  2. 多线程问题:Unity主线程中相关内容,不允许其他线程直接调用,很少有这样的需求,所以也不用太担心

    Unity主线程中相关内容,不允许其他线程直接调用,很少有这样的需求,所以也不用太担心

  3. 重复挂载问题:

    1. 手动重复挂载
    2. 代码重复添加

    需要人为干涉,定规则,或者通过代码逻辑强制处理