【Unity游戏开发】AssetBundle杂记--AssetBundle的二三事

1、简介

  马三在公司大部分时间作的都是游戏业务逻辑和编辑器工具等相关工做,所以对Unity AssetBundle这块的知识点并非很熟悉,本身也是有打算想了解并熟悉一下AssetBundle,掌握一下Unity资源管理方面的知识,所以有了这篇博客。html

  我会在博客中整理出一些本身学习时收集到的一些比较好的AssetBundle学习资料,而且结合如今正在参与开发的商业项目随时写下一些本身的拙见。本篇博客权当是马三本身的笔记和一些杂谈,会不断地随时随地的更新一些内容。git

2、收集整理的一些AssetBundle资料

1.Unity AssetBundle5讲系列

Unity5-ABSystem(一):AssetBundle原理github

Unity5-ABSystem(二):AssetBundle导出数组

Unity5-ABSystem(三):AssetBundle加载缓存

Unity5-ABSystem(四):AssetBundle依赖安全

Unity5-ABSystem(五):AssetBundle内存网络

2.慕容小匹夫系列

Unity3D 5.3 新版AssetBundle使用方案及策略数据结构

进击的AssetBundles和它的工具们多线程

3.何三思译Unity AssetBundle官方文档系列

【Unity3D技术文档翻译】第1.0篇 AssetBundles框架

【Unity3D技术文档翻译】第1.1篇 AssetBundle 工做流

【Unity3D技术文档翻译】第1.2篇 为打包 AssetBundles 准备资产

【Unity3D技术文档翻译】第1.3篇 建立 AssetBundles

【Unity3D技术文档翻译】第1.4篇 AssetBundle 依赖关系

【Unity3D技术文档翻译】第1.5篇 本地使用 AssetBundles

【Unity3D技术文档翻译】第1.6篇 使用 AssetBundle Manager

【Unity3D技术文档翻译】第1.7篇 AssetBundles 补丁更新

【Unity3D技术文档翻译】第1.8篇 AssetBundles 问题及解决方法

【Unity3D技术文档翻译】第1.9篇 使用 Unity AssetBundle Browser tool (AssetBundle系列完结)

4.Unity AssetBundle官方文档

AssetBundlesManual

A guide to AssetBundles and Resources

5.Unity Assetbundles官方说明系列

Unity5.4 Assetbundles官方说明一(AssetBundles打包详解)

Unity5.4 Assetbundles官方说明二(AssetBundle压缩与解压)

Unity5.4 Assetbundles官方说明三(AssetBundle资源包的内部结构)

Unity5.4 Assetbundles官方说明四(AssetBundles的下载和加载)

Unity5.4 Assetbundles官方说明五(从AssetBundles的加载和卸载资源对象)

Unity5.4 Assetbundles官方说明六(保留下载的AssetBundle)

Unity5.4 Assetbundles官方说明七(在AssetBundle中存储和加载二进制数据)

Unity5.4 Assetbundles官方说明八(数据安全方面的处理)

Unity5.4 Assetbundles官方说明九(资源包中包含脚本文件)

Unity5.4 Assetbundles官方说明十(官方疑难问题解答)

Unity5.4 Assetbundles十一:遇到的坑和整理的打包和加载流程(资源包更新的简易框架)

6.未规划分类

Unity AssetBundle 从入门到掌握(适合初学者)

Unity5 如何作资源管理和增量更新

Unity资源处理机制(Assets/WWW/AssetBundle/...)读取和加载资源方式详解

Unity3D中实现按资源名称自动化命名打包AssetBundle

Unity动态加载和内存管理(三合一)

Unity手游之路<十二>手游资源热更新策略探讨

Unity5 资源打包控制

Unity AssetBundle加载音频,没法播放音效并报错的坑

Unity打包AssetBundle自动分析资源依赖关系(包括UGUI图集打包)

Unity最佳实践-AssetBundle使用模式

Unity 5.x AssetBundle零冗余解决方案

资源依赖正确性测试

Unity AssetBundle 再回顾

AssetBundle资源打包加载管理

7.AssetBundle分组策略总结

