赞
踩
稍微写了下AssetBundle(AB包)的笔记。介绍了AB包是什么,以及如何使用的。后续有一部分基本是翻译的官方文档,侧重于概念类的,计划等后续有实际应用时再单独写出来。
AssetBundle是一个存档文件,包含可在运行时由Unity加载的特定于平台的非代码资源(比如模型、纹理、预制件、音频剪辑甚至整个场景)。AssetBundle可以表示彼此之间的依赖关系;例如,一个AssetBundle中的材质可以引用另一个 AssetBundle中的纹理。为了提高通过网络传输的效率,可以根据需要,选用内置压缩算法(LZMA 和 LZ4)来压缩 AssetBundle。
AssetBundle可用于可下载内容(DLC),减小初始安装包的大小,加载针对最终用户平台优化的资源,以及减轻运行时内存压力。
PS:简称,AB包。后面会混用,知道是同一个东西就好。
在讨论AssetBundle中包含什么之前,需要明确AssetBundle可以表示什么?虽然前面说了其为存档文件,是一个文件,但有时候它还是会表示一些其他东西的。
AssetBundle也可以指代磁盘上的实际文件。这称为AssetBundle存档。AssetBundle存档是一个容器,就像文件夹一样,可以在其中包含其他文件。这些被包含的文件有两种类型:
AssetBundle也可以指代通过代码进行交互以便从特定AssetBundle存档加载资源的实际AssetBundle对象。该对象包含添加到此存档文件的资源的所有文件路径的映射。
PS:在代码中我们就通过此对象来加载使用资源。
为Project视图中的资源指定AssetBundle,包含主体与变体两部分(可以理解为名称、后缀名称),在AssetBundle打包出来后,主体与变体就对应包的名称与后缀。后缀随意写。
那么如何指定呢?这里将给一个Cube预设体指定AssetBundle,如下:
然后我指定了名称、后缀、标签。如下:
PS:名称、后缀只能小写,即使输入大写也会被转为小写。
使用代码构建。代码要在Editor模式下运行,这部分是编辑器扩展的知识点,不懂可以查一查。代码如下:
- using System.IO;
- using UnityEditor;//编辑器头文件
-
- public class CreateAssetBundles
- {
- //将此方法加入菜单栏中
- [MenuItem("Assets/Build AssetBundles")]
- static void BuildAllAssetBundle()
- {
- //打包后的保存路径
- string assetBundleDirectory = "Assets/AssetBundles";
- //若保存路径不存在,则创建路径上的文件夹
- if (!Directory.Exists(assetBundleDirectory))
- {
- //创建文件夹
- Directory.CreateDirectory(assetBundleDirectory);
- }
- //打包方法(参数:保存路径、打包选项、打包平台)
- //会将Project中所有指定了AssetBundle的资源进行打包
- BuildPipeline.BuildAssetBundles(assetBundleDirectory,
- BuildAssetBundleOptions.None,
- BuildTarget.StandaloneWindows64);
-
- //BuildAssetBundles参数说明:
- //--------------------------------------------------------
- //保存路径:字面意思。
- //-------------------------------
- //打包选项:可选参数有很多,可查看官方文档,这里最主要介绍三个。
-
- //BuildAssetBundleOptions.None:
- //使用LZMA算法压缩。
- //LZMA压缩包小,但加载时间长。LZMA压缩包使用之前需要整体解压,比如包中有资源x、y,在只需使用x的情况下,需要将x、y一起解压。LZMA压缩包一旦被解压,这个包会使用LZ4重新压缩。在使用资源时,LZ4压缩包不需要整体解压,用谁解谁。
- //所以一般在下载的时候下载LZMA压缩包,下载下来之后,再使用LZ4算法压缩保存到本地。通过UnityWebRequestAssetBundle加载的LZMA压缩格式Asset Bundle会自动重新压缩为LZ4压缩格式并缓存在本地文件系统上。如果通过其他方式下载并存储AB包,则可以使用AssetBundle.RecompressAssetBundleAsync API对其进行重新压缩。
-
- //BuildAssetBundleOptions.UncompressedAssetBundle:
- //不压缩,包大,加载快。
-
- //BuildAssetBundleOptions.ChunkBasedCompression:
- //使用LZ4算法压缩。
-
- //PS:注意使用LZ4压缩,可以获得可以跟不压缩相媲美的加载速度,而且比不压缩文件要小。
- //-------------------------------
- //打包平台:字面意思。选什么平台,那么就只能在什么平台使用。
- //--------------------------------------------------------
- }
- }
然后不用运行Unity,只需要在菜单栏中执行即可。
前往我们指定的路径下,即可看到创建的AB包:
注意,不带.manifest后缀的才是AB包。带.manifest的文件是辅助文件,其记录AB包的一些信息,比如资源信息、依赖的AB包信息等,可以用文本编辑器打开看一下文件内容。另外注意额外生成的AssetBundles AB包,其会记录其他所有打包的AB包的相关信息,这里先不用管,后续会用到这个文件,到时候就知道其作用了。
如图,若在指定AB包名称时使用这种方式,则会在目标目录下创建一个test文件夹,再在其中创建cube的AB包。
PS:可以多级目录。
当多个资源指定同一个AssetBundle时(同名、同后缀),它们将被打包到一个包中。
AB包依赖问题的是资源的依赖问题,是资源的优化问题。
如果一个或多个资源(UnityEngine.Objects)引用了(使用了)另一个AB包中的资源,则此资源在打包为AB包时,会自动依赖于刚才那个AB包,打包的AB包中不存在引用的资源。
若在打包时,引用的资源不存在于其他AB包中,则引用的资源将被打包到当前AB包中。
若是有多个资源打包为多个AB包,这多个资源引用了一个不存在于其他AB包中的资源,则所引用的资源将会分别打包进当前AB包中,即存在多个副本,每个AB包里有一个副本。这将影响内存资源和加载时间。要想不要副本,就需要将引用的资源本身打包为AB包,注意这里的本身,本身的意思是以自己为对象打包,而不是以被引用的自己为对象打包(以自己为对象不意味着只有自己,也可以和别的资源一起打包),比如前面说的多个资源打包,第一个打的包里有了引用资源的副本,但其也不能为后续的AB包提供“依赖”功能,因为其是作为资源的引用打包的。
如果一个AB包中的资源x在另外的AB包中有依赖项(即x引用的资源),则在加载x对象之前,需要先加载包含这些依赖项的AB包,否则将会出现引用丢失问题。Unity不会尝试自动加载依赖项,所以需要我们自己注意。
AB包之间无加载顺序要求 ,只需要保证资源在加载前,其依赖项所在的AB包已经被加载。
这里我创建了Cube、Sphere预设体,它们使用了同一个材质、纹理,并分别将其打包为两个AB包。打包后结果:
可以看到两个AB包的大小都是46KB,总计92KB。
然后,将其删除,准备重新打包。但这一次除了打包两个预设体外,还将它们共用的材质和纹理打包为一个AB包,即使用“AB包依赖”。打包后的结果:
可以看到cube、sphere的AB包大小由46KB变为了3KB,材质、纹理AB包(图中share.myab)的大小为45KB,总计51KB。
开头我们说过,若有依赖关系需要注意:加载资源前,资源依赖项所在的AB包需要提前加载。
前面我们打包好了cube、sphere、share三个AB包,其中cube和sphere是依赖share的,接下来将使用从这三个AB包中加载资源,在场景中创建一个Cube和一个Sphere,分别演示share有无提前加载的情况。
没有提前加载
代码:
- using UnityEngine;
-
- public class InstantiateAssetBundles : MonoBehaviour
- {
- void Start()
- {
- //加载cube AB包
- AssetBundle cubeAB = AssetBundle.LoadFromFile("Assets/AssetBundles/cube.myab");
- //加载sphere AB包
- AssetBundle sphereAB = AssetBundle.LoadFromFile("Assets/AssetBundles/sphere.myab");
-
- //没有提前加载share AB包
-
- //加载AB包中的资源
- var cubePrefab = cubeAB.LoadAsset<GameObject>("Cube");
- var spherePrefab = sphereAB.LoadAsset<GameObject>("Sphere");
-
- //使用资源创建对象
- Instantiate(cubePrefab);
- Instantiate(spherePrefab);
- }
- }
运行结果:
没有提前加载的后果,引用的资源丢失了。
提前加载
代码:
- using UnityEngine;
-
- public class InstantiateAssetBundles : MonoBehaviour
- {
- void Start()
- {
- //(AB包之间无顺序要求,顺序任意)
- //加载cube AB包
- AssetBundle cubeAB = AssetBundle.LoadFromFile("Assets/AssetBundles/cube.myab");
- //加载sphere AB包
- AssetBundle sphereAB = AssetBundle.LoadFromFile("Assets/AssetBundles/sphere.myab");
- //加载share AB包(在资源前面,提前加载)
- AssetBundle shareAB = AssetBundle.LoadFromFile("Assets/AssetBundles/share.myab");
-
- //加载AB包中的资源
- var cubePrefab = cubeAB.LoadAsset<GameObject>("Cube");
- var spherePrefab = sphereAB.LoadAsset<GameObject>("Sphere");
-
- //使用资源创建对象
- Instantiate(cubePrefab);
- Instantiate(spherePrefab);
- }
- }
运行结果:
但是!
若我们加载share AB包,即是放到资源加载之后,甚至放到Instantiate之后,但只要我们加载了,材质依旧可以成功获取到。这个目前不清楚为什么,官方文档上的说明是要在资源加载之前。暂时先按照文档来操作吧。
不是从网络服务器上加载,而是在本地电脑中加载。
AssetBundle.LoadFromMemoryAsync
此函数异步加载内存中包含AB包数据的字节数组。也可以根据需要传递CRC值。如果AB包采用的是LZMA压缩方式,将在加载时解压缩AB包。LZ4压缩包则会以压缩状态加载。
异步加载代码:
- using System.Collections;
- using System.IO;
- using UnityEngine;
-
- public class LoadFromMemoryExample : MonoBehaviour
- {
- void Start()
- {
- //开启协程,异步加载AB包
- StartCoroutine(LoadFromMemoryAsync("Assets/AssetBundles/cube.myab"));
- }
-
- IEnumerator LoadFromMemoryAsync(string path)
- {
- //先使用File读取AB包的字节数组,然后异步加载此数组
- AssetBundleCreateRequest createRequest = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path));
- //等待加载完成
- yield return createRequest;
- //获取加载的AB包
- AssetBundle bundle = createRequest.assetBundle;
- //加载资源
- var prefab = bundle.LoadAsset<GameObject>("Cube");
- //使用资源创建对象
- Instantiate(prefab);
- }
- }
注意,File.ReadAllBytes(path) 可以被替换为获得字节数组的任何方式。除此之外,我们也可以使用同步加载的方式。
同步加载代码:
- using System.IO;
- using UnityEngine;
-
- public class LoadFromMemoryExample : MonoBehaviour
- {
- void Start()
- {
- //先使用File读取AB包的字节数组,然后同步加载此数组,获取AB包
- AssetBundle bundle = AssetBundle.LoadFromMemory(File.ReadAllBytes("Assets/AssetBundles/cube.myab"));
- //加载资源
- var prefab = bundle.LoadAsset<GameObject>("Cube");
- //使用资源创建对象
- Instantiate(prefab);
- }
- }
从本地存储中加载未压缩的AB包时,此API非常高效。如果AB包未压缩或采用了数据块 (LZ4) 压缩方式,LoadFromFile将直接从磁盘加载AB包。使用此方法加载LZMA压缩的AB包时,将首先解压缩AB包,然后再将其加载到内存中。
加载代码:
- using UnityEngine;
-
- public class LoadFromFileExample : MonoBehaviour
- {
- void Start()
- {
- //加载AssetBundle对象
- AssetBundle myLoadedAssetBundle = AssetBundle.LoadFromFile("Assets/AssetBundles/cube.myab");
- //若为空,则加载失败
- if (myLoadedAssetBundle == null)
- {
- Debug.Log("Failed to load AssetBundle!");
- return;
- }
- //加载AssetBundle对象中的资源
- var prefab = myLoadedAssetBundle.LoadAsset<GameObject>("Cube");
- //使用资源创建对象
- Instantiate(prefab);
- }
- }
上述方法是同步加载,除此之外,还可以异步加载,这里就不列代码了,跟从内存中加载的异步方法使用方式差不多。
从网络中加载AB包的方式有两种:
UnityWebRequestAssetBundle 的 DownloadHandlerAssetBundle (Unity 5.3 或更高版本)
其中前者基本不在使用了,所以这里介绍第二个。
UnityWebRequestAssetBundle 有一个特定 API 调用来处理 AssetBundle。首先,需要使用 UnityWebRequestAssetBundle.GetAssetBundle 来创建 Web 请求。返回请求后,请将请求对象传递给 DownloadHandlerAssetBundle.GetContent(UnityWebRequest)。GetContent 调用将返回 AssetBundle 对象。
示例代码:
- using System.Collections;
- using UnityEngine;
- using UnityEngine.Networking;
-
- public class LoadFromWeb : MonoBehaviour
- {
- void Start()
- {
- //开启协程(以本地路径为例,注意path应是绝对路径)
- StartCoroutine(InstantiateObject("file:///" + path));
- //网络地址如:@"http://ip地址/文件目录/文件...."
- }
-
- IEnumerator InstantiateObject(string url)
- {
- //创建AB包下载请求
- UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url, 0);
- //开始请求下载并等待
- yield return request.SendWebRequest();
- //下载完成后,从请求中获取AB包对象
- AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
- //从AB包中加载资源对象
- GameObject cube = bundle.LoadAsset<GameObject>("Cube");
- //使用资源对象实例化游戏对象
- Instantiate(cube);
- }
- }
下载AB包后,除了使用GetContent返回对象的方式获取AB包对象,还可以使用 DownloadHandlerAssetBundle 类对象的 assetBundle 属性,从而以 AssetBundle.LoadFromFile 的效率加载 AssetBundle。只需要替换上述代码中GetContent那行即可:
- //下载完成后,从请求中获取AB包对象
- AssetBundle bundle = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle;
另外,通过request我们也可以获取到下载的AB的字节数组,有了字节数组我们就可以做很多事情,比如通过字节数组将下载的AB包存储到本地。
- //获取字节数组
- byte[] data = request.downloadHandler.data;//字节数组,可以用于保存到本地
这个实际上在前面加载AB包的案例代码中都已经看到过了,这里再单独讲下。
加载资源的函数主要有以下几种: LoadAsset、LoadAllAssets 及其各自的异步对应选项 LoadAssetAsync 和 LoadAllAssetsAsync。
同步方法代码演示:
- //加载单个资源对象(泛型T)
- T gameObject = loadedAssetBundle.LoadAsset<T>(assetName);
-
- //加载所有资源对戏
- Object[] objectArray = loadedAssetBundle.LoadAllAssets();
异步方法代码演示:
- //加载单个资源对象(泛型T)
- AssetBundleRequest request = loadedAssetBundleObject.LoadAssetAsync<T>(assetName);//加载
- yield return request;//等待加载
- T loadedAsset = request.asset as T;//获取资源对象
-
- //加载所有资源
- AssetBundleRequest request = loadedAssetBundle.LoadAllAssetsAsync();//加载
- yield return request;//等待加载
- Object[] loadedAssets = request.allAssets;//获取资源对象
AssetBundles清单中包含了AB包的相关信息,这些信息是非常有用的,比如有哪些AB包,这些AB包所依赖的AB包等信息。所以加载清单,很必要。
具体要怎么获取清单对象呢?我们需要首先加载AssetBundles AB包(就是我创建AB包时额外给我们生成的那个),然后再从加载的AB包中获取清单对象。这里,清单对象可以视为资源对象。这么一说一切就通透了。代码:
- //加载AssetBundles AB包
- AssetBundle assetBundle = AssetBundle.LoadFromFile(AssetBundle文件Path);
- //加载清单
- AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");//此字符串不可修改
此时获取到了一个清单对象(manifest),我们便可以通过其获取相关信息。
清单常用于获取某AB包所依赖的AB包的名称,然后借助这些名称将依赖的AB包加载进来。代码:
- using UnityEngine;
-
- public class LoadFromFileExample : MonoBehaviour
- {
- void Start()
- {
- //加载cube AB包
- AssetBundle cubeAB = AssetBundle.LoadFromFile("Assets/AssetBundles/cube.myab");
- //加载AssetBundles AB包
- AssetBundle AB = AssetBundle.LoadFromFile("Assets/AssetBundles/AssetBundles");
- //加载清单
- AssetBundleManifest manifest = AB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
- //从清单中获取cube AB包所依赖的AB包名称
- string[] dependencies = manifest.GetAllDependencies("cube.myab");
- //加载所依赖的AB包
- foreach (string dependency in dependencies)
- {
- Debug.Log(dependency);//输出名称
- AssetBundle.LoadFromFile("Assets/AssetBundles/" + dependency);//加载
- }
- //加载cube AB包中的资源
- var prefab = cubeAB.LoadAsset<GameObject>("Cube");
- //使用资源创建对象
- Instantiate(prefab);
- }
- }
.manifest文件是在创建AB包时一起生成的辅助文件(清单文件),包含了AB包的一些信息。我们用文本编辑器打开AssetBundles.manifest文件就能发现其里面的内容就是我们上面清单中的内容。这些文件的作用就是提给我们在文件层面查看里面的内容,换言之,即使我们把.manifest删除,也不会影响我们代码的执行。
我们可以通过AssetBundle.Unload(bool)或者AssetBundle.UnloadAsync(bool)函数来卸载AB包,通过AB对象卸载的话只会卸载当前AB包,也可以通过AssetBundle类卸载所有AB包。
- //使用cubeAB包对象卸载cubeAB包
- cubeAB.Unload(true);
-
- //卸载所有AB包
- AssetBundle.UnloadAllAssetBundles(true);
另外,卸载方法是有一个bool参数需要填写的,此参数含义是“是否卸载从AB包中加载的对象。”注意这里的对象是什么,是资源对象,即一个类对象。注意!注意!注意!是类对象,不是在场景中实例化的游戏对象!一定要分清楚。实例化的游戏对象是不再属于AB包的,所以无法通过卸载AB包来卸载。当我们以ture卸载一个AB包时,其资源对象将会被销毁,但以资源对象在场景中实例化的游戏对象不会从场景中删除。
分一下层次的话,大概是这种:AB包→加载的对象→实例化的对象(场景中)。
这里将讨论参数选择(是否卸载加载的对象)会造成什么影响。还以之前的例子,一个Cube预设体,身上有一个材质文件。
整体AB包(无依赖)
打包一个cube AB包,预设体与材质都在其中。我们在场景中实例化一个对象,那么此时我们需要注意,明确目前存在的内容:
然后我们unload(true),此时场景中的对象会怎样呢?答案是:材质丢失(即贴图没了)。先看看上述内容卸载后的状态:
可以看到,我们的Cube依旧存在,但其所使用的材质、纹理已经被销毁了,所以就出现了材质丢失的情况。
若是unload(false),场景中的对象又会怎样呢?答案是:没有变化。看状态:
可以看到,只有AB包被销毁了,其他对象依旧存在,所以我们的实例化对象是没有影响的。这些对象依旧可以使用。
带有依赖的AB包
打包cube AB包、share AB包,share中是材质、纹理,cube AB包是依赖share AB包的,跟前代码案例的情况一样。明确内容:
这里有一个关系需要提前说明,Cube预设体对象依赖image材质、纹理对象。
那么这里unload(true) share AB包,此时场景中的对象会怎样呢?答案是:材质丢失。状态:
因为材质、纹理对象被销毁了,所以材质丢失。
若unload(flase) share AB 包,此时场景中的对象会怎样呢?答案是:没有变化。状态:
材质、纹理对象依旧存在。
若unload(false) cube AB、unload(false) share AB,然后再实例化一个对象,此时新对象是怎样的?答案是:带材质的对象。状态:
虽然AB包都被销毁了,但Cube预设体对象依赖的是材质、纹理对象,所以没影响,我们依旧可以通过预设体对象来正确的实例化对象。
若unload(true) cube AB、unload(false) share AB,然后我们再次加载cube AB包,再次加载Cube预设体对象,并以此对象在场景中实例化对象,此时新对象是怎样的?答案是:材质丢失。状态不就列了。那么这里为什么是材质丢失?虽然我们的材质、纹理对象还存在,但依赖它们的是之前的Cube预设体对象,我们ture卸载之后它就不复存在了,新加载的cube AB包中的Cube预设体对象是需要通过AB包来建立依赖(建立依赖时,若材质、纹理对象不存在便会被创建),但此时没有了share AB包对象,所以也就无法建立依赖,因此此时的Cube预设体对象是没有材质、纹理的(即使内存中存在上一个share AB包遗留的材质、纹理对象),实例化出来自然是材质丢失。需要注意,若我们在加载新Cube预设体之前,再次加载share AB包,那么新Cube预设体是能依赖到材质、纹理对象的,但是,这些材质、纹理对象不是之前的对象,而是新的对象,换言之是由新share AB包中诞生的材质、纹理对象。此时内存中存在两份材质、纹理对象,一份老的、一份新的,老的那份已经无法通过卸载AB包的API卸载了,因为其已经与AB包断开联系了。
通常,建议使用AssetBundle.Unload(true)比较好,AssetBundle.Unload(false)往往会带来比较复杂的情况。但倘若真的需要使用AssetBundle.Unload(false),那么我们应该知道如何卸载那些与AB包脱离关系的对象(设为rab对象)。卸载方法如下:
(这部分基本是官方文档翻译,然后加了些自己的补充)
AssetBundle 文件是一种归档格式,由一个小的标题数据结构和一个包含虚拟文件的内容部分组成。文件头(Header)部分从不压缩,内容部分可以选择压缩。默认情况下,Unity 采用全文件压缩(LZMA)方式压缩内容部分,并采用基于块的压缩(LZ4)方式缓存AB包。
LZMA压缩:使用 LZMA 压缩时,AssetBundle 文件的整个内容部分将作为一个单一流进行压缩。这种全内容压缩方式的文件大小小于基于块的压缩方式。这是从内容交付网络(CDN)下载的AB包的首选格式。缺点是必须将整个文件解压到 RAM 中,才能从这些AB包中读取资源。当一个AB包包含的资源是只要使用就需要加载AB包中所有资源时,就可以使用这种压缩方式,如将角色或场景的所有资产打包。这是调用 BuildPipeline.BuildAssetBundles 时,没有指定特定的压缩方式所使用的压缩方式,即参数为BuildAssetBundleOptions.None。
不压缩:AB包也可以完全不压缩数据的方式构建。未压缩的缺点是文件下载量较大,因为某些类型的内容在AB包内可以高度压缩。不过,由于无需解压缩,下载后的加载时间会快很多。当从一个较大的AB包中只加载几个对象时,这一点尤其有用。未压缩的AB包是16字节对齐的。在调用 BuildPipeline.BuildAssetBundles 时,指定 BuildAssetBundleOptions.UncompressedAssetBundle 标志即可不压缩。
LZ4压缩:LZ4 采用基于块的算法,允许以片段或 "块 "的形式解压缩AB包。在写入AB包时,每个 128KB 的内容块都会在保存前被压缩。由于每块内容都是单独压缩的,因此整体文件大小比用 LZMA 压缩的AB包要大。但这种方法可以有选择性地检索和加载请求对象所需的内容块,而不是解压缩整个AB包。LZ4 的加载时间与未压缩的AB包相当,但好处是可以减小磁盘大小。这种压缩格式是AB包缓存的首选格式,下文将对此进行介绍,对于作为安装的一部分分发的AB包(什么意思?为什么?)或在大小不是最重要的其他情况下,这种格式也是不错的选择。在调用 BuildPipeline.BuildAssetBundles 时,指定 BuildAssetBundleOptions.ChunkBasedCompression 标志,即可使用这种压缩方式。
由于不同的数据在压缩时会有不同程度的压缩比例,因此可以通过使用不同的压缩算法来测试不同压缩算法的压缩比例,然后根据结果决定使用哪种格式。
如果使用自定义缓存解决方案下载和存储数据,可以使用 AssetBundle.RecompressAssetBundleAsync 更改压缩方式,例如在下载后将 LZMA 格式的 AssetBundle 转换为未压缩或 LZ4 格式。
注意:WebGL 不支持AB包的 LZMA 压缩。在 WebGL 平台上对AB包使用 LZ4 压缩。有关更多信息,请参阅 Reduce load times with AssetBundles。
从网络服务下载AB包时,需要考虑缓存问题,这样设备就不必在每次运行播放器时都下载相同的内容。由于AB包可能会被更新,因此建立一种机制将本地缓存的AB包替换为较新版本也很重要。
Unity 提供了基于磁盘的内置缓存,用于存储通过 UnityWebRequestAssetBundle 下载的 AssetBundle。要启用缓存,必须在调用 UnityWebRequestAssetBundle.GetAssetBundle 时指定版本整数或版本哈希参数。默认情况下,添加到磁盘缓存的任何AB包都将转换为 LZ4 压缩。因此,最初下载和加载 LZMA 压缩格式的AB包需要较长的时间,因为要进行重新压缩,但随后的加载会使用缓存版本(LZ4压缩格式)并快速运行。如果 Caching.compressionEnabled 为 false,Unity 会以未压缩格式将AB包写入磁盘缓存。
通过互联网下载AB包时,必须采取措施确保缓存不接受被损坏或篡改的文件内容。在调用 UnityWebRequestAssetBundle.GetAssetBundle 时,您应指定预期的 CRC,这样 Unity 在将文件添加到缓存时就能将该值与下载的内容进行比较。在将 LZMA AssetBundle 转换为 LZ4 时,可以低成本执行 CRC 校验。一旦经过验证的文件到达缓存,就不需要再次重复 CRC 校验。另请参阅 AssetBundle Download Integrity and Security。
Caching 类可用于管理内置的 AssetBundle 缓存,例如清除其内容或检查 AssetBundle 是否已被缓存。
为了提高性能,Unity 会在加载基于块或未压缩的AB包时在内存中保留一些未压缩的数据。但无论底层AB包文件有多大,这种缓存的大小都是固定的。
要加载一个AB包, Unity需要随机访问它的内容,可以通过磁盘上的文件,内存中的文件或c# FileStream。它还要求 AssetBundle 文件未压缩或使用基于块的压缩(LZ4)。为了建立一个可加载的AB包,Unity 有时需要创建一个临时的内存AB包。这并不总是坏事,因为 AB包内容一旦进入内存,就可以快速加载。但在很多情况下,最好还是将文件以本地AB包或缓存下载的形式存在磁盘上,这样可以最大限度地减少内存使用量,而且即时压缩转换也不会拖慢加载速度。
在以下情况下,将创建临时内存中的AB包:
临时文件使用的内存在所有读取完成后,调用Unload释放。
注意:在支持基于磁盘的AB包缓存的平台上, Caching.compressionEnabled 设置将影响用于临时内存中的AB包的格式。默认情况下,它是true,内存中的AssetBundles使用LZ4。当 Caching.compressionEnabled 为false时,这些内存中的文件将被解压缩,因此可能会占用更多的RAM。在不支持缓存的平台上,内存格式总是LZ4。如果输入是不同的格式,则执行动态转换,这可能会增加加载时间。
注意:在调用AssetBundle.LoadFromFile、AssetBundle.LoadFromFileAsync、AssetBundle.LoadFromStream或AssetBundle.LoadFromStreamAsync做CRC检查。对于基于块的文件,将强制对文件的每个块进行完全读取和解压缩。这个计算是逐块进行的,而不是将整个文件加载到RAM中,所以它没有内存问题,但它会减慢加载时间。对于LZMA格式的AB包,执行CRC检查没有显著的额外成本,因为加载它总是读取并解压缩所有内容。
内存分析器,诸如Memory Profiler包之类的工具可以用来检查加载的AssetBundles使用了多少内存。
(这部分基本是官方文档翻译,然后加了些自己的补充)
AB包打补丁很简单,只需要下载新的AB包并替换现有的AB包。如果使用UnityWebRequestAssetBundle.GetAssetBundle管理应用程序缓存的AB包,则传递不同的版本或哈希参数将触发新AB包的下载。
在修补系统中要解决的更难的问题是检测要替换的 AssetBundle。修补系统需要两个信息列表:
修补程序应下载服务器端AB包列表并比较这些AB包列表。应重新下载缺少的AB包或已更改版本控制信息的AB包。
Unity不提供任何内置的差异补丁机制,UnityWebRequestAssetBundle.GetAssetBundle在使用内置缓存系统时不执行差异补丁。相反,它总是完整地下载新的AB包。如果需要不同的补丁,那么必须编写自定义下载程序。Unity以确定的方式构建数据排序的AB包,因此重建AB包补丁可能比完整文件小得多。未压缩或基于块的压缩(LZ4)将比默认的完整文件压缩(LZMA)更好地修补。大多数编写自己系统的开发人员选择为他们的AB包列表使用行业标准的数据格式,例如JSON,并使用标准的c#类来计算文件内容散列,例如MD5。文件内容散列可以作为AB包版本,或者您可以使用由构建系统生成的更传统的版本号。
后面翻译官方文档那块以后有时间再调整调整,自己写着都感觉有点小乱。
不得不说,官方文档写的真一言难尽,有些真不如直接看英文,而且最新版的文档有些部分也没有中文,所以还是得看英文的。话是这么说,不过文章里翻译那部分基本还是靠翻译软件翻译的,个别地方我则是自己去看然后修改,毕竟自己全看太费劲了,既然有科技就要活用嘛。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。