上一篇文章写了介绍了扩展方法递归查找子物体,单例基类,框架常用的消息机制的简单版本(这个会在加载loading界面用到这个机制,之后会介绍的)。这一篇先把一个简单的AB包管理器介绍一下,至于具体怎么打ab包,自行搜索引擎就行了。
Unity的PackManger提供了AB包打包相关的东西,看官网即可。
AB管理最重要的其实就是依赖项的处理,避过这个坑其实没什么好说的。
一、AB包管理的代码;
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ABManager : MonoBehaviour { private static ABManager instance; public static ABManager Instance { get { if (instance == null) { GameObject go = new GameObject(typeof(ABManager).ToString()); instance= go.AddComponent<ABManager>(); } return instance; } } private AssetBundle single;//单一的总包; private AssetBundleManifest mainfest;//AB包的清单 public string SingleName { get { #if UNITY_STANDALONE MDebug.Log("这是当前平台的ab包名字:STANDALONE"); return "STANDALONE"; #else return "ELSE"; #endif } } private string abPath = ""; public string ABPath { get { if (abPath == "") { #if UNITY_STANDALONE abPath = Application.streamingAssetsPath + "/"; MDebug.Log("当前平台的AB包路径:" + abPath); #else abPath = Application.persistentDataPath + "/"; #endif } return abPath; } } /// <summary> /// 已经加载但没卸载的AB /// </summary> private Dictionary<string, AssetBundle> loadDic = new Dictionary<string, AssetBundle>(); public T LoadAssets<T>(string assetName,string abName) where T : Object { AssetBundle ab = LoadAssetBundle(assetName); if (ab != null) { T asset = ab.LoadAsset<T>(abName); return asset; } return default(T); } public AssetBundle LoadAssetBundle(string abName) { AssetBundle ab = null; if (single == null) { single = AssetBundle.LoadFromFile(ABPath + SingleName); } if (mainfest == null) { mainfest = single.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); } //所有的依赖项 string[] deps = mainfest.GetAllDependencies(abName); for (int i = 0; i < deps.Length; i++) { string depABName = deps[i]; //是否加载过 if (!loadDic.ContainsKey(depABName)) { AssetBundle depAB = AssetBundle.LoadFromFile(ABPath + depABName); loadDic.Add(depABName, depAB); } } if(!loadDic.TryGetValue(abName,out ab)) { ab = AssetBundle.LoadFromFile(ABPath + abName); loadDic.Add(abName, ab); } return ab; } //卸载 public void UnLoadAB(string abName,bool unloadAllObjects = false) { AssetBundle ab = null; if(loadDic.TryGetValue(abName,out ab)) { ab.Unload(unloadAllObjects); loadDic.Remove(abName); Debug.Log("卸载了" + abName); } } public void UnLoadAllAB(bool unloadAllObjects = false) { foreach (var item in loadDic.Values) { item.Unload(unloadAllObjects); } loadDic.Clear(); } }
注意的唯一一点:就是卸载AB包的时候,传入ab.Unload(true);和ab.Unload(false);的区别;然后就是加载某个资源可能存在依赖项的问题,所以需要递归加载,知道某个资源所有的依赖全部加载完成。否则的话会造成部分资源的丢失。具体的APi我就不多介绍了,自己去查看unity官网的手册就可以了。而且介绍这些东西的人太多太多了。。。
接下来就是介绍如何加载状态,也就是场景的一部分逻辑了;
二、加载场景的一些状态处理;
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; using System; public delegate void LoadSceneComplete(params object[] args); public class LoadSceneMsg : AutoSingleton<LoadSceneMsg> { AsyncOperation async = null; IEnumerator loadScene = null; public void LoadScene(string sceneName,LoadSceneComplete loadComp,params object[] args) { loadScene = IELoadScene(sceneName, loadComp, args); StartCoroutine(loadScene); } IEnumerator IELoadScene(string sceneName,LoadSceneComplete callBack,params object[] args) { async = SceneManager.LoadSceneAsync(sceneName); async.allowSceneActivation = false; //进度条加载的值:如果不使用while循环的形式进行传参的话,那么progress的值是无法动态的传入的; while (async.progress < 0.9f) { MDebug.Log("async.progress" + async.progress); //这个执行的方法,实在Loadingpanel的代码中注册的。之后我会贴出来Loading的代码; MessageManager.DoFunc("loading", async.progress); yield return null; } async.allowSceneActivation = true; yield return new WaitForSeconds(0.5f); yield return async; MDebug.Log("加载场景" + sceneName); MessageManager.RemoveFunc("loading"); callBack(args); Resources.UnloadUnusedAssets(); StopCoroutine(loadScene); async = null; loadScene = null; GC.Collect(); } }
其实这里面需要介绍的不多,就是一个使用协程去加载了一个场景。至于具体如何调用上面的这两个类,会在状态的管理类中看到。要点,容易踩坑的地方就是想要动态获取加载进度的也就是async.progress的值,必须放在循环里。否则直接传过去,只会是固定值。关于所有的UI加载的界面,后面我会详细贴出来,目前还是把状态管理这方面的东西弄完;
三、最核心的部分,控制整个状态(场景)加载的流程。这里面我用了反射,如果有思路的小伙伴可以把这一整个所有的框架都用反射的形式,实现完全的代码和预制体分离。
using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Reflection; public class MySceneManager : AutoSingleton<MySceneManager> { //管理加载的过的状态,实际上看自己需求决定是否要这个。 private Dictionary<string, BaseScene> sceneDic = new Dictionary<string, BaseScene>(); //游戏当前所在的场景 private BaseScene m_currentScene = null; private void Start() { //这个我为了演示方便,直接就在这里声明周期函数直接调用了加载方法; LoadScene("LoginStateScene"); } //核心逻辑处理; public void LoadScene(string sceneName) { //加载场景; BaseScene target = null; //如果字典中没有,那说明这个没加载过或者被卸载了。 if (!sceneDic.TryGetValue(sceneName, out target)) { //反射,得到管理该场景类名字,通过类名得到类; target = Assembly.GetExecutingAssembly().CreateInstance(sceneName) as BaseScene; sceneDic.Add(sceneName, target); } if (target == null) { MDebug.Log("场景不存在:" + sceneName); } target = sceneDic[sceneName]; //执行切换场景的工作,就是从m_currentScene——>变成想要切换的场景; ChangeScene(target); //注意这里:把场景中的函数之一:LoadComplete作为参数穿进去了。详细看F12跳转到这个函数具体的实现逻辑。 LoadSceneMsg.Instance.LoadScene(sceneName, target.LoadComplete); } public void ChangeScene(BaseScene scene) { //当是进入游戏第一个场景的时候,也就是游戏刚开始,不存在场景,所以就是null。例如一进入游戏,直接loginScene,那么m_currentScene就不存在了。但是当从LoginScene->CityScene的时候,m_currentScene就是LogiScene,就需要执行执行 m_currentScene.Stop();了 if (m_currentScene != null) { m_currentScene.Stop(); } m_currentScene = scene; m_currentScene.Start(); } }
四、MDebug
有的可能会疑惑MDebug这个东西怎么来的。我之前忘记说了。这个是自己写的一个类,就是把Debug封装了一下,就是方便关闭Debug而已。实际上用处不大,缺点就是在Console控制台点击显示文字不能直接跳转到当前代码行(可以通过堆栈查看),优点就是不用一行一行的去删Debug信息。直接控制isDebug的值,就可以控制是否显示所有的Log信息了,页游开发。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class MDebug { static bool isDebug = true; public static void Log(object content) { if (isDebug) { Debug.Log(content); } } public static void LogError(object content) { if (isDebug) { Debug.LogError(content); } } public static void LogWranning(object centent) { if (isDebug) { Debug.LogWarning(centent); } } }
五、总结:
到这里位置,所有控制状态的类,如果没落下的话,应该是简短的说完了。其实难度不高,也都很简单。其中有部分小坑,看明白这些代码的人可以自己轻松解决,但是影响的不大。
总体来说核心内容就在这个文章里。一定要理清楚他的逻辑,是怎么调用 各个方法实现场景(状态)的切换的。看明白了就很简单。下面就是关于UI的处理的,其实本质上和他一样的,没什么太大的区别。