逻辑实体分组
一个UI界面或者全部UI界面一个包(这个界面里面的贴图和布局信息一个包)
一个角色或者全部角色一个包(这个角色里面的模型和动画一个包)
全部的场景所共享的部分一个包(包括贴图和模型)
按照类型分组
全部声音资源打成一个包,全部shader打成一个包,全部模型打成一个包,全部材质打成一个包
按照使用分组
把在某一时间内使用的全部资源打成一个包。能够按照关卡分,一个关卡所须要的全部资源包括角色、贴图、声音等打成一个包。也能够按照场景分,一个场景所须要的资源一个包
注意
常常更新的资源放在一个单独的包里面,跟不常常更新的包分离
把须要同时加载的资源放在一个包里面
能够把其余包共享的资源放在一个单独的包里面
把一些须要同时加载的小资源打包成一个包
若是对于一个同一个资源有两个版本,能够考虑经过后缀来区分,例如v一、v二、v3

3、AssetBundle踩坑与经验集锦

  一、先说一个遇到的坑,当大量(几百个)AssetBundle加载的时候(多是WWW加载的时候,也多是AssetBundle.LoadAsset的时候),Android手机上会闪退。看崩溃log是多线程文件访问的时候崩溃了。解决方法是减小同时加载的AB数量(这个是纯逻辑控制),使用AssetBundle.LoadFromFile接口。  

  二、打包AssetBundle使用LZ4压缩(BuildPipeline.BuildAssetBundles,第二个参数传递BuildAssetBundleOptions.ChunkBasedCompression),默认是LZMA压缩的,具备最高的压缩比。而替换为LZ4压缩,压缩比没有LZMA高,可是加载速度大幅提升。加载AssetBundle使用AssetBundle.LoadFromFile(Async),在Unity4的时候,只能使用WWW的接口来加载AB,由于CreateFromFile不支持压缩的AB。而Unity5的LoadFromFile是支持任意压缩格式的AB的。因此没有太大必要使用WWW了,并且这个接口像WWW.LoadFromCacheOrDownload接口同样,加载不压缩或者LZ4压缩格式的AB的时候是不会有额外的内存开销的。具体关于内存、加载速度的细节能够参考上面第三篇文章里面的介绍。  

三、资源规划好一个独立的资源工程。规划好一系列的文件夹,在导入相应资源的时候自动经过AssetImporter设置好AB的名字。监测资源导入能够用AssetPostprocessor 。带动画的模型须要建立好prefab,而不带动画只是用于换装的模型能够直接导出,不须要建立prefab,由于这些模型咱们只是取它的mesh数据。若是有打包图集,须要注意它和AB的匹配关系,举例来讲,若是三张图片指定了同一个图集,而又分别指定了不一样的AB名,则三个AB里面都包含了此图集(三张图片),这样就会形成严重的资源浪费。  

四、AssetBundle.LoadFromFile接口在Android平台下也是能够直接访问StreamingAssets文件夹里面的内容的。5.4版本能够直接使用Application.streamingAssetsPath。而以前的版本须要使用 Application.dataPath + "!assets/" + filePath;  由于streamingAssetsPath带了jar://,这个是给WWW用的URL路径,而LoadFromFile接口须要的是实际路径(不带jar://也不带file://)。注意 !assets/ 这个地方叹号后面没有/。网上搜索到的各类写法都有,只有这个是正确的,注意此处细节。

 

4、Unity AssetBundle爬坑手记

文章转载自:http://www.cnblogs.com/ybgame/p/3973177.html  

这篇文章从AssetBundle的打包,使用,管理以及内存占用各个方面进行了比较全面的分析,对AssetBundle使用过程当中的一些坑进行填补指引以及喷!
 
AssetBundle是Unity推荐的资源管理方式,官方列举了诸如热更新,压缩,灵活等等优势,但AssetBundle的坑是很是深的,不少隐藏细节让你使用起来须要十分谨慎,一不当心就会掉入深坑,打包没规划好,20MB的资源“压缩”到了30MB,或者大量的包致使打包以及加载时的各类低效,或者莫名其妙地丢失关联,或者内存爆掉,以及各类加载失败,在网上研究了大量关于AssetBundle的文章,但每次看完以后,仍是有很多疑问,因此只能经过实践来解答心中的疑问,为确保结果的准确性,下面的测试在编辑器下,Windows,IOS下都进行了测试比较。
 
