DontDestroyOnLoad in Unity — Best Practices
DontDestroyOnLoad (DDOL) is commonly intended to save the root Game Objects between Scenes in Unity, e.g. for Game Managers. Such Game Objects will be moved to a special transient runtime scene that called “DontDestroyOnLoad” and will be available alongside the Scene you loaded manually.
This is a Unity Tutorial on how to use DDOL tool correctly in terms of Software Architecture.
As a real project, I present to you my AR Masker app with the next Unity Assets that use DontDestroyOnLoad method:
Contents
Bad Practice of using DontDestroyOnLoad
Although Unity Docs expose that we can use “Don’t Destroy On Load” for Game Object on the Scene, this is not recommended and that’s why.
If you or your teammate will try to load this scene again during the game session, you will get another copy of this Game Object. The number of loads of this scene will be equal to the number of copies of this Game Object that will be available between scenes during the game session until manual destroying.
The simplest solution in this situation is to make “Init” scene that is loaded once at game start, and it will never be loaded again.
But this is not suitable for every project.
The problem is that the need to create DDOL Objects may arise as you develop the app. Imagine, that you have a complex project with a specific initialization flow, and you need to mark some objects as DDOL in that flow. Such a solution may cause some costs with redesign of the init flow.
A common bad practice of using DontDestroyOnLoad with rude controlling the number of copies looks like the kind of Singleton design pattern below. Such “workaround/crutch” can lead to a bunch of issues.
/*
===================================================
Unity Tutorials by MAKAKA GAMES: https://makaka.org
===================================================
*/
using UnityEngine;
public class SingletonDDOL : MonoBehaviour
{
private static SingletonDDOL instance;
public static SingletonDDOL GetInstance()
{
return instance;
}
private void Awake()
{
if (instance)
{
Destroy(gameObject);
return;
}
DontDestroyOnLoad(gameObject);
instance = this;
// init...
}
}
The 1st problem
If you, for some reason, use DontDestroyOnLoad(this) which is also allowed, it appropriately keeps the target component (C# script) because you are operating with a target MonoBehaviour, and not with the whole Game Object.
At first glance, using this, it seems that the Whole Game Object is not destroyed with all components when Loading Another Scene (or Reloading the Current Scene) and everything is fine, but it is not.
If you have another component attached to the same Game Object, then when reloading the scene:
- non-static fields’ values are not saved.
- predefined functions such as OnDestroy() will be invoked for this MonoBehaviour.
The 2nd problem
If the Game Object is in a scene, you’ll be tempted to:
- drag it into another Game Objects (which will fail because non-root Game Objects are not supported for DDOL).
- drag things into it (which will also fail if those things are not also DDOL).
Good Practice: Don’t Destroy On Load
Just don’t place Don’t Destroy On Load Game Objects in the Scenes. It’ll save you from:
- Data loss.
- Rude Controlling Copies of DDOL Game Objects.
- Misunderstanding of the working of DDOL and Redundant Bugs.
You can achieve this in 2 ways:
- Use RuntimeInitializeOnLoadMethod attribute.
- Use modified Singleton design pattern that provides pure-code solutions.
Singleton with No Predefined Data: for Non-Prefab using
/*
===================================================
Unity Tutorials by MAKAKA GAMES: https://makaka.org
===================================================
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// The code was developed by a 3rd-party: https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30
// to make a simple Unity singleton that has no
// predefined data associated with it, eg, a high score manager.
//
// To use: access with SingletonSimple.Instance
//
// To set up:
// - Copy this file (duplicate it)
// - rename class SingletonSimple to your own classname
// - rename CS file too
//
// DO NOT PUT THIS IN ANY SCENE; this code auto-instantiates itself once.
//
// I do not recommend subclassing unless you really know what you're doing.
public class SingletonSimple : MonoBehaviour
{
// This is really the only blurb of code you need to implement a Unity singleton
private static SingletonSimple _Instance;
public static SingletonSimple Instance
{
get
{
if (!_Instance)
{
_Instance = new GameObject().AddComponent<SingletonSimple>();
// name it for easy recognition
_Instance.name = _Instance.GetType().ToString();
// mark root as DontDestroyOnLoad();
DontDestroyOnLoad(_Instance.gameObject);
}
return _Instance;
}
}
// implement your Awake, Start, Update, or other methods here...
}
Singleton with Predefined Data: for Prefabs & ScriptableObjects
/*
===================================================
Unity Tutorials by MAKAKA GAMES: https://makaka.org
===================================================
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// The code was developed by a 3rd-party: https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625
// to make a Unity singleton that has some
// prefab-stored data associated with it, eg a music manager
//
// To use: access with SingletonViaPrefabFromResources.Instance
//
// To set up:
// - Copy this file (duplicate it)
// - rename class SingletonViaPrefabFromResources to your own classname
// - rename CS file too
// - create the prefab asset associated with this singleton
// NOTE: read docs on Resources.Load() for where it must exist:
// https://docs.unity3d.com/ScriptReference/Resources.Load.html
//
// DO NOT DRAG THE PREFAB INTO A SCENE! THIS CODE AUTO-INSTANTIATES IT!
//
// I do not recommend subclassing unless you really know what you're doing.
public class SingletonViaPrefabFromResources : MonoBehaviour
{
// This is really the only blurb of code you need to implement a Unity singleton
private static SingletonViaPrefab _Instance;
public static SingletonViaPrefab Instance
{
get
{
if (!_Instance)
{
var prefab = Resources.Load<GameObject>("PathToYourSingletonViaPrefab");
// create the prefab in your scene
var inScene = Instantiate<GameObject>(prefab);
// try find the instance inside the prefab
_Instance = inScene.GetComponentInChildren<SingletonViaPrefabFromResources>();
// guess there isn't one, add one
if (!_Instance) _Instance = inScene.AddComponent<SingletonViaPrefabFromResources>();
// mark root as DontDestroyOnLoad();
DontDestroyOnLoad(_Instance.transform.root.gameObject);
}
return _Instance;
}
}
// NOTE: alternatively to a prefab, you could use a ScriptableObject derived asset,
// make a reference to it here, and populated that reference at the Resources.Load
// line above.
// implement your Awake, Start, Update, or other methods here... (optional)
}