Unityで、シングルトンパターンを正しく実装するにはどうすればよいですか?


36

主に、Unityでシングルトンオブジェクトを作成するためのいくつかのビデオとチュートリアルを見てきましたGameManager。これらは、シングルトンのインスタンス化と検証にさまざまなアプローチを使用しているようです。

これに対する正しい、またはむしろ好ましいアプローチはありますか?

私が遭遇した2つの主な例は次のとおりです。

最初

public class GameManager
{
    private static GameManager _instance;

    public static GameManager Instance
    {
        get
        {
            if(_instance == null)
            {
                _instance = GameObject.FindObjectOfType<GameManager>();
            }

            return _instance;
        }
    }

    void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }
}

第二

public class GameManager
{
    private static GameManager _instance;

    public static GameManager Instance
    {
        get
        {
            if(_instance == null)
            {
                instance = new GameObject("Game Manager");
                instance.AddComponent<GameManager>();
            }

            return _instance;
        }
    }

    void Awake()
    {
        _instance = this;
    }
}

2つの間に見られる主な違いは次のとおりです。

最初のアプローチでは、ゲームオブジェクトスタックをナビゲートして、GameManager開発中にシーンのサイズが大きくなると非常に最適化されない可能性があると思われるインスタンスを見つけようとします。

また、最初のアプローチは、アプリケーションがシーンを変更したときにオブジェクトが削除されないようにマークします。これにより、オブジェクトがシーン間で保持されます。2番目のアプローチはこれに準拠していないようです。

2番目のアプローチは奇妙です。ゲッターでインスタンスがnullの場合、新しいGameObjectを作成し、それにGameMangerコンポーネントを割り当てます。ただし、このGameManagerコンポーネントがシーン内のオブジェクトに既にアタッチされていないと、これを実行できません。そのため、混乱が生じています。

推奨される他のアプローチ、または上記の2つのハイブリッドがありますか?シングルトンに関するビデオやチュートリアルはたくさんありますが、それらはすべて大きく異なるため、2つの比較を行うのは難しいため、どちらが最良/推奨アプローチであるかについての結論を導き出します。


GameManagerは何をすることになっていますか?GameObjectでなければなりませんか?
bummzack 16

1
それは実際に何をGameManagerすべきかという問題ではなく、オブジェクトのインスタンスが1つだけであることを保証する方法と、それを実施する最良の方法です。
CaptainRedmuff 16

このチュートリアルでは、シングルトンを実装する方法を非常にうまく説明しunitygeek.com/unity_c_singletonを、私はそれが便利です願っています
ラーフルラリット

回答:


30

それは異なりますが、通常は3番目の方法を使用します。使用したメソッドの問題は、オブジェクトが最初から含まれている場合、ツリーからそれらが削除されないことであり、あまりにも多くの呼び出しをインスタンス化することで作成できるため、混乱を招く可能性があります。

public class SomeClass : MonoBehaviour {
    private static SomeClass _instance;

    public static SomeClass Instance { get { return _instance; } }


    private void Awake()
    {
        if (_instance != null && _instance != this)
        {
            Destroy(this.gameObject);
        } else {
            _instance = this;
        }
    }
}

両方の実装の問題は、後で作成されるオブジェクトを破棄しないことです。動作する可能性はありますが、モンキーレンチを作業に投入すると、デバッグが非常に困難になる可能性があります。インスタンスが既に存在する場合は、必ずAwakeをチェックインしてください。存在する場合は、新しいインスタンスを破棄します。


2
あなたもお勧めしますOnDestroy() { if (this == _instance) { _instance = null; } }あなたは、各シーンで別のインスタンスを持つようにしたい場合は、。
ディートリッヒエップ

GameObjectをDestroy()する代わりに、エラーを発生させる必要があります。
Doodlemeat

2
おそらく。あなたはそれをログに記録したいかもしれませんが、非常に具体的なことをしようとしない限り、エラーを発生させるべきではないと思います。エラーを発生させると、実際には修正されるよりも多くの問題が発生すると想像できる多くの例があります。
PearsonArtPhoto

MonoBehaviourは、Unityによる英国の綴りで綴られていることに注意してください(「MonoBehavior」はコンパイルされません。これは常に行います)。それ以外の場合、これは適切なコードです。
マイケルエリックオーバーリン

私は遅れていることは知っていInstanceますが、静的プロパティがワイプされるため、この回答のシングルトンはエディタのリロードに耐えられないことを指摘したかっただけです。 、またはwiki.unity3d.com/index.php/Singleton(これは時代遅れかもしれませんが、私の実験ではうまくいくようです)
Jakub Arnold