首先你为何要选择AssetBundle,纵使他有千般好处,但通常选择AssetBundle的缘由就是,要作热更新,动态更新游戏资源,或者你Resource下的资源超过了它的极限(2GB仍是4GB?),若是你没有这样的需求,那么建议你不要使用这个坏东西,闹心~~
 
当你选择了AssetBundle以后,以及我开始喷AssetBundle以前,咱们须要对AssetBundle的工做流程作一个简单的介绍:
AssetBundle能够分为打包AssetBundle以及使用AssetBundle
 
打包须要在UnityEditor下编写一些简单的代码,来取出你要打包的资源,而后调用打包方法进行打包
1 Object obj = AssetDatabase.LoadMainAssetAtPath("Assets/Test.png");
2 BuildPipeline.BuildAssetBundle(obj, null,
3                                   Application.streamingAssetsPath + "/Test.assetbundle",
4                                  BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets
5                                  | BuildAssetBundleOptions.DeterministicAssetBundle, BuildTarget.StandaloneWindows);

在使用的时候,须要用WWW来加载Bundle,而后再用加载出来的Bundle来Load资源。

WWW w = new WWW("file://" + Application.streamingAssetsPath + "/Test.assetbundle");
myTexture = w.assetBundle.Load("Test");
【一,打包】
 
接下来咱们来看一下打包:
 
1.资源的搜集
 
    在打包前咱们能够经过遍历目录的方式来自动化地进行打包,能够有选择性地将一些目录打包成一个Bundle,这块也能够用各类配置文件来管理资源,也能够用目录规范来管理
    我这边是用一个目录规范对资源进行大的分类,分为公共以及游戏内,游戏外几个大模块,而后用一套简单命名规范来指引打包,例如用OBO(OneByOne)做为目录后缀来指引将目录下全部资源独立打包,默认打成一个包,用Base前缀来表示这属于公共包,同级目录下的其余目录须要依赖于它
 
    使用Directory的GetFiles和GetDirectories能够很方便地获取到目录以及目录下的文件
Directory.GetFiles("Assets/MyDirs", "*.*", SearchOption.TopDirectoryOnly);
    Directory.GetDirectories(Application.dataPath + "/Resources/Game", "*.*", SearchOption.AllDirectories);
2.资源读取
    GetFiles搜集到的资源路径能够被加载,加载以前须要判断一下后缀是否.meta,若是是则不取出该资源,而后将路径转换至Assets开头的相对路径,而后加载资源。
string newPath = "Assets" + mypath.Replace(Application.dataPath, "");
    newPath = newPath.Replace("\\", "/");
    Object obj = AssetDatabase.LoadMainAssetAtPath(newPath);
3.打包函数
 
    咱们调用BuildPipeline.BuildAssetBundle来进行打包:
    BuildPipeline.BuildAssetBundle有5个参数,第一个是主资源,第二个是资源数组,这两个参数必须有一个不为null,若是主资源存在于资源数组中,是没有任何关系的,若是设置了主资源,能够经过Bundle.mainAsset来直接使用它
    第三个参数是路径,通常咱们设置为  Application.streamingAssetsPath + Bundle的目标路径和Bundle名称
    第四个参数有四个选项,BuildAssetBundleOptions.CollectDependencies会去查找依赖,BuildAssetBundleOptions.CompleteAssets会强制包含整个资源,BuildAssetBundleOptions.DeterministicAssetBundle会确保生成惟一ID,在打包依赖时会有用到,其余选项没什么意义
    第五个参数是平台,在安卓,IOS,PC下,咱们须要传入不一样的平台标识,以打出不一样平台适用的包, 注意,Windows平台下打出来的包,不能用于IOS

 在打对应的包以前应该先选择对应的平台再打包

4.打包的决策
 
    在打包的时候,咱们须要对包的大小和数量进行一个平衡,全部资源打成一个包,一个资源打一个包,都是比较极端的作法,他们的问题也很明显,更多状况下咱们须要灵活地将他们组合起来
    打成一个包的缺点是加载了这个包,咱们不须要的东西也会被加载进来,占用额外内存,并且不利于热更新
    打成多个包的缺点是,容易形成冗余,首先影响包的读取速度,而后包之间的内容可能会有重复,且太多的包不利于资源管理
    哪些模块打成一个包,哪些模块打成多个包,须要根据实际状况来,例如游戏中每一个怪物都须要打成一个包,由于每一个怪物之间是独立的,例如游戏的基础UI,能够打成一个包,由于他们在各个界面都会出现
 
    PS.想打包进AssetBundle中的二进制文件,文件名的后缀必须为“.bytes”
