Dans vos projets Unity, vous utilisez plusieurs scènes pour diviser votre univers de jeu. Certaines des ces scènes auront des éléments qui devront être conservés d’une scène à une autre. Par exemple : en passant d’un niveau à un autre, la musique ne doit pas s’arrêter. En chargeant une nouvelle scène avec Application.LoadLevel, Unity va supprimer la totalité des objets de la scène courante et notre musique avec ! Il y a un moyen de contourner ce fonctionnement.
Il est possible de débrayer la destruction de certains objets mais cette méthode à elle seule peut être problématique. En effet, si un objet est défini comme « immortel » (non détruit lors d’un changement de scène), il ne sera plus jamais détruit jusqu’à la sortie du jeu. Pour reprendre le cas de la musique, on voudrait qu’elle soit jouée dans les niveaux de jeu, mais pas dans les menus. Cela veut dire qu’il faut gérer manuellement la suppression de ces objets et leur création s’ils n’existent pas encore.
Si vous avez beaucoup d’objets dans ce cas de figure, pas tous présents sur les mêmes scènes, ça peut rapidement devenir l’enfer à gérer… C’est là que le gestionnaire d’objets persistants entre en jeu.
Disclaimer
Cet article a été originalement écrit le 19/12/2013 sur mon précédent blog. Certaines informations présentées ici peuvent donc ne plus fonctionner telles quelles.
Solution
Ma solution est de créer un manager générique qui va s’occuper de tout ça. C’est lui qui tâchera de :
- Créer les GameObjects nécessaires qui n’existent pas encore
- Supprimer les GameObjects qui ne sont plus voulus
Cette sélection se fera via un fichier de configuration qui définira quel objet doit être présent, dans quelle scène. Un seul fichier pour faciliter les modifications futures et ajustements. Un seul fichier pour les gouverner tous. 😉
Mise en place
Situation d’exemple
Pour faciliter la compréhension de la suite, on va se placer dans la situation type suivante. Notre jeu sera composé de 5 scènes différentes :
On veut mettre en place les éléments suivants :
- Une musique MenuMusic devra être jouée uniquement sur la scène Menu
- Une musique LevelMusic devra être jouée uniquement sur les scènes Level_1 et Level_2
- Une musique BossMusic devra être jouée uniquement sur la scène Boss
- Un gestionnaire de score devra être présent sur les scènes Level_1, Level_2 et Boss
- La scène Credits ne doit rien avoir : pas de son ni de gestionnaire de score
Le fichier de configuration
Pour le fichier de configuration, on va utiliser du XML mais vous pouvez utiliser n’importe quel autre format pour stocker ces informations. L’objectif est de rendre ce fichier facile à modifier.
La structure va être très simple :
scences > scene > object
Le noeud scenes sera la racine du fichier.
Le noeud scene représentera une scène du jeu.
Le noeud object représentera un GameObject qui devra être présent sur la scène.
Les objets
Pour permettre la gestion automatique des objets persistants, il va falloir faire un petit travail de préparation. Tout d’abord, chacun des GameObjects que nous allons utiliser dans le manager doit être « immortel ». Dans la mesure où tous les GameObjets n’auront pas forcément un script attaché (par exemple pour lire une musique), le plus clair est d’en créer un : DontDestroyMeOnLoad qui se chargera de rendre nos GameObjects « immortels » (oui j’aime bien ce mot). Ils seront alors facilement identifiables via ce script.
public class DontDestroyMeOnLoad : MonoBehaviour { void Awake() { DontDestroyOnLoad(gameObject); } }
Pour empêcher la destruction d’un GameObject au changement de scène, il suffit d’appeler la méthode statiqueDontDestroyOnLoad en lui passant en paramètre le GameObject à « immortaliser ». Pourquoi mettre cet appel dans la méthode Awake et pas Start ? Eh bien parce que la méthode Awake est appelée avant Start. Ainsi, notre code d’identification des élus est exécuté en priorité sur le reste des autres scripts de la scène.
Créons maintenant les GameObjects que nous voulons utiliser :
- MenuMusic, auquel on va attacher notre musique de menu
- LevelMusic, auquel on va attacher notre musique de jeu
- BossMusic, auquel on va attacher notre musique de boss
- ScoreManager, auquel on pourrait attacher notre script de gestion du score
La scène dans laquelle vous allez créer ces GameObjects n’a pas d’importance. Considérez ça comme un brouillon, un espace de travail temporaire. N’oubliez pas d’attacher le script DontDestroyMeOnLoad à chacun d’eux !

