Unity에서 싱글 톤 패턴을 올바르게 구현하려면 어떻게해야합니까?


35

Unity에서 단일 객체를 만드는 데 주로 사용되는 여러 비디오와 자습서를 보았습니다. 주로 단일 객체 GameManager를 인스턴스화하고 유효성 검사하는 데 다른 접근 방식을 사용하는 것으로 보입니다.

이것에 대한 올바른 또는 선호되는 접근법이 있습니까?

내가 만난 두 가지 주요 예는 다음과 같습니다.

먼저

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;
    }
}

두 가지의 주요 차이점은 다음과 같습니다.

첫 번째 접근 방식은 게임 객체 스택을 탐색하여 인스턴스가 한 GameManager번만 발생하거나 발생해야하는 인스턴스를 찾으려고 시도 하지만 개발 중에 장면의 크기가 커짐에 따라 매우 최적화되지 않은 것처럼 보입니다.

또한 첫 번째 방법은 응용 프로그램이 장면을 변경할 때 객체가 삭제되지 않도록 표시하여 장면간에 객체가 유지되도록합니다. 두 번째 접근 방식은이를 준수하지 않는 것 같습니다.

두 번째 방법은 게터에서 인스턴스가 null 인 경우와 같이 이상한 것으로 보이며, 새로운 GameObject를 만들고 GameManger 컴포넌트를 할당합니다. 그러나이 GameManager 컴포넌트가 씬의 오브젝트에 이미 연결되어 있지 않으면 실행할 수 없으므로 혼란이 생길 ​​수 있습니다.

권장되는 다른 방법이나 위의 두 가지 방법이 있습니까? 싱글 톤에 관한 많은 비디오와 튜토리얼이 있지만 둘 다 다르기 때문에 둘 사이의 비교를 이끌어 내기가 어렵 기 때문에 어느 것이 가장 좋거나 선호되는 접근법인지에 대한 결론입니다.


GameManager가해야 할 일은 무엇입니까? GameObject 여야합니까?
— bummzack

1
실제로 무엇을해야하는지에 대한 질문이 GameManager아니라, 단지 하나의 객체 인스턴스를 보장하는 방법과 그것을 시행하는 가장 좋은 방법입니다.
— CaptainRedmuff

이 튜토리얼은 singleton unitygeek.com/unity_c_singleton 을 구현하는 방법에 대해 매우 훌륭하게 설명 했습니다. 유용하기를 바랍니다.
— Rahul Lalit

답변:


28

그것은 달려 있지만 일반적으로 세 번째 방법을 사용합니다. 사용하는 방법의 문제점은 객체가 포함되도록 시작된 경우 트리에서 객체를 제거하지 않고 너무 많은 호출을 인스턴스화하여 만들 수 있으므로 실제로 혼란을 줄 수 있다는 것입니다.

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; } }각 장면에서 다른 인스턴스를 원한다면을 원할 수도 있습니다 .
— Dietrich Epp

GameObject를 Destroy ()하는 대신 오류를 발생시켜야합니다.
— Doodlemeat

2
혹시. 당신은 그것을 기록하고 싶을 수도 있지만, 매우 구체적인 것을 시도하지 않는다면 오류를 제기해서는 안된다고 생각합니다. 오류를 발생 시키면 실제로 더 많은 문제가 발생한다고 생각할 수있는 많은 사례가 있습니다.
— PearsonArtPhoto 21

MonoBehaviour는 Unity에 의해 영국식 철자가 철자되어 있습니다 ( "MonoBehavior"는 컴파일되지 않음-항상이 작업을 수행함). 그렇지 않으면, 이것은 괜찮은 코드입니다.
— Michael Eric Oberlin

나는 늦었다는 것을 알고 있지만 정적 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

따라서 관심있는 모든 것이 글로벌 액세스라면 세 가지 모두 필요한 것을 얻을 수 있습니다. 싱글 톤 패턴의 사용은 게으른 인스턴스화, 강제 고유성 또는 글로벌 액세스를 원하는지에 대해 모호 할 수 있으므로 싱글 톤에 도달하는 이유를 신중하게 생각하고 해당 기능을 올바르게 구현하는 구현을 선택하십시오. 하나만 필요할 때 세 가지 모두에 표준을 사용하는 것보다

(예 : 내 게임에 항상 GameManager가있는 경우 게으른 인스턴스화에 신경 쓰지 않을 수도 있습니다. 어쩌면 내가 존재하는 유일성과 고유성을 보장하는 전역 액세스 일 수도 있습니다.이 경우 정적 클래스는 이러한 기능을 매우 간결하게 전달합니다. 장면 로딩 고려 사항 없음)