24

以下に簡単な要約を示します。

                 Create object   Removes scene   Global    Keep across
               if not in scene?   duplicates?    access?   Scene loads?

Method 1              No              No           Yes        Yes

Method 2              Yes             No           Yes        No

PearsonArtPhoto       No              Yes          Yes        No
Method 3

したがって、関心があるのがグローバルアクセスのみである場合、3つすべてが必要なものを取得します。シングルトンパターンの使用は、遅延インスタンス化、強制一意性、またはグローバルアクセスのいずれを使用するかについて少し曖昧になる可能性があるため、シングルトンに到達する理由を慎重に検討し、それらの機能を適切に実行する実装を選択してください1つだけが必要な場合に3つすべてに標準を使用するよりも。

(たとえば、ゲームに常にGameManagerがある場合、遅延インスタンス化は気にしないかもしれません-存在と一意性が保証されたグローバルアクセスのみである可能性があります-この場合、静的クラスはまさにそれらの機能を非常に簡潔に取得します、シーンの読み込みに関する考慮事項なし)

...しかし、書かれている方法1は絶対に使用しないでください。FindはMethod2 / 3のAwake()アプローチでより簡単にスキップできます。また、シーン間でマネージャーを保持している場合、既にマネージャーがいる2つのシーン間でロードする場合に備えて、重複キルが必要になる可能性が高くなります。


1
注:3つの方法すべてを組み合わせて、4つの機能すべてを備えた4番目の方法を作成することができるはずです。
Draco18s 16

3
この答えの主眼は、「すべてを実行するシングルトン実装を探す必要がある」ではなく、「このシングルトンから実際に必要な機能を特定し、その実装がすべての「ではないシングルトン
DMGregory

それがDMGregoryの良い点です。「すべて一緒に壊す」ことを提案するつもりは本当にありませんでしたが、「これらの機能については、単一のクラスで一緒に動作することを妨げるものは何もありません」。すなわち、「この答えの目的は、1つ選ぶことを提案することではありません
Draco18s

17

Singleton私が知っているUnityの一般的なパターンの最適な実装は、(もちろん)私自身です。

それはすべてを行うことができます、そしてそれはきちんと効率的にそうします

Create object        Removes scene        Global access?               Keep across
if not in scene?     duplicates?                                       Scene loads?

     Yes                  Yes                  Yes                     Yes (optional)