【二,解包】
    解包的第一步是将Bundle加载进来,new一个WWW传入一个URL便可加载Bundle,咱们能够传入一个Bundle的网址,从网络下载,也能够传入本地包的路径,通常咱们用file://开头+Bundle路径,来指定本地的Bundle,用 http://https://开头+Bundle网址来指定网络Bundle
    
string.Format("file://{0}/{1}", Application.streamingAssetsPath, bundlePath);
 
    在安卓下路径不同,若是是安卓平台的本地Bundle,须要用jar:file://做为前缀,而且须要设置特殊的路径才能加载
 
string.Format("jar:file://{0}!/assets/{1}", Application.dataPath, bundlePath);
 
    传入指定的URL以后,咱们能够用WWW来加载Bundle,加载Bundle须要消耗一些时间,因此咱们通常在协同里面加载Bundle,若是加载失败,你能够在www.error中获得失败的缘由
 
复制代码
IEnumerator LoadBundle(string url)
{
    WWW www = = new WWW(url);
    yield return www;

    if (www.error != null)
    {
    Debug.LogError("Load Bundle Faile " + url + " Error Is " + www.error);
    yield break;
    }

    //Do something ...
}
复制代码
    除了建立一个WWW以外,还有另外一个方法能够加载Bundle, WWW.LoadFromCacheOrDownload(url, version),使用这个函数对内存的占用会小不少,但每次从新打包都须要将该Bundle对应的版本号更新(第二个参数version),不然可能会使用以前的包,而不是最新的包,LoadFromCacheOrDownload会将Bundle从网络或程序资源中,解压到一个磁盘高速缓存,通常能够理解为解压到本地磁盘,若是本地磁盘已经存在该版本的资源,就直接使用解压后的资源。对于AssetBundle全部对内存占用的状况,后面会有一小节专门介绍它
 
    LoadFromCacheOrDownload会记录全部Bundle的使用状况,并在适当的时候删除最近不多使用的资源包,它容许存在两个版本号不一样但名字同样的资源包,这意味着你更新这个资源包以后,若是没有更新代码中的版本号,你可能取到的会是旧版本的资源包,从而产生其余的一些BUG。另外,当你的磁盘空间不足的时候(硬盘爆了),LoadFromCacheOrDownload只是一个普通的new WWW!后面关于内存介绍的小节也会对这个感叹号进行介绍的
 
    拿到Bundle以后,咱们就须要Load里面的资源,有Load,LoadAll以及LoadAsyn可供选择
 
复制代码
    //将全部对象加载资源
    Object[] objs = bundle.LoadAll();
 
    //加载名为obj的资源
    Object obj = bundle.Load("obj");
 
    //异步加载名为resName,类型为type的资源
    AssetBundleRequest res = bundle.LoadAsync(resName, type);
        yield return res;
    var obj = res.asset;
