Di Unity, bagaimana cara menerapkan pola singleton dengan benar?

36

Saya telah melihat beberapa video dan tutorial untuk membuat objek singleton di Unity, terutama untuk a GameManager, yang tampaknya menggunakan pendekatan berbeda untuk membuat instance dan memvalidasi singleton.

Apakah ada pendekatan yang benar, atau lebih tepatnya, lebih disukai untuk ini?

Dua contoh utama yang saya temui adalah:

Pertama

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

Kedua

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

Perbedaan utama yang bisa saya lihat antara keduanya adalah:

Pendekatan pertama akan mencoba untuk menavigasi tumpukan objek game untuk menemukan contoh GameManageryang walaupun ini hanya terjadi (atau seharusnya hanya terjadi) sekali tampaknya bisa sangat tidak dioptimalkan sebagai adegan tumbuh dalam ukuran selama pengembangan.

Juga, pendekatan pertama menandai objek yang tidak akan dihapus ketika aplikasi mengubah adegan, yang memastikan bahwa objek tetap ada di antara adegan. Pendekatan kedua tampaknya tidak mematuhi ini.

Pendekatan kedua tampak aneh karena dalam kasus di mana instance adalah null dalam pengambil, itu akan membuat GameObject baru dan menetapkan komponen GameManger untuk itu. Namun, ini tidak dapat berjalan tanpa terlebih dahulu komponen GameManager ini telah dilampirkan ke sebuah objek dalam adegan, jadi ini menyebabkan saya kebingungan.

Apakah ada pendekatan lain yang akan direkomendasikan, atau gabungan dari keduanya di atas? Ada banyak video dan tutorial mengenai lajang, tetapi semuanya sangat berbeda sehingga sulit untuk menarik perbandingan antara keduanya dan dengan demikian, kesimpulan yang mana yang merupakan pendekatan terbaik / disukai.

CaptainRedmuff
sumber
Apa yang seharusnya dilakukan GameManager? Apakah itu harus menjadi GameObject?
bummzack
1
Ini sebenarnya bukan pertanyaan tentang apa yang GameManagerharus dilakukan, melainkan bagaimana memastikan hanya ada satu contoh objek dan cara terbaik untuk menegakkannya.
CaptainRedmuff
tutorial ini menjelaskan dengan sangat baik, bagaimana menerapkan singleton unitygeek.com/unity_c_singleton , saya harap ini berguna
Rahul Lalit

Jawaban:

30

Tergantung, tetapi biasanya saya menggunakan metode ketiga. Masalah dengan metode yang Anda gunakan adalah bahwa jika objek dimasukkan untuk memulai, itu tidak akan menghapusnya dari pohon, dan mereka masih dapat dibuat dengan membuat terlalu banyak panggilan, yang dapat membuat hal-hal benar-benar membingungkan.

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

Masalah dengan kedua implementasi Anda adalah bahwa mereka tidak menghancurkan objek yang dibuat nanti. Itu bisa bekerja, tetapi orang bisa melemparkan kunci inggris ke dalam karya yang bisa mengakibatkan kesalahan debug yang sangat sulit di telepon. Pastikan untuk memeriksa Sedarlah jika sudah ada instance, dan jika demikian, hancurkan instance baru.

PearsonArtPhoto
sumber
2
Anda mungkin juga ingin OnDestroy() { if (this == _instance) { _instance = null; } }, jika Anda ingin memiliki contoh yang berbeda di setiap adegan.
Dietrich Epp
Alih-alih Hancurkan () di GameObject Anda harus meningkatkan kesalahan.
Doodlemeat
2
Mungkin. Anda mungkin ingin mencatatnya, tetapi saya rasa Anda tidak perlu membuat kesalahan, kecuali Anda mencoba melakukan sesuatu yang sangat spesifik. Ada banyak contoh yang dapat saya bayangkan bahwa meningkatkan kesalahan sebenarnya akan menyebabkan lebih banyak masalah daripada memperbaiki.
PearsonArtPhoto
Anda mungkin ingin mencatat bahwa MonoBehaviour dieja dengan ejaan Inggris oleh Unity ("MonoBehavior" tidak akan dikompilasi - saya melakukan ini sepanjang waktu); jika tidak, ini adalah beberapa kode yang layak.
Michael Eric Oberlin
Saya tahu saya datang terlambat, tetapi hanya ingin menunjukkan, bahwa jawaban tunggal ini tidak dapat bertahan dengan editor yang dimuat ulang, karena Instanceproperti statis akan terhapus. Contoh yang tidak dapat ditemukan di salah satu jawaban di bawah ini , atau wiki.unity3d.com/index.php/Singleton (yang mungkin kedaluwarsa, tetapi tampaknya bekerja dari percobaan saya dengan itu)
Jakub Arnold
24

