using System; using System.Collections; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; namespace NeoSaveGames.SceneManagement { [CreateAssetMenu(fileName = "FpsManager_NeoSceneManager", menuName = "NeoFPS/Managers/Scene Manager", order = NeoFPS.NeoFpsMenuPriorities.manager_scene)] [HelpURL("https://docs.neofps.com/manual/savegamesref-so-scenemanager.html")] public class NeoSceneManager : NeoFPS.NeoFpsManager { private static RuntimeBehaviour s_ProxyGameObject = null; private static bool s_Busy = false; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void LoadNeoSceneManager() { GetInstance("FpsManager_NeoSceneManager"); } protected override void Initialise() { s_ProxyGameObject = GetBehaviourProxy(); } public override bool IsValid() { return true; } [SerializeField, Tooltip("The loading screen scene to load by default if a custom one is not specified.")] private int m_DefaultLoadingScreenIndex = -1; [SerializeField, Tooltip("The minimum amount of time the loading screen should be shown for. This prevents the loading screen from appearing for fractions of a second. Useful if you show tips.")] private float m_MinLoadScreenTime = 2f; [Header("Scene Load Events")] [SerializeField] private SceneLoadEvent m_OnSceneLoaded = new SceneLoadEvent(); [SerializeField] private UnityEvent m_PreSceneActivation = new UnityEvent(); [SerializeField] private UnityEvent m_OnSceneLoadFailed = new UnityEvent(); enum LoadMode { Index, Name, Neither } [Serializable] public class SceneLoadEvent : UnityEvent { } public class RuntimeBehaviour : MonoBehaviour { } public static int defaultLoadingScreenIndex { get { if (instance != null) return instance.m_DefaultLoadingScreenIndex; else { Debug.LogWarning("NeoSceneManager instance not found"); return -1; } } } public static event UnityAction onSceneLoaded { add { if (instance != null) instance.m_OnSceneLoaded.AddListener(value); } remove { if (instance != null) instance.m_OnSceneLoaded.RemoveListener(value); } } public static event UnityAction preSceneActivation { add { if (instance != null) instance.m_PreSceneActivation.AddListener(value); } remove { if (instance != null) instance.m_PreSceneActivation.RemoveListener(value); } } public static event UnityAction onSceneLoadFailed { add { if (instance != null) instance.m_OnSceneLoadFailed.AddListener(value); } remove { if (instance != null) instance.m_OnSceneLoadFailed.RemoveListener(value); } } private void OnValidate() { m_MinLoadScreenTime = Mathf.Clamp(m_MinLoadScreenTime, 0f, 60f); } public static bool isSceneValid(string sceneName) { return sceneName != null && Application.CanStreamedLevelBeLoaded(sceneName); } public static bool isSceneValid(int sceneIndex) { return Application.CanStreamedLevelBeLoaded(sceneIndex); } #region OVERRIDES public static void LoadScene(string sceneName) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(sceneName, -1, null, defaultLoadingScreenIndex, null, null)); } public static void LoadScene(int sceneIndex) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(null, sceneIndex, null, defaultLoadingScreenIndex, null, null)); } public static void LoadScene(string sceneName, string loadingSceneName) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(sceneName, -1, loadingSceneName, -1, null, null)); } public static void LoadScene(int sceneIndex, int loadingIndex) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(null, sceneIndex, null, loadingIndex, null, null)); } public static void LoadScene(string sceneName, int loadingIndex) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(sceneName, -1, null, loadingIndex, null, null)); } public static void LoadScene(int sceneIndex, string loadingSceneName) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(null, sceneIndex, loadingSceneName, -1, null, null)); } public static void LoadScene(string sceneName, Action onComplete) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(sceneName, -1, null, defaultLoadingScreenIndex, null, onComplete)); } public static void LoadScene(int sceneIndex, Action onComplete) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(null, sceneIndex, null, defaultLoadingScreenIndex, null, onComplete)); } public static void LoadScene(string sceneName, string loadingSceneName, Action onComplete) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(sceneName, -1, loadingSceneName, -1, null, onComplete)); } public static void LoadScene(int sceneIndex, int loadingIndex, Action onComplete) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(null, sceneIndex, null, loadingIndex, null, onComplete)); } public static void LoadScene(string sceneName, int loadingIndex, Action onComplete) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(sceneName, -1, null, loadingIndex, null, onComplete)); } public static void LoadScene(int sceneIndex, string loadingSceneName, Action onComplete) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(null, sceneIndex, loadingSceneName, -1, null, onComplete)); } public static void LoadScene(string sceneName, Action activationCallback) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(sceneName, -1, null, defaultLoadingScreenIndex, activationCallback, null)); } public static void LoadScene(int sceneIndex, Action activationCallback) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(null, sceneIndex, null, defaultLoadingScreenIndex, activationCallback, null)); } public static void LoadScene(string sceneName, string loadingSceneName, Action activationCallback) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(sceneName, -1, loadingSceneName, -1, activationCallback, null)); } public static void LoadScene(int sceneIndex, int loadingIndex, Action activationCallback) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(null, sceneIndex, null, loadingIndex, activationCallback, null)); } public static void LoadScene(string sceneName, int loadingIndex, Action activationCallback) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(sceneName, -1, null, loadingIndex, activationCallback, null)); } public static void LoadScene(int sceneIndex, string loadingSceneName, Action activationCallback) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(null, sceneIndex, loadingSceneName, -1, activationCallback, null)); } public static void LoadScene(string sceneName, Action activationCallback, Action onComplete) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(sceneName, -1, null, defaultLoadingScreenIndex, activationCallback, onComplete)); } public static void LoadScene(int sceneIndex, Action activationCallback, Action onComplete) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(null, sceneIndex, null, defaultLoadingScreenIndex, activationCallback, onComplete)); } public static void LoadScene(string sceneName, string loadingSceneName, Action activationCallback, Action onComplete) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(sceneName, -1, loadingSceneName, -1, activationCallback, onComplete)); } public static void LoadScene(int sceneIndex, int loadingIndex, Action activationCallback, Action onComplete) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(null, sceneIndex, null, loadingIndex, activationCallback, onComplete)); } public static void LoadScene(string sceneName, int loadingIndex, Action activationCallback, Action onComplete) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(sceneName, -1, null, loadingIndex, activationCallback, onComplete)); } public static void LoadScene(int sceneIndex, string loadingSceneName, Action activationCallback, Action onComplete) { if (instance != null && !s_Busy) s_ProxyGameObject.StartCoroutine(LoadSceneInternal(null, sceneIndex, loadingSceneName, -1, activationCallback, onComplete)); } #endregion static IEnumerator LoadSceneInternal(string sceneName, int sceneIndex, string loadingSceneName, int loadingSceneIndex, Action activationCallback, Action onComplete) { s_Busy = true; // Check how to load the scene LoadMode sceneLoadMode = LoadMode.Neither; if (isSceneValid(sceneIndex)) sceneLoadMode = LoadMode.Index; else { if (isSceneValid(sceneName)) sceneLoadMode = LoadMode.Name; } if (sceneLoadMode == LoadMode.Neither) { Debug.LogError(string.Format("Attempting to load invalid scene. Check it exists and is added to the build options.", sceneName)); instance.m_OnSceneLoadFailed.Invoke(); if (onComplete != null) onComplete(); } else { // Check how to load the loading overlay scene LoadMode overlayLoadMode = LoadMode.Neither; if (isSceneValid(loadingSceneIndex)) overlayLoadMode = LoadMode.Index; else { if (isSceneValid(loadingSceneName)) overlayLoadMode = LoadMode.Name; } if (overlayLoadMode == LoadMode.Neither) { Debug.LogError("\"Loading\" scene is not valid. Reverting to standard SceneManager."); AsyncOperation op = null; if (sceneLoadMode == LoadMode.Index) op = SceneManager.LoadSceneAsync(sceneIndex, LoadSceneMode.Single); else op = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single); while (!op.isDone) yield return null; // Signal completion if (onComplete != null) onComplete(); } else { var startTime = Time.realtimeSinceStartup; AsyncOperation op = null; Scene loadingScene = new Scene(); // Open "loading" scene (immediate) if (overlayLoadMode == LoadMode.Index) { op = SceneManager.LoadSceneAsync(loadingSceneIndex, LoadSceneMode.Single); loadingScene = SceneManager.GetSceneByBuildIndex(loadingSceneIndex); } else { op = SceneManager.LoadSceneAsync(loadingSceneName, LoadSceneMode.Single); loadingScene = SceneManager.GetSceneByName(loadingSceneName); } while (!op.isDone) yield return null; // Load the main scene in the background (async) Scene scene = new Scene(); if (sceneLoadMode == LoadMode.Index) { op = SceneManager.LoadSceneAsync(sceneIndex, LoadSceneMode.Additive); scene = SceneManager.GetSceneByBuildIndex(sceneIndex); } else { op = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive); //sceneName = sceneName.Substring(7, sceneName.Length - 13); scene = SceneManager.GetSceneByName(sceneName); if (!scene.IsValid()) scene = SceneManager.GetSceneByPath(sceneName); } SceneManager.sceneLoaded += OnSceneLoaded; // Prevent activation and wait for pre-activation to complete op.allowSceneActivation = false; while (op.progress < 0.9f) yield return null; // Wait for minimum load time while (Time.realtimeSinceStartup < startTime + instance.m_MinLoadScreenTime) yield return null; // Invoke pre-activation event instance.m_PreSceneActivation.Invoke(); // Allow activation and wait for complete op.allowSceneActivation = true; while (!op.isDone) yield return null; // Set the scene to active and signal activation SceneManager.SetActiveScene(scene); if (activationCallback != null) activationCallback(scene); #if UNITY_2019_3_OR_NEWER // Rebuild light-probes LightProbes.Tetrahedralize(); #endif // Unload the "loading" scene op = SceneManager.UnloadSceneAsync(loadingScene.buildIndex); if (op == null) { Debug.LogError("Can't unload \"Loading\" scene, because the main scene failed to load."); // Invoke failed event instance.m_OnSceneLoadFailed.Invoke(); } else { while (!op.isDone) yield return null; // Invoke succeeded event instance.m_OnSceneLoaded.Invoke(scene.buildIndex); } // Signal completion if (onComplete != null) onComplete(); } } s_Busy = false; } static void OnSceneLoaded(Scene s, LoadSceneMode mode) { SceneManager.SetActiveScene(s); SceneManager.sceneLoaded -= OnSceneLoaded; } } }