using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; namespace Unity.BossRoom.Editor { /// /// Class that permits auto-loading a bootstrap scene when the editor switches play state. This class is /// initialized when Unity is opened and when scripts are recompiled. This is to be able to subscribe to /// EditorApplication's playModeStateChanged event, which is when we wish to open a new scene. /// /// /// A critical edge case scenario regarding NetworkManager is accounted for here. /// A NetworkObject's GlobalObjectIdHash value is currently generated in OnValidate() which is invoked during a /// build and when the asset is loaded/viewed in the editor. /// If we were to manually open Bootstrap scene via EditorSceneManager.OpenScene(...) as the editor is exiting play /// mode, Bootstrap scene would be entering play mode within the editor prior to having loaded any assets, meaning /// NetworkManager itself has no entry within the AssetDatabase cache. As a result of this, any referenced Network /// Prefabs wouldn't have any entry either. /// To account for this necessary AssetDatabase step, whenever we're redirecting from a new scene, or a scene /// existing in our EditorBuildSettings, we forcefully stop the editor, open Bootstrap scene, and re-enter play /// mode. This provides the editor the chance to create AssetDatabase cache entries for the Network Prefabs assigned /// to the NetworkManager. /// If we are entering play mode directly from Bootstrap scene, no additional steps need to be taken and the scene /// is loaded normally. /// [InitializeOnLoad] public class SceneBootstrapper { const string k_PreviousSceneKey = "PreviousScene"; const string k_ShouldLoadBootstrapSceneKey = "LoadBootstrapScene"; const string k_LoadBootstrapSceneOnPlay = "Boss Room/Load Bootstrap Scene On Play"; const string k_DoNotLoadBootstrapSceneOnPlay = "Boss Room/Don't Load Bootstrap Scene On Play"; const string k_TestRunnerSceneName = "InitTestScene"; static bool s_RestartingToSwitchScene; static string BootstrapScene => EditorBuildSettings.scenes[0].path; // to track where to go back to static string PreviousScene { get => EditorPrefs.GetString(k_PreviousSceneKey); set => EditorPrefs.SetString(k_PreviousSceneKey, value); } static bool ShouldLoadBootstrapScene { get { if (!EditorPrefs.HasKey(k_ShouldLoadBootstrapSceneKey)) { EditorPrefs.SetBool(k_ShouldLoadBootstrapSceneKey, true); } return EditorPrefs.GetBool(k_ShouldLoadBootstrapSceneKey, true); } set => EditorPrefs.SetBool(k_ShouldLoadBootstrapSceneKey, value); } static SceneBootstrapper() { EditorApplication.playModeStateChanged += EditorApplicationOnplayModeStateChanged; } [MenuItem(k_LoadBootstrapSceneOnPlay, true)] static bool ShowLoadBootstrapSceneOnPlay() { return !ShouldLoadBootstrapScene; } [MenuItem(k_LoadBootstrapSceneOnPlay)] static void EnableLoadBootstrapSceneOnPlay() { ShouldLoadBootstrapScene = true; } [MenuItem(k_DoNotLoadBootstrapSceneOnPlay, true)] static bool ShowDoNotLoadBootstrapSceneOnPlay() { return ShouldLoadBootstrapScene; } [MenuItem(k_DoNotLoadBootstrapSceneOnPlay)] static void DisableDoNotLoadBootstrapSceneOnPlay() { ShouldLoadBootstrapScene = false; } static void EditorApplicationOnplayModeStateChanged(PlayModeStateChange playModeStateChange) { if (IsTestRunnerActive()) { return; } if (!ShouldLoadBootstrapScene) { return; } if (s_RestartingToSwitchScene) { if (playModeStateChange == PlayModeStateChange.EnteredPlayMode) { // for some reason there's multiple start and stops events happening while restarting the editor playmode. We're making sure to // set stoppingAndStarting only when we're done and we've entered playmode. This way we won't corrupt "activeScene" with the multiple // start and stop and will be able to return to the scene we were editing at first s_RestartingToSwitchScene = false; } return; } if (playModeStateChange == PlayModeStateChange.ExitingEditMode) { // cache previous scene so we return to this scene after play session, if possible PreviousScene = EditorSceneManager.GetActiveScene().path; if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) { // user either hit "Save" or "Don't Save"; open bootstrap scene if (!string.IsNullOrEmpty(BootstrapScene) && System.Array.Exists(EditorBuildSettings.scenes, scene => scene.path == BootstrapScene)) { var activeScene = EditorSceneManager.GetActiveScene(); s_RestartingToSwitchScene = activeScene.path == string.Empty || !BootstrapScene.Contains(activeScene.path); // we only manually inject Bootstrap scene if we are in a blank empty scene, // or if the active scene is not already BootstrapScene if (s_RestartingToSwitchScene) { EditorApplication.isPlaying = false; // scene is included in build settings; open it EditorSceneManager.OpenScene(BootstrapScene); EditorApplication.isPlaying = true; } } } else { // user either hit "Cancel" or exited window; don't open bootstrap scene & return to editor EditorApplication.isPlaying = false; } } else if (playModeStateChange == PlayModeStateChange.EnteredEditMode) { if (!string.IsNullOrEmpty(PreviousScene)) { EditorSceneManager.OpenScene(PreviousScene); } } } static bool IsTestRunnerActive() { return EditorSceneManager.GetActiveScene().name.StartsWith(k_TestRunnerSceneName); } } }