Berikut ringkasan singkatnya:

                 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

Jadi, jika yang Anda pedulikan adalah akses global, ketiganya mendapatkan apa yang Anda butuhkan. Penggunaan pola Singleton bisa sedikit ambigu tentang apakah kita ingin instantiasi malas, keunikan yang ditegakkan, atau akses global jadi pastikan untuk berpikir dengan hati-hati tentang mengapa Anda meraih singleton, dan memilih implementasi yang membuat fitur-fitur itu benar, alih-alih daripada menggunakan standar untuk ketiganya saat Anda hanya membutuhkannya.

(mis. jika game saya akan selalu memiliki GameManager, mungkin saya tidak peduli tentang instantiation yang malas - mungkin itu hanya akses global dengan jaminan keberadaan & keunikan yang saya pedulikan - dalam hal ini kelas statis membuat saya persis fitur-fitur itu dengan sangat ringkas, tanpa pertimbangan pemuatan adegan)

... tapi jelas tidak menggunakan Metode 1 seperti yang tertulis. Find dapat dilewati dengan lebih mudah dengan pendekatan Awake () Method2 / 3, dan jika kita menjaga manajer melintasi adegan maka kita sangat mungkin ingin membunuh duplikat, jika kita pernah memuat antara dua adegan dengan manajer yang sudah ada di dalamnya.

DMGregory
sumber
1
Catatan: Anda harus menggabungkan ketiga metode untuk membuat metode ke-4 yang memiliki keempat fitur.
Draco18s
3
Inti dari jawaban ini bukanlah "Anda harus mencari implementasi Singleton yang melakukan semuanya" tetapi "Anda harus mengidentifikasi fitur mana yang sebenarnya Anda inginkan dari singleton ini, dan memilih implementasi yang memberikan fitur-fitur tersebut - bahkan jika implementasi tersebut adalah sama sekali bukan singleton "
DMGregory
Itu poin bagus DMGregory. Sebenarnya bukan maksud saya untuk menyarankan "hancurkan semuanya" tetapi "tidak ada tentang fitur-fitur ini yang mencegah mereka bekerja bersama dalam satu kelas." yaitu "Dorongan dari jawaban ini adalah TIDAK untuk menyarankan memilih satu. "
Draco18s
17

Implementasi terbaik dari Singletonpola umum untuk Unity yang saya tahu adalah (tentu saja) milik saya.

Ia dapat melakukan segalanya , dan melakukannya dengan rapi dan efisien :

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

     Yes                  Yes                  Yes                     Yes (optional)

Keuntungan lain:

  • Ini aman dari benang .
  • Itu menghindari bug yang terkait dengan memperoleh (menciptakan) instance tunggal ketika aplikasi berhenti dengan memastikan bahwa singleton tidak dapat dibuat setelahnya OnApplicationQuit(). (Dan ia melakukannya dengan satu bendera global, bukan masing-masing jenis tunggal yang memiliki bendera sendiri)
  • Ini menggunakan Pembaruan Mono Unity 2017 (kira-kira setara dengan C # 6). (Tetapi dapat dengan mudah diadaptasi untuk versi kuno)
  • Muncul dengan beberapa permen gratis!

Dan karena berbagi itu peduli , ini dia:

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!
XenoRo
sumber
Ini cukup solid. Berasal dari latar belakang pemrograman dan latar belakang non-Persatuan, dapatkah Anda menjelaskan mengapa singleton tidak dikelola dalam konstruktor alih-alih dalam metode Sedarlah? Anda mungkin dapat membayangkan bahwa bagi pengembang mana pun di luar sana, melihat Singleton yang dipaksakan di luar konstruktor adalah
pengangkat
1
@netpoetica Sederhana. Unity tidak mendukung konstruktor. Itu sebabnya Anda tidak melihat konstruktor yang digunakan di setiap kelas mewarisi MonoBehaviour, dan saya percaya setiap kelas yang digunakan oleh Unity secara langsung.
XenoRo
Saya tidak yakin saya mengikuti cara memanfaatkan ini. Apakah ini dimaksudkan untuk sekadar menjadi orang tua dari kelas yang bersangkutan? Setelah menyatakan SampleSingletonClass : Singleton, SampleSingletonClass.Instancekembali dengan SampleSingletonClass does not contain a definition for Instance.
Ben I.
@ Ben. Anda harus menggunakan Singleton<>kelas generik . Itu sebabnya generik adalah anak dari Singletonkelas dasar .
XenoRo
Oh tentu! Cukup jelas. Saya tidak yakin mengapa saya tidak melihat itu. = /
Ben I.
6

Saya hanya ingin menambahkan bahwa mungkin berguna untuk menelepon DontDestroyOnLoadjika Anda ingin singleton Anda bertahan di seluruh adegan.

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);
    } 
}
zcabjro
sumber
Itu sangat berguna. Saya baru saja akan mengirim komentar pada tanggapan @ PearsonArtPhoto untuk menanyakan pertanyaan yang tepat ini:]
CaptainRedmuff
5

