Menu
CoddyTech

泛型单例

本课属于 Coddy Unity 学习旅程的“Unity 游戏架构”单元。第 43 课,共 64 课。

第 3 单元中的单例只需 5 行代码,每个管理器都会重复编写这些代码:静态的 Instance、用于设置它的 Awake,以及重复实例检查。泛型基类可以把这些代码只写一次。每个管理器都从这个基类派生,并将自身类型作为名称:

public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    public static T Instance { get; private set; }

    protected virtual void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this as T;
    }
}

public class ScoreManager : Singleton<ScoreManager>
{
    public int score;
    public void Add(int points) { score += points; }
}

public class SoundManager : Singleton<SoundManager>
{
    public void Play(string clip) { Debug.Log("Playing " + clip); }
}
// 在任何脚本中
ScoreManager.Instance.Add(10);
SoundManager.Instance.Play("coin");

泛型类的静态成员分别属于由它生成的每种类型:Singleton<ScoreManager> 和 Singleton<SoundManager> 是两种各自拥有一个 Instance 的类型,因此这些管理器不会共用同一个 Instance。ScoreManager.Instance 的类型已经是 ScoreManager,这正是 T 的作用。在基类中,this 只被视为 Singleton<T>,因此 this as T 会将它转换为它实际所属的管理器类型。

这个基类是抽象类:Unity 只会将管理器放到对象上。如果管理器需要自己的 Awake,就不能直接编写 void Awake()。这样会隐藏基类的方法(编译器会发出警告),Unity 也只会调用管理器自己的方法,Instance 永远不会被设置。请重写该方法,并先调用基类的方法:

protected override void Awake()
{
    base.Awake();
    score = 0;
}
challenge icon

挑战

中等

为两个管理器提供一个在场景加载后仍能保留的基类。

  1. 在 PersistentSingleton 中,编写 protected virtual void Awake():第一个实例成为 Instance 并保留下来(DontDestroyOnLoad);后续实例会销毁自身。
  2. Wallet 和 MusicPlayer 继承它;删除它们的 Instance 和 Wallet 的 Awake。
  3. MusicPlayer 重写 Awake:调用 base.Awake(),然后,如果它就是 Instance,则记录 Now playing: Adventure。

按“开始”,前往森林再回来:获得 1 枚金币。然后按“检查”。

亲手试试

using UnityEngine;

public abstract class PersistentSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
    public static T Instance { get; private set; }

    // Awake:切换场景时保留第一个实例,销毁之后创建的实例
}

Unity 游戏架构中的所有课

自己练习: Unity 在线编辑器