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
    }

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

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

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