Birlik'te, singleton desenini nasıl doğru uygularım?


36

Birlik'te tekil nesneler oluşturmak için, özellikle GameManagerbir tekil tonu somutlaştırmak ve onaylamak için farklı yaklaşımlar kullandığı anlaşılan birkaç video ve eğitim gördüm .

Buna doğru ya da tercih edilen bir yaklaşım var mı?

Karşılaştığım iki ana örnek:

İlk

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

İkinci

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

İkisi arasında görebildiğim ana fark şudur:

İlk yaklaşım, oyun GameManagergeliştirme sırasında sahnelerin boyutunda büyürken, bunun yalnızca gerçekleşmesine rağmen (ya da sadece bir kez olması gerektiği gibi) bir zaman bulmak için oyun nesnesi yığınında gezinmeye çalışacaktır .

Ayrıca, birinci yaklaşım, uygulama sahneyi değiştirdiğinde nesnenin sahneler arasında kalmasını sağlayan nesnenin silinmemesini işaretler. İkinci yaklaşım buna uygun görünmüyor.

İkinci yaklaşım, örneğin alıcıda boş olduğu durumda tuhaf görünüyor, yeni bir GameObject oluşturacak ve buna bir GameManger bileşeni atayacaktır. Ancak, bu ilk önce bu GameManager bileşenini sahnede bir nesneye zaten ekli olmadan çalıştırılamaz, bu yüzden bu bana biraz karışıklık yaratıyor.

Tavsiye edilecek başka yaklaşımlar ya da yukarıdaki ikisinin bir melezi var mı? Singleton'larla ilgili birçok video ve ders var, ancak ikisi arasında bir karşılaştırma yapmak çok zor, o yüzden ikisi arasında bir karşılaştırma yapmak zor, hangisinin en iyi / tercih edilen yaklaşım olduğu sonucuna varılıyor.


GameManager'ın ne yapması gerekiyor? GameObject olmak zorunda mı?
Bummzack

1
Bu gerçekten ne yapılması GameManagergerektiğiyle ilgili bir soru değil, nesnenin sadece bir örneğinin olmasını ve bunu uygulamanın en iyi yolunun nasıl sağlanacağını garanti ediyor.
CaptainRedmuff

Bu dersler çok güzel bir şekilde açıkladı, singleton unitygeek.com/unity_c_singleton nasıl uygulanacağını, nasıl faydalı olacağını umuyorum
Rahul Lalit

Yanıtlar:


30

Bu değişir, ancak genellikle üçüncü bir yöntem kullanırım. Kullandığınız yöntemlerle ilgili sorun, nesnenin başlaması için dahil edilmesi durumunda, onları ağaçtan kaldırmayacağı ve hala kafa karıştırıcı olabilecek çok fazla çağrının başlatılmasıyla oluşturulabilecek olmalarıdır.

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

Her iki uygulamanızın da sorunu, daha sonra yaratılan bir nesneyi yok etmemeleridir. İşe yarayabilirdi, ancak bir satır boyunca hata ayıklama çok zor neden olabilir çalışmalarına bir İngiliz anahtarı atmak olabilir. Henüz bir örnek olup olmadığını ve yeni örneği yok etmek için Awake'i kontrol ettiğinizden emin olun.


2
OnDestroy() { if (this == _instance) { _instance = null; } }Her sahnede farklı bir örneğe sahip olmak istiyorsanız da isteyebilirsiniz .
Dietrich Epp

GameObject içinde Destroy () yapmak yerine bir hataya neden olmalısınız.
Doodlemeat

2
Muhtemelen. Oturum açmak isteyebilirsiniz, ancak çok özel bir şey yapmaya çalışmadığınız sürece bir hataya yol açmamanız gerektiğini düşünüyorum. Bir hatanın yükselmesinin gerçekte daha sonra soruna yol açacağını tahmin edebileceğim birçok örnek var.
PearsonArtPhoto

MonoBehaviour'un Unity tarafından İngiliz yazımıyla yazıldığını not etmek isteyebilirsiniz ("MonoBehavior" derlenmeyecektir - bunu her zaman yaparım); Aksi halde, bu bazı iyi kodlar.
Michael Eric Oberlin

Geç geldiğimi biliyorum, ancak sadece bu cevabın singleton'ının bir editör yüklemesinde hayatta kalmayacağını, çünkü statik Instanceözellik silindiğini belirtmek istedim. Aşağıdaki cevaplardan birinde bulunamayan bir örnek , veya wiki.unity3d.com/index.php/Singleton (ki bu modası geçmiş olabilir, ancak deneyimlerime göre çalışıyor gibi görünüyor)
Jakub Arnold

