Search code examples
unity-game-enginebuildeditorscenemulti-scene

Multi-scene launching of build, as is, from Unity, how?


Multiscene editing in Unity, blessed be it, permits the launching (via Editor Play mode) of the current scenes, in their current hierarchical state.

However, building and running the project doesn't recognise the current scene setup in the editor, and starts with whatever is set in the Build Settings.

Is there some way to make builds aware of the current editor state of Multi-scene editing hierarchy, and build and run that setup?


Solution

  • 1. Getting the editor scenes into the build settings

    First for collecting the settings you can use an editor script using

    1.a. Update on MenuItem click

    I made it an extra button in the menu since you might not want to have it always automatically.

    using System.Collections.Generic;
    using System.Linq;
    using UnityEditor;
    using UnityEditor.SceneManagement;
    
    public static class UpdateBuildSettigns
    {
        [MenuItem("Example/UpdateBuildSettings")]
        public static void UpdateSettings()
        {
            // get current editor setup
            SceneSetup[] editorScenes = EditorSceneManager.GetSceneManagerSetup();
    
            // filter list e.g. get only scenes with isActive true
            var activeEditorScenes = editorScenes.Where(scene => scene.isLoaded);
    
            // set those scenes as the buildsettings
            List<EditorBuildSettingsScene> editorBuildSettingsScenes = new List<EditorBuildSettingsScene>();
            foreach (var sceneAsset in activeEditorScenes)
            {
                string scenePath = sceneAsset.path;
    
                // ignore unsaved scenes
                if (!string.IsNullOrEmpty(scenePath)) continue;
    
                editorBuildSettingsScenes.Add(new EditorBuildSettingsScene(scenePath, true));
            }
    
            // Set the Build Settings window Scene list
            EditorBuildSettings.scenes = editorBuildSettingsScenes.ToArray();
        }
    }
    

    Updating on menu button

    enter image description here


    1.b. Update automaticly on (un)loading scenes

    If you want it happening automatically you could also add the call as callback to EditorSceneManager.sceneOpened and EditorSceneManager.sceneClosed using InitializeOnLoad and a static constructor to get the callbacks added after recompile or opening the UnityEditor like

    using System.Collections.Generic;
    using System.Linq;
    using UnityEditor;
    using UnityEditor.SceneManagement;
    using UnityEngine.SceneManagement;
    
    [InitializeOnLoad]
    public static class UpdateBuildSettigns
    {
        // ofcourse you still can also call it via menu item
        [MenuItem("Example/UpdateBuildSettings")]
        public static void UpdateSettings()
        {
            //...
        }
    
        static UpdateBuildSettigns()
        {
            // it is always save to remove callbacks even if they are not there
            // makes sure they are always only added once
            //
            // this is a static constructor so actually there should be no
            // callbacks yet ... but .. you never know ;)
            EditorSceneManager.sceneOpened -= OnSceneLoaded;
            EditorSceneManager.sceneClosed -= OnSceneUnloaded;
    
            EditorSceneManager.sceneOpened += OnSceneLoaded;
            EditorSceneManager.sceneClosed += OnSceneUnloaded;
        }
    
        private static void OnSceneUnloaded(Scene current)
        {
            UpdateSettings();
        }
    
        private static void OnSceneLoaded(Scene current, OpenSceneMode mode)
        {
            UpdateSettings();
        }
    }
    

    Using automatic update

    enter image description here


    1.c. Enable/Disable automatic updates

    If you want more control you can also add extra menu entries for enabling and disabling the automatic updates like

    // flag to check if auto-updates are currently enabled
    private static bool isEnabled;
    
    // disable the "EnableAutoUpdate" button if already enabled
    [MenuItem("Example/EnableAutoUpdate", true)]
    private static bool CanEnable()
    {
        return !isEnabled;
    }
    
    // disable the "DisableAutoUpdate" button if already disabled
    [MenuItem("Example/DisableAutoUpdate", true)]
    private static bool CanDisable()
    {
        return isEnabled;
    }
    
    // add callbacks
    [MenuItem("Example/EnableAutoUpdate")]
    private static void EnableAutoUpdate()
    {
        // it is always save to remove callbacks even if they are not there
        // makes sure they are always only added once
        EditorSceneManager.sceneOpened -= OnSceneLoaded;
        EditorSceneManager.sceneClosed -= OnSceneUnloaded;
    
        EditorSceneManager.sceneOpened += OnSceneLoaded;
        EditorSceneManager.sceneClosed += OnSceneUnloaded;
    
        isEnabled = true;
    }
    
    // remove callbacks
    [MenuItem("Example/DisableAutoUpdate")]
    private static void DisableAutoUpdate()
    {
        EditorSceneManager.sceneOpened -= OnSceneLoaded;
        EditorSceneManager.sceneClosed -= OnSceneUnloaded;
    
        isEnabled = false;
    }
    

    Note since this uses the UnityEditor namespace you should either place this script in an Editor folder or use proper pre-processors like

    #if UNITY_EDITOR
    
    // above code here
    
    #endif
    

    2. Loading all scenes from the build settings

    Than later when running the app in the first scene there should be a script responsible for loading all those scenes. Something like e.g.

    // making it a component to make sure it is inside of one scene
    public class SceneLoader : MonoBehaviour
    {
        private void Start()
        {
            var thisScene = SceneManager.GetActiveScene();
    
            // load all scenes
            for(int i = 0; i < SceneManager.sceneCountInBuildSettings; i++)
            {
                // skip if is current scene since we don't want it twice
                if(thisScene.buildIndex == i) continue;
    
                 // Skip if scene is already loaded
                if(SceneManager.GetSceneByBuildIndex(i).IsValid()) continue;
    
                SceneManager.LoadScene(i, LoadSceneMode.Additive);
                // or depending on your usecase
                SceneManager.LoadSceneAsync(i, LoadSceneMode.Additive);
            }
        }
    }
    

    refs: