單一模式利用 Unitys 實體 - 元件系統

核心思想是使用 GameObjects 來表示單例,這有多個優點:

  • 將複雜性保持在最低限度,但支援依賴注入等概念
  • 單例擁有正常的 Unity 生命週期作為實體元件系統的一部分
  • 單例可以延遲載入並在常規需要的地方快取(例如在更新迴圈中)
  • 不需要靜態欄位
  • 無需修改現有的 MonoBehaviours / Components 即可將其用作單例
  • 容易重置(只需銷燬 Singletons GameObject),下次使用時會再次載入延遲
  • 易於注入模擬(在使用之前只需使用模擬初始化)
  • 使用普通的 Unity 編輯器進行檢查和配置,並且可以在編輯器時間發生( Unity 編輯器中可訪問的 Singleton 的螢幕截圖

Test.cs(使用示例單例):

using UnityEngine;
using UnityEngine.Assertions;

public class Test : MonoBehaviour {
    void Start() {
        ExampleSingleton singleton = ExampleSingleton.instance;
        Assert.IsNotNull(singleton); // automatic initialization on first usage
        Assert.AreEqual("abc", singleton.myVar1);
        singleton.myVar1 = "123";
        // multiple calls to instance() return the same object:
        Assert.AreEqual(singleton, ExampleSingleton.instance); 
        Assert.AreEqual("123", ExampleSingleton.instance.myVar1);
    }
}

ExampleSingleton.cs(包含一個示例和實際的 Singleton 類):

using UnityEngine;
using UnityEngine.Assertions;

public class ExampleSingleton : MonoBehaviour {
    public static ExampleSingleton instance { get { return Singleton.get<ExampleSingleton>(); } }
    public string myVar1 = "abc";
    public void Start() { Assert.AreEqual(this, instance, "Singleton more than once in scene"); } 
}

/// <summary> Helper that turns any MonBehaviour or other Component into a Singleton </summary>
public static class Singleton {
    public static T get<T>() where T : Component {
        return GetOrAddGo("Singletons").GetOrAddChild("" + typeof(T)).GetOrAddComponent<T>();
    }
    private static GameObject GetOrAddGo(string goName) {
        var go = GameObject.Find(goName);
        if (go == null) { return new GameObject(goName); }
        return go;
    }
}

public static class GameObjectExtensionMethods { 
    public static GameObject GetOrAddChild(this GameObject parentGo, string childName) {
        var childGo = parentGo.transform.FindChild(childName);
        if (childGo != null) { return childGo.gameObject; } // child found, return it
        var newChild = new GameObject(childName);        // no child found, create it
        newChild.transform.SetParent(parentGo.transform, false); // add it to parent
        return newChild;
    }

    public static T GetOrAddComponent<T>(this GameObject parentGo) where T : Component {
        var comp = parentGo.GetComponent<T>();
        if (comp == null) { return parentGo.AddComponent<T>(); }
        return comp;
    }
}

GameObject 的兩個擴充套件方法在其他情況下也很有用,如果你不需要它們,可以在 Singleton 類中移動它們並將它們設為私有。