その他の利点:

  • それはだスレッドセーフ
  • アプリケーションの終了時にシングルトンインスタンスを取得(作成)することに関連するバグを回避しますOnApplicationQuit()。(そして、それぞれのシングルトンタイプが独自のものを持つ代わりに、単一のグローバルフラグでそうします)
  • Unity 2017のMonoアップデート(C#6とほぼ同等)を使用します。(しかし、それは古代のバージョンに簡単に適合させることができます)
  • 無料のキャンディーが付いてきます!

そして、共有は思いやりがあるので、ここにあります:

public abstract class Singleton<T> : Singleton where T : MonoBehaviour
{
    #region  Fields
    [CanBeNull]
    private static T _instance;

    [NotNull]
    // ReSharper disable once StaticMemberInGenericType
    private static readonly object Lock = new object();

    [SerializeField]
    private bool _persistent = true;
    #endregion

    #region  Properties
    [NotNull]
    public static T Instance
    {
        get
        {
            if (Quitting)
            {
                Debug.LogWarning($"[{nameof(Singleton)}<{typeof(T)}>] Instance will not be returned because the application is quitting.");
                // ReSharper disable once AssignNullToNotNullAttribute
                return null;
            }
            lock (Lock)
            {
                if (_instance != null)
                    return _instance;
                var instances = FindObjectsOfType<T>();
                var count = instances.Length;
                if (count > 0)
                {
                    if (count == 1)
                        return _instance = instances[0];
                    Debug.LogWarning($"[{nameof(Singleton)}<{typeof(T)}>] There should never be more than one {nameof(Singleton)} of type {typeof(T)} in the scene, but {count} were found. The first instance found will be used, and all others will be destroyed.");
                    for (var i = 1; i < instances.Length; i++)
                        Destroy(instances[i]);
                    return _instance = instances[0];
                }

                Debug.Log($"[{nameof(Singleton)}<{typeof(T)}>] An instance is needed in the scene and no existing instances were found, so a new instance will be created.");
                return _instance = new GameObject($"({nameof(Singleton)}){typeof(T)}")
                           .AddComponent<T>();
            }
        }
    }
    #endregion

    #region  Methods
    private void Awake()
    {
        if (_persistent)
            DontDestroyOnLoad(gameObject);
        OnAwake();
    }

    protected virtual void OnAwake() { }
    #endregion
}

public abstract class Singleton : MonoBehaviour
{
    #region  Properties
    public static bool Quitting { get; private set; }
    #endregion

    #region  Methods
    private void OnApplicationQuit()
    {
        Quitting = true;
    }
    #endregion
}
//Free candy!

これはかなり堅実です。プログラミングのバックグラウンドとUnity以外のバックグラウンドから、シングルトンがAwakeメソッドではなくコンストラクターで管理されない理由を説明できますか?おそらく...シングルトンは、コンストラクタの外に強制眉調達者で見て、そこにすべての開発者にそれを想像することができます
netpoetica

1
@netpoeticaシンプル。Unityはコンストラクターをサポートしていません。そのためMonoBehaviour、継承するクラスでコンストラクターが使用されていないことがわかります。Unityで直接使用されるクラスは一般的に信じられます。
XenoRo

私はこれをどのように利用するのかわからない。これは単に問題のクラスの親になることを意味していますか?宣言後SampleSingletonClass : SingletonSampleSingletonClass.Instanceに戻りSampleSingletonClass does not contain a definition for Instanceます。
ベンI.

@BenI。ジェネリックSingleton<>クラスを使用する必要があります。そのため、ジェネリックは基本Singletonクラスの子です。
XenoRo

もちろんです!それは非常に明白です。なぜ私はそれを見なかったのか分かりません。= /
ベンI.

6

DontDestroyOnLoadシングルトンをシーン間で保持したい場合に呼び出すと便利な場合があることを付け加えます。

public class Singleton : MonoBehaviour
{ 
    private static Singleton _instance;

    public static Singleton Instance 
    { 
        get { return _instance; } 
    } 

    private void Awake() 
    { 
        if (_instance != null && _instance != this) 
        { 
            Destroy(this.gameObject);
            return;
        }

        _instance = this;
        DontDestroyOnLoad(this.gameObject);
    } 
}

それは非常に便利です。この正確な質問をするために、@ PearsonArtPhotoの応答にコメントを投稿しようとしていました。]
CaptainRedmuff

5

別のオプションは、クラスを2つの部分に分割することです。シングルトンコンポーネントの通常の静的クラスと、シングルトンインスタンスのコントローラとして機能するMonoBehaviourです。このようにして、シングルトンの構築を完全に制御でき、シーン全体で持続します。これにより、特定のコンポーネントを見つけるためにシーンを掘り下げる必要なく、シングルトンのデータを必要とする可能性のあるオブジェクトにコントローラーを追加することもできます。

public class Singleton{
    private Singleton(){
        //Class initialization goes here.
    }

    public void someSingletonMethod(){
        //Some method that acts on the Singleton.
    }

    private static Singleton _instance;
    public static Singleton Instance 
    { 
        get { 
            if (_instance == null)
                _instance = new Singleton();
            return _instance; 
        }
    } 
}

public class SingletonController: MonoBehaviour{
   //Create a local reference so that the editor can read it.
   public Singleton instance;
   void Awake(){
       instance = Singleton.Instance;
   }
   //You can reference the singleton instance directly, but it might be better to just reflect its methods in the controller.
   public void someMethod(){
       instance.someSingletonMethod();
   }
} 

これはすごく素敵!
CaptainRedmuff 16

1
私はこの方法を理解するのに苦労していますが、この主題についてもう少し拡大していただけますか。ありがとうございました。
16進数

3

以下は、シングルトン抽象クラスの私の実装です。4つの基準に対してどのように積み重ねられるかを示します

             Create object   Removes scene   Global    Keep across
           if not in scene?   duplicates?    access?   Scene loads?

             No (but why         Yes           Yes        Yes
             should it?)