... 그러나 방법 1을 서면으로 사용하지 마십시오. Method2 / 3의 Awake () 접근 방식을 사용하면 찾기를 더 쉽게 건너 뛸 수 있으며, 여러 장면에서 관리자를 유지하는 경우 관리자가 이미있는 두 장면 사이에로드 할 경우를 대비하여 중복 제거가 필요할 수 있습니다.


1
참고 : 세 가지 방법을 모두 결합하여 네 가지 기능이 모두있는 네 번째 방법을 만들 수 있어야합니다.
— Draco18s

3
이 답변의 추진력은 "모든 것을 수행하는 싱글 톤 구현을 찾아야"하는 것이 아니라 "이 싱글 톤에서 실제로 원하는 기능을 식별하고 해당 기능을 제공하는 구현을 선택해야합니다. 전혀 싱글 톤이 아님 "
— DMGregory

DMGregory가 좋은 지적입니다. "모두 뭉개 버리기"를 제안하려는 의도가 아니라 "단일 클래스에서 함께 작동하지 못하게하는 기능에 대해서는 아무것도 없습니다." 즉, "이 답변의 추력 제안 아닙니다 하나를 선택하십시오. "
— 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의 모노 업데이트 (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!

이것은 꽤 견고합니다. 프로그래밍 배경과 유니티가 아닌 배경에서 온다면 왜 싱글 톤이 Awake 메소드가 아닌 생성자에서 관리되지 않는지 설명 할 수 있습니까? 아마도 개발자가 외부에서 시행하는 싱글 톤이 눈썹을
— 높이는

1
@netpoetica 간단합니다. 유니티는 생성자를 지원하지 않습니다. 그렇기 때문에 클래스 상속에서 생성자가 사용되는 것을 보지 못하고 MonoBehaviourUnity에서 직접 사용하는 클래스는 일반적으로 믿습니다.
— XenoRo

이 사용법을 잘 모르겠습니다. 이것은 단순히 해당 수업의 부모가되어야 하는가? 선언 후 SampleSingletonClass : Singleton, SampleSingletonClass.Instance다시 함께 제공됩니다 SampleSingletonClass does not contain a definition for Instance.
— Ben I.

@BenI. 일반 Singleton<>클래스 를 사용해야합니다 . 이것이 제네릭이 기본 Singleton클래스 의 자식 인 이유 입니다.
— XenoRo

오 당연하지! 분명하다. 왜 그런지 모르겠습니다. = /
— Ben 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

다른 옵션은 클래스를 두 부분으로 나눌 수 있습니다. Singleton 구성 요소의 일반 정적 클래스와 Singleton 인스턴스의 컨트롤러 역할을하는 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

1
이 방법을 이해하는 데 어려움을 겪고 있습니다.이 주제에 대해 조금 더 확장 할 수 있습니까? 고맙습니다.
— hex

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
    }

바보 같은 질문이 될 수도 있지만 당신은 왜 했는가 OnApplicationQuitCallback와 OnEnableCallback같은 abstract대신 빈의 virtual방법? 적어도 내 경우에는 종료 / 활성화 논리가 없으며 빈 재정의가 더러워집니다. 하지만 뭔가 빠졌을 수도 있습니다.
— Jakub Arnold

@JakubArnold 나는 이것을 한동안 보지 않았지만 언뜻보기에는 당신이 옳은 것처럼 보입니다. 가상 방법으로 더 좋을 것입니다
— aBertrand

사실 @JakubArnold 나는 그때부터 나의 생각을 기억하고 생각 : 나는 그들이 사용할 수있는 구성 요소로이를 사용하는 사람들을 인식하게하고 싶었다 OnApplicationQuitCallback과 OnEnableCallback: 종류의 차종이 덜 분명의 가상 메소드로 가지고. 어쩌면 조금 이상한 생각이지만 내가 기억하는 한 그것은 합리적이었습니다.
— aBertrand

2

실제로 Unity에서 Singleton을 사용하는 의사 공식적인 방법이 있습니다. 다음 은 기본적으로 Singleton 클래스를 만들고 스크립트가 해당 클래스에서 상속되도록하는 설명입니다.


독자가 링크에서 수집하기를 원하는 정보에 대한 요약을 답변에 포함시켜 링크 전용 답변을 피하십시오. 이렇게하면 링크를 사용할 수없는 경우에도 답변이 유용합니다.
— 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 스크립트이므로 Coroutines를 사용할 수 있습니다. 이 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.