复制代码
    咱们常常会把各类游戏对象作成一个Prefab,那么Prefab也会是咱们Bundle中常见的一种资源,使用Prefab时须要注意一点, 在Bundle中加载的Prefab是不能直接使用的,它须要被实例化以后,才能使用,而对于这种Prefab,实例化以后,这个Bundle就能够被释放了
 
    //须要先实例化
    GameObject obj = GameObject.Instantiate(bundle.Load("MyPrefab")) as GameObject;
 
    对于从Bundle中加载出来的Prefab,能够理解为咱们直接从资源目录下拖到脚本上的一个Public变量,是未被实例化的Prefab,只是一个模板
 
    若是你用上面的代码来加载资源,当你的资源慢慢多起来的时候,你可能会发现一个很坑爹的问题,你要加载的资源加载失败了,例如你要加载一个GameObject,可是整个加载过程并无报错,而当你要使用这个GameObject的时候,出错了,而一样的代码,咱们在PC上可能没有发现这个问题,当咱们打安卓或IOS包时,某个资源加载失败了。
 
    出现这种神奇的问题,首先是怀疑打包的问题,包太大了?删掉一些内容,不行!从新打一个?仍是不行!而后发现来来回回,都是这一个GameObject报的错,难道是这个GameObject里面部分资源有问题?对这个GameObject各类分析,把它大卸八块,处理成一个很简单的GameObject,仍是不行!难道是名字的问题?把这个GameObject的名字改了一下,能够了!
 
    原本事情到这就该结束了,可是,这也太莫名其妙了吧!并且,最重要的是,哥就喜欢原来的名字!!把这个资源改为新的名字,怎么看怎么变扭,怎么看都没有原来的名字好看,因此继续折腾了起来~
 
    首先单步跟踪到这个资源的Load,资源被成功Load出来了,可是Load出来的东西有点怪怪的,明显不是一个GameObject,而是一个莫名其妙的东西,多是Unity生成的一个中间对象,也许是一个索引对象,反正不是我要的东西,打包的GameObject怎么会变成这个玩意呢?因而在加载Bundle的地方,把Bundle LoadAll了一下,而后查看这个Bundle里面的内容
 
 
    在这里咱们能够看到,有一个叫RoomHallView和RoomMainView的GameObject,而且,LoadAll以后的资源比我打包的资源要多不少,看样子全部关联到的资源都被自动打包进去了,数组的427是RoomHallView的GameObject,而431才是RoomMainView的GameObject。能够看到名字叫作RoomMainView和RoomHallView的对象有好几个,GameObject,Transform,以及一个只有名字的对象,它的类型是一个ReferenceData。
 
    仔细查看能够发现,RoomHallView的GameObject是排在数组中全部名为RoomHallView对象的最前面,而RoomMainView则是ReferenceData排在前面,当咱们Load或者LoadAsyn时,是一次数组的遍历,当遍历到名字匹配的对象时,则将对象返回,LoadAsyn会对类型进行匹配,但因为咱们传入的是Object,而几乎全部的对象都是Object,因此返回的结果就是第一个名字匹配的对象
 
     在Load以及LoadAsyn时,除了名字,把要加载对象的类型也传入,再调试,原来的名字也能够正常被读取到了,这个细节很是的坑,由于在官网并无提醒,并且示例的sample也没有说应该注意这个地方,而且出现问题的概率很小。因此一旦出现,就坑死了
 
bundle.Load("MyPrefab", typeof(GameObject))
 
    另外, 不要在IOS模拟器上测试AssetBundle,你会收到bad url的错误
【三,依赖】
 
    依赖和打包息息相关,之因此把依赖单独分开来说,是由于这玩意太坑了.......
 
【1.打包依赖】
 
    在咱们打包的时候,将两个资源打包成单独的包,那么两个资源所共用的资源,就会被打包成两份,这就形成了冗余,因此咱们须要将公共资源抽出来,打成一个Bundle,而后后面两个资源,依赖这个公共包,那么还有另一种方法,就是把它们三打成一个包,但这不利于后期维护。
咱们使用BuildPipeline.PushAssetDependencies()和BuildPipeline.PopAssetDependencies()来开启Bundle之间的依赖关系,当咱们调用PushAssetDependencies以后,会开启依赖模式,当咱们依次打包 A B C时,若是A包含了B的资源,B就不会再包含这个资源,而是直接依赖A的,若是A和B包含了C的资源,那么C的这个资源旧不会被打包进去,而是依赖A和B。这时候只要有一样的资源,就会向前依赖,当咱们但愿,B和C依赖A,但B和C之间不互相依赖,就须要嵌套Push Pop了,当咱们调用PopAssetDependencies就会结束依赖
 
复制代码
  string path = Application.streamingAssetsPath;
  BuildPipeline.PushAssetDependencies();
 
  BuildTarget target = BuildTarget.StandaloneWindows;
  
  BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/UI_tck_icon_houtui.png"), null,
                                 path + "/package1.assetbundle",
                                 BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets
                                 | BuildAssetBundleOptions.DeterministicAssetBundle, target);
 
 
  BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/New Material.mat"), null,
                                 path + "/package2.assetbundle",
                                 BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets
                                 | BuildAssetBundleOptions.DeterministicAssetBundle, target);
 
 
  BuildPipeline.PushAssetDependencies();
  BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/Cube.prefab"), null,
                                 path + "/package3.assetbundle",
                                 BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets
                                 | BuildAssetBundleOptions.DeterministicAssetBundle, BuildTarget.StandaloneWindows);
  BuildPipeline.PopAssetDependencies();
 
 
 
  BuildPipeline.PushAssetDependencies();
  BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/Cubes.prefab"), null,
                                 path + "/package4.assetbundle",
                                 BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets
                                 | BuildAssetBundleOptions.DeterministicAssetBundle, target);
  BuildPipeline.PopAssetDependencies();
 
  BuildPipeline.PopAssetDependencies();