Le manager
On a notre fichier de configuration, nos Prefabs sont prêts, il est maintenant temps de lier le tout. On arrive maintenant au cœur de la chose : le manager. On va créer le script ensemble, petit à petit, pas de panique.
Ce script devra être attaché à un GameObject présent sur toutes les scènes où vous voulez l’utiliser. Il ne sera pas immortel contrairement aux objets qu’il va gérer. Allons-y et créons le script PersistentManager.
public class PersistentManager : MonoBehaviour { public TextAsset ConfigFile; void Start () { LoadConfigFile(); DestroyObjects(); CreateObjects(); Destroy(gameObject); } private void LoadConfigFile() { } private void DestroyObjects() { } private void CreateObjects() { } }
- LoadConfigFile
- CreateObjects
- DestroyObjects
Pour charger la configuration, je ne vais pas entrer dans les détails, c’est du parsing de XML que l’on va stocker dans un Dictionary. Voilà le source :
private Dictionary _scenesConfig; private void LoadConfigFile() { _scenesConfig = new Dictionary&lft;string string="">(); // Load and parse the XML file var xmlData = new XmlDocument(); xmlData.LoadXml(ConfigFile.text); // Get the list of scene nodes var sceneNodeList = xmlData.GetElementsByTagName("scene"); // Loop through scenes foreach(XmlNode node in sceneNodeList) { var currentSceneName = node.Attributes["name"].Value; // Fetch all objects defined in the config file var childList = node.ChildNodes; var childCount = node.ChildNodes.Count; var nameArray = new string[childCount]; var i = 0; foreach(XmlNode child in childList) { nameArray[i] = child.Attributes["name"].Value; i++; } _scenesConfig[currentSceneName] = nameArray; } }
private void CreateObjects() { var currentScene = Application.loadedLevelName; if(_scenesConfig.ContainsKey(currentScene)) { foreach(string objectName in _scenesConfig[currentScene]) { if(!GameObject.Find(objectName)) { var resource = Resources.Load (objectName); var o = GameObject.Instantiate(resource); o.name = objectName; } } } }
Le fonctionnement est assez simple : on parcourt le tableau des objets que l’on veut voir sur cette scène. S’ils ne sont pas présents, on les crée. Comme on n’a pas de lien direct vers nos Prefabs, il faut passer par Ressources.Load pour les récupérer. Ne pas avoir besoin d’un lien direct vers les Prefabs facilite grandement la tâche : il suffit de créer unPrefab et il sera directement utilisable en modifiant le fichier de configuration. C’est pour cette raison qu’ils doivent être placés dans un dossier nommé Resources (plus d’infos sur la doc).
Il y a une autre petite astuce à noter au niveau du nommage. Lorsque l’on utilise la méthode Instantiate, le nom de l’objet dupliqué est suffixé par « (Clone) ». C’est quelque chose qui nous embête un peu puisqu’on recherche nos objets par leur nom. On force donc son nom au nom du Prefab.
private void DestroyObjects()
{
var currentScene = Application.loadedLevelName;
if(_scenesConfig.ContainsKey(currentScene))
{
DontDestroyMeOnLoad[] objects = (DontDestroyMeOnLoad[]) GameObject.FindObjectsOfType(typeof(DontDestroyMeOnLoad));
var objectsToKeep = _scenesConfig[currentScene];
var keepTabLength = objectsToKeep.Length;
foreach(DontDestroyMeOnLoad current in objects)
{
var keepCurrent = false;
var i = 0;
var loop = true;
while(i < keepTabLength && loop)
{
if(current.name == objectsToKeep[i])
{
loop = false;
keepCurrent = true;
}
i++;
}
if(!keepCurrent)
{
Destroy(current.gameObject);
}
}
}
}
[/code]
Cette méthode est très lente. Il n’est pas recommandé de l’utiliser à chaque boucle.
Dans notre cas, elle sera utilisée une seule fois par changement de scène donc ça ne posera pas de problème.
Pour finir, on vérifie si le nom des objets remontés est présent dans la configuration de la scène courante. Si ce n’est pas le cas, on les supprime tout simplement.
Conclusion
Si l’on reprends notre exemple, voilà ce qui va se passer :
Lancement de la scène Menu. La manager détecte que la musique de menu n’est pas là donc il instancie son Prefab. Le manager disparaît rapidement de la hiérarchie :





Voilà, c’en est fini. Vous avez maintenant un système autonome de gestion des objets persistants entre les scènes. Et à l’utilisation, c’est simple comme bonjour !
Le code source des deux scripts DontDestroyMeOnLoad et PersistentManager sont disponibles sur le dépôt github Unity Toolbox.
Vous pouvez également télécharger le projet complet d’exemple.
Merci à Yannick Comte pour la relecture.
Crédits image : David Baxendale (image redimensionnée)