using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; namespace AssetBundleMaster.AssetLoad { using AssetBundleMaster.Extention; using AssetBundleMaster.ContainerUtilities; public sealed class LevelLoader : AssetLoader { public const string LevelExt = ".unity"; public string levelLoadPathExt { get; private set; } public string levelFullLoadPath { get; private set; } public string levelName { get { return assetName; } } public UnityEngine.SceneManagement.LoadSceneMode loadSceneMode { get; private set; } public Scene scene { get; private set; } public bool loadLevelStarted { get; private set; } // if started, cant be stop // this global cache all level loaders that private static readonly Dictionary> ms_sceneLoadedCalls = new Dictionary>(); private static Dictionary ms_loadedScenes = new Dictionary(); public System.Action onSceneUnloaded; // these BuildSetting scenes is the scenes added to BuildSettings for point out unique scene loading in Resources Mode private static readonly Dictionary ms_allBuildSettingScenes = new Dictionary(); // static constructor static LevelLoader() { UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded; UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded; // this take a little over head in editor mode if(GameConfig.Instance.resourceLoadMode == ResourcesLoadMode.Resources) { const string ResourcesFolderName = "Resources/"; const string AssetFolderName = "Assets/"; int sceneCount = UnityEngine.SceneManagement.SceneManager.sceneCountInBuildSettings; for(int i = 0; i < sceneCount; i++) { var levelAssetPath = Utility.LeftSlash(UnityEngine.SceneManagement.SceneUtility.GetScenePathByBuildIndex(i)); // level name is not unique string levelName = System.IO.Path.GetFileNameWithoutExtension(levelAssetPath); if(ms_allBuildSettingScenes.ContainsKey(levelName) == false) { ms_allBuildSettingScenes[levelName] = levelAssetPath; } // relative is also not a unique path via Resources mode can have same file in diferent folder string relativePath = levelAssetPath; int indexResources = levelAssetPath.LastIndexOf(ResourcesFolderName); if(indexResources >= 0) { relativePath = levelAssetPath.Substring(indexResources + ResourcesFolderName.Length); } if(ms_allBuildSettingScenes.ContainsKey(relativePath) == false) { ms_allBuildSettingScenes[relativePath] = levelAssetPath; } // scene path showing in BuildSettings window is unique string buildSettingPath = levelAssetPath; int indexAsset = levelAssetPath.IndexOf(AssetFolderName); if(indexAsset >= 0) { buildSettingPath = levelAssetPath.Substring(indexAsset + AssetFolderName.Length); } if(ms_allBuildSettingScenes.ContainsKey(buildSettingPath) == false) { ms_allBuildSettingScenes[buildSettingPath] = levelAssetPath; } } } } public LevelLoader(string loadPath, string levelName, LoadSceneMode loadSceneMode, AssetSource assetSource, AssetBundleLoader assetBundleLoader) : base(loadPath, levelName, typeof(UnityEngine.Object), assetSource, assetBundleLoader, true) { this.loadLevelStarted = false; this.loadSceneMode = loadSceneMode; this.levelLoadPathExt = this.loadPath.EndsWith(LevelExt) ? this.loadPath : (this.loadPath + LevelExt); this.levelFullLoadPath = levelLoadPathExt; switch(GameConfig.Instance.resourceLoadMode) { case ResourcesLoadMode.Resources: { this.levelFullLoadPath = this.loadPath; var sceneLoadPath = ms_allBuildSettingScenes.TryGetValue(this.levelLoadPathExt) ?? ms_allBuildSettingScenes.TryGetValue(levelName); if(string.IsNullOrEmpty(sceneLoadPath) == false) { this.levelFullLoadPath = sceneLoadPath; } } break; #if UNITY_EDITOR case ResourcesLoadMode.AssetBundle_EditorTest: case ResourcesLoadMode.AssetDataBase_Editor: #endif case ResourcesLoadMode.AssetBundle_StreamingAssets: case ResourcesLoadMode.AssetBundle_PersistentDataPath: { this.levelFullLoadPath = string.Concat(GameConfig.Instance.assetBuildRoot, "/", this.levelLoadPathExt); } break; } ms_sceneLoadedCalls.GetValue(this.levelFullLoadPath, () => { return ObjectPool.GlobalAllocator>.Allocate(); }).Add(this); } #region Main Funcs /// /// try to stop load level if not yet start loading /// /// public bool StopLoadLevel() { if(false == loadLevelStarted) { RemoveLoadedCall(); return true; } return false; } /// /// unload scene, we have 3 cases when to call unload a scene: /// 1. not yet start loading, this is only happen before assetbundle loaded /// 2. is loading scene, it can not be stoped but call unload scene after scene loaded /// 3. scene loaded, call unload scene /// /// public void UnloadScene(System.Action unloadedCall = null) { if(unloadedCall != null) { onSceneUnloaded += unloadedCall; } StopLoadLevel(); if(loadLevelStarted && isDone == false) { Debug.Log("UnloadScene : scene is in loading, unload will call after scene loaded. Frame : " + Time.frameCount); // is loading scene ... completed.PushCall((_loader) => { Unload(); // onSceneUnloaded call by global }); } else { Unload(); if(false == loadLevelStarted) { if(onSceneUnloaded != null) { onSceneUnloaded.Invoke(new Scene()); // not yet start load scene, no scene info } } } } #endregion #region Override Funcs public override void LoadRequest(LoadThreadMode loadAssetMode, ThreadPriority priority = ThreadPriority.Normal) { this.loadAssetMode = loadAssetMode; if(isDone == false) { if(assetBundleLoader != null) { base.LoadRequest(loadAssetMode, priority); // load asset bundle as assets } else { #if UNITY_EDITOR LoadLevel_Editor(this.levelFullLoadPath, this.loadSceneMode, loadAssetMode); #else LoadLevel(this.levelName, this.loadSceneMode, this.loadAssetMode); #endif } } } protected override void OnAssetBundleLoaded() { if(assetBundleLoader != null && assetBundleLoader.isDone) { LoadLevel(this.levelName, this.loadSceneMode, this.loadAssetMode); } } public override bool Unload() { RemoveLoadedCall(); if(isDone && scene.isLoaded && scene.IsValid()) { UnityEngine.SceneManagement.SceneManager.UnloadSceneAsync(scene); } base.Unload(); return isDone; } public void Unload(bool unloadScene) { if(unloadScene) { Unload(); } else { base.Unload(); } } #endregion #region Help Funcs // load level in runtime, Notice: AssetBundle Mode don't need to add scenes to Buildsettings but Resources Mode need it private void LoadLevel(string levelName, LoadSceneMode loadSceneMode, LoadThreadMode loadMode) { this.loadLevelStarted = true; switch(GameConfig.Instance.resourceLoadMode) { case ResourcesLoadMode.Resources: { var index = UnityEngine.SceneManagement.SceneUtility.GetBuildIndexByScenePath(levelFullLoadPath); if(index >= 0) { switch(loadMode) { case LoadThreadMode.Synchronous: { UnityEngine.SceneManagement.SceneManager.LoadScene(index, loadSceneMode); } break; case LoadThreadMode.Asynchronous: { UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(index, loadSceneMode); // the load cant be stop right here } break; } } } break; default: { switch(loadMode) { case LoadThreadMode.Synchronous: { UnityEngine.SceneManagement.SceneManager.LoadScene(levelName, loadSceneMode); } break; case LoadThreadMode.Asynchronous: { UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(levelName, loadSceneMode); // the load cant be stop right here } break; } } break; } } #if UNITY_EDITOR // load level in editor mode, dont need to add scene to Buildsettings private void LoadLevel_Editor(string editorLevelLoadPath, LoadSceneMode loadSceneMode, LoadThreadMode loadMode) { this.loadLevelStarted = true; switch(loadSceneMode) { case UnityEngine.SceneManagement.LoadSceneMode.Single: { if(loadMode == LoadThreadMode.Synchronous) { #if UNITY_2018_1_OR_NEWER UnityEditor.SceneManagement.EditorSceneManager.LoadSceneInPlayMode(editorLevelLoadPath, new LoadSceneParameters(LoadSceneMode.Single)); #else UnityEditor.EditorApplication.LoadLevelInPlayMode(editorLevelLoadPath); #endif } else { #if UNITY_2018_1_OR_NEWER UnityEditor.SceneManagement.EditorSceneManager.LoadSceneAsyncInPlayMode(editorLevelLoadPath, new LoadSceneParameters(LoadSceneMode.Single)); #else UnityEditor.EditorApplication.LoadLevelAsyncInPlayMode(editorLevelLoadPath); #endif } } break; case UnityEngine.SceneManagement.LoadSceneMode.Additive: { if(loadMode == LoadThreadMode.Synchronous) { #if UNITY_2018_1_OR_NEWER UnityEditor.SceneManagement.EditorSceneManager.LoadSceneInPlayMode(editorLevelLoadPath, new LoadSceneParameters(LoadSceneMode.Additive)); #else UnityEditor.EditorApplication.LoadLevelAdditiveInPlayMode(editorLevelLoadPath); #endif } else { #if UNITY_2018_1_OR_NEWER UnityEditor.SceneManagement.EditorSceneManager.LoadSceneAsyncInPlayMode(editorLevelLoadPath, new LoadSceneParameters(LoadSceneMode.Additive)); #else UnityEditor.EditorApplication.LoadLevelAdditiveAsyncInPlayMode(editorLevelLoadPath); #endif } } break; } } #endif // remove loaded calls in global private void RemoveLoadedCall() { var loadingList = ms_sceneLoadedCalls.TryGetValue(this.levelFullLoadPath); if(loadingList != null) { loadingList.Remove(this); } } // global scene loaded call private static void OnSceneLoaded(Scene scene, LoadSceneMode loadSceneMode) { WeakList levelLoaders = null; string sceneInfo = string.Empty; foreach(var data in ms_sceneLoadedCalls) { if(scene.path.EndsWith(data.Key)) { sceneInfo = data.Key; levelLoaders = data.Value; break; } } if(levelLoaders != null) { LevelLoader levelLoader = null; if(levelLoaders.Count > 0) { levelLoader = levelLoaders[0]; levelLoaders.RemoveAt(0); if(levelLoader != null) { levelLoader.scene = scene; levelLoader.loadState = LoadState.Loaded; } } if(levelLoaders.Count == 0) { ms_sceneLoadedCalls.Remove(sceneInfo); ObjectPool.GlobalAllocator>.DeAllocate(levelLoaders); } ms_loadedScenes[scene] = levelLoader; if(levelLoader != null) { levelLoader.Call(); } } } // global scene unloaded call private static void OnSceneUnloaded(Scene scene) { var levelLoader = ms_loadedScenes.TryGetValue(scene); if(levelLoader != null) { ms_loadedScenes.Remove(scene); if(levelLoader.onSceneUnloaded != null) { levelLoader.onSceneUnloaded.Invoke(scene); } } } #endregion } }