Unity3D学习笔记(二十九):AssetBundle
AssetBundle
什么是AssetBundle?
AssetBundle是把一些资源文件或场景文件,以某种方式保存在一个文件中。一个AssetBundle可以包含模型、材质、图片或场景等。但是AssetBundle不能包含脚本文件。(脚本打包时需要编译,界面逻辑的热更新依赖Lua)
AssetBundle主要用于做热更新使用。
如何创建AB包
![](https://img2018.cnblogs.com/blog/1517599/201902/1517599-20190212000321962-1550271518.png)
第一个参数:
----无
----新建
----删除未使用的AssetBundle
第二个参数:
----后缀:做资源高清和标清的区分,不能做资源AB包的区分
![](https://img2018.cnblogs.com/blog/1517599/201902/1517599-20190212000332595-1762584827.png)
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; public class CreateAssetBundle : MonoBehaviour { [MenuItem("AssetBundle/CreateAB")] static void CreateAB() { //第一个参数:AB包的输出路径 //第二个参数:打包的参数设置,我们设置的是强制性的重新打包 //第三个参数:AB包的适用平台,不同的平台使用的AB包是不一样的 BuildPipeline.BuildAssetBundles( Application.streamingAssetsPath + "/AssetBundle/", BuildAssetBundleOptions.ForceRebuildAssetBundle, BuildTarget.StandaloneWindows64 ); } }
.meta文件:记录该资源在Unity里的相关设置(配置信息表)
作用:SVN团队合作时,同时更新资源和.meta文件
![](https://img2018.cnblogs.com/blog/1517599/201902/1517599-20190212000358088-1912138657.png)
如何使用AB包里的文件
注意:避免在一个AB包里出现同名同类型文件,只会加载其中一个
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class LoadABTexture : MonoBehaviour { private Image image; private void Awake() { image = GetComponent<Image>(); } // Use this for initialization void Start () { //要是AB包中的资源文件 //第一种方式:先加载AB包 //AssetBundle ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/AssetBundle/ui"); //第二种方式: WWW www = new WWW("file://" + Application.streamingAssetsPath + "/AssetBundle/ui"); AssetBundle ab = www.assetBundle; Sprite sp = ab.LoadAsset<Sprite>("beijing_02.jpg"); image.sprite = sp; image.SetNativeSize(); } }
打包加载Json.txt文件
![](https://img2018.cnblogs.com/blog/1517599/201902/1517599-20190212000420377-1757584350.png)
AssetBundle jsonAB = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/AssetBundle/json"); TextAsset json = jsonAB.LoadAsset<TextAsset>("Json"); Debug.Log(json);
打包加载预制体
AB包的依赖性
如果一个AB包a中的资源,使用了另一个AB包b中的资源,那么这个a就依赖于b。
当你使用AB包a中的资源的时候,需要先把依赖的AB包b先加载进来。
三层依赖关系(常用):预制体prefab->材质mat->图片ui
四层依赖关系(难做)
注意:同一个AB包(在卸载之前)只能加载一次(不管是否在同一脚本中,只要是在一个项目中)
//从AB包中加载预制体,把预制体实例到界面 //加载AB包 AssetBundle uiAB = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/AssetBundle/ui"); AssetBundle matAB = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/AssetBundle/mat"); AssetBundle prefabAB = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/AssetBundle/prefab"); //从AB包中加载预制体 GameObject prefab = prefabAB.LoadAsset<GameObject>("Cube"); //实例化预制体 Instantiate(prefab, transform);
每一个AB包都对应一个.manifest文件(资源清单),包含如下信息
ManifestFileVersion: 0//版本号 CRC: 1860656504//CRC循环冗余码 Hashes://资源文件的哈希码,用于检查增量的构建AB包 AssetFileHash: serializedVersion: 2 Hash: 82edae0095f36a303261e62246d831ae TypeTreeHash: serializedVersion: 2 Hash: 81fd706e1561f1cfc1872b1168421ee0 HashAppended: 0 ClassTypes://该AB包中所有资源使用到的类类型,一般情况下对应的都是地址 - Class: 1 Script: {instanceID: 0} - Class: 114 Script: {fileID: 11500000, guid: d533bd1959b71b4459e871de8a9975af, type: 3} - Class: 115 Script: {instanceID: 0} Assets://对应该AB包中的所有资源 - Assets/Prefabs/Cube.prefab Dependencies://该AB包的直接依赖 - "D:/Unity3D\u6E38\u620F\u5F00\u53D1\u5DE5\u7A0B\u5E08\u73ED1803\u671F-\u706B\u661F\u65F6\u4EE3/Unity3D/Unity_Projects/m3w3d2_Lesson32/Assets/StreamingAssets/AssetBundle/ui"//路径里的中文用16进制显示
单一的AssetBundle.Manifest文件:
1、所有的AssetBundle包
2、所有的AB包的依赖(可以通过获取AssetBundle.Manifest文件,来获取AB包的依赖关系)
注意:AssetBundle.Manifest文件的名字,会根据父文件夹的名字改变
ManifestFileVersion: 0 CRC: 1678997246 AssetBundleManifest: AssetBundleInfos: Info_0: Name: ui Dependencies: {} Info_1: Name: mat Dependencies: {} Info_2: Name: prefab Dependencies: Dependency_0: ui
不知道资源的依赖关系,自动去获取依赖项和加载依赖项
使用AB包的步骤
1、加载总的构建的AB包
2、从单一的AB包中去加载构建清单
3、从构建清单中获取指定的AB包的所有的依赖项,并加载所有的依赖项
4、加载资源所在的AB包
5、从AB包中加载资源
//使用AB包的步骤 //1、加载总的构建的AB包 AssetBundle singleAB = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/AssetBundle/AssetBundle"); //2、从单一的AB包中去加载构建清单 AssetBundleManifest singleManifest = singleAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); //3、从构建清单中获取指定的AB包的所有的依赖项,并加载所有的依赖项 //singleManifest.GetAllDependencies("prefab");获取所有的依赖,不管直接还是间接 //singleManifest.GetDirectDependencies("prefab");获取所有的直接依赖 string[] deps = singleManifest.GetAllDependencies("prefab"); for (int i = 0; i < deps.Length; i++) { //Debug.Log(deps[i]); AssetBundle depAB = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/AssetBundle/" + deps[i]); } //4、加载资源所在的AB包 AssetBundle prefabAB = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/AssetBundle/prefab"); //5、从AB包中加载资源 GameObject prefab = prefabAB.LoadAsset<GameObject>("Cube"); Instantiate<GameObject>(prefab, transform);
public string[] GetAllDependencies(string assetBundleName);//获取所有依赖(老版本没有) public string[] GetDirectDependencies(string assetBundleName);//获取直接依赖
AB包的卸载
Unload(bool)
当传入true时,不光卸载内存中的AssetBundle对象,还卸载从这个AB包中加载到的资源。
当传入false时,只卸载内存中的AB包,不卸载从AB中加载的资源。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class UnloadAB : MonoBehaviour { public AssetBundle uiAB; public AssetBundle matAB; public AssetBundle prefabAB; public GameObject prefab; // Use this for initialization void Start () { uiAB = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/AssetBundle/ui"); matAB = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/AssetBundle/mat"); prefabAB = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/AssetBundle/prefab"); prefab = prefabAB.LoadAsset<GameObject>("Cube"); Instantiate<GameObject>(prefab, transform); } // Update is called once per frame void Update () { if (Input.GetKeyDown(KeyCode.Space))//按空格键卸载 { uiAB.Unload(false); matAB.Unload(false); prefabAB.Unload(false); } } }
字典:
简介:
1、字典里的每一个元素都是一对键值对(由两个元素组成:一个是键一个是值)
2、字典的键必须是唯一的,值不需要唯一
3、键和值都可以是任意的类型(比如:基本类型,自定义类型)
4、可以通过键去访问值
字典的使用:
1、必须引用命名空间using System.Collections.Generic;
2、
using System.Collections; using System.Collections.Generic; using UnityEngine; public class LessonDictionary : MonoBehaviour { //动态数组的申请方式 List<string> list = new List<string>(); //定义一个键是string类型,值是string类型的字典 Dictionary<string, string> dic1 = new Dictionary<string, string>(); //定义一个键是string类型,值是GameObject类型的字典 Dictionary<string, GameObject> dic2 = new Dictionary<string, GameObject>(); // Use this for initialization void Start () { //对list进行添加元素 list.Add("nihao"); //字典的添加元素,添加的是键值对,第一个参数键,第二个参数值 dic1.Add("a", "A"); //这种方式也能添加元素 dic1["b"] = "C"; //list改变值的方式 list[0] = "1"; //字典改变这个键对应的值的方式 dic1["a"] = "B"; //list通过索引访问元素 Debug.Log(list[0]); //字典通过键去访问这个键对应的值 Debug.Log(dic1["a"]); Debug.Log(dic1["b"]); //list删除元素 list.Remove("1"); //字典删除元素,通过键去删除这个键值对 dic1.Remove("a"); //list的遍历 for (int i = 0; i < list.Count; i++) { Debug.Log(list[i]); } foreach (var item in list)//不能改值,且效率低,产生内存碎片 { Debug.Log(item); } //字典的遍历 //遍历字典的键 foreach (var item in dic1.Keys) { Debug.Log(item);//打印的键 Debug.Log(dic1[item]);//打印的值 } //遍历字典的值 foreach (var item in dic1.Values) { Debug.Log(item);//打印字典的值 } //遍历字典的键值对 foreach (KeyValuePair<string,string> item in dic1) { Debug.Log(item.Key);//打印字典的键 Debug.Log(item.Value);//打印字典的值 } //list里是否有“1”这个元素 if (list.Contains("1")) { } //判断字典里是否有“a”这个键 if (dic1.ContainsKey("a")) { } string value = ""; //这个方法的返回值,这个字典里有没有第一个参数的这个键 //如果有,那么把这个键所对应的值放在out参数的value里 if (dic1.TryGetValue("a", out value)) { } //删除list的所有元素 list.Clear(); //删除字典的所有元素 dic1.Clear(); } }
专门负责加载AB包的管理器
using System.Collections; using System.Collections.Generic; using UnityEngine; public class LoadManager { #region 单例 private static LoadManager instance; public static LoadManager Instance { get { if (instance == null) { instance = new LoadManager(); } return instance; } } private LoadManager() { abDic = new Dictionary<string, AssetBundle>(); abPath = Application.streamingAssetsPath + "/AssetBundle/"; singleABName = "AssetBundle"; } #endregion /// <summary> /// 用来存储已经加载的AB包,键是AB包的名字,值就是AB包。 /// </summary> public Dictionary<string, AssetBundle> abDic; //用来存储单一的ab包 public AssetBundle singleAB; //单一的构建清单,所有的ab包的依赖全部从这获取 public AssetBundleManifest singleManifest; //存储ab包的路径 public string abPath; //单一的ab包的名字 public string singleABName; /// <summary> /// 加载单一的ab包,和单一的构建清单 /// </summary> private void LoadSingleAssetBundle() { //每次加载单一的ab包需要判断是否加载果过。 //singleAB为null没加载过,不为null就是加载过 if (singleAB == null) { singleAB = AssetBundle.LoadFromFile(abPath + singleABName); } //从单一的ab包中加载单一的构建清单 if (singleManifest == null) { singleManifest = singleAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); } } /// <summary> /// 加载指定ab包的所有的依赖项 /// </summary> /// <param name="abName"></param> private void LoadAllDependencies(string abName) { LoadSingleAssetBundle(); //首先先获取指定的这个ab包的所有的依赖项 //从单一的构建清单中获取 string[] deps = singleManifest.GetAllDependencies(abName); //遍历去加载依赖项 for (int i = 0; i < deps.Length; i++) { //加载该依赖项前,先判断之前加载没加载过该依赖项 //就是判断存储ab包的字典里有没有这个ab包 if (!abDic.ContainsKey(deps[i]) ) { //如果未加载过,需要加载 AssetBundle ab = AssetBundle.LoadFromFile(abPath + deps[i]); //ab包加载完之后,把加载来的ab包存储在字典里 abDic[deps[i]] = ab; } } } /// <summary> /// 加载指定的ab包,并且返回该ab包 /// </summary> /// <param name="abName"></param> /// <returns></returns> public AssetBundle LoadAssetBundle(string abName) { LoadAllDependencies(abName); //加载指定的ab包 //加载前先判断是否已经加载过,如果加载过,把加载过的ab包给你 //如果未加载过,就加载该ab包 //方法一: //if (abDic.ContainsKey(abName))//证明该ab包已经加载过 //{ // return abDic[abName]; //} //AssetBundle ab = ab = AssetBundle.LoadFromFile(abPath + abName); //abDic[abName] = ab; //return ab; //方法二:优化重构 AssetBundle ab = null; if (!abDic.TryGetValue(abName, out ab)) { //如果进入到这,证明该键没有指定的值,那么证明该ab包未加载,需要加载 ab = AssetBundle.LoadFromFile(abPath + abName); //把加载进来的ab包添加到字典中 abDic[abName] = ab; } return ab; } /// <summary> /// 加载指定的ab包中的指定名字的指定类型的资源 /// </summary> /// <typeparam name="T">指定资源的类型</typeparam> /// <param name="abName">ab包的名字</param> /// <param name="assetName">资源的名字</param> /// <returns></returns> public T LoadAssetByAB<T>(string abName, string assetName) where T : Object { //先获取指定的ab包 AssetBundle ab = LoadAssetBundle(abName); if (ab != null) { return ab.LoadAsset<T>(assetName); } else { Debug.LogError("指定的ab包的名字有误!"); } return null; } /// <summary> /// 卸载指定的ab包 /// </summary> /// <param name="abName"></param> /// <param name="unloadAllloadedObjects"></param> public void UnloadAssetBundle(string abName, bool unloadAllloadedObjects) { //方法一: //if (abDic.ContainsKey(abName)) //{ // abDic[abName].Unload(unloadAllloadedObjects); // abDic.Remove(abName); //} //方法二:优化重构 //先判断有没有这个ab包 AssetBundle ab = null; if (abDic.TryGetValue(abName, out ab)) { //卸载ab包 ab.Unload(unloadAllloadedObjects); //从容器中删除该ab包 abDic.Remove(abName); } } /// <summary> /// 卸载全部的ab包 /// </summary> /// <param name="unloadAllloadedObjects"></param> public void UnloadAllAssetBundle(bool unloadAllloadedObjects) { //遍历每一个ab包,调用ab包的卸载的方法 //遍历键,通过键去获取值进行卸载 foreach (var item in abDic.Keys) { abDic[item].Unload(unloadAllloadedObjects); } //直接遍历值去卸载 foreach (var item in abDic.Values) { item.Unload(unloadAllloadedObjects); } abDic.Clear(); } }
测试管理器
using System.Collections; using System.Collections.Generic; using UnityEngine; public class TestManager : MonoBehaviour { public UnityEngine.UI.Image image; // Use this for initialization void Start () { //先加载指定ab包,再加载包中的指定名字的指定类型的资源 //AssetBundle ab = LoadManager.Instance.LoadAssetBundle("prefab"); //GameObject prefab = ab.LoadAsset<GameObject>("Cube"); //直接加载指定的ab包中的指定名字的指定类型的资源 GameObject prefab = LoadManager.Instance.LoadAssetByAB<GameObject>("prefab", "Cube"); Instantiate(prefab, transform); //AssetBundle ab1 = LoadManager.Instance.LoadAssetBundle("prefab");//重复加载也不会报错 //AssetBundle ui = LoadManager.Instance.LoadAssetBundle("ui"); //Sprite sp = ui.LoadAsset<Sprite>("beijing_02"); Sprite sp = LoadManager.Instance.LoadAssetByAB<Sprite>("ui", "beijing_02"); image.sprite = sp; } // Update is called once per frame void Update () { } }
热更新的流程(判断是否进行热更新)
1、先把最新的资源打包成AB包,把最新的AB包上传到服务器上,并且修改服务器端资源版本号。
2、客户端一启动游戏,首先判断本地的版本号与服务器的版本号是否一致。
3、如果不一致,从服务器下载最新的资源AB包替换本地的AB包。
4、 解析最新的AB包从中加载想要的资源。