Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Singelton pattern duplication ondestroy problem.

Discussion in 'Scripting' started by Lars-Kristian, Nov 24, 2014.

  1. Lars-Kristian

    Lars-Kristian

    Joined:
    Jun 26, 2013
    Posts:
    64
    Hi dear form users. Today I stumbled upon a problem I do not know how to solve. I tried to search the forum and such but I did not find any solutions.

    I have a MenuManager object that is using the singleton pattern principle. Nothing special, just a normal implementation.

    Code (CSharp):
    1. public class MenuManager : MonoBehaviour
    2. {  
    3.     private static MenuManager _instance;
    4.    
    5.     public static MenuManager instance
    6.     {
    7.         get
    8.         {
    9.             if (_instance == null)
    10.             {
    11.                 _instance = GameObject.FindObjectOfType<MenuManager>();
    12.                 if(_instance == null)
    13.                 {
    14.                     GameObject go = Resources.Load("Prefab/Menu Canvas", typeof(GameObject)) as GameObject;
    15.                     GameObject.Instantiate(go);
    16.                     _instance = go.GetComponent(typeof(MenuManager)) as MenuManager;
    17.                 }
    18.                 DontDestroyOnLoad(_instance.gameObject);
    19.             }
    20.            
    21.             return _instance;
    22.         }
    23.     }
    24.    
    25.     void Awake()
    26.     {
    27.         if (_instance == null)
    28.         {
    29.             _instance = this;
    30.             DontDestroyOnLoad(this.gameObject);
    31.         }
    32.         else
    33.         {
    34.             if (this != _instance)
    35.             {
    36.                 Destroy(this.gameObject);
    37.             }
    38.         }
    39.        
    40.         Init();
    41.     }
    42. ...
    The problem occur when I exit play mode, then a copy of the MenuManager gets made and it survives the transition to edit mode. I now have 2 MenuManagers in the Hierarchy. This is not good and my game does not like it.

    I think this is the cause:

    I also have this MenuScreen object that subscribes to an event owned by the MenuManager. When MenuScreen.OnDestroyed is called, it unsubscribes from the event. This is what I think happens. The MenuManager is destroyed before the MenuScreen. When the MenuScreen tries to unsubscribe to the MenuManager, a new copy is made.

    Does anyone have knowledge on how to solve this problem?

    I have tried changing the script execution order but that did not work.
     
  2. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    Code (csharp):
    1.  
    2.  
    3. static MenuManager _instance;
    4. public static MenuManager GetInstance()
    5. {
    6.    if(_instance == null)
    7.    {
    8.       _instance = new GameObject("_MenuManager").AddComponent<MenuManager>();
    9.       DontDestroyOnLoad(_instance.gameObject);
    10.    }
    11.  
    12.    return _instance;
    13. }
    14.  
    15. public static void DoSomeStuff()
    16. {
    17.    GetInstance().Stuff();
    18. }
    19.  
    20. void Stuff()
    21. {
    22.   Debug.Log("stuffff");
    23. }
    24.  
    25.  
     
    Rilantes likes this.
  3. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Any particular reason the "Singleton" needs to be a MonoBehaviour?
     
  4. Lars-Kristian

    Lars-Kristian

    Joined:
    Jun 26, 2013
    Posts:
    64
    Maybe not, but the menu screens are children of the manager (The new UI Canvas). Would this solve my problem?
    The problem is that the code instantiate an new GameObject when OnDestroy is called. By making the MenuManager an non-MonoBehaviour might work, but I would still need to instantiate a GameObject if the MenuManager instance is null.

    @JamesLeeNZ Yeah, My code is more or less the same? Or are you suggesting that I can use the method at line 15. to check if the instance equals null or not?

    Would that be a bad idea? o_O
     
    Last edited: Nov 24, 2014
  5. Lars-Kristian

    Lars-Kristian

    Joined:
    Jun 26, 2013
    Posts:
    64
    Nice it worked. :D
    Sorry for not thinking of this at first, my brain is kind of slow today. Thanks for the help. :)
     
  6. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    Your code was more or less the same, the benefit of my solution is you never have to worry about making sure you have an instance in the scene, it will just create one whenever you decide to use it. Its just a cleaner solution (imho ;p).

    You also don't have to worry about script order execution, especially if you make the mistake of trying to access your menu from another scripts awake function, which does happen.
     
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,377
    I created an abstract class that I can inherit from anytime I need a MonoBehaviour Singleton.

    https://code.google.com/p/spacepuppy-unity-framework/source/browse/trunk/SpacepuppyBase/Singleton.cs

    Combined with the inspector:
    https://code.google.com/p/spacepupp...pyBaseEditor/Inspectors/SingletonInspector.cs

    This allows creating a central gameobject singletons can be attached to at design time, with a drop-down that lists all known singletons that can be attached. A menu command exists to create the central gameobject to attach to.

    Of course it's designed to allow you to put the singleton on any gameobject you like, if you want it to operate independent of the central gameobject. For instance if the central singleton gameobject needs to persist for all loads, but you have a singleton that only lives as long as the scene is up. It'd go on its own gameobject, and you'd untick the 'MaintainOnLoad' checkbox for it.

    If the singleton doesn't exist when you go to access it, it gets created on the central singleton gameobject.

    Now, you don't have to copy paste the singleton boiler plate every time you create one. Just inherit from com.spacepuppy.Singleton, and you're done.

    I recently did a gamejam with a bunch of novices, so it was just faster and easier to create a lot of our code in like 9 or so singletons (wasn't the best design to use as many singletons as we did, but we only had 48 hours...), this abstract class proved very useful for the gamejam (as well as a lot more in my spacepuppy framework).

    Article about our gamejam if you're interested:
    http://jupiterlighthousestudio.com/game-jam/
     
  8. Lars-Kristian

    Lars-Kristian

    Joined:
    Jun 26, 2013
    Posts:
    64
    Sweet, I will save that one for later.

    Nice game btw. Impressive work for only 48 hours love the audience in the theater.
    I only got to try it out with a keyboard, but I imagine it would be ridiculous with a microphone. :D
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,377
    It's just hard with a mic.
     
  10. Stoven

    Stoven

    Joined:
    Jul 28, 2014
    Posts:
    171
    I got a Singleton script from a unity wiki source on the internet and only modified one aspect of it (I was still learning about the difference between Monobehaviours and regular classes)

    Notice the last bit about OnDestroy. I think it describes your problem perfectly XD

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. namespace SingletonSpace
    4. {
    5.     /// <summary>
    6.     /// Be aware this will not prevent a non singleton constructor
    7.     ///   such as `T myT = new T();`
    8.     /// To prevent that, add `protected T () {}` to your singleton class.
    9.     ///
    10.     /// As a note, this is made as MonoBehaviour because we need Coroutines.
    11.     /// </summary>
    12.     public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
    13.     {
    14.         private static T _instance;
    15.  
    16.         private static object _lock = new object();
    17.  
    18.         public static T Instance
    19.         {
    20.             get
    21.             {
    22.                 return GetInstance();
    23.             }
    24.         }
    25.  
    26.         public static T GetInstance()
    27.         {
    28.             if (applicationIsQuitting)
    29.             {
    30.                 Debug.LogWarning("[Singleton] Instance '" + typeof(T) +
    31.                     "' already destroyed on application quit." +
    32.                     " Won't create again - returning null.");
    33.                 return null;
    34.             }
    35.  
    36.             lock (_lock)
    37.             {
    38.                 if (_instance == null)
    39.                 {
    40.                     _instance = (T)FindObjectOfType(typeof(T));
    41.  
    42.                     if (FindObjectsOfType(typeof(T)).Length > 1)
    43.                     {
    44.                         Debug.LogError("[Singleton] Something went really wrong " +
    45.                             " - there should never be more than 1 singleton!" +
    46.                             " Reopenning the scene might fix it.");
    47.                         return _instance;
    48.                     }
    49.  
    50.                     if (_instance == null)
    51.                     {
    52.                         GameObject singleton = new GameObject();
    53.                         _instance = singleton.AddComponent<T>();
    54.                         singleton.name = "(singleton) " + typeof(T).ToString();
    55.  
    56.                         DontDestroyOnLoad(singleton);
    57.  
    58.                         Debug.Log("[Singleton] An instance of " + typeof(T) +
    59.                             " is needed in the scene, so '" + singleton +
    60.                             "' was created with DontDestroyOnLoad.");
    61.                     }
    62.                     else
    63.                     {
    64.                         Debug.Log("[Singleton] Using instance already created: " +
    65.                             _instance.gameObject.name);
    66.                     }
    67.                 }
    68.  
    69.                 return _instance;
    70.             }
    71.  
    72.         }
    73.  
    74.         private static bool applicationIsQuitting = false;
    75.         /// <summary>
    76.         /// When Unity quits, it destroys objects in a random order.
    77.         /// In principle, a Singleton is only destroyed when application quits.
    78.         /// If any script calls Instance after it have been destroyed,
    79.         ///   it will create a buggy ghost object that will stay on the Editor scene
    80.         ///   even after stopping playing the Application. Really bad!
    81.         /// So, this was made to be sure we're not creating that buggy ghost object.
    82.         /// </summary>
    83.         public void OnDestroy()
    84.         {
    85.             applicationIsQuitting = true;
    86.         }
    87.     }
    88. }
    I never ran into the problem of a Singleton persisting from Play mode back to Edit mode while using this script.
     
  11. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    The problem was most likely that he was flagging the prefab gameObject as DontDestoryOnLoad, rather than the new instance.