2018年6月27日水曜日

[Unity C#] 汎用的かつシンプルなSingletonの実装方法(サンプルあり)

Unityで実装する際、結構な頻度でManagerクラスが必要だったりするので簡単なSingletonクラスを作成しました。
一言にSingletonクラスといっても、Attributeの設定やら初期設定やら色々有るとは思いますが、今回自分が作成したのは以下の3パターンのみです。

今回実装したSingletonクラス一覧

  1. 全てのクラスで継承可能なSingletonクラス。
    • インスタンス生成時は必ず引数なしのコンストラクタが呼び出されます。
    • 引数ありのコンストラクタが使いたい場合は別途Initialize関数でも作ってください。
  2. 全てのMonoBehaviourを継承したいクラスで継承可能なMonoSingletonクラス。
    • Scene上で同じクラスを保持しているオブジェクトがあるかを確認し、ない場合はインスタンス生成します。
    • インスタンス生成が必要な際は、まず空のGameObjectを作成し、AddComponentで自身を追加します。
    • つまりこちらも引数なしのコンストラクタが呼び出されます。
    • 引数ありのコンスタクタを使いたい場合は(ry
  3. Scene遷移時に削除されないようにしたMonoSingletonを継承したクラス、PersistentMonoSingleton.
    • MonoSingletonを継承し、Scene遷移時に削除されないようにしただけ。
    • Sceneに依存するのか、生存し続けるのかわからなくなりそうだったのでクラス分けしただけです。

サンプルコード

今回のサンプルはかなりシンプル。
ただUpdate関数内でひたすらInstance取得を行うだけ。
また、各Singletonクラスでは生成時にログ吐く用に設定してあります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
/// <summary>
/// Singleton manager sample.
/// </summary>
public class SingletonManager : MonoBehaviour
{
    /// <summary>
    /// Update this instance.
    /// </summary>
    private void Update()
    {
        // Get instance of singleton every frame to check.
        SampleSingleton singleton = SampleSingleton.Instance;
        SampleMonoSingleton monoSingleton = SampleMonoSingleton.Instance;
        SamplePersistentMonoSingleton persistentMonoSingleton = SamplePersistentMonoSingleton.Instance;
 
        if(singleton == null)
        {
            Debug.LogError("SampleSingleton is null.");
        }
        if(monoSingleton == null)
        {
            Debug.LogError("SampleMonoSingleton is null.");
        }
        if(persistentMonoSingleton == null)
        {
            Debug.LogError("SamplePersistentMonoSingleton is null.");
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TC;
 
public class SampleSingleton : Singleton<SampleSingleton>
{
    public SampleSingleton()
    {
        Debug.Log("Constructor called for " + GetType().FullName);
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TC;
 
public class SampleMonoSingleton : MonoSingleton<SampleMonoSingleton>
{
    public SampleMonoSingleton()
    {
        Debug.Log("Constructor called for " + GetType().FullName);
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TC;
 
public class SamplePersistentMonoSingleton : PersistentMonoSingleton<SamplePersistentMonoSingleton>
{
    public SamplePersistentMonoSingleton()
    {
        Debug.Log("Constructor called for " + GetType().FullName);
    }
}

実装サンプル






実際のソースコード

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
namespace TC
{
    /// <summary>
    /// Singleton class for all non MonoBehaviour class.
    /// </summary>
    public abstract class Singleton<T> where T : new()
    {
        private static T instance;
        public static T Instance
        {
            get
            {
                if(instance == null)
                {
                    instance = new T();
                }
                return instance;
            }
        }
 
        /// <summary>
        /// Creates new instance if need.
        /// </summary>
        /// <returns><c>true</c>, if instance is successfully loaded or created, <c>false</c> otherwise.</returns>
        public static bool CreateIfNeed()
        {
            if(instance != null)
            {
                return false;
            }
            // Create instance by calling Getter.
            return Instance != null;
        }
    }
 
    /// <summary>
    /// Singleton class for all Monobehaviour class.
    /// </summary>
    public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
    {
        // Callback on when Instance is requested.
        protected static System.Action onRequestInstance = null;
        // Callback on when Instance is newly created.
        protected static System.Action onCreateInstance = null;
        // Callback on when Instance is found from scene and set to Instance.
        protected static System.Action onUpdateInstance = null;
 
        private static T instance;
        public static T Instance
        {
            get
            {
                if(instance == null)
                {
                    T[] validInstances = GameObject.FindObjectsOfType<T>();
                    if(validInstances == null || validInstances.Length <= 0)
                    {
                        // Create new instance.
                        GameObject gameObject = new GameObject();
                        // At this moment, instance settings will not be applied.
                        // Each settings such as name will be updated on Awake.
                        instance = gameObject.AddComponent<T>();
 
                        if(onCreateInstance != null)
                        {
                            onCreateInstance();
                        }
                    }
                    else
                    {
                        // Load instance from scene.
                        instance = validInstances[0];
                        if(validInstances.Length > 1)
                        {
                            Debug.Log("More than 1 instance is created. Destroying duplicate instances.");
                            for(int i = 1; i < validInstances.Length; i++)
                            {
                                Destroy(validInstances[i].gameObject);
                            }
                        }
 
                        if(onUpdateInstance != null)
                        {
                            onUpdateInstance();
                        }
                    }
                }
 
                if(onRequestInstance != null)
                {
                    onRequestInstance();
                }
                return instance;
            }
        }
 
        /// <summary>
        /// Creates new instance if need.
        /// </summary>
        /// <returns><c>true</c>, if instance is successfully loaded or created, <c>false</c> otherwise.</returns>
        public static bool CreateIfNeed()
        {
            if(instance != null)
            {
                return false;
            }
            // Create instance by calling Getter.
            return Instance != null;
        }
 
 
        /// <summary>
        /// Awake this instance.
        /// </summary>
        protected virtual void Awake()
        {
            InitializeInstance();
        }
 
        /// <summary>
        /// Initializes the instance.
        /// </summary>
        protected virtual void InitializeInstance()
        {
            gameObject.name = this.GetType().FullName;
        }
    }
 
    /// <summary>
    /// Persistent singleton.
    /// This class will not be destroyed on load.
    /// </summary>
    public abstract class PersistentMonoSingleton<T> : MonoSingleton<T> where T : MonoBehaviour
    {
        /// <summary>
        /// Awake this instance.
        /// </summary>
        protected override void Awake()
        {
            base.Awake();
            GameObject.DontDestroyOnLoad(Instance);
        }
    }
}

実際のソースコードはこちらにUPしてあります。
https://github.com/table-cloth/tc-common/blob/develop/Assets/Scripts/Singleton.cs

0 件のコメント:

コメントを投稿

技術ブログを始める際に困ったこと。コードの見やすさについて。

以前の記事 で Atom使ってマークダウンで記事書けばすっごい楽! ということを書きましたが、色々試しているうちにどうやらマークダウンだけでは少々問題がありそうだったので、主な問題とその解決方法について書いていきます。 まぁタイ...