Search Unity

Load required scenes before Awake/Start of objects when entering play mode from level scene

Discussion in 'Scripting' started by Sangemdoko, Jul 27, 2021.

  1. Sangemdoko

    Sangemdoko

    Joined:
    Dec 15, 2013
    Posts:
    222
    Hi

    I'm working on a project with multiple scenes. One scene has some managers which are required by some objects in all levels.
    Starting from a start scene allows me to setup everything and load the required scenes in order.

    For play testing purposes though I want to be able to start play mode from any level scene. So I've added a prefab in all level scenes that loads the required scenes. I made sure that the script is executed before everything using execution order.

    The issue is that the Awake/Start functions of all the components in the Level scene are being called before the Required scenes have finished loading completely.

    My solution for now is putting the entire Level inside a parent game object and deactivate it before loading the scene asynchrounously and activating it once it is done.

    That works perfectly fine but I'm wondering if someone has a better solution?

    I know that having a flatter hiearchy is better. Is it worth getting all root gameobjects and deactivate those instead of having a single master parent? Or maybe I should remove the master parent once the required scenes are loaded?
     
  2. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,343
    You can set the startup scene via code in the editor. Works even if you hit play from any of your scenes: https://docs.unity3d.com/ScriptReference/SceneManagement.EditorSceneManager-playModeStartScene.html

    Here is what I do to have fast scene testing:
    1) Set playModeStartScene to the "main" scene which contains the bootstrap code etc.
    2) Have some editor code which saves (in editor prefs) what the opened scene was when play was hit (or you do this manually with a button or a menu entry).
    3) Have some code in the main scene which detects if there was another scene loaded (checks the editor pref from step 2). If yes then clear the editor pref and automatically load that scene after the main scene finished initializing. I #ifdef that code so it's only active in the editor.
    4) Unset/Restore playModeStartScene

    That way you will have the comfort of testing without having to manually change your scene to "main" AND you are actually going through the normal loading and setup code (testing the real thing). Caveat: you are loading the whole game, so depending on your setup it might take quite a lot longer than loading just your level.
     
  3. Sangemdoko

    Sangemdoko

    Joined:
    Dec 15, 2013
    Posts:
    222
    Thank you! I didn't know PlayModeStartScene was a thing, I think that might be what I want.

    For debugging I also have a transform that I use to spawn the character in a specific position. I'm sure I can modify the code a bit to make it work using the editor prefs you mentioned. To know whether I should spawn in the original spawn point or a debug one.
     
  4. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,639
  5. FOXAcemond

    FOXAcemond

    Joined:
    Jan 23, 2015
    Posts:
    99
    I'm curious, how do you do that?
     
  6. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,343
    The EditorSceneManager has a lot of events which you can use to store the last opened scene. In the past I stored it in EditorPrefs and then used it at runtime. Nowadays I just use a Button to set the info explicitly.
     
    FelipeCavaco and Joe-Censored like this.
  7. Pandazole

    Pandazole

    Joined:
    Sep 23, 2019
    Posts:
    28
    This is my current solution:


    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEditor.SceneManagement;
    3. using UnityEngine;
    4. using UnityEngine.SceneManagement;
    5. using System.Collections.Generic;
    6.  
    7. [InitializeOnLoad]
    8. public class EditorInitializer
    9. {
    10.     // your first scene path:
    11.     const string firstSceneToLoad = "Assets/Scenes/TheImportantFirstSceneEverChangePath.unity";
    12.     // Editor pref save name, no need to change
    13.     const string activeEditorScene = "PreviousScenePath";
    14.     const string isEditorInitialization = "EditorIntialization";
    15.  
    16.     // The scenes names that you want to do the editor initialization, only these scenes will work,
    17.     // alternatively, you can do the initialization in all scenes by removing this list.
    18.     static List<string> validScenes = new List<string>
    19.     {
    20.         "GameplayScene_1",
    21.         "Level_2",
    22.         "Test Scene"
    23.     };
    24.     // The scenes names that you want to load in addition to the first scene. Loaded in the list order.
    25.     static List<string> extraScenesToLoad = new List<string>
    26.     {
    27.         "ExtraManager_ForGameplayOnly",
    28.         "DeveloperScene_CheatScene_DebugScene_Scene"
    29.     };
    30.  
    31.     static EditorInitializer()
    32.     {
    33.         EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
    34.     }
    35.     static void OnPlayModeStateChanged(PlayModeStateChange state)
    36.     {
    37.         // remove "IsValidScene" method if you want to do the initialization in all scenes.
    38.         if (state == PlayModeStateChange.ExitingEditMode && IsValidScene(validScenes, out string sceneName))
    39.         {
    40.             EditorPrefs.SetString(activeEditorScene, sceneName);
    41.             EditorPrefs.SetBool(isEditorInitialization, true);
    42.             SetStartScene(firstSceneToLoad);
    43.         }
    44.         if (state == PlayModeStateChange.EnteredPlayMode && EditorPrefs.GetBool(isEditorInitialization))
    45.         {
    46.             LoadExtraScenes();
    47.         }
    48.         if(state == PlayModeStateChange.EnteredEditMode)
    49.         {
    50.             EditorPrefs.SetBool(isEditorInitialization, false);
    51.         }
    52.     }
    53.     static void SetStartScene(string scenePath)
    54.     {
    55.         SceneAsset firstSceneToLoad = AssetDatabase.LoadAssetAtPath<SceneAsset>(scenePath);
    56.         if (firstSceneToLoad != null)
    57.             EditorSceneManager.playModeStartScene = firstSceneToLoad;
    58.         else
    59.             Debug.Log("Could not find Scene " + scenePath);
    60.     }
    61.     static void LoadExtraScenes()
    62.     {
    63.         // extra scenes to load
    64.         foreach (string scenePath in extraScenesToLoad)
    65.         {
    66.             SceneManager.LoadScene(scenePath, LoadSceneMode.Additive);
    67.         }
    68.         // the original scene loading
    69.         var prevScene = EditorPrefs.GetString(activeEditorScene);
    70.         SceneManager.LoadScene(prevScene, LoadSceneMode.Additive);
    71.     }
    72.     static bool IsValidScene(List<string> scenesToCheck, out string sceneName)
    73.     {
    74.         sceneName = SceneManager.GetActiveScene().name;
    75.         return scenesToCheck.Contains(sceneName);
    76.     }
    77. }
    78.  
    The scenes referenced here as string names of your scene asset, I handle them differently though.

    This script is meant to load multiple scenes, before loading what is already opened in the editor view, then load it. Mainly for managers and debugging scenes.

    So if you are in scene "level 1" in the editor and press the play button, this will work as(Change scene paths and names though!):

    Loading FirstScene > Loading extra multiple scenes > Loading level 1 scene

    Although is not meant to be just initializing scene 1 within any scene, you can do it by deleting the extra scenes loading.
     
    Last edited: Dec 6, 2023
    ysftulek likes this.
  8. Maraudical

    Maraudical

    Joined:
    Feb 8, 2021
    Posts:
    7
    This is amazing! For anyone else that needs this I have a few recommendations:

    I prefer the valid scenes as a blacklist than a whitelist so that when I hit play from my start scene it doesn't load it twice.
    Code (CSharp):
    1. static List<string> invalidScenes = new List<string>
    2. {
    3.     Path.GetFileNameWithoutExtension(firstSceneToLoad),
    4. };
    5.  
    6. static bool IsValidScene(List<string> scenesToCheck, out string sceneName)
    7. {
    8.     sceneName = SceneManager.GetActiveScene().name;
    9.     return !scenesToCheck.Contains(sceneName);
    10. }
    You'll have to invert the returned bool value in the IsValidScene method too.

    Additionally, when using this editor initializer it will always end up setting the active scene to your start scene (maybe you want this?) but I want it to use the lighting settings of my current scene so after all of the scenes are loaded it needs to set the active scene to the current one again. You can't use coroutines here because its not in a MonoBehaviour but use what you want, I just used UniTask.

    Code (CSharp):
    1. ...
    2. // In OnPlayModeStateChanged method change
    3. LoadExtraScenes();
    4. // to
    5. LoadExtraScenes().Forget();
    6.  
    7.  
    8. static async UniTaskVoid LoadExtraScenes()
    9. {
    10.     // extra scenes to load
    11.     foreach (string scenePath in extraScenesToLoad)
    12.     {
    13.         SceneManager.LoadScene(scenePath, LoadSceneMode.Additive);
    14.     }
    15.     // the original scene loading
    16.     var prevScene = EditorPrefs.GetString(activeEditorScene);
    17.     AsyncOperation async = SceneManager.LoadSceneAsync(prevScene, LoadSceneMode.Additive);
    18.     await UniTask.WaitUntil(() => async.isDone);
    19.     SceneManager.SetActiveScene(SceneManager.GetSceneByName(prevScene));
    20. }
    Then in the Start() method of my custom scene loader that automatically loads the next scene when playing from my first scene I check if the editor is initializing to make sure it doesn't double up the next scene it loads. Note that current scene is just what my custom loader remembers as the active scene to load and unload.
    Code (CSharp):
    1. #if UNITY_EDITOR
    2. using UnityEditor;
    3. #endif
    4.  
    5. // In Start method
    6. #if UNITY_EDITOR
    7. if (EditorPrefs.GetBool("EditorIntialization"))
    8.     currentScene = EditorPrefs.GetString("PreviousScenePath");
    9. else
    10. #endif
    11. Load(startScene, false);
     
    Last edited: Mar 23, 2024
  9. Pandazole

    Pandazole

    Joined:
    Sep 23, 2019
    Posts:
    28
    Yes, true. I do not use baked lighting so this is not an issue for me.

    Currently, I modified my workflow a little bit, and the runtime initializer handles the active scene and the additional scenes, I do not use LoadExtraScenes(); anymore, But still helpful when you need a specific layout to be loaded.

    Still using the basics of this editor initializer since it keeps the previously loaded scene in the editor, plus prevents any logic start working before loading the runtime initializer.