Unity资源打包学习笔记(一)、详解AssetBundle的流程

转载请标明出处:http://www.cnblogs.com/zblade/web

  本文参照unity官网上对于assetBundle的一系列讲解,主要针对assetbundle的知识点作一个梳理笔记,也为后续的资源打包设计作一个基础学习,本文的代码和图片均来自unity官网,详情能够查看Unity的DOCUMENTATION。算法

1、什么是AssetBundlewindows

  AssetBundle就像一个ZIP压缩文件,里面存储着不一样平台的特殊资源(models/texture/prefabs/materials/audio clip/scenes...), 这些资源均可以在运行时进行加载。具体的assetBundle中主要包含什么?主要包含两种互相关联的东西:数组

1. 磁盘上的文件:也就是assetbundle文档,能够将其视为一个容器或者文件夹,其中包含两类文件:序列化文件和资源文件,序列化文件就是资源在打包后对应的各个平台的序列化操做后的文件,资源文件主要是针对textures/audio等较大的文件打包的二进制文件,这类文件在加载的时候是在其余线程执行的(效率更高)。缓存

2. 就是实际的assetbundle对象了,能够经过代码来进行资源加载,其中主体是各个资源在进行加载的时候的存储路径图。用图表示:函数

2、如何对资源进行分组便于打包AssetBundle学习

  虽然能够对AssetBundle自由的进行规划,可是在进行项目的资源管理的时候,仍是有一些规划建议能够采用:动画

1.依据逻辑实体进行分组ui

  这种资源分类方式是依据资源的功能进行分类,例如 UI/角色/场景/code等具体各自功能规划的部分来进行资源分组,能够将全部的textures/layout data都打入UI相关分类中,能够将全部的模型和动画都打入角色相关的资源中,将场景相关的贴图和模型都打入场景资源中。spa

  采用逻辑实体分组,对于资源的下载更新更为有利,因为资源的分类,能够在进行资源更新的时候,只更新对应的资源,而无需更新冗余的其余资源。

  使用这种分类方式最合适的策略,就是将资源进行详细的分类(when and where will be used)

2.类型分组

  主要依据资源的类型来进行分组,这样对于不一样的应用平台都具备必定的适用性。好比对于audio文件的压缩设置,在mac和windows上都是一致的,那么能够将audio文件都归类为一类文件,实现文件资源的复用(不一样平台的打包设置),对于shaders而言,对于不一样平台须要不一样的编译设置,那么就须要分类处理。这类分类方法,对于在不一样的版本中变更频率较低的代码文件和prefabs显得更有优点。

3.相互关联的内容分组

  这种策略的,就是将须要同时进行加载的资源都归类为一个分组,例如将不一样场景中的角色都依据场景来进行分组,这就要求单独一个场景中的资源只能用于该场景,各个分组之间没有互相关联的关系。这种分类方式,对于资源的加载时间有较大的缩减,这种分类方式的使用场合主要在场景资源中,在不一样的场景资源中,其包含的资源各自互相不关联。

在一个项目中,能够将上述的几种策略都交互使用,对应具体的应用需求来灵活的采用分组策略,固然unity也提供了一些资源分组的tips:

  • 分离高频和低频更新的资源;
  • 将须要同时下载的资源合并进一个组,例如Model以及其关联的animations;
  • 若是出现多个bundle中的多个object都依赖于另外一个彻底不一样的bundle,那么将这些依赖关系都移动到一个单独的bundle,这样能够下降依赖关系的复杂度;多个bundle均依赖于另外一个bundle中的资源,那么将这些bundle以及其依赖的资源归类到一个资源,这样能够下降资源的重复率(避免一份资源被拷贝到多个不一样的bundle中);
  • 不可能同时加载的资源,须要归类的各自的assetbundle中,例如标准和高配的资源;
  • 若是一个assetbundle中资源在加载的时候低于50%须要被加载,那么能够考虑将这些须要被加载的资源单独分类为一个资源(避免冗余的加载);
  • 若是一组Objects对应的是一个资源的不一样版本,那么能够考虑assetbundle variants

 

3、如何打包AssetBundle

  unity资源打包的接口,就是BuildPipeline.BuildAssetBundles函数,其对应具体的三个参数,第一个是bundle的输出路径,下面来详细分析一下剩下的2个参数:

一、BuildAssetBundleOptions

  具体的参数设置,能够参看API当中的详细信息,下面主要集中说三个参数,分别对应三种压缩格式的选择。