复制代码
 
    上面的代码演示了如何使用依赖,这个测试使用了一个纹理,一个材质,一个正方体Prefab,还有两个正方体组成的Prefab,材质使用了纹理,而两组正方体都使用了这个材质,上面的代码用Push开启了依赖,打包纹理,而后打包材质(材质自动依赖了纹理),而后嵌套了一个Push,打包正方体(正方体依赖前面的材质和纹理),而后Pop,接下来再嵌套了一个Push,打包那组正方体(不依赖前面的正方体,依赖材质和纹理)
 
    若是咱们只开启最外面的Push Pop,而不嵌套Push Pop,那么两个正方体组成的Prefab就会依赖单个正方体的Prefab,依赖是一把双刃剑,它能够去除冗余,但有时候咱们又须要那么一点点冗余
 
【2.依赖丢失】
 
    当咱们的Bundle之间有了依赖以后,就不能像前面那样简单地直接Load对应的Bundle了,咱们须要 把Bundle所依赖的Bundle先加载进来,这个加载只是WWW或者 LoadFromCacheOrDownload,并不须要对这个Bundle进行Load,若是BundleB依赖BundleA,当咱们要加载BundleB的资源时,假设BundleA没有被加载进来,或者已经被Unload了,那么BundleB依赖BundleA的部分就会丢失,例如每一个正方体上都挂着一个脚本,当咱们不嵌套Push Pop时,单个正方体的Bundle没有被加载或者已经被卸载,咱们加载的那组正方体上的脚本就会丢失, 脚本也是一种资源,当一个脚本已经被打包了,依赖这个包的资源,就不会被再打进去
 
Cubes和Cube都挂载同一个脚本,TestObje,Cubes依赖Cube,将Cube所在的Bundle Unload,再Load Cubes的Bundle,Cubes的脚本丢失,脚本,纹理,材质等一切资源,都是如此
 
【3.更新依赖】
 
    在打包的时候咱们须要指定BuildAssetBundleOptions.DeterministicAssetBundle选项,这个选项会为每一个资源生成一个惟一的ID,当这个资源被从新打包的时候,肯定这个ID不会改变,包的依赖是根据这个ID来的,使用这个选项的好处是,当资源须要更新时,依赖于该资源的其余资源,不须要从新打包
 
    A -> B -> C
 
    当A依赖B依赖C时,B更新,须要从新打包C,B,而A不须要动,打包C的缘由是,由于B依赖于C,若是不打包C,直接打包B,那么C的资源就会被重复打包,并且B和C的依赖关系也会断掉
 
