// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved. // This code can only be used under the standard Unity Asset Store End User License Agreement // A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms using Doozy.Runtime.Common; using Doozy.Runtime.Common.Attributes; using Doozy.Runtime.Common.Utils; using Doozy.Runtime.SceneManagement.Events; using Doozy.Runtime.SceneManagement.ScriptableObjects; using Doozy.Runtime.Signals; using UnityEngine; using UnityEngine.SceneManagement; // ReSharper disable MemberCanBePrivate.Global namespace Doozy.Runtime.SceneManagement { /// /// Loads and unloads scenes and has callbacks for when the active scene changed, a scene was loaded and a scene was unloaded. /// [AddComponentMenu("Doozy/Scene Management/Scene Director")] public class SceneDirector : SingletonBehaviour { #if UNITY_EDITOR [UnityEditor.MenuItem("GameObject/Doozy/Scene Management/Scene Director", false, 9)] private static void CreateComponent(UnityEditor.MenuCommand menuCommand) { GameObjectUtils.AddToScene(true, true); } #endif /// Stream category name public const string k_StreamCategory = "SceneManagement"; public const string k_StreamName = nameof(SceneDirector); [ClearOnReload] private static SignalStream s_stream; /// Signal stream for this component type public static SignalStream stream => s_stream ??= SignalsService.GetStream(k_StreamCategory, k_StreamName); /// Reference to the SceneManagement global settings public static SceneManagementSettings settings => SceneManagementSettings.instance; /// Debug flag public bool debug => DebugMode | settings.DebugMode; /// Enable relevant debug messages to be printed to the console public bool DebugMode; [SerializeField] private ActiveSceneChangedEvent OnActiveSceneChanged; /// UnityEvent executed when the active Scene has changed public ActiveSceneChangedEvent onActiveSceneChanged => OnActiveSceneChanged ?? (OnActiveSceneChanged = new ActiveSceneChangedEvent()); [SerializeField] private SceneLoadedEvent OnSceneLoaded; /// UnityEvent executed when a Scene has loaded public SceneLoadedEvent onSceneLoaded => OnSceneLoaded ?? (OnSceneLoaded = new SceneLoadedEvent()); [SerializeField] private SceneUnloadedEvent OnSceneUnloaded; /// UnityEvent executed when a Scene has unloaded public SceneUnloadedEvent onSceneUnloaded => OnSceneUnloaded ?? (OnSceneUnloaded = new SceneUnloadedEvent()); /// Signal receiver private SignalReceiver receiver { get; set; } private void ProcessSignal(Signal signal) { if (!signal.hasValue) return; if (!(signal.valueAsObject is SceneLoaderSignalData data)) return; } protected override void Awake() { base.Awake(); receiver = new SignalReceiver().SetOnSignalCallback(ProcessSignal); stream.ConnectReceiver(receiver); } private void OnEnable() { SceneManager.activeSceneChanged += ActiveSceneChanged; SceneManager.sceneLoaded += SceneLoaded; SceneManager.sceneUnloaded += SceneUnloaded; } private void OnDisable() { SceneManager.activeSceneChanged -= ActiveSceneChanged; SceneManager.sceneLoaded -= SceneLoaded; SceneManager.sceneUnloaded -= SceneUnloaded; } protected override void OnDestroy() { base.OnDestroy(); stream.DisconnectReceiver(receiver); } /// Method called by the SceneManager.activeSceneChanged UnityAction /// Replaced Scene /// Next Scene private void ActiveSceneChanged(Scene current, Scene next) { onActiveSceneChanged?.Invoke(current, next); if (debug) Log($"{ObjectNames.NicifyVariableName(nameof(ActiveSceneChanged))} - Replaced Scene: {current.name} / Next Scene: {next.name}"); } /// Method called by the SceneManager.sceneLoaded UnityAction /// Loaded Scene /// LoadSceneMode used to load the scene private void SceneLoaded(Scene scene, LoadSceneMode mode) { onSceneLoaded?.Invoke(scene, mode); if (debug) Log($"{ObjectNames.NicifyVariableName(nameof(SceneLoaded))} - Scene: {scene.name} / LoadSceneMode: {mode}"); } /// Method called by the SceneManager.sceneUnloaded UnityAction /// Unloaded Scene private void SceneUnloaded(Scene unloadedScene) { onSceneUnloaded?.Invoke(unloadedScene); if (debug) Log($"{ObjectNames.NicifyVariableName(nameof(SceneUnloaded))} - Scene: {unloadedScene.name}"); } /// Create a SceneLoader that loads the Scene asynchronously in the background by its index in Build Settings, then returns a reference to the newly created SceneLoader /// Index of the Scene in the Build Settings to load /// If LoadSceneMode.Single then all current Scenes will be unloaded before loading public static SceneLoader LoadSceneAsync(int sceneBuildIndex, LoadSceneMode loadSceneMode) { if (instance.debug) Log($"{ObjectNames.NicifyVariableName(nameof(LoadSceneAsync))} - sceneBuildIndex: {sceneBuildIndex} / loadSceneMode: {loadSceneMode}", instance); return SceneLoader .GetLoader() .SetSceneBuildIndex(sceneBuildIndex) .SetLoadSceneBy(GetSceneBy.BuildIndex) .SetLoadSceneMode(loadSceneMode) .LoadSceneAsync(); } /// Create a SceneLoader that loads the Scene asynchronously in the background by its name in Build Settings, then returns a reference to the newly created SceneLoader /// Name or path of the Scene to load /// If LoadSceneMode.Single then all current Scenes will be unloaded before loading public static SceneLoader LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode) { if (instance.debug) Log($"{ObjectNames.NicifyVariableName(nameof(LoadSceneAsync))} - sceneName: {sceneName} / loadSceneMode: {loadSceneMode}", instance); return SceneLoader .GetLoader() .SetSceneName(sceneName) .SetLoadSceneBy(GetSceneBy.Name) .SetLoadSceneMode(loadSceneMode) .LoadSceneAsync(); } /// /// Create a SceneLoader that loads the given Scene asynchronously in the background, then returns a reference to the newly created SceneLoader /// /// Scene to load /// If LoadSceneMode.Single then all current Scenes will be unloaded before loading /// public static SceneLoader LoadSceneAsync(Scene scene, LoadSceneMode loadSceneMode) { if (instance.debug) Log($"{ObjectNames.NicifyVariableName(nameof(LoadSceneAsync))} - scene: {scene.name} / loadSceneMode: {loadSceneMode}", instance); return SceneLoader .GetLoader() .LoadSceneAsync(scene, loadSceneMode); } /// Destroys all GameObjects associated with the given Scene and removes the Scene from the SceneManager /// Scene to unload. public static AsyncOperation UnloadSceneAsync(Scene scene) { if (instance.debug) Log($"{ObjectNames.NicifyVariableName(nameof(UnloadSceneAsync))} - scene: {scene.name}", instance); return scene.IsValid() ? SceneManager.UnloadSceneAsync(scene) : null; } /// Destroys all GameObjects associated with the given Scene and removes the Scene from the SceneManager /// Index of the Scene in BuildSettings public static AsyncOperation UnloadSceneAsync(int sceneBuildIndex) { if (instance.debug) Log($"{ObjectNames.NicifyVariableName(nameof(UnloadSceneAsync))} - sceneBuildIndex: {sceneBuildIndex}", instance); return SceneManager .GetSceneByBuildIndex(sceneBuildIndex).IsValid() ? SceneManager.UnloadSceneAsync(sceneBuildIndex) : null; } /// Destroys all GameObjects associated with the given Scene and removes the Scene from the SceneManager /// Name or path of the Scene to unload. public static AsyncOperation UnloadSceneAsync(string sceneName) { if (instance.debug) Log($"{ObjectNames.NicifyVariableName(nameof(UnloadSceneAsync))} - sceneName: {sceneName}", instance); return SceneManager .GetSceneByName(sceneName).IsValid() ? SceneManager.UnloadSceneAsync(sceneName) : null; } // ReSharper disable once MemberCanBePrivate.Global /// Adds SceneDirector to scene and returns a reference to it public static SceneDirector AddToScene(bool selectGameObjectAfterCreation = false) => GameObjectUtils.AddToScene(true, selectGameObjectAfterCreation); private void Log(string message) => Log($"[{name}] {message}", this); private static void Log(string message, Object context) => Debugger.Log($"({nameof(SceneDirector)}) {message}", context); } }