Unity热更新之AssetBundle
一、概述
1、AB包是什么
AssetBundle简称AB包。
这是特定定于平台的资产压缩包,有点类是压缩文件。
资产包括:模型、贴图、预设体、音效、材质球等
2、AB包的作用
-
相对于Resources下的资源,AB包能更好管理资源
-
AB包能够减小包体大小:可以压缩资源并减少初始包大小
-
AB包更适合热更新:即资源热更新(脚本热更新主要是Lua)
二、AB包的使用
1、导入
高版本Unity中,已经不能通过包管理器进行下载(高版本Unity用Addressables功能封装了AB包功能)
目前使用资料区下载AssetBundles-Browser-master包文件导入工程中。(可以网上找)
之后即可在window菜单栏中打开AssetBundle Browser面板。
2、生成AB包
(1)Configure页签:
可以对想要的资源或预设体进行打包,点击想要打包的资源,在Inspector窗口下方选择AssetBundle的文件名,之后即可在窗口Configure中看到打包的信息。

(2)Bulid页签(最重要)
-
Build Target:打包的目标平台
-
OutputPath:打包输出路径
-
CearFolders:是否清空文件夹
-
CopytoStreamingAssets:是否将打包复制到StreamingAssets文件夹
-
Compression:压缩方式
-
LZMA:此压缩格式是表示整个 AssetBundle 的数据流,这意味着如果您需要从这些存档中读取某个资源,就必须将整个流解压缩。压缩率比LZ4高,,但使用时需要解压全部资源。
-
LZ4:如果 Unity 需要从 LZ4 存档中访问资源,只需解压缩并读取包含所请求资源的字节的块,即要用哪个解压哪个。
-
-
Exclude Type Information:在资源包中不包含资源的类型信息。
-
Force Rebuild:重新打包时需要重新构建包,与ClearFolders不同,他不会删除不存在的包。
-
Ignore Type Tree Changes:增量构建检查时,忽略类型树的修改
-
Append Hash:将文件哈希值附加在资源包名上
-
Strict Mode:严格模式,如果打包时报错,则打包直接失败
-
Dry Run Build:运行时构建

打包时,会在Assets同级目录里创建一个AssetBundles的文件夹,如果勾选了Copy to StreamingAssets,还会把资源拷贝到Assets文件夹下的StreamingAssets文件夹中。
打包后,可以查看资源文件内部:
主包:其中和目录名一样的包是主包,记录了AB包依赖的关键信息。
AB包文件:是用二进制存储的资源文件。
manifest文件:即一些配置文件信息,加载时提供关键信息,如资源信息,依赖关系,版本信息等等

(3)Inspect页签
主要用于查看包文件的一些信息
(4)答疑解惑
AB包打包只对资源进行打包,而不打包代码,代码打包由Lua来实现。
但打包预制体时,物体身上的组件不也打包了吗?难道不是说明能打包代码吗?
其实打包物体时,身上的组件脚本是利用了反射的机制,在打包时,只是记录了这些组件脚本的引用,而不是代码本身,所以AB包其实是不能打包代码的。
我们可以打开预制体文件验证一下,可以发现预制体内部记录的组件只是脚本ID,并非真正的代码。
三、加载AB包资源
1、加载资源
- 不使用协程
// 1、加载AB包(不可重复加载同一个AB包)
AssetBundle ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "model");
// 2、加载AB包中的资源(有多种重载)
GameObject obj = ab.LoadAsset<GameObject>("Cube");
- 使用协程
IEnumerator LoadABRes(string ABName,string resName)
{
AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/" + "head");
yield return abcr;
AssetBundleRequest abr = abcr.assetBundle.LoadAssetAsync(resName,typeof(Sprite));
yield return abr;
image.sprite = abr.asset as Sprite;
}
2、卸载资源
// 1、卸载所有AB包
AssetBundle.UnloadAllAssetBundles(false); //bool参数为true时,会把通过AB包加载的资源也卸载了。
// 2、只卸载自己的AB包
ab.Unload(true); //参数一样
3、依赖
一个资源身上用到了别的AB包中的资源,那这两个包就产生了依赖,这个时候如果只加载它创建对象,会出现资源丢失的的情况,此时需要把依赖包一起加载了才能正常。
- 实例:新建一个材质球,赋给Cube预制体,运行时可以发现Meterial被默认自动打包到依赖它的AB包中了(如model包)。
如果手动设置,比如将Material设置到head包中,如果只加载了model包,则Cube的材质球会丢失。只有加载了材质球所在的head包,才能恢复正常。
4、加载依赖
如果知道依赖包,可以手动去加载,但是大部分情况下,资源的依赖是非常复杂的,此时就需要用到主包来获取依赖信息。
下图是主包的Manifest文件内容