1)BuildAssetBundleOptions.None :

  采用LZMA的压缩格式, 这种压缩格式要求资源在使用以前须要所有被解压,这就会带来在使用一个极小的文件的时候会额外带来较长的解压时间消耗。比较蛋疼的是,一旦这个bundle被解压以后,在磁盘上又会以LZ4的格式从新压缩,LZ4的压缩格式,在使用资源的时候不须要所有解压。

这种压缩格式主要用于一个bundle中资源都须要被加载的时候,例如打包角色或者场景资源的时候,这种压缩格式在初始化下载的时候被推荐(更小的包体),这些资源在被解压后,又会以LZ4的格式被缓存。

2) BuildAssetBundleOptions.UncompressedAssetBundle:

  无压缩的打包,加载的文件更大,可是时间更快(省去解压的时间)

3)BuildAssetBundleOptions.ChunkBasedCompression:

  采用LZ4的压缩格式,相比于LZMA而言文件体积更大,可是不要求在使用以前整个bundle都被解压。LZ4使用chunk based 算法,这就运行文件以chunk或者piece的方式加载,只解压一个chunk文件,而无需解压bundle中其他不相关的chunk。

二、BuildTarget

  也就是当前资源须要被使用的平台的分类,在打完资源后,会发现文件夹中有更多的文件,通常是2*(n+1), 对于各个不一样的资源,都会有一个manifest文件,通常是 bundlename+".manifest",除此以外,还会有一个额外的manifest文件,对应不一样的平台会有不一样的额外的manifest(这是一个整体的manifest文件)。

对于manifest中内容,能够在文本文件中打开,通常举例:

ManifestFileVersion: 0 CRC: 2422268106 Hashes: AssetFileHash: serializedVersion: 2 Hash: 8b6db55a2344f068cf8a9be0a662ba15 TypeTreeHash: serializedVersion: 2 Hash: 37ad974993dbaa77485dd2a0c38f347a HashAppended: 0 ClassTypes: - Class: 91 Script: {instanceID: 0} Assets: Asset_0: Assets/Mecanim/StateMachine.controller Dependencies: {}

  首先是版本,而后是CRC校验码,hash码,classtypes,包含的资源及其路径,依赖关系(dependencies)。

PS:

  对于依赖关系就多详细讲解一下,所谓的依赖关系,指的是一个bundle中的一个或者多个UnityEngine.Objects包含对另外一个bundle中的一个UnityEngine.Object的引用,若是一个bundle中的object对包含对其余非该bundle的object的引用,可是该object不属于任何一个bundle,那么这种依赖关系就不存在(这儿会带来一个问题,若是多个bundle均引用一个不属于bundle的object,那么在加载的时候,各个bundle均会将该object进行copy到其内存中,这就带来内存的额外冗余占用),因此依赖关系就是两个或者多个bundle之间的object的引用关系。

  对于依赖资源的加载,须要在该资源被加载前加载,unity不会自动的加载依赖资源,这须要在代码中实现依赖资源的加载。

 

4、如何使用AssetBundle

  在说完bundle的分类,打包后,接下来就是如何在实际的游戏中加载使用这些bundle。在unity5之后,提供了4种不一样类型的加载接口,下面逐一分析一下这四种不一样接口的使用:

一、AssetBundle.LoadFromMemoryAsync(byte[] binary, unit crc = 0)

  这个方法用来加载ab数据的bytes数组,若是数据是使用LZMA的压缩格式,那么在加载的时候会进行解压的操做,LZ4格式的数据则会保持其压缩的状态,使用示例:

using UnityEngine; using System.Collections; using System.IO; public class Example : MonoBehaviour { IEnumerator LoadFromMemoryAsync(string path) { AssetBundleCreateRequest createRequest = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path)); yield return createRequest; AssetBundle bundle = createRequest.assetBundle; var prefab = bundle.LoadAsset<GameObject>("MyObject"); Instantiate(prefab); } }

  固然,对于bytes数组,也可使用File.ReadAllBytes(path)的方式来加载数组。

二、 AssetBundle.LoadFromFile

  在加载非压缩文件或者LZ4压缩类型文件的时候,该接口效率极高,对于LZMA压缩格式的文件,也会在加载的时候执行解压的操做,使用示例:

public class LoadFromFileExample extends MonoBehaviour { function Start() { var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "myassetBundle")); if (myLoadedAssetBundle == null) { Debug.Log("Failed to load AssetBundle!"); return; } var prefab = myLoadedAssetBundle.LoadAsset.<GameObject>("MyObject"); Instantiate(prefab); } }

ps: 在unity5.3及更早的版本中,在安卓平台上若是从streaming assets路径中加载文件会失败(路径文件夹中会额外包含.jar文件)。