【四,内存】
 
    在使用WWW加载Bundle时,会开辟一块内存,这块内存是Bundle文件解压以后的内存,这意味着这块内存很大,经过Bundle.Unload能够释放掉这块内存,Unload true和Unload false 都会释放掉这块内存,而这个Bundle也不能再用,若是要再用,须要从新加载Bundle, 须要注意的是,依赖这个Bundle的其余Bundle,在Load的时候,会报错
 
    获得Bundle以后,咱们用Bundle.Load来加载资源,这些资源会从Bundle的内存被复制出来,做为Asset放到内存中,这意味着,这块内存,也很大,Asset内存的释放,与Unity其余资源的释放机制同样,能够经过Resources.UnloadUnuseAsset来释放没有引用的资源,也能够经过Bundle.Unload(true)来强制释放Asset,这会致使全部引用到这个资源的对象丢失该资源
 
 
    上面两段话能够得出一个结论,在new WWW(url)的时候,会开辟一块内存存储解压后的Bundle,而在资源被Load出来以后,又会开辟一块内存来存储Asset资源,WWW.LoadFromCacheOrDownload(url)的功能和new WWW(url)同样,但LoadFromCacheOrDownload是将Bundle解压到磁盘空间而不是内存中,因此LoadFromCacheOrDownload返回的WWW对象,自己并不会占用过多的内存(只是一些索引信息,每一个资源对应的磁盘路径,在Load时从磁盘取出),针对手机上内存较小的状况, 使用 WWW. LoadFromCacheOrDownload代替new WWW能够有效地节省内存。但LoadFromCacheOrDownload大法也有不灵验的时候,当它不灵验时,L oadFromCacheOrDownload 返回的WWW对象将占用和new WWW同样的内存,因此 无论你的Bundle是如何建立出来的,都须要在不使用的时候,及时地Unload掉
 
    另外使用LoadFromCacheOrDownload须要注意的问题是——第二个参数,版本号,Bundle从新打包以后,版本号没有更新,取出的会是旧版本的Bundle,而且一个Bundle缓存中可能会存在多个旧版本的Bundle,例如1,2,3 三个版本的Bundle
 
 
    在Bundle Load完以后,不须要再使用该Bundle了,进行Unload,若是有其余Bundle依赖于该Bundle,则应该等依赖于该Bundle的Bundle不须要再Load以后,Unload这个Bundle,通常出如今大场景切换的时候。
    
    咱们知道在打包Bundle的时候,有一个参数是mainAsset,若是传入该参数,那么资源会被视为主资源打包,在获得Bundle以后,能够用AssetBundle.mainAsset直接使用,那么是否在WWW获取Bundle的时候,就已经将mainAsset预先Load出来了呢?不是! 在咱们调用 AssetBundle.mainAsset取出mainAsset时,它的get方法会阻塞地去Load mainAsset,而后返回,AssetBundle.mainAsset等同于Load("mainAssetName")  
 
    PS.重复Load同一个资源并不会开辟新的内存来存储这个资源
 
【五,其余】
 
    在使用AssetBundle的开发过程当中,咱们常常会对资源进行调整,调整以后须要对资源进行打包才能生效,对开发效率有很大的影响,因此在开发中咱们使用Resource和Bundle兼容的方式
 
    首先将资源管理封装到一个Manager中,从Bundle中Load资源仍是从Resource里面Load资源,都由它决定,这样能够保证上层逻辑代码不须要关心当前的资源管理类型
 
    固然,咱们全部要打包的对象,都在Resource目录下,而且使用严格的目录规范,而后使用脚本对象,来记录每一个资源所在的Bundle,以及所对应的Resource目录,在资源发生变化的时候,更新脚本对象,Manager在运行时使用脚本对象的配置信息,这里的脚本对象咱们是使用代码自动生成的,固然,你也能够用配置表,效果也是同样的
 
    版本管理也能够交由脚本对象来实现,每次打包的资源,须要将其版本号+1,脚本对象可存储全部资源的版本号,版本号能够用于LoadFromCacheOrDownload时传入,也能够手动写入配置表,在我设计的脚本对象中,每一个资源都会有一个所属Bundle,Resource下相对路径,版本号等三个属性
 
    在版本发布的时候,你须要先打包一次Bundle,而且将Resource目录改为其余的名字,而后再打包,确保Resource目录下的资源没有被重复打包,而若是你想打的是Resource版本,则须要将StreamingAssets下的Bundle文件删除
 
    脚本对象的使用以下:
    1.先设计好存储结构
    2.写一个继承于ScriptObject的类,用可序列化的容器存储数据结构(List或数组),Dictionary等容器没法序列化,public以后在
    
复制代码
[Serializable]
public class ResConfigData
{
    public string ResName; //资源名字
    public string BundleName; //包名字
    public string Path; //资源路径
    public int Vesrion; //版本号
}
 
[System.Serializable]
public class ResConfig : ScriptableObject
{
    public List<ResConfigData> ConfigDatas = new List<ResConfigData>();
}
复制代码
 
    4.在指定的路径读取对象,读取不到则建立对象
 
复制代码
ResConfig obj = (ResConfig)AssetDatabase.LoadAssetAtPath(path, typeof(ResConfig));
if (obj == null)
{
   obj = ScriptableObject.CreateInstance<ResConfig>();
   AssetDatabase.CreateAsset(obj, path);
}
复制代码
 
    3.写入数据,直接修改obj的数组,并保存(不保存下次启动Unity数据会丢失)
 
EditorUtility.SetDirty(obj);
 
    因为数组操做不方便,因此咱们能够将数据转化为方便各类增删操做的Dictionary容器存储,在保持时将其写入到持久化的容器中

5、未完待续

相关文章
相关标签/搜索