选中资源,在它的Inspector窗口底部,可以将当前资源加入某个标签或新建一个标签,标签的名字就是AB包的名字,这里的包名是强制小写的


AB包打包工具可以通过编辑器扩展自己写一个,调用AB包相关的API即可。在这里我直接使用官方的省事;
Configure
显示当前所有的标签和对应有哪些资源,如图,model包中包含一个Cube资源,一目了然

Build

Inspect
主要是可视化的查看AB包中的详细信息

跳转到打包后的输出路径,查看文件夹下的内容

主要包含以下几个部分:
使用StreamingAssets该文件夹,我们在打包的时候已经勾选了复制到StreamingAssets文件夹当中了,使用这个文件夹的目的是:
跨平台兼容,无论当前是在任何平台,UNITY都会根据不同的平台自动返回正确的路径
Windows: Application.dataPath + "/StreamingAssets"
macOS: Application.dataPath + "/Resources/Data/StreamingAssets"
Android: "jar:file://" + Application.dataPath + "!/assets"
iOS: Application.dataPath + "/Raw"
StreamingAssets 文件夹中的资源会被原样打包到最终游戏中,不会被 Unity 压缩或处理,保持 AssetBundle 完整性
这个文件夹是是只读的,确保 AssetBundle 不会被意外修改,保证安全
性能优势,直接从安装包中加载,速度快,不需要额外的文件复制操作
csharpstring path = Application.streamingAssetsPath + "/" + "model";
AssetBundle ab = AssetBundle.LoadFromFile(path);
csharp//建议使用泛型+名字的方式,避免出现同名不同类型的情况
GameObject obj = ab.LoadAsset<GameObject>("Cube");
//也是指定类型的方式来加载AB包中的资源,不同于泛型的是这是通过反射的方式,因为在热更新中如果使用的是Lua脚本,就只能使用这种方式,因为Lua脚本不支持泛型
GameObject obj = ab.LoadAsset("Cube", typeof(GameObject)) as GameObject;
//在场景中实例化出加载的对象
Instantiate(obj, Vector3.zero, Quaternion.identity);
AB包不能重复加载,不然会报错
csharp//>异步加载AB包资源-协程
IEnumerator LoadABResAsync(string abName, string resName)
{
//1.异步加载AB包
AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/" + abName);
yield return abcr;
//2.从AB包中异步加载资源
AssetBundleRequest abr = abcr.assetBundle.LoadAssetAsync(resName, typeof(GameObject));
yield return abr;
//3.获取异步加载的资源
GameObject obj = abr.asset as GameObject;
yield return obj;
}
csharp//>卸载AB包中的资源,true同时卸载场景中已经从该AB包中加载过的实例,false只卸载AB包自己
ab.Unload(false);
AssetBundle.UnloadAllAssetBundles(false);//卸载当前所有
加载一个AB包之前先要判断它是否有相关的依赖,先加载依赖包,然后再加载当前AB包
csharpAssetBundle abMain = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "iOS");
csharpAssetBundleManifest abMainfest = abMain.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
csharpstring[] dependencies = abMainfest.GetAllDependencies("model");
csharp//临时存储依赖包,方便后续能够及时卸载,实际项目中我们应该用一个容器存储已经加载的AB包
List<AssetBundle> dependencyABs = new List<AssetBundle>(dependencies.Length);
for (int i = 0; i < dependencies.Length; i++)
{
AssetBundleCreateRequest dependencyRequest = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/" + dependencies[i]);
yield return dependencyRequest; // 等待异步加载完成
dependencyABs.Add(dependencyRequest.assetBundle);
}
添加引用计数目的是方便知晓某个AB包的引用次数,使用资源时引用计数+1,释放资源时引用计数-1,便于及时卸载,Addressables中包含了这个功能,我们使用AB包也可以在代码中自己添加这个逻辑
该管理器已包含了上述所描述的知识点
csharpusing System.Collections;
using System.Collections.Generic;
using OpenCover.Framework.Model;
using UnityEngine;
using UnityEngine.Events;
public class ABMgr : SingletonAutoMono<ABMgr>
{
//主包
private AssetBundle mainAB = null;
//主包依赖获取配置文件
private AssetBundleManifest manifest = null;
//选择存储 AB包的容器
//AB包不能够重复加载 否则会报错
//使用引用计数管理AB包对象
private Dictionary<string, ABInfo> abDic = new Dictionary<string, ABInfo>();
private class ABInfo
{
public AssetBundle assetBundle;
//引用计数
public int linkCount;
}
/// <summary>
/// 获取AB包加载路径
/// </summary>
private string PathUrl
{
get
{
return Application.streamingAssetsPath + "/";
}
}
/// <summary>
/// 主包名 根据平台不同 报名不同
/// </summary>
private string MainName
{
get
{
#if UNITY_IOS
return "IOS";
#elif UNITY_ANDROID
return "Android";
#else
return "PC";
#endif
}
}
/// <summary>
/// 加载主包 和 配置文件
/// 因为加载所有包是 都得判断 通过它才能得到依赖信息
/// 所以写一个方法
/// </summary>
private void LoadMainAB()
{
if (mainAB == null)
{
mainAB = AssetBundle.LoadFromFile(PathUrl + MainName);
manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}
}
/// <summary>
/// 加载指定包的依赖包
/// </summary>
/// <param name="abName"></param>
private void LoadDependencies(string abName)
{
//加载主包
LoadMainAB();
//获取依赖包
string[] strs = manifest.GetAllDependencies(abName);
for (int i = 0; i < strs.Length; i++)
{
if (!abDic.ContainsKey(strs[i]))
{
AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + strs[i]);
abDic.Add(strs[i], new ABInfo { assetBundle = ab, linkCount = 1 });
}
else
{
abDic[strs[i]].linkCount++;
}
}
}
/// <summary>
/// 泛型资源同步加载
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="abName"></param>
/// <param name="resName"></param>
/// <returns></returns>
public T LoadRes<T>(string abName, string resName) where T : Object
{
//加载依赖包
LoadDependencies(abName);
//加载目标包
if (!abDic.ContainsKey(abName))
{
AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + abName);
abDic.Add(abName, new ABInfo { assetBundle = ab, linkCount = 1 });
}
else
{
abDic[abName].linkCount++;
}
//得到加载出来的资源
T obj = abDic[abName].assetBundle.LoadAsset<T>(resName);
//如果是GameObject 因为GameObject 100%都是需要实例化的
//所以我们直接实例化
if (obj is GameObject)
return Instantiate(obj);
else
return obj;
}
/// <summary>
/// Type同步加载指定资源
/// </summary>
/// <param name="abName"></param>
/// <param name="resName"></param>
/// <param name="type"></param>
/// <returns></returns>
public Object LoadRes(string abName, string resName, System.Type type)
{
//加载依赖包
LoadDependencies(abName);
//加载目标包
if (!abDic.ContainsKey(abName))
{
AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + abName);
abDic.Add(abName, new ABInfo { assetBundle = ab, linkCount = 1 });
}
else
{
abDic[abName].linkCount++;
}
//得到加载出来的资源
Object obj = abDic[abName].assetBundle.LoadAsset(resName, type);
//如果是GameObject 因为GameObject 100%都是需要实例化的
//所以我们直接实例化
if (obj is GameObject)
return Instantiate(obj);
else
return obj;
}
/// <summary>
/// 名字 同步加载指定资源
/// </summary>
/// <param name="abName"></param>
/// <param name="resName"></param>
/// <returns></returns>
public Object LoadRes(string abName, string resName)
{
//加载依赖包
LoadDependencies(abName);
//加载目标包
if (!abDic.ContainsKey(abName))
{
AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + abName);
abDic.Add(abName, new ABInfo { assetBundle = ab, linkCount = 1 });
}
else
{
abDic[abName].linkCount++;
}
//得到加载出来的资源
Object obj = abDic[abName].assetBundle.LoadAsset(resName);
//如果是GameObject 因为GameObject 100%都是需要实例化的
//所以我们直接实例化
if (obj is GameObject)
return Instantiate(obj);
else
return obj;
}
/// <summary>
/// 泛型异步加载资源
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="abName"></param>
/// <param name="resName"></param>
/// <param name="callBack"></param>
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
{
//加载依赖包
LoadDependencies(abName);
//加载目标包
if (!abDic.ContainsKey(abName))
{
AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + abName);
abDic.Add(abName, new ABInfo { assetBundle = ab, linkCount = 1 });
}
else
{
abDic[abName].linkCount++;
}
//异步加载包中资源
AssetBundleRequest abq = abDic[abName].assetBundle.LoadAssetAsync<T>(resName);
yield return abq;
if (abq.asset is GameObject)
callBack(Instantiate(abq.asset) as T);
else
callBack(abq.asset as T);
}
/// <summary>
/// Type异步加载资源
/// </summary>
/// <param name="abName"></param>
/// <param name="resName"></param>
/// <param name="type"></param>
/// <param name="callBack"></param>
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)
{
//加载依赖包
LoadDependencies(abName);
//加载目标包
if (!abDic.ContainsKey(abName))
{
AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + abName);
abDic.Add(abName, new ABInfo { assetBundle = ab, linkCount = 1 });
}
else
{
abDic[abName].linkCount++;
}
//异步加载包中资源
AssetBundleRequest abq = abDic[abName].assetBundle.LoadAssetAsync(resName, type);
yield return abq;
if (abq.asset is GameObject)
callBack(Instantiate(abq.asset));
else
callBack(abq.asset);
}
/// <summary>
/// 名字 异步加载 指定资源
/// </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)
{
//加载依赖包
LoadDependencies(abName);
//加载目标包
if (!abDic.ContainsKey(abName))
{
AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + abName);
abDic.Add(abName, new ABInfo { assetBundle = ab, linkCount = 1 });
}
else
{
abDic[abName].linkCount++;
}
//异步加载包中资源
AssetBundleRequest abq = abDic[abName].assetBundle.LoadAssetAsync(resName);
yield return abq;
if (abq.asset is GameObject)
callBack(Instantiate(abq.asset));
else
callBack(abq.asset);
}
//卸载AB包的方法 (引用计数管理)
public void UnLoadAB(string name)
{
if (abDic.ContainsKey(name))
{
abDic[name].linkCount--;
if (abDic[name].linkCount <= 0)
{
abDic[name].assetBundle.Unload(false);
abDic.Remove(name);
}
}
}
//强制卸载指定AB包 (忽略引用计数)
public void ForceUnLoadAB(string name)
{
if (abDic.ContainsKey(name))
{
abDic[name].assetBundle.Unload(false);
abDic.Remove(name);
}
}
//获取AB包的引用计数
public int GetABReferenceCount(string name)
{
if (abDic.ContainsKey(name))
{
return abDic[name].linkCount;
}
return 0;
}
//清空AB包的方法
public void ClearAB()
{
AssetBundle.UnloadAllAssetBundles(false);
abDic.Clear();
//卸载主包
mainAB = null;
manifest = null;
}
}
本文作者:xuxuxuJS
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!