1

I have a strange problem with DontDestroyOnLoad. I have a map as a starting scene. From there, the user can click on certain map-objects and a new level is loaded with Application.LoadLevel() When the level is finished the map is loaded again. But, the objects with DontDestroyOnLoad attached are duplicated on the second load.

Current script:

void Awake()
{
    DontDestroyOnLoad(transform.gameObject);
}

I searched online and found this possible solution to remove the duplicates:

public class NoDestroy : MonoBehaviour {

    public static NoDestroy instance;

    void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
        else
        {
            Destroy(this.gameObject);
            return;
        }

        DontDestroyOnLoad(this.gameObject);

    }
    }

The above script simply does not work. How can I fix the problem?

  • never, ever use statics in Unity. ***it is not an OO system, it is ECS.*** never use "this" keyword in Unity, if you do so you are doing something totally wrong. ***it is not an OO system, it is ECS.*** http://stackoverflow.com/a/35723305/294884 – Fattie Mar 22 '16 at 20:51
  • (heh) Note that it is *technically absolutely not possible* to "remove duplicates" (at the least, not inside frames, so that's the end of it) - you often see ridiculous code examples online where people try to "remove duplicates" to make a situation as in OO where you have "something like a singleton". ECS is totally different. – Fattie Mar 22 '16 at 21:01

5 Answers5

3

Because the above script uses a static instance, it'll only work if a `single GameObject has it attached - worse yet, it'll delete other objects which try to use the same behavior.

The biggest problem is that whenever you load a scene, all objects in that scene get loaded. If they weren't unloaded (thanks to DontDestroyOnLoad) they'll be duplicated, since the scene doesn't know that they "already exist"

The above script might work better if you try to box your persistant objects under an umbrella, and only the umbrella object (usually called Toolbox) isn't destroyed. This is mostly appropriate for manager scripts, however.

If you know the objects that are meant to not be destroyed, consider loading them via a "Loading" scene. Since this moves the persistent objects out of your map scene, they won't get duplicated when reloading the map scene. Bonus to this pattern since it makes it easier to implement curtain drop.

If you want to implement this as a simple behavioural script, consider adding an ID like so

public class NoDestory : MonoBehaviour
{
    private static Dictionary<string, GameObject> _instances = new Dictionary<string, GameObject>();
    public string ID; // HACK: This ID can be pretty much anything, as long as you can set it from the inspector

    void Awake()
    {
        if(_instances.ContainsKey(ID))
        {
            var existing = _instances[ID];

            // A null result indicates the other object was destoryed for some reason
            if(existing != null)
            {
                if(ReferenceEquals(gameObject, existing)
                    return;

                Destroy(gameObject);

                // Return to skip the following registration code
                return;
            }
        }

        // The following code registers this GameObject regardless of whether it's new or replacing
        _instances[ID] = gameObject;

        DontDestroyOnLoad(gameObject);
    }
}

This will prevent the duplication of an object with the same ID value as one that already exists, as well as allowing recreation if said object has been Destory-ed elsewhere. This can be further refined by adding a special Editor script to hand over a new ID each time the script is implemented.

David
  • 10,458
  • 1
  • 28
  • 40
  • Thanks for your answer, but your script just destroys the object when I switch to the new level. Why don't you use `DontDestroyOnLoad(transform.gameObject);` anywhere in your code? ` –  Mar 22 '16 at 19:58
  • Edited - that should stop the system from seeing itself as a collision, which might occur if `Awake` is called twice – David Mar 22 '16 at 20:01
  • Note the TODO at the end - I didn't put it in since I presumed you'd use this alongside your existing `DontDestroyOnLoad` script. If that's not the case, replace the `TODO~ line with the call in your sample – David Mar 22 '16 at 20:02
  • Glad to hear it - I'll make one last update since the TODO line was used after all. Note that I also added the `ReferenceEquals` check to ensure this script doesn't self-destruct if `Awake` is called twice, which can happen if code is executing in edit mode. – David Mar 22 '16 at 20:05
  • I have a problem. Other GameObjects that depend on the GameObject that has `NoDestroy` script attached, report `Missing Transform` in the Inspector, after the second run. Can you fix this? –  Mar 22 '16 at 20:17
  • launch program, click on object, click duplicate. it's just a non-starter – Fattie Mar 22 '16 at 21:01
  • As stated, you need to either manually change the IDs, or create an Editor script to assign random ids. This is something I would need to fire up Unity to resolve – David Mar 22 '16 at 21:01
  • **Exactly as Aravol says**, you must (of course) have a **preload scene** in every unity project. It's the most famous, basic, failing of the lads in Copenhagen that they forgot to include a Preload scene as part of the structure of Unity. They'll add it in U6 or U7. So click to create one. It's "just that simple". http://stackoverflow.com/a/35891919/294884 – Fattie Mar 22 '16 at 21:03
  • @Aravol I don't understand why my GameObject is reporting `Missing Transform` in Inspector in 2nd run... What should I do exactly? –  Mar 22 '16 at 21:07
  • you should do it as if you are using an ECS system ;-) good luck! – Fattie Mar 22 '16 at 21:08
  • 1
    a few code typos in the above solution which should be corrected.. _instances NOT _instance Destroy NOT Destory ReferenceEquals NOT ReferennceEquals – Bachalo Jan 19 '18 at 14:52
  • A static resource load also works: https://low-scope.com/unity-tips-1-dont-use-your-first-scene-for-global-script-initialization/ I wrote up an answer based on it here: https://stackoverflow.com/a/61789407/999943 – phyatt May 14 '20 at 04:42
1

You could possibly make an "Initializer" scene that's the starting scene of the project, and all your "Don't Destroy On Load" objects get initialized in there. Then you immediately change to the real starting scene (your map screen) and all your objects exist and aren't duplicated. If you have a starting screen already, you might be able to use that instead of creating a whole new scene.

0

In case if anyone still needs the answer:

Answer which is available everywhere (WRONG ONE):

private static Sample sampleInstance;
void Awake()
{
    DontDestroyOnLoad(this);

    if (sampleInstance == null)
    {
        sampleInstance = this;
    }
    else
    {
        DestroyObject(gameObject);
    }
}

Tl;dr Jump to solution.

What happens here is, when new scene is loaded, everything's fine. But as soon as you go back to previous scene, your original "Sample" game object is NOT destroyed and the new "Sample" game object which get created, is destroyed. So, what happens is, wherever you have referenced your Sample script (like in button onclick etc.) start referencing the duplicate script (which was destroyed of course), due to which they don't reference any script. Hence button.onClick no longer works.

So, the correct solution is to destroy the original Sample game object and not the duplicate, hence making the duplicate as the new original.

Here it is (THE CORRECT SOLUTION):

private static GameObject sampleInstance;
private void Awake()
{
    if (sampleInstance != null)
        Destroy(sampleInstance);

    sampleInstance = gameObject;
    DontDestroyOnLoad(this);
}

I have tested it. This works for me!!

costomato
  • 102
  • 2
  • 8
0
if (Instance==null)
{
  Instance = this;
  DontDestroyOnLoad(gameObject);
}
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
  • Your answer could be improved by adding more information on what the code does and how it helps the OP. – Tyler2P Nov 04 '22 at 19:33
0

For use cases where you have some startup logic that only needs to be initialized once, consider creating a Startup scene that only loads once at the beginning of your game. That way, whatever scene switching you do from that point on won't create duplicates of the game objects created with the Startup scene.

In relation to networking, that's what Unity did in their Boss Room example: https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Assets/Scripts/ApplicationLifecycle/ApplicationController.cs#L94

BatteryAcid
  • 8,381
  • 5
  • 28
  • 40