又来力,又是新的一篇总结性质的文章,这次接上篇热更新来讲一讲热更新过程中的重要角色AssetBundle,也就是ab包。
1. AB包概念
AssetBundle是将资源使用Unity提供的一种用于存储资源的压缩格式打包后的集合,它可以存储任何一种Unity可以识别的资源,如模型,纹理图,音频,场景等资源。也可以加载开发者自定义的二进制文件。他们的文件类型是.assetbundle/.unity3d,他们先前被设计好,很容易就下载到我们的游戏或者场景当中。
主要优点:AB包可以显著减少初始包体大小,在运行时动态加载卸载(即用即加载)
2. AB包的使用
首先,先讲讲游戏中AB包使用的大致流程:
- 创建Asset bundle,开发者在unity编辑器中通过脚本将所需要的资源打包成AssetBundle文件。
- 上传服务器,开发者将打包好的AssetBundle文件上传至服务器中。使得游戏客户端能够获取当前的资源,进行游戏的更新。
- 下载AssetBundle,首先将其下载到本地设备中,然后再通过AssetBundle的加载模块将资源加到游戏之中。
- 加载,通过Unity提供的API可以加载资源里面包含的模型、纹理图、音频、动画、场景等来更新游戏客户端。
- 卸载AssetBundle,卸载之后可以节省内存资源,并且要保证资源的正常更新。
可以看到,AB包的使用大致分为创建打包、下载、加载、卸载这四个主要步骤,接下来结合代码来详细讲讲这四个步骤具体实现。
2.1 创建并打包
注意,只有在Assets目录下的资源文件才可以用来创建AB包,在Unity中具体操作如下:
首先选中Assets/Materials/Black这个材质
接着可以在Inspector窗口最下面看到这样的界面,下边AssetBundle一行中左边是具体的AB包名(固定为小写),右边是定义后缀。
在定义好之后,接下来通过代码来进行AssetBundle打包。
示例:
BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
打包过程中AB包可以通过设置BuildAssetBundleOptions来控制压缩方式,Unity共提供了三种压缩方式:
- LZMA:默认的压缩方式,压缩包体相对小,解压时间相对长
- LZ4:压缩包体相对大,解压时间相对短
- 不压缩:压缩包体最大,访问速度最快
打包完成后会生成多个文件,具体到文中的例子来说会生成下面四个文件,其中.manifest文件可以看作是AB包的配置文件。其中AssetBundles包为主包,和父文件夹名相同。
2.2 下载
将AB包从服务器下载到本地的过程,这个可以合并入加载流程,具体细节留到后续再进行补充
2.3 加载
这个过程总共分为两步,首先需要先加载AB包,然后再从AB包中加载资源
2.3.1 加载AB包
- AssetBundle.LoadFromMemory(Async optional)
- AssetBundle.LoadFromStream(Async optional)
- AssetBundle.LoadFromFile(Async optional)
- UnityWebRequest’s DownloadHandlerAssetBundle
- WWW.LoadFromCacheOrDownload (on Unity 5.6 or older)
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
- ab.LoadAllAssets<>() (Async optional)
- ab.LoadAsset<>() (Async optional)
- ab.LoadAssetWithSubAssets<>() (Async optional)
这些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);
2.4 卸载
- ab.UnLoad(bool):特定的ab包卸载
- AssetBundle.UnloadAllAssetBundles(bool) : 卸载所有ab包
其中bool参数决定是否一并卸载从ab包中加载的资源文件,一般都是用false
2.5 AB包管理器
使用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打包及资源加载