Unity热更新——AssetBundle(AB包)+管理器

什么是热更新?

游戏或者软件更新时,无需重新下周客户端进行安装,而是在应用程序启动的情况下,在内部进行的资源或者代码更新

1、了解AB包是什么:

特定于平台的资产压缩包,有点类似压缩文件,资产包括:模型、贴图、预设体、音效、材质球等等

2、AB包有什么作用:

相对Resources下的资源,AB包更好管理资源,区别是:

AssetBundle Resources
可以从网络下载,也可以本地自己打包,资源可以分布在多个包 在打包的时候会压缩一起打包,包括许多无用文件
存储位置可自定义 Resouces文件夹下
压缩方式可定义 只能压缩为二进制
后续可以动态更新 只读(只能通过Resouces.Load加载),无法修改

作用是:

(1)减少包体大小:压缩资源(节约硬盘空间),减少初始包大小

(2)热更新:

资源热更新:对模型、贴图等进行更新

脚本热更新:版本更新添加活动等

热更新基本规则:

Unity热更新——AssetBundle(AB包)+管理器_第1张图片

3、生成AB包资源文件

(1)Unity编辑器开发,自定义打包工具

(2)官方提供好的打包工具:Asset Bundle Browser

路径:Window->Package Manager->搜索Asset Bundle Browser,加载成功后在Window窗口会有AssetBundles

注意:高版本Unity用Addressables(以后会学习)功能封装了AB包功能,在Package Manager中可能搜不到Asset Bundle Browser,如果要使用在github上下载压缩包后解压到Packages文件夹下即可

注意:U3D中的C#脚本不能被打包为AB包,所以采用Lua热更新(注意:物体的组件本质是根据反射来对数据进行解析,因此真正打包的不是C#代码本身,而是组件关联的数据)

在资源的Inspector窗口最下方有一个AssetBundle窗口,第一个窗口是包名,第二个窗口是后缀名(一般不修改),注意是针对预设体而言,如果是Heirachy中的对象在Inpector窗口是不会有此选项的!

在AssetBundles面板中:

Configure 会出现关联的AB包
Build 进行AB包的生成 Build Target:构建的平台目标(IOS,Android,windows)
Output Path:输出路径
Clear Floders:是否清空文件夹,一般会选择,即在多次打包时,将原文件夹清空(即原来所有的包清空重新构建),但是资源很大时会很耗时
Copy to StreamingAssets:将打包的AB包从Output Path复制到特数的StreamingAssets文件夹(与备份不同,在某些平台是只读文件夹,在PC是可读可写)
Compression:压缩方式 No Compression:不压缩,解压快,但包很大,不推荐使用
LZMA:压缩最小,但解压很慢,缺点是如果只需要AB包中的一个资源,会将包中的所有资源解压
LZ4:压缩率没有LZMA大,但是可以单独解压一个资源,内存占用低(建议使用)

打包之后:

分两个文件:*,*.manifest

(1)关键AB包(和目录名一样的包):主包,存储着包与包之间的依赖关键关系

(2)*(没有后缀名的文件):资源文件

(3)*.manifest:AB包文件信息,对应资源文件相关的关键配置信息。当加载时提供了资源信息,依赖关系,版本信息等关键信息。

其他:(了解即可)

ETI:在资源包中,不包含资源的类型信息

FR:重新打包时需要重新构建包,和ClearFolder类似,不同的是FR不会删除不再存在的包(即在打包的时候删除了某个包,但是原文件夹中的包不会被清除,浪费存储空间)

ITC:增量构建检查时,忽略类型数的更改

Append Hash:将文件哈希值附加到资源包名上(几乎不用)

SM:严格模式,如果打包时报错了,打包直接失败无法成功

DRB:运行时构建

Inspector界面:主要用于观测包的相关信息(大小,路径等)

4、使用AB包资源文件

使用Unity的API进行加载使用:AssetBundle.***

(1)加载AB包

// 从StreamAssets读取有专门的API
AssetBundle ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + 包名(无后缀));

(2)加载AB包中的资源

