赞
踩
又来力,又是新的一篇总结性质的文章,这次接上篇热更新来讲一讲热更新过程中的重要角色AssetBundle,也就是ab包。
AssetBundle是将资源使用Unity提供的一种用于存储资源的压缩格式打包后的集合,它可以存储任何一种Unity可以识别的资源,如模型,纹理图,音频,场景等资源。也可以加载开发者自定义的二进制文件。他们的文件类型是.assetbundle/.unity3d,他们先前被设计好,很容易就下载到我们的游戏或者场景当中。
主要优点:AB包可以显著减少初始包体大小,在运行时动态加载卸载(即用即加载)
首先,先讲讲游戏中AB包使用的大致流程:
可以看到,AB包的使用大致分为创建打包、下载、加载、卸载这四个主要步骤,接下来结合代码来详细讲讲这四个步骤具体实现。
注意,只有在Assets目录下的资源文件才可以用来创建AB包,在Unity中具体操作如下:
首先选中Assets/Materials/Black这个材质
接着可以在Inspector窗口最下面看到这样的界面,下边AssetBundle一行中左边是具体的AB包名(固定为小写),右边是定义后缀。
在定义好之后,接下来通过代码来进行AssetBundle打包。
示例:
BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
打包过程中AB包可以通过设置BuildAssetBundleOptions来控制压缩方式,Unity共提供了三种压缩方式:
打包完成后会生成多个文件,具体到文中的例子来说会生成下面四个文件,其中.manifest文件可以看作是AB包的配置文件。其中AssetBundles包为主包,和父文件夹名相同。
将AB包从服务器下载到本地的过程,这个可以合并入加载流程,具体细节留到后续再进行补充
这个过程总共分为两步,首先需要先加载AB包,然后再从AB包中加载资源
2.3.1 加载AB包
1、一般来说,只要有可能,就应该使用AssetBundle.LoadFromFile。这个API在速度、磁盘使用和运行时内存使用方面是最有效的。
2、对于必须下载或热更新AssetBundles的项目,强烈建议对使用Unity5.3或更高版本的项目使用UnityWebRequest,对于使用Unity5.2或更老版本的项目使用WWW.LoadFromCacheOrDownload。
3、当使用UnityWebRequest或WWW.LoadFromCacheOrDownload时,要确保下载程序代码在加载AssetBundle后正确地调用Dispose。另外,C#的using语句是确保WWW或UnityWebRequest被安全处理的最方便的方法。
4、对于需要独特的、特定的缓存或下载需求的大项目,可以考虑使用自定义的下载器。编写自定义下载程序是一项重要并且复杂的任务,任何自定义的下载程序都应该与AssetBundle.LoadFromFile保持兼容
5、注意不能多次加载相同的AB包
2.3.2 从AB包中加载资源
具体到特定AB包的API
这些API的同步版本总是比异步版本快至少一个帧(其实是因为异步版本为了确保异步,都至少延迟了1帧),异步加载每帧会加载多个对象,直到他们的时间切片切出
2.3.3 AssetBundle依赖加载问题
具体情境:当A包中的obj1引用了B包中的obj2的时候,单独加载A包后再加载obj1会存在缺少相应的引用的问题,所以这个时候需要将B包也加载进来。注意,这里并不需要在加载A包之前提前加载B包,也不需要在B包中显式加载obj2,只需要在从A包中加载obj1之前加载B包即可。
常用的解决办法是通过主包的固定文件中的依赖信息来加载依赖包,具体代码如下:
//依赖加载问题 //1.加载主包 AssetBundle abMain = AssetBundle.LoadFromFile("AssetBundles/AssetBundles"); //2.加载主包中的配置文件 AssetBundleManifest abMainManifest = abMain.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); //3.从配置文件中获取AB包prefab的依赖信息 var dependencies = abMainManifest.GetAllDependencies("prefab"); //4.加载依赖包 foreach (var str in dependencies) { AssetBundle.LoadFromFile("AssetBundles" + "/" + str); } //5.加载包prefab AssetBundle abPrefab = AssetBundle.LoadFromFile("AssetBundles/prefab"); //6.从包prefab中加载相应资源 var prefabs = abPrefab.LoadAllAssets<GameObject>(); foreach (var prefab in prefabs) Instantiate(prefab);
其中bool参数决定是否一并卸载从ab包中加载的资源文件,一般都是用false
使用AB包管理器来进行统一管理
MonoSingleton.cs 泛型单例
//泛型单例 using UnityEngine; using System; namespace DefaultNamespace { public class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T> { private static T instance; public static T Instance { get { if (instance != null) return instance; instance = FindObjectOfType<T>(); if (instance == null) { instance = new GameObject("Singleton " + typeof(T)).AddComponent<T>(); instance.Init(); } return instance; } } //如果子类中有Awake则只会调用子类中的Awake,为方便统一管理,子类最好不要有Awake操作,需要其他初始化操作的话最好重写Init方法 private void Awake() { } protected virtual void Init() { } } }
ABMgr.cs
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using Object = UnityEngine.Object; namespace DefaultNamespace { public class ABMgr : MonoSingleton<ABMgr> { //主包 private AssetBundle _mainAB; //主包配置文件 用于加载依赖包 private AssetBundleManifest _mainABManifest; //字典 用来管理已加载的AB包 private Dictionary<string, AssetBundle> _abCache; //AB包存放路径 根据平台来定 private string BasePath { get { #if UNITY_EDITOR || UNITY_STANDALONE return Application.streamingAssetsPath + "/"; #elif UNITY_IPHONE return ""; #elif UNITY_ANDROID return ""; #endif } } //主包名称 根据平台来定 private string MainName { get { #if UNITY_EDITOR || UNITY_STANDALONE return "StandaloneWindows"; #elif UNITY_IPHONE return "IOS"; #elif UNITY_ANDROID return "Android"; #endif } } protected override void Init() { base.Init(); _abCache = new Dictionary<string, AssetBundle>(); } #region 同步加载AB包 public AssetBundle LoadAB(string abName) { AssetBundle ab; if (_mainAB == null) { _mainAB = AssetBundle.LoadFromFile(BasePath + MainName + "/" + "StandaloneWindows"); _mainABManifest = _mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); } var dependencies = _mainABManifest.GetAllDependencies(abName); foreach (var name in dependencies) { if (_abCache.ContainsKey(name)) continue; //这里需要递归加载 LoadAB(name); } if (_abCache.ContainsKey(abName)) return _abCache[abName]; else { ab = AssetBundle.LoadFromFile(BasePath + MainName + "/" + abName); _abCache.Add(abName, ab); return ab; } } #endregion #region 同步加载资源 /// <summary> /// 泛型加载资源 /// </summary> /// <param name="abName">AB包名</param> /// <param name="assetName">资源名</param> public T LoadAsset<T>(string abName, string assetName) where T : Object { AssetBundle ab = LoadAB(abName); return ab.LoadAsset<T>(assetName); } //不指定类型 有重名情况下不建议使用 使用时需显示转换类型 public Object LoadAsset(string abName,string resName) { AssetBundle ab = LoadAB(abName); return ab.LoadAsset(resName); } //利用参数传递类型,适合对泛型不支持的语言调用,使用时需强转类型 public Object LoadAsset(string abName, string resName,System.Type type) { AssetBundle ab = LoadAB(abName); return ab.LoadAsset(resName,type); } #endregion #region 异步加载资源 /// <summary> /// 泛型异步加载资源 /// </summary> /// <param name="abName">AB包名</param> /// <param name="resName">资源名</param> /// <param name="OnComplete">完成回调</param> public void LoadAssetAsync<T>(string abName, string assetName, Action<Object> OnComplete) where T : Object { StartCoroutine(LoadAsset<T>(abName, assetName, OnComplete)); } private IEnumerator LoadAsset<T>(string abName, string assetName, Action<Object> OnComplete) where T : Object { AssetBundle ab = LoadAB(abName); AssetBundleRequest abr = ab.LoadAssetAsync<T>(assetName); yield return abr; OnComplete(abr as T); } #endregion #region 卸载AB包 //卸载单个AB包 public void UnLoadAB(string abName) { if (_abCache.ContainsKey(abName)) { _abCache[abName].Unload(false); _abCache.Remove(abName); } } //卸载所有AB包 public void UnLoadAll() { AssetBundle.UnloadAllAssetBundles(false); _abCache.Clear(); _mainAB = null; _mainABManifest = null; } #endregion } }
参考:
Unity中AB包详解(超详细,特性,打包,加载,管理器)
Unity手游实战:从0开始SLG——资源管理系统-基础篇(三)AssetBundle原理
Unity热更新:AssetBundleBrowser打包及资源加载
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。