24

İşte hızlı bir özeti:

                 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

Tek umursadığın şey küresel erişimse, üçü de sana ihtiyacın olanı verir. Singleton modelinin kullanımı, tembel bir örnekleme mi, özel kullanım mı, genel erişim mi istediğimiz konusunda biraz belirsiz olabilir, bu yüzden neden singleton'a ulaştığınızı ve bu özellikleri doğru yapan bir uygulamayı seçtiğinizi dikkatlice düşünün. İhtiyacınız olduğunda, her üçü için bir standart kullanmaktan daha fazlası.

(örn. eğer oyunumda her zaman bir GameManager olacaksa, belki tembel bir örnekleme umursamıyorum - belki de sadece garantili varoluş ve benzersizliği olan küresel bir erişimdir - bu durumda statik bir sınıf bana tam olarak bu özellikleri tam olarak verir, sahne yükleme hakkında önemli noktalar olmadan)

... ancak kesinlikle Yöntem 1'i yazılı olarak kullanmayın. Metod 2 / 3'ün Awake () yaklaşımı ile Bul daha kolay atlanabilir ve eğer yöneticiyi sahneler arasında tutarsak, iki sahne arasına zaten bir yönetici koyarsak, muhtemelen çift katil öldürmek istiyoruz.


1
Not: Dört özelliğin de bulunduğu 4. bir yöntem oluşturmak için bu üç yöntemi de birleştirmek mümkün olmalıdır.
Draco18

3
Bu cevabın itici gücü "her şeyi yapan bir Singleton uygulamasına bakmalısınız" değil, bu singleton'dan gerçekten hangi özellikleri istediğinizi belirlemeli ve bu özellikleri sunan bir uygulamayı seçmelisiniz - bu uygulama olsa bile hiç bir singleton değil "
DMGregory

Bu iyi bir nokta DMGregory. "Hepsini bir araya topla" önermek niyetim değildi, ama "tek bir sınıfta birlikte çalışmalarını engelleyen bu özellikler hakkında hiçbir şey". yani, "Bu cevabın
iticiliği

17

SingletonBildiğim Birlik için genel bir kalıbın en iyi uygulaması elbette ki benim.

Her şeyi yapabilir ve çok düzgün ve verimli bir şekilde yapar :

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

     Yes                  Yes                  Yes                     Yes (optional)

Diğer avantajlar:

  • Bu var evreli .
  • Singleton'ların sonradan yaratılamamasını sağlayarak, uygulamadan çıkarken singleton örnekleri edinme (oluşturma) ile ilgili hataları önler OnApplicationQuit(). (Ve bunu kendine ait olan her bir tekil tip yerine, tek bir küresel bayrakla yapar)
  • Unity 2017'nin Mono Güncellemesini kullanır (kabaca C # 6'ya eşdeğerdir). (Ancak eski sürüm için kolayca uyarlanabilir)
  • Biraz bedava şekerle geliyor !

Ve paylaşım önemsemediği için işte burada:

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!

Bu oldukça sağlam. Bir programlama arkaplanından ve Unity olmayan bir arkaplandan geliyorsanız, singleton'ın neden Awake yönteminden ziyade yapıcıda yönetilmediğini açıklayabilir misiniz? Muhtemelen dışarıdaki herhangi bir geliştiriciye, bir yapıcı dışında zorlanan bir Singleton'ı görmenin bir kaş
toplayıcısı

1
@ netpoetica Basit. Birlik, yapıcıları desteklemez. Bu nedenle, yapıcıların kalıtımsal bir sınıfta kullanıldığını görmüyorsunuz MonoBehaviourve ben Unity tarafından doğrudan kullanılan herhangi bir sınıfın olduğuna inanıyorum.
XenoRo

Bunu nasıl kullanacağımı takip ettiğimden emin değilim. Bu sadece söz konusu sınıfın ebeveyni anlamına mı geliyor? Bildirmek sonra SampleSingletonClass : Singleton, SampleSingletonClass.Instancegeri birlikte gelir SampleSingletonClass does not contain a definition for Instance.
Ben I.

@BenI. Genel Singleton<>sınıfı kullanmanız gerekir . Bu, jenerik, temel Singletonsınıfın bir çocuğu olmasıdır .
XenoRo

Oh elbette! Bu oldukça açık. Bunu neden göremediğimden emin değilim. = /
Ben I.

