Search Unity

Singleton - DontDestroyOnLoad only work for root GameObjects error

Discussion in 'Scripting' started by beBoss, Jul 30, 2017.

  1. beBoss

    beBoss

    Joined:
    Feb 2, 2016
    Posts:
    14
    Well, firstly will say that I check a few similar threads but still cannot figure it out what I am doing wrong!
    I am not pro dev, so yeah, probably I am not getting the idea very clearly but still trying.

    The error:
    Code (CSharp):
    1. DontDestroyOnLoad only work for root GameObjects or components on root GameObjects.
    2. UnityEngine.Object:DontDestroyOnLoad(Object)
    3. Singleton`1:Awake() (at Assets/Scripts/Singleton.cs:31)
    The error comes from:
    Code (CSharp):
    1. DontDestroyOnLoad(gameObject);
    Here is my code:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class Singleton<T> : MonoBehaviour  where T: MonoBehaviour
    4. {
    5.     private static T instance;
    6.  
    7.     public static T Instance
    8.     {
    9.         get
    10.         {
    11.             if (instance == null)
    12.             {
    13.                 instance = GameObject.FindObjectOfType<T>();
    14.  
    15.                 if (instance == null)
    16.                 {
    17.                     GameObject singleton = new GameObject(typeof(T).Name);
    18.                     instance = singleton.AddComponent<T>();
    19.                 }
    20.             }
    21.  
    22.             return instance;
    23.         }
    24.     }
    25.  
    26.     public virtual void Awake()
    27.     {
    28.         if (instance == null)
    29.         {
    30.             instance = this as T;
    31.             DontDestroyOnLoad(gameObject);
    32.         }
    33.         else
    34.         {
    35.             Destroy(gameObject);
    36.         }
    37.     }
    38. }
    One more thing, Visual studio (mac version) shows me this hint:


    Should I change it from GameObject just to Object?
     
    Grey_Felix likes this.
  2. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    It means in the hierarchy, the game object with the code "DontDestroyOnLoad" must be the 'root' (ie: not a child game object).
     
  3. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,187
    Right, so basically...

    Parent
    -Child
    --Grandchild

    Your parent must be dontdestroyonload, not the other two.

    As far as your Visual Studios warning. It just suggest you can simplify the code. You don't have to do it if you write code and you understand it. All it's really designed to do is removed parts of code that aren't really needed to do what you try to do. If you right click on it, it should offer you an option to resolve it and what suggestions they have.
     
    sofujinia_mori and beBoss like this.
  4. Hazzius

    Hazzius

    Joined:
    Jun 21, 2014
    Posts:
    11
    You may delete "GameObject".
    Use simple "instance = FindObjectOfType<T>();"
     
  5. Nopease

    Nopease

    Joined:
    May 3, 2020
    Posts:
    3
    still facing this issue? xD is the instance = FindObjectOfType<T>(); worked?
     
  6. babilGames

    babilGames

    Joined:
    Sep 26, 2020
    Posts:
    3
    That does not work for me. But i moved gameobject which the script attached to main root in hierarchy. I mean under scene and it worked.
     
  7. Freddybnj_2024

    Freddybnj_2024

    Joined:
    Aug 1, 2017
    Posts:
    1
    I am having the same issue with the DontDestroyOnLoad. I can't move all my objects to root due to dependencies within the UI... Anyway... when you say to put the script in the Parent... does it matter if the parent is only an empty object? Still not working when I add it to the Parent object. Thx...
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,690
    Then you cannot singly mark it DontDestroyOnLoad() as long as it remains a child GameObject.

    It's really that simple.

    The act of marking something DDOL (even just a script!) moves the GameObject (and ALL its children) physically to a temporary transient scene called
    DontDestroyOnLoad
    .

    ddol.png

    This is why you can never ONLY mark a child GameObject as DDOL.

    If you want, you can mark the entire hierarchy that a GameObject is within as DDOL, but this might have other issues.

    You would accomplish this shotgun "DDOL ALL THE THINGS!" approach by:

    Code (csharp):
    1. // mark the root transform of my ENTIRE HIERARCHY as DDOL
    2. DontDestroyOnLoad( transform.root.gameObject);
    But use caution because the above comes with massive potential complications.

    Execute the above code, press Pause and study what just happened in the Hierarchy; note what has been moved into the DontDestroyOnLoad meta scene.
     
    Brathnann likes this.
  9. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,992
    There are generally two solutions to the issue. However which one makes more sense depends on the usecase.

    If you have a script on a child object that should call DontDestroyOnLoad you can either:
    • detach the child from the parent so it becomes a root object
    • or call DontDestroyOnLoad on the actual root object of the object tree
    Both solutions may have different wanted / unwanted side effects.

    So solution 1 is to do

    Code (CSharp):
    1. transform.parent = null;
    2. DontDestroyOnLoad(this);
    solution 2 would be this:

    Code (CSharp):
    1. DontDestroyOnLoad(transform.root);
    The first solution will break the current hierarchy so it doesn't work for example on UI elements which have to be under a Canvas object. However preserving such an object without the canvas wouldn't make any sense in the first place.

    The second solution will preserve the hierarchy as it is. It will simply mark the top most / root object of the current object tree as DontDestroyOnLoad which will ensure the script which calls this method remains loaded and the hierarchy is preserved. However it will of course preserve the whole object tree which may also contain other objects which you may not need.

    If neither of those solutions fit your needs, you just need a different object structure and have to group things in a way so it makes sense.

    Technically there's a third solution which is essentially a hybrid of the two. If you just need a partial branch of the object tree you just need to detach that branch and call DDOL on the root of that branch once detached. In case of a canvas that is nested in some other gameobjects would mean to detach the canvas object and keep that alive which will keep the nested UI element alive that you're interested in.

    Hopefully people understand why only root objects are preserved. Imagine gameobjects as cartboard boxes to store objects in it. You can put smaller boxes into larger ones and store arbitrary items in those boxes. Those are your gameobjects. Now imagine you have a very important item, say the last picture of your grandma. So you put a shiny golded sticker on the back of the picture that reads "DO NOT TRASH THIS, IT'S IMPORTANT". So anyone who cleans your room and reads that sign would not throw it in the trash. However if the picture is buried somewhere in a closed box, it will get thrown out with everything inside the boxes because who's cleaning up the room doesn't search through every nested box. Even if they did, in order to just keep the picture it has to be taken out of the box(es) so when the room as been cleared out, only that item will remain, directly inside that room. Of course instead of tagging the picture you could tag the outer box so when the cleaner goes through the things in the room it won't touch the box that has the "keep" tag on.

    Yes, real world analogies do not always make much sense and can not always be well transferred into the digital world, but I hope this gives a better understanding how it works, or more importantly why the other approaches do not work.
     
    harlandpj, Nightw0lv and Kurt-Dekker like this.
  10. herohelpme002

    herohelpme002

    Joined:
    Jan 12, 2022
    Posts:
    1
    I have the same issue and the code below worked for me
    1. DontDestroyOnLoad(transform.root);
    I'm doing audio switch between scene to scene.

    Instead of
    DontDestroyOnLoad(gameObject);
    I changed it to
    DontDestroyOnLoad(transform.root.gameObject);
    The error issue solved.\

    Thanks.
     
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,690
    The struggle disappears if you simply STOP placing DontDestroyOnLoad objects in scenes.

    YES, ten billion tutorials tell you to do this. This is because tutorial writers generally only need their stuff to barely work. The moment you change a scene (or add a new level) you are in deep trouble with either duplicate DDOL objects or null references.

    NEVER put a GameObject in any Scene if it is going to have DontDestroyOnLoad called on it.

    Just Don't Do It!! Seriously.


    Simple Singleton (UnitySingleton):

    Some super-simple Singleton examples to take and modify:

    Simple Unity3D Singleton (no predefined data):

    https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30

    Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

    https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625

    These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance

    Alternately you could start one up with a
    RuntimeInitializeOnLoad
    attribute.

    The above solutions can be modified to additively load a scene instead, BUT scenes do not load until end of frame, which means your static factory cannot return the instance that will be in the to-be-loaded scene. This is a minor limitation that is simple to work around.

    If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

    Code (csharp):
    1. public void DestroyThyself()
    2. {
    3.    Destroy(gameObject);
    4.    Instance = null;    // because destroy doesn't happen until end of frame
    5. }
    There are also lots of Youtube tutorials on the concepts involved in making a suitable GameManager, which obviously depends a lot on what your game might need.

    OR just make a custom ScriptableObject that has the shared fields you want for the duration of many scenes, and drag references to that one ScriptableObject instance into everything that needs it. It scales up to a certain point.

    And finally there's always just a simple "static locator" pattern you can use on MonoBehaviour-derived classes, just to give global access to them during their lifecycle.

    WARNING: this does NOT control their uniqueness.

    WARNING: this does NOT control their lifecycle.

    Code (csharp):
    1. public static MyClass Instance { get; private set; }
    2.  
    3. void OnEnable()
    4. {
    5.   Instance = this;
    6. }
    7. void OnDisable()
    8. {
    9.   Instance = null;     // keep everybody honest when we're not around
    10. }
    Anyone can get at it via
    MyClass.Instance.
    , but only while it exists.
     
  12. Murgilod

    Murgilod

    Joined:
    Nov 12, 2013
    Posts:
    10,139
    I'm almost entirely certain that the account you're replying to is just a ChatGPT poster.
     
    SisusCo likes this.
  13. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,690
    OH YOU'RE RIGHT!!! This is the first "soft" tell:

    The second tell is they put an invisible hyperlink at the end! Select all to see it.

    Screen Shot 2023-11-18 at 1.26.02 PM.png

    Shame on you dirty rotten @sr4218845... I reported him but he'll probably just spam all weekend long until they rip all his posts out.
     
    Last edited: Nov 18, 2023
    SisusCo likes this.