unity3d
Unityのシングルトン
サーチ…
備考
シングルトンの拘束を受けていない使用が悪い考えであるという説得力のある議論があるのは間違いありませんが、 gameprogrammingpatterns.comのSingletonのように 、シームレスなバックグラウンドミュージックのように複数のシーンでGameObjectをUnityに永続させたい場合があります。インスタンスを複数存在させることはできません。シングルトンの完璧な使用例
このスクリプトをGameObjectに追加することにより、インスタンス化されると(Sceneの任意の場所に挿入するなどして)、シーン全体でアクティブなままになり、インスタンスは1つしか存在しません。
ScriptableObject ( UnityDoc )インスタンスは、ユースケースによってはシングルトンに対する有効な代替手段を提供します。それらは暗黙のうちに単一のインスタンスルールを強制しませんが、シーン間で状態を保持し、Unityのシリアル化プロセスをうまく使います。依存関係がエディタを介して注入されると、 Inversion of Controlも促進されます。
// MyAudioManager.cs
using UnityEngine;
[CreateAssetMenu] // Remember to create the instance in editor
public class MyAudioManager : ScriptableObject {
public void PlaySound() {}
}
// MyGameObject.cs
using UnityEngine;
public class MyGameObject : MonoBehaviour
{
[SerializeField]
MyAudioManager audioManager; //Insert through Inspector
void OnEnable()
{
audioManager.PlaySound();
}
}
参考文献
RuntimeInitializeOnLoadMethodAttributeを使用した実装
Unity 5.2.5以降、 RuntimeInitializeOnLoadMethodAttributeを使用してMonoBehaviourの実行順序をバイパスして初期化ロジックを実行することができます。よりクリーンで堅牢な実装を作成する方法を提供します。
using UnityEngine;
sealed class GameDirector : MonoBehaviour
{
// Because of using RuntimeInitializeOnLoadMethod attribute to find/create and
// initialize the instance, this property is accessible and
// usable even in Awake() methods.
public static GameDirector Instance
{
get; private set;
}
// Thanks to the attribute, this method is executed before any other MonoBehaviour
// logic in the game.
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void OnRuntimeMethodLoad()
{
var instance = FindObjectOfType<GameDirector>();
if (instance == null)
instance = new GameObject("Game Director").AddComponent<GameDirector>();
DontDestroyOnLoad(instance);
Instance = instance;
}
// This Awake() will be called immediately after AddComponent() execution
// in the OnRuntimeMethodLoad(). In other words, before any other MonoBehaviour's
// in the scene will begin to initialize.
private void Awake()
{
// Initialize non-MonoBehaviour logic, etc.
Debug.Log("GameDirector.Awake()", this);
}
}
結果の実行順序:
-
GameDirector.OnRuntimeMethodLoad()
が開始しました... -
GameDirector.Awake()
-
GameDirector.OnRuntimeMethodLoad()
完了しました。 -
OtherMonoBehaviour1.Awake()
-
OtherMonoBehaviour2.Awake()
など
Unity C#のシンプルなSingleton MonoBehaviour
この例では、クラスのプライベートな静的インスタンスが最初に宣言されています。
静的フィールドの値はインスタンス間で共有されるため、このクラスの新しいインスタンスが作成されると、 if
は新しいインスタンス(またはそのゲームオブジェクト)を破棄して最初のSingletonオブジェクトへの参照を見つけます。
using UnityEngine;
public class SingletonExample : MonoBehaviour {
private static SingletonExample _instance;
void Awake(){
if (_instance == null){
_instance = this;
DontDestroyOnLoad(this.gameObject);
//Rest of your Awake code
} else {
Destroy(this);
}
}
//Rest of your class code
}
高度なUnityシングルトン
この例では、インターネット上にあるMonoBehaviourシングルトンの複数のバリアントを1つにまとめ、グローバルな静的フィールドに応じて動作を変更できるようにします。
この例は、Unity 5を使ってテストされました。このシングルトンを使用するには、次のように拡張するだけですpublic class MySingleton : Singleton<MySingleton> {}
通常のAwake
代わりにAwakeSingleton
を使用するには、 AwakeSingleton
をオーバーライドする必要があります。さらに微調整するには、後述の静的フィールドのデフォルト値を変更します。
- この実装では、 DisallowMultipleComponent属性を使用して、 GameObjectごとに1つのインスタンスを保持します。
- このクラスは抽象クラスであり、拡張は可能です。また、通常の
Awake
を実装する代わりに、オーバーライドする必要がある仮想メソッドAwakeSingleton
も含まれています。 - この実装はスレッドセーフです。
- このシングルトンは最適化されています。インスタンスのヌルチェックの代わりにインスタンス
instantiated
フラグを使用instantiated
ことにより、Unityの==
演算子の実装に伴うオーバーヘッドを回避します。 ( 続きを読む ) - この実装は、Unityによって破壊されようとしているときにシングルトンインスタンスへの呼び出しを許可しません。
- このシングルトンには、次のオプションがあります。
-
FindInactive
:非アクティブなFindInactive
接続された同じタイプのコンポーネントの他のインスタンスを探すかどうか。 -
Persist
:シーン間生きているコンポーネントを保持するかどうか。 -
DestroyOthers
:同じタイプの他のコンポーネントを破棄し、1つだけ保持するかどうか。 -
Lazy
:シングルトンインスタンスを「オンザフライで」(Awake
)設定するのか、「オンデマンドでのみ」(ゲッターが呼び出されるか)のみ設定します。
using UnityEngine;
[DisallowMultipleComponent]
public abstract class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
private static volatile T instance;
// thread safety
private static object _lock = new object();
public static bool FindInactive = true;
// Whether or not this object should persist when loading new scenes. Should be set in Init().
public static bool Persist;
// Whether or not destory other singleton instances if any. Should be set in Init().
public static bool DestroyOthers = true;
// instead of heavy comparision (instance != null)
// http://blogs.unity3d.com/2014/05/16/custom-operator-should-we-keep-it/
private static bool instantiated;
private static bool applicationIsQuitting;
public static bool Lazy;
public static T Instance
{
get
{
if (applicationIsQuitting)
{
Debug.LogWarningFormat("[Singleton] Instance '{0}' already destroyed on application quit. Won't create again - returning null.", typeof(T));
return null;
}
lock (_lock)
{
if (!instantiated)
{
Object[] objects;
if (FindInactive) { objects = Resources.FindObjectsOfTypeAll(typeof(T)); }
else { objects = FindObjectsOfType(typeof(T)); }
if (objects == null || objects.Length < 1)
{
GameObject singleton = new GameObject();
singleton.name = string.Format("{0} [Singleton]", typeof(T));
Instance = singleton.AddComponent<T>();
Debug.LogWarningFormat("[Singleton] An Instance of '{0}' is needed in the scene, so '{1}' was created{2}", typeof(T), singleton.name, Persist ? " with DontDestoryOnLoad." : ".");
}
else if (objects.Length >= 1)
{
Instance = objects[0] as T;
if (objects.Length > 1)
{
Debug.LogWarningFormat("[Singleton] {0} instances of '{1}'!", objects.Length, typeof(T));
if (DestroyOthers)
{
for (int i = 1; i < objects.Length; i++)
{
Debug.LogWarningFormat("[Singleton] Deleting extra '{0}' instance attached to '{1}'", typeof(T), objects[i].name);
Destroy(objects[i]);
}
}
}
return instance;
}
}
return instance;
}
}
protected set
{
instance = value;
instantiated = true;
instance.AwakeSingleton();
if (Persist) { DontDestroyOnLoad(instance.gameObject); }
}
}
// if Lazy = false and gameObject is active this will set instance
// unless instance was called by another Awake method
private void Awake()
{
if (Lazy) { return; }
lock (_lock)
{
if (!instantiated)
{
Instance = this as T;
}
else if (DestroyOthers && Instance.GetInstanceID() != GetInstanceID())
{
Debug.LogWarningFormat("[Singleton] Deleting extra '{0}' instance attached to '{1}'", typeof(T), name);
Destroy(this);
}
}
}
// this might be called for inactive singletons before Awake if FindInactive = true
protected virtual void AwakeSingleton() {}
protected virtual void OnDestroy()
{
applicationIsQuitting = true;
instantiated = false;
}
}
ベースクラスによるシングルトン実装
いくつかのシングルトンクラスを特徴とするプロジェクトでは(たいていの場合)、シングルトンの振る舞いを基本クラスに抽象化することはきれいで便利です。
using UnityEngine;
using System.Collections.Generic;
using System;
public abstract class MonoBehaviourSingleton<T> : MonoBehaviour {
private static Dictionary<Type, object> _singletons
= new Dictionary<Type, object>();
public static T Instance {
get {
return (T)_singletons[typeof(T)];
}
}
void OnEnable() {
if (_singletons.ContainsKey(GetType())) {
Destroy(this);
} else {
_singletons.Add(GetType(), this);
DontDestroyOnLoad(this);
}
}
}
次に、MonoBehaviourは、MonoBehaviourSingletonを拡張することによってシングルトンパターンを実装することができます。このアプローチにより、シングルトン自体のパターンを最小限に抑えてパターンを利用することができます。
using UnityEngine;
using System.Collections;
public class SingletonImplementation : MonoBehaviourSingleton<SingletonImplementation> {
public string Text= "String Instance";
// Use this for initialisation
IEnumerator Start () {
var demonstration = "SingletonImplementation.Start()\n" +
"Note that the this text logs only once and\n"
"only one class instance is allowed to exist.";
Debug.Log(demonstration);
yield return new WaitForSeconds(2f);
var secondInstance = new GameObject();
secondInstance.AddComponent<SingletonImplementation>();
}
}
シングルトンパターンの利点の1つは、インスタンスへの参照に静的にアクセスできることです。
// Logs: String Instance
Debug.Log(SingletonImplementation.Instance.Text);
しかし、カップリングを減らすためには、この方法を最小限に抑える必要があります。このアプローチには、Dictionaryの使用によるわずかなパフォーマンスコストもありますが、このコレクションには各シングルトンクラスのインスタンスが1つしか含まれていないため、DRY原則(自分自身を繰り返してはいけません)、可読性利便性は低いです。
ユニティエンティティを利用したシングルトンパターン - コンポーネントシステム
コアアイデアは、GameObjectを使用してシングルトンを表現することです。シングルトンには複数の利点があります。
- 複雑さを最小限に抑えながら、依存性注入などの概念をサポート
- シングルトンは、Entity-Componentシステムの一部として通常のUnityライフサイクルを持ちます
- シングルトンは、規則的に必要とされる場所(例えば、更新ループ内)で、遅延ロードされ、
- 静的フィールドは必要ありません
- 既存のMonoBehaviours /コンポーネントをシングルトンとして使用するように変更する必要はありません
- 簡単にリセットすることができます(ただ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(サンプルと実際のシングルトンクラスが含まれています):
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の2つの拡張メソッドは、シングルトンクラス内でそれらを移動して非公開にする必要がない場合にも、他の状況で役に立ちます。
MonoBehaviour&ScriptableObjectベースのシングルトンクラス
ほとんどのシングルトンの例では、MonoBehaviourを基本クラスとして使用しています。主な欠点は、このSingletonクラスが実行時にのみ存続することです。これにはいくつかの欠点があります。
- コード変更以外のシングルトンフィールドを直接編集する方法はありません。
- シングルトンに他のアセットへの参照を格納する方法はありません。
- シングルトンをUnity UIイベントの宛先として設定する方法はありません。私は "Proxy Component"と呼ばれるものを使用して終了します。その唯一の提案は、 "GameManager.Instance.SomeGlobalMethod()"を呼び出す1行のメソッドを持つことです。
備考に記載されているように、基本クラスとしてScriptableObjectsを使用してこれを解決しようとする実装がありますが、MonoBehaviourのランタイム利点を失います。この実装では、実行時にScriptableObjectを基本クラスおよび関連するMonoBehaviorとして使用することで、この問題を解決しています。
- これはアセットであるため、他のUnityアセットのようにそのプロパティをエディタで更新することができます。
- Unityのシリアライゼーションプロセスでうまくいきます。
- シングルトンのリファレンスをエディタから他のアセットに割り当てることができます(依存関係はエディタを介して注入されます)。
- Unityイベントは、シングルトンのメソッドを直接呼び出すことができます。
- "SingletonClassName.Instance"を使用してコードベース内のどこからでも呼び出すことができます
- Update、Awake、Start、FixedUpdate、StartCoroutineなどの実行時MonoBehaviourイベントとメソッドにアクセスできます。
/************************************************************
* Better Singleton by David Darias
* Use as you like - credit where due would be appreciated :D
* Licence: WTFPL V2, Dec 2014
* Tested on Unity v5.6.0 (should work on earlier versions)
* 03/02/2017 - v1.1
* **********************************************************/
using System;
using UnityEngine;
using SingletonScriptableObjectNamespace;
public class SingletonScriptableObject<T> : SingletonScriptableObjectNamespace.BehaviourScriptableObject where T : SingletonScriptableObjectNamespace.BehaviourScriptableObject
{
//Private reference to the scriptable object
private static T _instance;
private static bool _instantiated;
public static T Instance
{
get
{
if (_instantiated) return _instance;
var singletonName = typeof(T).Name;
//Look for the singleton on the resources folder
var assets = Resources.LoadAll<T>("");
if (assets.Length > 1) Debug.LogError("Found multiple " + singletonName + "s on the resources folder. It is a Singleton ScriptableObject, there should only be one.");
if (assets.Length == 0)
{
_instance = CreateInstance<T>();
Debug.LogError("Could not find a " + singletonName + " on the resources folder. It was created at runtime, therefore it will not be visible on the assets folder and it will not persist.");
}
else _instance = assets[0];
_instantiated = true;
//Create a new game object to use as proxy for all the MonoBehaviour methods
var baseObject = new GameObject(singletonName);
//Deactivate it before adding the proxy component. This avoids the execution of the Awake method when the the proxy component is added.
baseObject.SetActive(false);
//Add the proxy, set the instance as the parent and move to DontDestroyOnLoad scene
SingletonScriptableObjectNamespace.BehaviourProxy proxy = baseObject.AddComponent<SingletonScriptableObjectNamespace.BehaviourProxy>();
proxy.Parent = _instance;
Behaviour = proxy;
DontDestroyOnLoad(Behaviour.gameObject);
//Activate the proxy. This will trigger the MonoBehaviourAwake.
proxy.gameObject.SetActive(true);
return _instance;
}
}
//Use this reference to call MonoBehaviour specific methods (for example StartCoroutine)
protected static MonoBehaviour Behaviour;
public static void BuildSingletonInstance() { SingletonScriptableObjectNamespace.BehaviourScriptableObject i = Instance; }
private void OnDestroy(){ _instantiated = false; }
}
// Helper classes for the SingletonScriptableObject
namespace SingletonScriptableObjectNamespace
{
#if UNITY_EDITOR
//Empty custom editor to have cleaner UI on the editor.
using UnityEditor;
[CustomEditor(typeof(BehaviourProxy))]
public class BehaviourProxyEditor : Editor
{
public override void OnInspectorGUI(){}
}
#endif
public class BehaviourProxy : MonoBehaviour
{
public IBehaviour Parent;
public void Awake() { if (Parent != null) Parent.MonoBehaviourAwake(); }
public void Start() { if (Parent != null) Parent.Start(); }
public void Update() { if (Parent != null) Parent.Update(); }
public void FixedUpdate() { if (Parent != null) Parent.FixedUpdate(); }
}
public interface IBehaviour
{
void MonoBehaviourAwake();
void Start();
void Update();
void FixedUpdate();
}
public class BehaviourScriptableObject : ScriptableObject, IBehaviour
{
public void Awake() { ScriptableObjectAwake(); }
public virtual void ScriptableObjectAwake() { }
public virtual void MonoBehaviourAwake() { }
public virtual void Start() { }
public virtual void Update() { }
public virtual void FixedUpdate() { }
}
}
ここには、(たくさんのコメントがある)SingletonScriptableObjectを使ったGameManagerシングルトンクラスの例があります:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//this attribute is optional but recommended. It will allow the creation of the singleton via the asset menu.
//the singleton asset should be on the Resources folder.
[CreateAssetMenu(fileName = "GameManager", menuName = "Game Manager", order = 0)]
public class GameManager : SingletonScriptableObject<GameManager> {
//any properties as usual
public int Lives;
public int Points;
//optional (but recommended)
//this method will run before the first scene is loaded. Initializing the singleton here
//will allow it to be ready before any other GameObjects on every scene and will
//will prevent the "initialization on first usage".
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void BeforeSceneLoad() { BuildSingletonInstance(); }
//optional,
//will run when the Singleton Scriptable Object is first created on the assets.
//Usually this happens on edit mode, not runtime. (the override keyword is mandatory for this to work)
public override void ScriptableObjectAwake(){
Debug.Log(GetType().Name + " created." );
}
//optional,
//will run when the associated MonoBehavioir awakes. (the override keyword is mandatory for this to work)
public override void MonoBehaviourAwake(){
Debug.Log(GetType().Name + " behaviour awake." );
//A coroutine example:
//Singleton Objects do not have coroutines.
//if you need to use coroutines use the atached MonoBehaviour
Behaviour.StartCoroutine(SimpleCoroutine());
}
//any methods as usual
private IEnumerator SimpleCoroutine(){
while(true){
Debug.Log(GetType().Name + " coroutine step." );
yield return new WaitForSeconds(3);
}
}
//optional,
//Classic runtime Update method (the override keyword is mandatory for this to work).
public override void Update(){
}
//optional,
//Classic runtime FixedUpdate method (the override keyword is mandatory for this to work).
public override void FixedUpdate(){
}
}
/*
* Notes:
* - Remember that you have to create the singleton asset on edit mode before using it. You have to put it on the Resources folder and of course it should be only one.
* - Like other Unity Singleton this one is accessible anywhere in your code using the "Instance" property i.e: GameManager.Instance
*/