加载依赖代码如下:
// 加载主包
AssetBundle abMain = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "PC");
// 加载主包的固定文件
AssetBundleManifest abManifest = abMain.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
// 从固定文件中获取依赖信息
string[] strs = abManifest.GetAllDependencies("model");
// 得到依赖包名字,加载依赖包
for (int i=0;i<strs.Length; i++)
{
AssetBundle.LoadFromFile(Application.stre amingAssetsPath + "/" + strs[i]);
}
四、AB包管理器
这里加载资源时,如果是GameObject类型,则返回实例化后的对象(其实我觉得可以去掉)
using System.Collections;
using System.Collections.Generic;
using System.Security;
using UnityEngine;
using UnityEngine.Events;
public class ABMgr : SingletonAutoMono<ABMgr>
{
private AssetBundle mainAB = null;
private AssetBundleManifest manifest = null;
private Dictionary<string,AssetBundle> abDic = new Dictionary<string,AssetBundle>();
/// <summary>
/// 路径名
/// </summary>
private string PathUrl
{
get
{
return Application.streamingAssetsPath + "/";
}
}
/// <summary>
/// 主包名
/// </summary>
private string MainABName
{
get
{
#if UNITY_IOS
return "IOS";
#elif UNITY_ANDROID
return "Android";
#else
return "PC";
#endif
}
}
/// <summary>
/// 加载AB包及其依赖
/// </summary>
/// <param name="abName">包名</param>
public void LoadAB(string abName)
{
if (mainAB == null)
{
mainAB = AssetBundle.LoadFromFile(PathUrl + MainABName);
manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}
// 加载依赖包
AssetBundle ab = null;
string[] strs = manifest.GetAllDependencies(abName);
for (int i = 0; i < strs.Length; i++)
{
if (!abDic.ContainsKey(strs[i]))
{
ab = AssetBundle.LoadFromFile(PathUrl + strs[i]);
abDic.Add(strs[i], ab);
}
}
// 加载目标资源包
if (!abDic.ContainsKey(abName))
{
ab = AssetBundle.LoadFromFile(PathUrl + abName);
abDic.Add(abName, ab);
}
}
/// <summary>
/// 同步加载资源
/// </summary>
/// <param name="abName">包名</param>
/// <param name="resName">资源名</param>
public Object LoadRes(string abName,string resName)
{
// 加载AB包
LoadAB(abName);
// 加载资源并返回
Object obj = abDic[abName].LoadAsset(resName);
if (obj is GameObject)
{
return Instantiate(obj);
}
else
{
return obj;
}
}
public Object LoadRes(string abName, string resName,System.Type type)
{
// 加载AB包
LoadAB(abName);
// 加载资源并返回
Object obj = abDic[abName].LoadAsset(resName,type);
if (obj is GameObject)
{
return Instantiate(obj);
}
else
{
return obj;
}
}
public T LoadRes<T>(string abName,string resName) where T : Object
{
// 加载AB包
LoadAB(abName);
// 加载资源并返回
T obj = abDic[abName].LoadAsset<T>(resName);
if (obj is GameObject)
{
return Instantiate(obj);
}
else
{
return obj;
}
}
/// <summary>
/// 异步加载资源(只是加载资源异步,AB包加载并没有使用异步)
/// </summary>
/// <param name="abName">包名</param>
/// <param name="resName">资源名</param>
/// <param name="callback">回调函数</param>
public void LoadResAsync(string abName,string resName,UnityAction<Object> callback)
{
StartCoroutine(ReallyLoadResAsync(abName,resName,callback));
}
private IEnumerator ReallyLoadResAsync(string abName, string resName, UnityAction<Object> callback)
{
LoadAB(abName);
AssetBundleRequest abr = abDic[abName].LoadAssetAsync(resName);
yield return abr;
if (abr.asset is GameObject)
callback(Instantiate(abr.asset));
else
callback(abr.asset);
}
public void LoadResAsync(string abName, string resName, System.Type type, UnityAction<Object> callback)
{
StartCoroutine(ReallyLoadResAsync(abName, resName, type, callback));
}
private IEnumerator ReallyLoadResAsync(string abName, string resName, System.Type type, UnityAction<Object> callback)
{
LoadAB(abName);
AssetBundleRequest abr = abDic[abName].LoadAssetAsync(resName,type);
yield return abr;
if (abr.asset is GameObject)
callback(Instantiate(abr.asset));
else
callback(abr.asset);
}
public void LoadResAsync<T>(string abName, string resName, UnityAction<T> callback) where T : Object
{
StartCoroutine(ReallyLoadResAsync<T>(abName, resName, callback));
}
private IEnumerator ReallyLoadResAsync<T>(string abName, string resName, UnityAction<T> callback) where T : Object
{
LoadAB(abName);
AssetBundleRequest abr = abDic[abName].LoadAssetAsync<T>(resName);
yield return abr;
if (abr.asset is GameObject)
callback(Instantiate(abr.asset) as T);
else
callback(abr.asset as T);
}
public void UnLoad(string abName)
{
if (abDic.ContainsKey(abName))
{
abDic[abName].Unload(false);
abDic.Remove(abName);
}
}
public void ClearAB()
{
AssetBundle.UnloadAllAssetBundles(false);
abDic.Clear();
mainAB = null;
manifest = null;
}
}