Pilihan lain mungkin untuk membagi kelas menjadi dua bagian: kelas statis reguler untuk komponen Singleton, dan MonoBehaviour yang bertindak sebagai pengontrol untuk instance singleton. Dengan cara ini Anda memiliki kontrol penuh atas konstruksi singleton, dan itu akan bertahan di seluruh adegan. Ini juga memungkinkan Anda menambahkan pengontrol ke objek apa pun yang mungkin memerlukan data tunggal, alih-alih harus menggali melalui adegan untuk menemukan komponen tertentu.

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();
   }
} 
Mr.Underhill89
sumber
Ini sangat bagus!
CaptainRedmuff
1
Saya mengalami kesulitan memahami metode ini, dapatkah Anda mengembangkan sedikit lebih banyak tentang hal ini? Terima kasih.
hex
3

Berikut ini adalah implementasi saya dari kelas abstrak tunggal di bawah ini. Berikut adalah bagaimana hal itu sesuai dengan 4 kriteria

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

             No (but why         Yes           Yes        Yes
             should it?)

Ini memiliki beberapa keunggulan lain dibandingkan dengan beberapa metode lain di sini:

  • Itu tidak menggunakan FindObjectsOfTypeyang merupakan pembunuh kinerja
  • Ini fleksibel karena tidak perlu membuat objek game baru yang kosong selama permainan. Anda cukup menambahkannya di editor (atau selama pertandingan) ke objek game pilihan Anda.
  • Ini aman dari utas

    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
    }
    
Merek
sumber
Mungkin pertanyaan konyol, tapi mengapa Anda membuat OnApplicationQuitCallbackdan OnEnableCallbacksebagai abstractbukan hanya kosong virtualmetode? Setidaknya dalam kasus saya, saya tidak memiliki logika keluar / aktifkan dan memiliki pengesampingan kosong terasa kotor. Tapi saya mungkin kehilangan sesuatu.
Jakub Arnold
@ JakubArnold Saya belum melihat ini dalam beberapa saat tetapi pada pandangan pertama sepertinya Anda benar, akan lebih baik sebagai metode virtual
aBertrand
@JakubArnold Sebenarnya saya pikir saya ingat pemikiran saya dari dulu: Saya ingin membuat mereka yang menggunakan ini sebagai komponen yang dapat mereka gunakan OnApplicationQuitCallbackdan OnEnableCallback: menjadikannya sebagai metode virtual semacam membuatnya kurang jelas. Mungkin pemikiran yang sedikit aneh tapi sejauh yang saya ingat itu rasional saya.
aBertrand
2

Sebenarnya ada cara resmi palsu untuk menggunakan Singleton di Unity. Inilah penjelasannya, pada dasarnya buat kelas Singleton dan buat skrip Anda mewarisi dari kelas itu.

Alakanu
sumber
Harap hindari jawaban hanya tautan, dengan memasukkan dalam jawaban Anda setidaknya ringkasan informasi yang Anda harap akan diperoleh pembaca dari tautan tersebut. Dengan begitu jika tautannya tidak tersedia, jawabannya tetap bermanfaat.
DMGregory
2

Saya akan menganggap implementasi saya juga untuk generasi mendatang.

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

Bagi saya, baris Destroy(gameObject.GetComponent(instance.GetType()));ini sangat penting karena sekali saya meninggalkan skrip singleton pada gim lain. Menangkan adegan dan seluruh objek gim dihapus. Ini hanya akan Hancurkan komponen jika sudah ada.

Uri Popov
sumber
1

Saya menulis kelas singleton yang memudahkan untuk membuat objek singleton. Ini adalah skrip MonoBehaviour, sehingga Anda dapat menggunakan Coroutine. Ini berdasarkan pada artikel Wiki Persatuan ini , dan saya akan menambahkan opsi untuk membuatnya dari Prefab nanti.

Jadi, Anda tidak perlu menulis kode Singleton. Cukup unduh Singleton.cs Base Class ini , tambahkan ke proyek Anda, dan buat singleton Anda memperluasnya:

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

  public string globalVar;

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

Sekarang kelas MySingleton Anda adalah singleton, dan Anda dapat menyebutnya dengan Instance:

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

Berikut adalah tutorial lengkapnya: http://www.bivis.com.br/2016/05/04/unity-reusable-singleton-tutorial/

Bivis
sumber