ここにある他の方法のいくつかと比較して、いくつかの利点があります。

  • FindObjectsOfTypeどちらがパフォーマンスキラーであるかは使用しません
  • ゲーム中に新しい空のゲームオブジェクトを作成する必要がないという点で柔軟性があります。エディターで(またはゲーム中に)選択したゲームオブジェクトに追加するだけです。
  • スレッドセーフです

    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    
    public abstract class Singleton<T> : MonoBehaviour where T : Singleton<T>
    {
        #region  Variables
        protected static bool Quitting { get; private set; }
    
        private static readonly object Lock = new object();
        private static Dictionary<System.Type, Singleton<T>> _instances;
    
        public static T Instance
        {
            get
            {
                if (Quitting)
                {
                    return null;
                }
                lock (Lock)
                {
                    if (_instances == null)
                        _instances = new Dictionary<System.Type, Singleton<T>>();
    
                    if (_instances.ContainsKey(typeof(T)))
                        return (T)_instances[typeof(T)];
                    else
                        return null;
                }
            }
        }
    
        #endregion
    
        #region  Methods
        private void OnEnable()
        {
            if (!Quitting)
            {
                bool iAmSingleton = false;
    
                lock (Lock)
                {
                    if (_instances == null)
                        _instances = new Dictionary<System.Type, Singleton<T>>();
    
                    if (_instances.ContainsKey(this.GetType()))
                        Destroy(this.gameObject);
                    else
                    {
                        iAmSingleton = true;
    
                        _instances.Add(this.GetType(), this);
    
                        DontDestroyOnLoad(gameObject);
                    }
                }
    
                if(iAmSingleton)
                    OnEnableCallback();
            }
        }
    
        private void OnApplicationQuit()
        {
            Quitting = true;
    
            OnApplicationQuitCallback();
        }
    
        protected abstract void OnApplicationQuitCallback();
    
        protected abstract void OnEnableCallback();
        #endregion
    }

愚かな質問かもしれませんが、なぜ空のメソッドの代わりにOnApplicationQuitCallbackand OnEnableCallbackas を作成したのですか?少なくとも私の場合は、終了/有効化ロジックがなく、空のオーバーライドが汚れているように感じます。しかし、私は何かを見逃しているかもしれません。abstractvirtual
ヤクブアーノルド

私はしばらくの間、この見ていないが、一見それはあなたがしている権利、仮想メソッドとして良いだろうように見えます@JakubArnold
aBertrand

実は@JakubArnold私は当時から私の考えを覚えていると思う:私は、彼らが使用できることをコンポーネントとしてこれを使用する人を意識作りたかったOnApplicationQuitCallbackOnEnableCallback:親切になり、それはあまり目立たないの仮想メソッドとしてそれを持ちます。たぶん少し奇妙な考えかもしれませんが、私が覚えている限り、それは私の合理的なものでした。
aBertrand

2

実際、Unityでシングルトンを使用する疑似公式の方法があります。ここでは説明があり、基本的にシングルトンクラスを作成し、スクリプトがそのクラスから継承します。


リンクのみの回答は避けてください。少なくとも、読者がリンクから収集することを希望する情報の要約を回答に含めてください。そうすれば、リンクが利用できなくなっても、答えは有用なままです。
DMGregory

2

将来の世代のためにも私の実装を捨てます。

void Awake()
    {
        if (instance == null)
            instance = this;
        else if (instance != this)
            Destroy(gameObject.GetComponent(instance.GetType()));
        DontDestroyOnLoad(gameObject);
    }

私にとって、この行Destroy(gameObject.GetComponent(instance.GetType()));は非常に重要です。シーン内の別のgameObjectにシングルトンスクリプトを残し、ゲームオブジェクト全体が削除されていたからです。これは、コンポーネントが既に存在する場合にのみ破棄します。


1

シングルトンオブジェクトを簡単に作成できるシングルトンクラスを作成しました。これはMonoBehaviourスクリプトであるため、コルーチンを使用できます。このUnity Wikiの記事に基づいており、後でPrefabから作成するオプションを追加します。

したがって、シングルトンコードを記述する必要はありません。ダウンロードするだけこのSingleton.cs Base Classをしてプロジェクトに追加し、それを拡張するシングルトンを作成するだけです。

public class MySingleton : Singleton<MySingleton> {
  protected MySingleton () {} // Protect the constructor!

  public string globalVar;

  void Awake () {
      Debug.Log("Awoke Singleton Instance: " + gameObject.GetInstanceID());
  }
}

これで、MySingletonクラスはシングルトンになり、インスタンスで呼び出すことができます。

MySingleton.Instance.globalVar = "A";
Debug.Log ("globalVar: " + MySingleton.Instance.globalVar);

完全なチュートリアルは次のとおりです。http//www.bivis.com.br/2016/05/04/unity-reusable-singleton-tutorial/

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.