6

DontDestroyOnLoadSingleton'unuzun sahnelerde kalmasını istiyorsanız, aramanın faydalı olabileceğini eklemek isterim .

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

Bu çok kullanışlı. Bu soruyu sormak için @ PearsonArtPhoto'un yanıtıyla ilgili bir yorum yapmak üzereydim:]
CaptainRedmuff

5

Başka bir seçenek de sınıfı iki bölüme ayırmak olabilir: Singleton bileşeni için normal bir statik sınıf ve singleton örneği için denetleyici görevi gören bir MonoBehaviour. Bu şekilde, tektonun yapımı üzerinde tam kontrol sahibi olacaksınız ve sahneler arasında sürecek. Bu ayrıca, belirli bir bileşeni bulmak için sahneyi kazmak yerine, tek tek verilere ihtiyaç duyabilecek herhangi bir nesneye denetleyiciler eklemenizi sağlar.

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

Bu çok güzel!
CaptainRedmuff

1
Bu yöntemi anlamakta sorun yaşıyorum, bu konuda biraz daha genişleyebilir misiniz? Teşekkür ederim.
hex

3

İşte benim singleton özet sınıfını aşağıda uyguladım. İşte 4 kritere göre nasıl bir araya geldiğini

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

             No (but why         Yes           Yes        Yes
             should it?)

Buradaki diğer yöntemlerden bazılarına kıyasla birkaç başka avantajı var:

  • FindObjectsOfTypePerformans katili olanı kullanmaz
  • Esnek, oyun sırasında yeni bir boş projektör oluşturması gerekmediğinden esnek. Sadece editöre (ya da oyun sırasında) seçtiğiniz bir hedefine ekleyin.
  • İplik güvenli

    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
    }

Saçma bir soru olabilir, ama neden yaptınız OnApplicationQuitCallbackve OnEnableCallbacksıra abstractyerine sadece boş bir virtualyöntemleriyle? En azından benim durumumda herhangi bir çıkma / etkinleştirme mantığım yok ve boş bir geçersiz kılma olması kirli hissettiriyor. Ama bir şeyleri özlüyorum.
Jakub Arnold

@JakubArnold Buna bir süredir bakmadım ama ilk bakışta haklı görünüyorsun, sanal yöntemlerden daha iyi olurdu
aBertrand

@ JakakArnold Aslında sanırım arkamdaki düşüncelerimi hatırlıyorum: Bunu, kullanabilecekleri bir bileşen olarak kullananları OnApplicationQuitCallbackve OnEnableCallback: sanal yöntemler olarak kullanmak , onu daha az belirgin kılıyor. Belki biraz garip bir düşünce ama hatırladığım kadarıyla bu benim mantığımdı.
aBertrand

2

Aslında, Birlik'te Singleton'ı kullanmanın sahte bir resmi yolu var. İşte açıklama, temel olarak bir Singleton sınıfı oluşturun ve komut dosyalarınızı o sınıftan miras alın.


Lütfen cevabınıza cevap vermekten kaçının, cevabınıza en azından okuyucunun linkten memnun kalacağını düşündüğünüz bilgilerin bir özetini ekleyin. Bu şekilde bağlantı hiç kullanılamaz hale gelirse, cevap faydalı kalır.
DMGregory

2

Uygulamamı da gelecek nesiller için harcayacağım.

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

Benim için bu çizgi Destroy(gameObject.GetComponent(instance.GetType()));çok önemli çünkü bir keresinde bir sahnede başka bir gameObject üzerine singleton senaryosu bırakmıştım ve tüm oyun nesnesi silinmişti. Bu, yalnızca zaten mevcutsa, bileşeni yok edecektir.


1

Singleton nesneleri oluşturmayı kolaylaştıran singleton sınıfı yazdım. Bu bir MonoBehaviour betiğidir, bu yüzden Coroutines'i kullanabilirsiniz. Bu Unity Wiki makalesine dayanıyor ve daha sonra Prefabrik'ten yaratma seçeneği ekleyeceğim.

Yani Singleton kodlarını yazmanıza gerek yoktur. Sadece bu Singleton.cs Temel Sınıfını indirin , projenize ekleyin ve genişleten singleton'unuzu oluşturun:

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

  public string globalVar;

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

Şimdi MySingleton sınıfınız bir singleton, ve bunu Instance ile çağırabilirsiniz:

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

İşte tam bir öğretici: http://www.bivis.com.br/2016/05/04/unity-reusable-singleton-tutorial/

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.