ab.LoadAsset(“Cube")

有三个重载:资源名,Type指定类型,泛型

注意:只是用名字加载,会出现同名不同类型资源分不清,因此不建议使用,且:同一个AB包不能够重复加载,否则报错

eg:

// 同步加载
GameObject obj = ab.LoadAsset("Cube");
GameObject obj = ab.LoadAsset("Cube", typeof(GameObject)) as GameObject;(通过Lua在C#代码加载对象时只能通过类型,因为Lua不支持泛型)
Instantiate(obj);
//异步加载——>协程
public GameObject obj;
StartCoroutine(LoadABRes("modle", "Cube"))
IEnumerator LoadABRes(string ABName, string resName) {
    // 第一步:加载AB包
    AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/" + ABName);
    yield return abcr;
    // 第二步:加载资源
    AssetBundleRequest abq = abcr.assetBundle.LoadAssetAsync(resName, typeof(GameObject));
    yield return abq;
    obj = abq.asset as GameObject;
}
// 为了解除所有AB包的绑定,可以使用以下API
AssetBundle.UnloadAllAssetBundles();

如果参数为true,那么解绑定包时会将场景中通过AB包加载的资源一起卸载,false时只对AB包解绑

// 解绑单个AB包
ab.Unload()

参数true/false的意义与UnloadAllAssetBundles()相同

5、AB包的依赖

在包中的一个资源A如果使用了另一个资源B,那么会自动的将B放在同一个包中,但是如果将B的AssetBundle选择另一个包,那么B会打包到另一个包中,此时如果加载包中的A并创建对象,那么A组件中与B相关的内容会丢失,除非此时也将另一个包中的B进行加载,即:

加载AB包中的资源需要将资源的依赖包一起加载才能正常显示

步骤:

(1)加载AB包1号(假设此包中的资源对2号包中的资源有依赖)

(2)加载AB包2号

(3)加载1号包中的资源

但是实际要完成以上操作,我们需要提前知道哪些包之间存在依赖关系,因此需要利用主包获取依赖信息

步骤:假设寻找包"model“的依赖包

(1)加载主包,假设主包名为Main(打包后主包名和路径名一样)

AssetBundle abMain = AssetBundle.LoadFromFIle(Application.streamingAssetsPath + "/" + ”Main");

(2)加载主包中的固定文件

AssetBundleManifest abManifest = abMain.LoadAsset("AssetBundleManifest");

(3)从固定文件中,得到依赖信息,返回的即是依赖包的名字

string[] strs = abManifest.GetAllDependecies(”model");

(4)加载相关依赖包

List dependecies = new LIst();
for(int = 0; i < strs.Length; ++i) {
    dependecies.Add( AssetBundle.LoadFromFIle(Application.streamingAssetsPath + "/" + strs[i]));
}

注意,只能知道包与包之间的依赖关系,不能具体知道包中的资源的具体依赖关系

6、AB包资源加载管理器

需要用到:字典、协程、单例模式、AB包相关API,委托(Lambda表达式)

// 继承这种自动创建的单例模式基类不需要我们手动去拖动或者API去添加
// 使用时直接GetInstance即可
public class SingletonAutoMono : MonoBehaviour where T:MonoBehaviour {
    private static T instance;

    public static T GetInstance(){
        if (istance == null) {
            GameObject obj = new GameObject();
            // 设置对象名字为脚本名
            obj.name = typeof(T).ToString();
            // 让这个单例模式对象过场景不移除,因为单例模式对象往往是存在于
            // 整个程序生命周期的
            DontDestroyOnLoad(obj);
            instance = obj.AddComponent();  
        }
        return instance;
    }
}
public class ABMgr : SingletonAutoMono {
	// AB包管理器目的是:让外部更方便地进行资源加载
	// AB包不能重复加载,重复加载会报错,其中的资源不限制
	// 用字典来存储加载过的AB包
	private Dictionary abDic = new Dictionary ();
    // 主包和相关配置文件只需要加载一次即可,因此声明相关变量
	// 主包
	private AssetBundle mainAB  = null;	
	// 依赖包获取用的配置文件
	private AssetBundleManifest manifest = null;

    private ABMgr() { }

	// AB包存放路径,方便修改
	private string PathUrl {
		get {	return Application.streamingAssetsPath + "/";  /* 假设是该路径 */ }
	}

	// 主包名,方便修改
	private string MainABName {
		get {
#if UNITY_IOS
			return "IOS";
#elif UNITY_ANDROID
			return "Andriod";
#else
			return "PC";
#endif
		}
	}

	// 确保了每个包都只加载了一次
	public void LoadAB(string abName) {
		// 加载AB主包和其中的关键配置文件
		if (mainAB == null) {
			mainAB = AssetBundle.LoadFromFile(PahtUrl + MainABName);
			manifest = mainAB.LoadAsset("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 + str[i]);
    			abDic.Add(strs[i],ab);
			}
		}

		// 加载资源来源包
     	// 如果没有加载过,再加载
		if (!abDic.ContainsKey(abName)) {
			ab = AssetBundle.LoadFromFile(PathUrl + abName);
			abDic.Add(abName. ab);
		}
	}



	// 对同步加载进行重载,因为通过泛型可以避免as转换,并且Lua不支持泛型,
    // 因此还需要使用type重载
	// 同步加载, 不指定类型
    // 加载abName包中的resName资源
	public Object LoadRes(string abName, string resName) {
		// 加载AB包
		LoadAB(abName);
		// 加载资源
		Object obj = abDic[abName].LoadAsset(resName);
		// 为了外面方便,再加载资源时,判断一下资源是不是GameObject
		// 如果是,直接实例化,否则返回给外部
		if (obj is GameObject)
			return Instantiate(obj);
		else
			return obj;
	}

	// 同步加载,根据type指定类型
	public Object LoadRes(string abName, string resName, System.Type type) {
		// 加载AB包
		LoadAB(abName);
		// 加载资源
		Object obj = abDic[abName].LoadAsset(resName, type);
		// 为了外面方便,再加载资源时,判断一下资源是不是GameObject
		// 如果是,直接实例化,否则返回给外部
		if (obj is GameObject)
			return Instantiate(obj);
		else
			return obj;
	}

	// 同步加载,根据泛型指定类型
	// 必须要加约束,因为LoadAsset方法带有约束
	public T LoadRes (string abName, string resName)  where T:Object{
		// 加载AB包
		LoadAB(abName);
		// 加载资源
		T obj = abDic[abName].LoadAsset(resName, type);
		// 为了外面方便,再加载资源时,判断一下资源是不是GameObject
		// 如果是,直接实例化,否则返回给外部
		if (obj is GameObject)
			return Instantiate(obj);
		else
			return obj;
	}

	// 异步加载的方法,由于异步加载无法马上使用资源,需要使用委托
    // 来知道资源加载完后应该怎样使用资源
    // 这里的异步加载,AB包并没有使用异步加载
    // 只是从AB包中加载资源时,使用异步
    // 和同步一样重载
    // 根据名字异步加载资源
    public void LoadResAsync(string abName, string resName, UnityAction callBack) {
        StartCoroutine(ReallyLoadResAsync(abName, resName, callBack));
    }

    private IEnumerator ReallyLoadResAsync(string abName, string resName, UnityAction callBack) {
		LoadAB(abName);
		AssetBundlesRequest abr = abDic[abName].LoadAssetAsync(resName);
        yield return abr;
        
        // 异步加载结束后,通过委托传递给外部来使用
		if (abr.asset is GameObject)
			callBack(Instantiate(abr.asset));
		else
			callBack(abr.asset);
    }

    // 根据type异步加载资源
    public void LoadResAsync(string abName, string resName, System.Type type, UnityAction callBack) {
        StartCoroutine(ReallyLoadResAsync(abName, resName, type, callBack));
    }

    private IEnumerator ReallyLoadResAsync(string abName, string resName, System.Type type, UnityAction callBack) {
		LoadAB(abName);
		AssetBundlesRequest 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(string abName, string resName, UnityAction callBack) where T:Object{
        StartCoroutine(ReallyLoadResAsync(abName, resName, callBack));
    }

    private IEnumerator ReallyLoadResAsync(string abName, string resName, UnityAction callBack) {
		LoadAB(abName);
		AssetBundlesRequest abr = abDic[abName].LoadAssetAsync(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;
	}
}

留个坑,上面只实现了资源的异步加载,以后实现AB包的异步加载;

调用时:

同步加载:

(1)通过名字:

Object obj = ABMgr.GetInstance().LoadRes("model", "Cube");

或者

GameObject obj = ABMgr.GetInstance().LoadRes("model", "Cube") as GameObject;

(2)通过类型:

GameObject obj = ABMgr.GetInstance().LoadRes("model", "Cube", typeof(GameObject)) as GameObject;

(3)通过泛型:

GameObject obj = ABMgr.GetInstance().LoadRes("model", "Cube");

异步加载:

(1)通过名字

ABMgr.GetInstance().LoadResAsync("model","Cube", (obj) => {
    (obj as GameObject).transform.position = -Vector3.up;
});

(2)通过类型

ABMgr.GetInstance().LoadResAsync("model","Cube", typeof(GameObject), (obj) => {
    (obj as GameObject).transform.position = -Vector3.up;
});

(3)通过泛型

ABMgr.GetInstance().LoadResAsync("model","Cube", (obj) => {
    obj.transform.position = -Vector3.up;
});

你可能感兴趣的:(Unity学习,#,Unity--热更新,unity)