Unity资源热更之AssetBundle(2)———旧版本AssetBundle
转自 http://blog.csdn.net/y1196645376/article/details/52567171
这篇文章主要讲的是旧版的AssetBundle打包一系列方案。
1.API介绍:
a.BuildPipeline.BuildAssetBundle :打包常规资源生成AssetBundle. |
- Object mainAsset: 指定mainAsset,这样解析该AB包的时候可以通过assetBundle.mainAsset得到。
- Object[] assets : 指定所有asset,传入对象数组.解析的时候可以通过LoadAsset(“name”)得到。
- pathName : 指定生成的AB包的存储路径
- BuildAssetBundleOptions:打包时候的一些特定属性选项。
CompleteAssets : 用于保证资源的完备性,默认开启; CollectDependencies:用于收集资源的依赖项,默认开启; DeterministicAssetBundle:用于为资源维护固定ID,默认开启; ForceRebuildAssetBundle:用于强制重打所有AssetBundle文件,新增; IgnoreTypeTreeChanges:用于判断AssetBundle更新时,是否忽略TypeTree的变化,新增; AppendHashToAssetBundleName:用于将Hash值添加在AssetBundle文件名之后,开启这个选项可以直接通过文件名来判断哪些Bundle的内容进行了更新(4.x下普遍需要通过比较二进制等方法来判断,但在某些情况下即使内容不变重新打包,Bundle的二进制也会变化),新增。 ChunkBasedCompression:用于使用LZ4格式进行压缩,5.3新增。 |
- targetPlatform:适用平台。选择该AB应该使用哪些平台。
StandaloneWindows: 打包32位windows平台上的包。 StandaloneWindows64: 打包64位windows平台上的包。 iOS: 打包ios平台上的包。 Android : 打包Android平台上的包。 当然远不止支持这几个平台格式,但是其他的平台不是常用就不介绍了。 |
b.BuildStreamedSceneAssetBundle:将场景打包成AsssetBundle包。 |
- levels:欲打包的所有场景文件路径。
- localtionPath : 生成AssetBundle包保存的相对路径。
- target : 同上。
-
options : 同上。
www下载好场景AB包之后通过获取成员属性assetBundle就可以吧场景信息读入到内存中了。然后只需要通过Application.LoadLevel(name)就可以加载对应的场景。
c.WWW:通过一个路径进行下载(无论是服务器路径还是本地路径下载操作都一样)。 |
-
Url:下载路径;请注意:如果是本地链接请加上前缀”file://”,如果是http链接请加上前缀”http://”。
www下载后的得到的对象可以通过获取成员属性assetBundle,即可得到下载文件的AB包。不过该方法下载得到的资源统统是放在内存里面的,也就是说每次打开游戏都需要重新下载。
d.WWW.LoadFromCacheOrDownload : 通过路径和版本号下载。 |
- Url: 下载路径;请注意:如果是本地链接请加上前缀”file://”,如果是http链接请加上前缀”http://”。
-
Version:版本号;请注意这个版本号不需要你自己去维护,每次Unity从服务器下载资源的到本地的时候都会维护这个资源的版本号。当下次调用该API下载该资源会先对比本地版本号和参数版本号是否匹配,如果不匹配就从服务器重新下载,否则就从磁盘缓存文件中读取。
www下载后的得到的对象可以通过获取成员属性assetBundle,即可得到下载文件的AB包。该方法下载的资源是缓存到电脑缓存文件中的。
e.BuildPipeline.PushAssetDependencies: 资源入栈。 BuildPipeline.PopAssetDependencies: 资源出栈。 |
当我们对场景中多个复杂的物体进行打包的适合涉及到资源依赖关系。可以看到,Push和Pos都是成对使用,一个Push/Pop对就相当于一个Layer(层),层可以嵌套,内层可以依赖外层的资源。也就是说内层某资源在打包时,如果其引用的某个资源已经在外层加载了,那么内层的这个资源包就会包含该资源的引用而不是资源本身。Push/Pop实际上维持了一个依赖的堆栈。那么,在加载依赖资源包时,需要注意的是:先加载依赖的资源,然后加载其他资源,需要确保这个顺序。 |
2.实战使用:
1.单个打包 && 合并打包。 |
-
首先,打开工程,在场景中创建两个Cube分别命名为Cube1,Cube2。然后制作成预制体。
-
然后编写下面一段脚本:(脚本源自雨松MOMO大神,本人可能根据实际情况略有改动)
using UnityEngine; using System.Collections; using UnityEditor; public class OldAssetBundleEditor : MonoBehaviour { [MenuItem("AB Editor/Create AssetBunldes Main")] static void CreateAssetBunldesMain() { //获取在Project视图中选择的所有游戏对象 Object[] SelectedAsset = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets); //遍历所有的游戏对象 foreach (Object obj in SelectedAsset) { string sourcePath = AssetDatabase.GetAssetPath(obj); //本地测试:建议最后将Assetbundle放在StreamingAssets文件夹下,如果没有就创建一个,因为移动平台下只能读取这个路径 //StreamingAssets是只读路径,不能写入 //服务器下载:就不需要放在这里,服务器上客户端用www类进行下载。 string targetPath = Application.dataPath + "/StreamingAssets/" + obj.name + ".assetbundle"; if (BuildPipeline.BuildAssetBundle(obj, null, targetPath, BuildAssetBundleOptions.CollectDependencies)) { Debug.Log(obj.name + "资源打包成功"); } else { Debug.Log(obj.name + "资源打包失败"); } } //刷新编辑器 AssetDatabase.Refresh(); } [MenuItem("AB Editor/Create AssetBunldes ALL")] static void CreateAssetBunldesALL() { Caching.CleanCache(); string Path = Application.dataPath + "/StreamingAssets/ALL.assetbundle"; Object[] SelectedAsset = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets); foreach (Object obj in SelectedAsset) { Debug.Log("Create AssetBunldes name :" + obj); } //这里注意第二个参数就行 if (BuildPipeline.BuildAssetBundle(null, SelectedAsset, Path, BuildAssetBundleOptions.CollectDependencies)) { AssetDatabase.Refresh(); } } }
-
这个时候在Unity工具栏处应该出现了一个AB Editor的选项按钮。
-
我们在Project资源视图栏中同时选中我们刚才制作好的Cube1,Cube2的Prefab。然后在分别点击上图所示的AB Editor栏下的两个按钮。这个时候就会分别生成两个Cube的单个打包和合并打包。不过请注意,一定要保证你的工程Assets目录下有StreamingAssets文件夹。
- 以上我们就对Cube1,Cube2打包完成了,接下来我们来编写代码读取这两个AssetBundle资源。然后实例化到场景中去。
using UnityEngine; using System.Collections; public class OldAssetBundleLoad : MonoBehaviour { //不同平台下StreamingAssets的路径是不同的,这里需要注意一下。 public static readonly string PathURL = #if UNITY_ANDROID "jar:file://" + Application.dataPath + "!/assets/"; #elif UNITY_IPHONE Application.dataPath + "/Raw/"; #elif UNITY_STANDALONE_WIN || UNITY_EDITOR "file://" + Application.dataPath + "/StreamingAssets/"; #else string.Empty; #endif void Start() { //StartCoroutine(LoadMainGameObject(PathURL + "Cube1.assetbundle")); //StartCoroutine(LoadMainGameObject(PathURL + "Cube2.assetbundle")); StartCoroutine(LoadALLGameObject(PathURL + "ALL.assetbundle")); } //读取一个资源 private IEnumerator LoadMainGameObject(string path) { WWW bundle = new WWW(path); yield return bundle; //加载到游戏中 yield return Instantiate(bundle.assetBundle.mainAsset); bundle.assetBundle.Unload(false); } //读取全部资源 private IEnumerator LoadALLGameObject(string path) { WWW bundle = new WWW(path); yield return bundle; //通过Prefab的名称把他们都读取出来 Object obj0 = bundle.assetBundle.LoadAsset("Cube1"); Object obj1 = bundle.assetBundle.LoadAsset("Cube2"); //加载到游戏中 yield return Instantiate(obj0); yield return Instantiate(obj1); bundle.assetBundle.Unload(false); } }
- 写好脚本后,将该脚本挂在场景的Camera上面,然后把场景的Cube1,Cube2删掉。运行游戏,你会发现脚本通过解析AssetBundle包将Cube1,Cube2实例化到场景中。
2.引入依赖关系的概念。 |
-
我们回到上面那个demo,打开StreamingAssets文件夹查看刚才打包好的AB包。
-
仔细我们可以发现,分开打包的总大小居然是合并打包的2倍。这是为什么呢?其实是这样的:
当选择了BuildAssetBundleOptions.CollectDependencies时,果然你没有将A依赖的资源B使用BuildPipeline.PushAssetDependencies() 和BuildPipeline.PopAssetDependencies()来进行依赖打包,那么A会把其引用的资源都打包到自己的assetbundle 中。如果引用的资源是图片、sprite或自定义的shader(内置的shader不会打包,这里的自定义shader被unity看作是一种资源,打包处理的时候也是如同资源来处理的),那么会打包到assetbundle 中。如果引用的是代码,那么会打包一个对工程中代码的引用,也就是说引用的代码必须存在于工程中,这样当c被加载到本地的时候才可以和本地的代码进行关联,如果本地没有这个代码,则会丢失对这个脚本的引用。 -
那这么说:是不是把所有Prefab都打包到一个AssetBundle里面就是最佳方案么?答案是并不:
1. 更新麻烦:如果所有资源都在一个包下,那么任意一个资源更改了都要把这个包整体更换。 2. 维护麻烦:因为有的物体是共用的某些被依赖项,所以如果涉及到修改这些被依赖项,就会牵一发而动全身。 |
- 所以说:我们需要找到一个解决方案来解决以上两个问题:1.依赖资源共享,2.可维护性。
——而这个解决方案就是我们所说的依赖打包。
3.依赖打包的流程 |
-
依赖关系打包的实质就是将那些被依赖的资源先打包 ( 这里我把这类资源用”底层资源”代替 )。对于那些引用了底层资源的资源 ( 这里我把这类资源用”顶层资源”代替 )在打包的时候就不用再将底层资源打包到自己的包里面,而是添加对底层资源的引用,这样就避免了重复打包底层资源了。而且不同资源可以根据项目的分类分别打包到不同的AB包里面,也方便了后期的更新和维护。
-
废话不多说,我们来了解如何使用依赖关系打包。这里我提供两个依赖关系的示例:
- 和上面那个例子一样,两个Cube引用同一个materials。这样的依赖关系应该是这样:
所以,打包方式是从最底层开始:green->Cube1->Cube2,后面两个可以交换顺序。当然仅仅是体现在打包顺序上是不够的,我们这里要引入一个资源栈的概念。一个Push-Pop对应一个资源栈,资源栈可以内部再嵌套多个资源栈。那么我们试图LoadAsset的时候,我们就需要将这个asset所在栈的外部栈的所有asset全部load出来。 - BuildPipeline.PushAssetDependencies: 资源入栈,BuildPipeline.PopAssetDependencies: 资源出栈。
这里我提供一个我总结的一个小技巧:
1.先把所有材料编号:A-green,B-Cube1,C-Cube2。
2.然后将每个编号两边加上括号,表示每个材料都在一个独立的栈:(A),(B),(C)
3.如果X依赖Y,那么X的栈加入Y的栈里:(A(B)),(A(C))。
4.然后合并:(A(B)(C))。
- 和上面那个例子一样,两个Cube引用同一个materials。这样的依赖关系应该是这样:
- 打包代码:
using UnityEngine; using System.Collections; using UnityEditor; public class OldAssetBundleDependence : MonoBehaviour { [MenuItem("AB Editor/Create Dependence AssetBunldes")] public static void Pack() { string path = Application.dataPath + "/StreamingAssets"; BuildTarget target = BuildTarget.StandaloneWindows64; BuildAssetBundleOptions op = BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets | BuildAssetBundleOptions.DeterministicAssetBundle; // ( BuildPipeline.PushAssetDependencies(); // A BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/Materials/green.mat"), null, path + "/green.assetbundle", op, target); // ( BuildPipeline.PushAssetDependencies(); // B BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/Prefab/Cube1.prefab"), null, path + "/Cube1.assetbundle", op, target); // ) BuildPipeline.PopAssetDependencies(); // ( BuildPipeline.PushAssetDependencies(); // C BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/Prefab/Cube2.prefab"), null, path + "/Cube2.assetbundle", op, target); // ) BuildPipeline.PopAssetDependencies(); // ) BuildPipeline.PopAssetDependencies(); } }
-
清注意,若要使用以上代码请保证:在你的工程目录下也应有此几个资源文件且路径和名字应当一样。
-
保存代码点击AB Editor/Create Dependence AssetBunldes,就会在StreamingAssets文件夹下生成三个AB包。仔细观察发现和我们最开始的单个打包得到的3个包的区别:
很明显可以看出差别了。所以使用依赖打包可以很大程度上减少资源的重复,减小包的大小。 |
- 不过值得注意的是:通过依赖关系打包生成的AssetBundle不能像之前那样加载,因为有了依赖关系所以加载某个asset的时候需要先把它的所有依赖项都提前加载。例如对于刚才的OldAssetBundleLoad脚本,我们在Start函数去解析AB包实例化Cube1。
void Start() { StartCoroutine(LoadMainGameObject(PathURL + "Cube1.assetbundle")); }
-
运行场景,发现成功实例化出Cueb1,然而身上的材质却丢失了。
-
这时候,我们修改Start函数,在实例化Start函数之前先去Load材质资源:
void Start() { StartCoroutine(LoadAB(PathURL + "green.assetbundle")); StartCoroutine(LoadMainGameObject(PathURL + "Cube1.assetbundle")); } public IEnumerator LoadAB(string path) { bundle = new WWW(path); yield return bundle.assetBundle.mainAsset; }
- 这次运行发现材质已经成功找到了,所以在使用依赖打包的时候特别需要注意的是需要先Load依赖文件。对了,我们需要注意的是一个AB包不能被重复Load,也就是说假如我用以上的代码生成了Cube1。但是如果我用同样的方式再生成一个Cube2,就不能去加载材质了。因为材质已经被Load过一次,所以就导致我们在创建一个物体的时候我们不知道它的依赖资源哪些已经被Load过的。这就必须要让我们遵循一个原则,就是Load过的之后及时UnLoad(false)。不过,更好的解决方法应当是,在应该创建一个资源管理类,拿一个字典专门记录所有的Load过的AB包。这样我们就知道哪些Load过哪些没Load过了。
4.依赖打包的扩展 |
- 对于依赖打包的AB包,我们解析的时候需要额外注意依赖包的Load,但是这个过程我们可以让代码自动化完成。因为5.X的新AssetBundle采用的就是这个方案,所以我就简单说一下思路不给出代码了。我们可以在依赖关系打包的时候,生成依赖关系信息。然后实例化的时候就可以根据这个信息然后动态获取一个AssetBundle的所有依赖包的Path.这样解析起来就很方便了。
以上就是Unity老版本的AssetBundle打包攻略,接下来我会介绍Unity新版本的AssetBundle使用攻略。
Demo源码地址: 点这里