user6068287
user6068287

Reputation:

Duplicates because of DontDestroyOnLoad()

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?

Upvotes: 2

Views: 9047

Answers (5)

BatteryAcid
BatteryAcid

Reputation: 8881

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

Upvotes: 0

user13428647
user13428647

Reputation:

if (Instance==null)
{
  Instance = this;
  DontDestroyOnLoad(gameObject);
}

Upvotes: 0

costomato
costomato

Reputation: 112

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!!

Upvotes: 1

Carter Jensen
Carter Jensen

Reputation: 11

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.

Upvotes: 1

David
David

Reputation: 10708

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.

Upvotes: 3

Related Questions