三、WWW.LoadFromCacheOrDownload

  这个接口会被淘汰(被UnityWebRequest替换),那么就不过多的讲解这个接口(注意这个接口会进行存储分配的操做以容纳资源,若是分配不足以存储会使得加载失败)。

四、UnityWebRequest

  这个接口,会有两步操做,首先是建立一个web request(调用UnityWebRequest.GetAssetBundle), 而后进行资源的获取(调用DownloadHandlerAssetBundle.GetContent),unity提供的使用示例为:

IEnumerator InstantiateObject() { string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName; UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0); yield return request.Send(); AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request); GameObject cube = bundle.LoadAsset<GameObject>("Cube"); GameObject sprite = bundle.LoadAsset<GameObject>("Sprite"); Instantiate(cube); Instantiate(sprite); }

  使用这种方式,可使得开发者更为灵活的操做下载数据,同时进行内存使用分配,会逐渐的被用来WWW接口。

  在加载完assetBundle后,接下来,就是如何从bundle中获取资源(asset),其基本的接口模板为:

T objectFromBundle = bundleObject.LoadAsset<T>(assetName);

  若是想获取全部的assets则可使用接口:

Unity.Object[] objectArray = loadedAssetBundle.LoadAllAssets();

  一旦获取到asset,那么就能够在游戏中使用这些资源了(通常是实例化建立操做)。

五、加载AssetBundle Manifest

  除了加载assetbundle,通常还会加载其对应的manifest(与其存储在同一个文件夹下的相同名字的manifest),通常加载manifest的操做示例:

AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath); AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

  在前文也说起到,若是一个assetbundle依赖于另外一个assetbundle,那么须要提早加载依赖相关的bundle,那么依据manifest,能够加载其依赖的assetbundle:

AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath); AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); string[] dependencies = manifest.GetAllDependencies("assetBundle"); //Pass the name of the bundle you want the dependencies for.
foreach(string dependency in dependencies) { AssetBundle.LoadFromFile(Path.Combine(assetBundlePath, dependency)); }

  如今,已经加载了assetbundle, 也获取了assetbundle的dependencies,以及其中assets,这样就能够管理这些assetbundle了。

 

5、管理AssetBundle

  unity在场景中的Object被移除的时候不自动释放objects,资源的清理须要再特定的时间触发(场景切换)或者手动的管理。因此怎么加载和卸载资源显得尤其重要,不合适的加载可能会致使资源的重复加载,不合适的卸载可能会带来资源的缺失(好比丢失贴图)。

  对于assetbundle的资源管理,最重要的是掌握何时调用AssetBundle.Unload(bool)这个函数,传入true/false会有不一样的卸载策略。这个API会卸载对应的assetbundle的头部信息,参数对应着是否同时卸载从该assetbundle中实例化的全部Objects。

  AssetBundle.Unload(true)会卸载assetbundle中的全部gameobjects以及其依赖关系,可是并不包括基于其Objects实例化(复制)的Object(由于这些object不属于该assetbundle,只是引用),因此当卸载贴图相关的assetbundle的时候,场景中对其引用的实例化物体上会出现贴图丢失,也就是场景中会出现红色的区域,unity都会将其处理成贴图丢失。

  举例说明,假设材质M来自于assetbundle AB, 若是 AB.Unload(true), 那么场景中任何M的实例都会被卸载和销毁,若是AB.Unload(false), 那么就会切断材质M实例与AB之间的关系:

  那么若是该assetbundle AB在后面再次被加载,unity不会从新关联其关系,这样在后续的使用中,就会出现一份材质的多个实例:

  因此一般状况下,AssetBundle.Unload(false) 并不能带来较为合理的释放结果,AssetBundle.Unload(true)一般用来确保不会在内存中屡次拷贝同一个资源,因此其更多的被项目所采纳,此外还有两个经常使用的方法用来确保其使用:

1)在游戏中,对于场景的卸载有明确的规划,好比在场景切换中或者场景加载中;

2)管理好对每一个单独的object的计数,只有在没有引用的时候才卸载该assetbundle,这样能够规避加载和卸载过程当中的多分内存拷贝问题。

  若是要使用AssetBundle.Unload(false), 那么这些实例化的对象能够经过2中途径卸载:

1)清除对不须要物体的全部引用,场景和代码中都须要清楚,而后调用Resources.UnloadUnusedAssets;

2) 在场景加载的时候采用非增量的方式加载,这会清楚当前场景中的全部Objects,而后反射自动调用Resources.UnloadUnusedAssets

若是你不想管理这些assetbundle,unity推出了AssetBundle Manager,能够学习了解一下,此外Unity还推出了一些AssetBundle Browser Tool, 也能够学习了解一下。

相关文章
相关标签/搜索