前面四篇讲解了打包AB的相关知识,思路和工程也都贴出了,由于功能代码量还是蛮多的,这里暂时就先不一一讲解了,注释的应该还是比较详细的。这一篇就主要讲解AB包的使用,总不能打好AB包而不会用吧。
如同Resource.Load()一般,读取AB的方法也很简单,AssetBundle.LoadFromFile(assetBundlePath),这个方法是官方推荐使用的,也是读取最快的。当然如果你的AB资源是放在服务器上做热更新用的,应该用WWW去读取,这边暂时不对此做详细的描述。通过上面的方法读取到AssetBundle对象后,自然要获取里面的Asset。使用assetBundle.LoadAsset(assetName)我们就可以获取到打包成AB前的Asset,进行使用。相关代码在工程里面Assets\Scripts\Utilities\AssetBundle文件夹内。
注意:AssetBundle对象其实只是存储AB包的文件信息,内存占用相对较小,只有LoadAsset时才读取AB包的内容到内存。
这里先讲讲关于AB的依赖,按照前面的内容,我们知道,如果一个UI界面上,用到一张图片。然后我们将其打包AB资源,需要打包成两个,一个是ui.ab,一个是image.ab,(ui.ab依赖于image.ab)。当我们获取ui.ab并且取出里面的Asset并实例化出来后,会发现里面的图片并不能显示出来。其原因在于,我们没有获取其依赖的image.ab。只要我们事先读取image.ab,生成对应的AssetBundle即可。同时我们根据前面的知识知道,AB包所有的依赖关系Unity都帮我们存好放在了与AB包父目录同名的文件当中(文中为StreamingAssets)。其实这个也是一个AB文件,我们只需要从程序运行的时候读取该AB,获取AssetBundleManifest对象存放在内存当中,然后每次读取AB包的时候都根据AB的包名从AssetBundleManifest对象中获取其相关依赖的包名获取对应AB即可。
注意:依赖的AB包,我们读取的时候只需要生成对应的AssetBundle对象,无需手动去LoadAsset我们需要的资源,Unity会自动替我们处理。
读取AssetBundleManifest的代码如下:
public static readonly string streamingAssetsFileName = "StreamingAssets";
static AssetBundleManifest m_mainfest;
static AssetBundleManifest mainfest {
get {
if(m_mainfest == null) {
AssetBundle ab = AssetBundle.LoadFromFile(streamingAssetsPath + "/" + streamingAssetsFileName);
if(ab != null) {
m_mainfest = (AssetBundleManifest)ab.LoadAsset("AssetBundleManifest");
//释放AssetBundle对象
ab.Unload(false);
ab = null;
} else {
Debug.LogError("Get Mainfest Error");
return null;
}
}
return m_mainfest;
}
}
///
/// StreamingAssets 路径
///
public static string streamingAssetsPath {
get {
#if UNITY_ANDROID
return Application.dataPath + "!assets"; // 安卓平台
#else
return Application.streamingAssetsPath; // 其他平台
#endif
}
//get { return Application.streamingAssetsPath; }
}
这段代码还有一个需要注意的就是读取路径啦,安卓和其他的有所不同。关于unity各个路径对应的手机路径看见一篇不错的博客有兴趣的可以看看。
下面再讲讲关于AB的释放,官方给出的方法是assetBundle.Unload(bool);关于参数的bool值,若为false表示只释放AssetBundle本身,而要是为true,则会连AssetBundle里面的Asset一起释放。
举个例子:一张包含图片的AB,我们读取该AB的时候,生成一个AssetBundle对象,然后使用LoadAsset获取,生成一个Texture对象,最后在UGUI里面的RawImage里面设置Texture为该texture。这一段功能一共多出两个新的实例:assetbundle和texture。当我们调用的assetBundle.Unload(false);时,assetbundle会被释放掉,而texture仍然存在内存当中,RawImage可以继续正常显示该图片,但是如果Unload(true);时,assetbundle和texture都将被释放掉,RawImage原来指定的图片也将消失。如图:
可能大家会有疑问,为什么panel3的背景图没了,这个和Asset的使用有关,Texture的Asset,我们RawImage指向它只是增加一个引用,并不会重新clone一份分配新的内存,所有Texture Asset释放的时候,所有用到改Texture的地方都引用不到了。而prefab的Asset,我们Instantiate使用的时候,会clone这个Asset,形成一个新的实例。所以prefab asset释放后,这个clone对象依旧存在,那么为什么背景图没了呢?依照texture可以知道,即便clone了新的对象,他们的texture和sprite依旧引用同一个,即prefab Asset里面的,释放后就引用不到了。
备注:除了Unload方法,还有以下几个方法可以释放AB的Asset资源,可以再Profiler里面查看资源情况:
Resources.UnloadAsset(assetname);回收指定的缓存。
Resources.UnloadUnusedAsset();回收没有被引用的缓存。
GC.Collect();加上这句可立即释放内存。
从上面的这些知识又可以衍生出几个注意点:
比如:当我们有两个AB,a.ab和b.ab都依赖于c.ab,那么按照前面的读取这两个AB,在不加特殊处理的情况下就会去读取两次c.ab,这种情况Unity是不允许的(当然你可以读取一个AB的Asset后立刻把AB Unload掉,但是这样会导致过多的AB读取与释放)。因此我们需要添加一个Dictionary,以abname和AssetBundle为键值对。当读一个新的AB的时候先判断Dictionary中是否存储了该AB,存储了则直接使用该AB,若没存储再使用LoadFromFile去读取,读取成功后存入Dictionary。
接着便是释放AB时机的时候,因为手游肯定要尽可能的节省内存的开支,所以用不到AB以及其Asset一定要释放掉。当AB包越多时,其相互依赖的关系也会越复杂。一个AB可能被多个AB所依赖或者Asset还被使用,所以释放的时候一定要确定没有其他依赖并且Asset没有引用的时候。所以我们可以引入一个计数器的概念。每个Asset被加载的时候AB的计数器++,Asset需要被卸载的时候AB的计数器--。当计数器为0的时候,执行Unload(true)。
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
namespace Utility {
///
/// 读取AB的工具类
///
public class AssetBundleUtility {
//public static readonly string suffix = ".assetbundle";
public static readonly string streamingAssetsFileName = "StreamingAssets";
static AssetBundleManifest m_mainfest;
static AssetBundleManifest mainfest {
get {
if(m_mainfest == null) {
AssetBundle ab = AssetBundle.LoadFromFile(streamingAssetsPath + "/" + streamingAssetsFileName);
if(ab != null) {
m_mainfest = (AssetBundleManifest)ab.LoadAsset("AssetBundleManifest");
//释放AssetBundle对象
ab.Unload(false);
ab = null;
} else {
Debug.LogError("Get Mainfest Error");
return null;
}
}
return m_mainfest;
}
}
///
/// 缓存使用到的AssetBundle
///
static Dictionary cacheAssetBundleItemDic = new Dictionary();
///
/// 同步加载AB资源
///
/// AB资源路径
/// AB资源文件名
/// 是否存在依赖关系
/// AB包信息
public static AssetBundleItem Load(string path, string fileName, bool isHasDependence = true) {
if(mainfest != null) {
path = path.ToLower();
fileName = fileName.ToLower();
if(isHasDependence) {
//读取依赖
string[] dps = mainfest.GetAllDependencies(path);
int len = dps.Length;
for(int i = 0; i < len; i++) {
AssetBundleItem dItem;
if(cacheAssetBundleItemDic.ContainsKey(dps[i])) {
dItem = cacheAssetBundleItemDic[dps[i]];
} else {
dItem = new AssetBundleItem(dps[i], fileName, false);
dItem.assetBundle = AssetBundle.LoadFromFile(dItem.pathName);
cacheAssetBundleItemDic.Add(dps[i], dItem);
}
Debug.Log("wjr---ABLoad---Dependence---" + dps[i]);
dItem.refCount++;
}
}
AssetBundleItem ab;
if(cacheAssetBundleItemDic.ContainsKey(path)) {
ab = cacheAssetBundleItemDic[path];
} else {
ab = new AssetBundleItem(path, fileName, isHasDependence);
ab.assetBundle = AssetBundle.LoadFromFile(ab.pathName);
cacheAssetBundleItemDic.Add(path, ab);
}
Debug.Log("wjr---ABLoad---" + path);
ab.refCount++;
return ab;
}
return null;
}
///
/// AB的引用-1,若为0则删除该AB
///
///
public static void Delete(string path) {
path = path.ToLower();
if(cacheAssetBundleItemDic.ContainsKey(path)) {
AssetBundleItem ab = cacheAssetBundleItemDic[path];
if(ab.isHasDependence) {
//删除依赖
string[] dps = mainfest.GetAllDependencies(path);
for(int i = 0, len = dps.Length; i < len; i++) {
Delete(dps[i]);
}
}
ab.refCount--;
if(ab.refCount <= 0) {
Debug.Log("wjr---ABDelete---" + ab.pathName);
ab.assetBundle.Unload(true);
ab = null;
cacheAssetBundleItemDic.Remove(path);
}
}
}
private static StringBuilder getPathResult = new StringBuilder();
private static string tmpPath = string.Empty;
///
/// 资源同步加载路径(无 file:///)
///
public static string GetAssetPath(string path) {
// 先尝试从 persist 目录加载
if(true) {
getPathResult.Length = 0;
getPathResult.Append(sandboxPath);
getPathResult.Append("/");
getPathResult.Append(path);
tmpPath = getPathResult.ToString();
if(File.Exists(tmpPath)) {
getPathResult.Length = 0;
return tmpPath;
}
}
getPathResult.Length = 0;
getPathResult.Append(streamingAssetsPath);
getPathResult.Append("/");
getPathResult.Append(path);
tmpPath = getPathResult.ToString();
return tmpPath;
}
///
/// 沙盒路径
/// 可读可写,一般存放网上下载的资源
///
public static string sandboxPath {
get { return Application.persistentDataPath; }
}
///
/// StreamingAssets 路径
///
public static string streamingAssetsPath {
get {
#if UNITY_ANDROID
return Application.dataPath + "!assets"; // 安卓平台
#else
return Application.streamingAssetsPath; // 其他平台
#endif
}
//get { return Application.streamingAssetsPath; }
}
}
///
/// 存储单个AB资源信息
///
public class AssetBundleItem {
public string pathName;
public string fileName;
public AssetBundle assetBundle;
public int refCount;
public bool isHasDependence;
public AssetBundleItem(string path, string file, bool isHasDependence) {
pathName = AssetBundleUtility.GetAssetPath(path);
fileName = file;
assetBundle = null;
refCount = 0;
this.isHasDependence = isHasDependence;
}
public Object LoadAsset(System.Type type) {
return LoadAsset(fileName, type);
}
public Object LoadAsset(string name, System.Type type) {
if(assetBundle != null) {
return assetBundle.LoadAsset(name, type);
}
return null;
}
}
}
希望喜欢文章的朋友们给点个赞。