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

本章原文所在章节:【Unity Manual】→【Working in Unity】→【Advanced Development】→【AssetBundles】→【Using AssetBundles Natively】

本地使用 AssetBundles

从 Unity5 开始,我们可以使用4个不同的 API 来加载 AssetBundles。使用哪个 API,取决于 AssetBundle 在哪个平台上被加载,以及创建 AssetBundles 时使用的是哪种压缩方法(不压缩、LZMA算法、LZ4算法)。

这4个 API 分别是:

AssetBundle.LoadFromMemoryAsync

这个方法的参数是一个包含了 AssetBundle 数据的字节数组。如果需要的话,你还可以传入一个 CRC(循环冗余校验码) 参数。如果 AssetBundle 使用 LZMA 算法压缩,那么 AssetBundle 在加载的时候会被解压。如果 AssetBundle 使用 LZ4 算法压缩,它将直接以压缩形式被加载。

下面是一个如何使用这个方法的例子:

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);
}

当然,这不是唯一使用该方法的方式。参数 File.ReadAllBytes(path) 可以被任何一个字节数组或者一个返回字节数组的方法替换。

AssetBundle.LoadFromFile

这个 API 在加载本地存储的未压缩 AssetBundle 时具有很高效率。如果 AssetBundle 是未压缩,或者是数据块形式(LZ4 算法压缩)的,LoadFromFile 将从磁盘中直接加载它。如果 AssetBundle 是高度压缩(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);
}
}

注意:在安卓设备上,如果 Unity 是5.3或者更老的版本,这个方法在读取资产流路径(Streaming Assets path)的时候会失败。这是因为那个路径是在一个 .jar 文件的内部。Unity5.4 以及更高的版本没有这个问题,可以正常的读取资产流。

WWW.LoadFromCacheOrDownload

这个 API 已经被废弃(建议使用 UnityWebRequest)(三思:这句话不是我加的,官方文档中就是有这句话)

这个 API 对于从远程服务器加载 AssetBundles,或者加载本地 AssetBundles 都很有用。这个 API 是 UnityWebRequest 不尽如人意的老版本。

从远程服务器加载的 AssetBundle 将会被自动缓存。如果 AssetBundle 是压缩形式的,一个工作线程将加速解压这个 AssetBundle 并写入缓存。一旦一个 AssetBundle 已经被解压且被缓存,它将完全像使用 AssetBundle.LoadFromFile 方法一样被加载。

下面是一个如何使用这个方法的例子:

using UnityEngine;
using System.Collections; public class LoadFromCacheOrDownloadExample : MonoBehaviour
{
IEnumerator Start ()
{
while (!Caching.ready)
yield return null; var www = WWW.LoadFromCacheOrDownload("http://myserver.com/myassetBundle", 5);
yield return www;
if(!string.IsNullOrEmpty(www.error))
{
Debug.Log(www.error);
yield return;
}
var myLoadedAssetBundle = www.assetBundle; var asset = myLoadedAssetBundle.mainAsset;
}
}

由于缓存 AssetBundle 字节数据的开销较大,建议所有开发者在使用 WWW.LoadFromCacheOrDownload 方法时,确保 AssetBundles 都比较小——最多几兆字节。同样建议所有开发者在内存比较有限的平台(比如移动设备)上使用这个方法时,确保同时只下载一个 AssetBundle,防止内存泄漏。

如果缓存文件夹没有足够的空间来缓存额外的文件,LoadFromCacheOrDownload 将会从缓存中迭代删除最近最少使用的 AssetBundles,直到有足够的空间来存储新的 AssetBundle。如果空间还是不够(比如硬盘满了,或者所有缓存的文件都正在被使用),LoadFromCacheOrDownload() 将绕开缓存,直接将文件以流的形式存进内存。

如果想要使用 LoadFromCacheOrDownload 的版本变量,方法参数(第二个参数)需要改变。如果参数与当前缓存的 AssetBundle 的版本变量一致,那么就可以从缓存中加载这个 AssetBundle。

UnityWebRequest

UnityWebRequest 有个专门的 API 来处理 AssetBundles。首先,你需要使用 UnityWebRequest.GetAssetBundle 方法来创建你的 web 请求。在请求返回后,将请求放入 DownloadHandlerAssetBundle.GetContent(UnityWebRequest) 作为参数。GetContent 方法将返回你的 AssetBundle 对象。

在下载完 AssetBundle 后,你同样可以使用 DownloadHandlerAssetBundle 类的 assetBundle 属性来加载 AssetBundle,这就和使用 AssetBundle.LoadFromFile 方法一样高效。

下面有个例子展示:如何加载一个包含两个 GameObjects 的 AssetBundle,并实例化它们。想要运行这段程序,我们只需要调用 StartCoroutine(InstantiateObject()) 方法:

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);
}

使用 UnityWebRequest 的优点是,它允许开发者用更灵活的方式来处理下载的数据,并且潜在地排除了不必要的内存占用。和 UnityEngine.WWW 类相比,这是更现代,也更推荐的 API。

从 AssetBundles 中加载资源

现在,你已经成功下载了你的 AssetBundle,是时候从中加载一些资源。

通常的代码片段:

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

T 是你想加载的资源类型。

当你决定如何加载资源的时候,有一对方法供使用。我们可以使用 LoadAssetLoadAllAssets 方法,以及与它们对应的异步方法: LoadAssetAsyncLoadAllAssetsAsync

下面是一个从一个 AssetBundle 中同步加载资源的例子:

  • 加载一个 GameObject:

GameObject gameObject = loadedAssetBundle.LoadAsset<GameObject>(assetName);

  • 加载所有资源:

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

现在,和上面展示的方法(要么返回你正在加载的对象,要么返回一组对象)不同的是,异步方法返回的是一个 AssetBundleRequest

在可以使用资源前,你需要等待处理完成。如下:

AssetBundleRequest request = loadedAssetBundleObject.LoadAssetAsync<GameObject>(assetName);
yield return request;
var loadedAsset = request.asset;

以及:

AssetBundleRequest request = loadedAssetBundle.LoadAllAssetsAsync();
yield return request;
var loadedAssets = request.allAssets;

一旦你已经加载好你的资源,是时候行动了!你可以像使用 Unity 中的其他对象一样使用加载的对象。

加载 AssetBundle Manifests(资源清单)

加载 AssetBundle manifests 非常的有用。尤其是当处理 AssetBundle 依赖关系的时候。

为了获取可以使用的 AssetBundleManifest,你需要加载一个额外的 AssetBundle(即那个和文件夹名称相同的文件),并且从中加载出一个 AssetBundleManifest 类型的对象。

从 AssetBundle 中加载 manifest 完全和从中加载其他资源一样,如下:

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

现在,你可以通过上面例子获取到的 manifest 对象来使用 AssetBundleManifest 类的 API。从现在开始,你可以使用这个 manifest 来获取关于 AssetBundle 的信息,包括:依赖数据、hash 数据,以及版本变量数据。

还记得前面章节我们讨论过的,如果一个 bundleA 对 bundleB 有依赖,那么在从 bundleA 中加载任何资源之前,我们需要先加载 bundleB 吗?Manifest 对象就使得动态查找正在加载的依赖关系成为可能。比如我们想要加载一个名叫“assetBundle”的 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 依赖,以及其他资源,是时候讨论如何管理这些加载好的 AssetBundles 了。

(这里可以休息一下,下面是另一块内容)

管理加载好的 AssetBundles

你也可以查看 Unity 的 Managing Loaded AssetBundles 教程。(篇幅较大,建议先看本节内容。后面我会找时间把这个教程作为补充文档一起翻译了)

在 Objects 被从场景中移除的时候,Unity 不会自动将它们卸载。资源的清理是在某个特定时机被触发,当然也可以手动触发。

知道什么时候加载和卸载一个 AssetBundle 很重要。不合时宜的卸载 AssetBundle 可能导致重复对象(duplicating objects)错误,或者其他未预料到的情况,比如纹理丢失。

理解如何管理 AssetBundle 最重要的事是什么时候调用 AssetBundle.Unload(bool) 方法,以及该方法的参数应该传入 true 还是 false。该方法卸载 AssetBundle 的头信息;方法参数决定了是否同时卸载从 AssetBundle 中加载并实例化的所有 Objects。

如果你传入 true 参数,那么你从 AssetBundle 中加载的所有对象将被卸载,即便这些对象正在被使用。这就是我们前面提到的,导致纹理丢失的原因。

假设 Material M 是从 AssetBundle AB 中加载的,如下:

  • 如果 AB.Unload(true) 被调用,那么任何使用 Material M 的实例都将被卸载并消除,即便它们正在场景中被使用。

  • 如果 AB.Unload(false) 被调用,那么将切断所有使用 Material M 的实例与 AssetBundle AB 的联系。

如果 AssetBundle AB 在被卸载后不久再次被加载,Unity 并不会将已经存在的使用 Material M 的实例与 AssetBundle AB 重新联系。因此将存在两份被加载的 Material M。

通常情况下,使用 AssetBundle.Unload(false) 不会获得理想情况。大多数项目应该使用 AssetBundle.Unload(true) 方法,以避免内存中出现重复对象(duplicating objects)。

大多数项目应该使用 AssetBundle.Unload(true) 方法,并且要采取措施确保没有重复对象。两种通常采取的措施如下:

  • 在应用的生命周期中找到合适的时机来卸载 AssetBundle,比如关卡之间,或者加载场景的时候。

  • 为每个对象采取引用计数管理方法,只有当 AssetBundle 的所有对象都没有被使用的时候,再卸载 AssetBundle。这样就可以避免应用出现重复对象的问题。

如果应用必须使用 AssetBundle.Unload(false) 方法,对象将只能在以下两种情况下被卸载:

如果你不想自己管理加载的 AssetBundle、依赖关系,以及资源,你可能需要使用 AssetBundle 管理器(AssetBundle Manager,下一章节将介绍)。

【Unity3D技术文档翻译】第1.5篇 本地使用 AssetBundles的更多相关文章

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

    上一章:[Unity3D技术文档翻译]第1.6篇 使用 AssetBundle Manager 本章原文所在章节:[Unity Manual]→[Working in Unity]→[Advanced ...

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

    上一章:[Unity3D技术文档翻译]第1.5篇 使用 AssetBundles 本章原文所在章节:[Unity Manual]→[Working in Unity]→[Advanced Develo ...

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

    上一章:[Unity3D技术文档翻译]第1.3篇 创建 AssetBundles 本章原文所在章节:[Unity Manual]→[Working in Unity]→[Advanced Develo ...

  4. 【Unity3D技术文档翻译】第1.3篇 创建 AssetBundles

    上一章:[Unity3D技术文档翻译]第1.2篇 为打包 AssetBundles 准备资产 本章原文所在章节:[Unity Manual]→[Working in Unity]→[Advanced ...

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

    上一章:[Unity3D技术文档翻译]第1.7篇 AssetBundles 补丁更新 本章原文所在章节:[Unity Manual]→[Working in Unity]→[Advanced Deve ...

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

    上一章:[Unity3D技术文档翻译]第1.8篇 AssetBundles 问题及解决方法 本章原文所在章节:[Unity Manual]→[Working in Unity]→[Advanced D ...

  7. 【Unity3D技术文档翻译】第1.0篇 AssetBundles

    前言 "Unity圣典"是目前对官方文档翻译比较详细的,然而文档的最新更新日期是2013年,已经远远落后最新版本,参考意义有限.官方文档.脚本手册是学习Unity3D最直接有效的途 ...

  8. 【Unity3D技术文档翻译】第1.1篇 AssetBundle 工作流

    译者前言:本章是关于从创建到加载,再到使用 AssetBundle 的整个流程的概述.阅读本章将对 AssetBundle 的工作流程有个简单而全面的了解. 本章原文所在章节:[Unity Manua ...

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

    本章原文所在章节:[Unity Manual]→[Working in Unity]→[Advanced Development]→[AssetBundles]→[Preparing Assets f ...

随机推荐

  1. 解决eclipse出现This Android SDK requires Andro...date ADT to the latest version.问题

    更新完android SDK之后,eclipse出现了“This Android SDK requires Andro...date ADT to the latest version.”问题,这是因 ...

  2. myeclipse 奔溃解决办法

    myeclipse 突然奔溃 解决办法 JVM terminated.Exit code=1 意思是说java 虚拟机挂了,而不是myeclipse挂了. 进入myeclipse 目录 查看myecl ...

  3. Java 获取年 月 日 时 分 秒

    /** * 英文简写(默认)如:2010-12-01 */ public static String FORMAT_SHORT = "yyyy-MM-dd"; /** * 英文全称 ...

  4. Linuxc - 操作系统内存分配

    静态变量是存储在数据段的,在函数中可以共用. 全局变量也是存储在数据段的,在全局中可以共用. 指针变量本质上是地址,数组变量本质上也是地址. 数组是可靠的,不可变的地址.指针变量是不可靠的,可变的.数 ...

  5. 懒人小技巧, Toad 常用偷懒方法

    用toad很久了, 感叹它的功能强大的同时整理了一些相关技巧,  有以前在网上看到的也有自己平时用的时候偶尔发现的, 分享一下, 它对用户体验方面做的真的很到位, 通过各种方式来方便用户的操作. 菜鸟 ...

  6. cookie sessionStorage localStorage 之间的关系

    先说一个cookie 因为HTTP是无状态的 所以cookie诞生 用于保存会话信息 大小 4096b 一般在4095b以内 数量限制 20 -50 根据浏览器不同 操作的是一个字符串 可以设置参数 ...

  7. linux_运维职责

    运维准则: 不要删文件,移动文件,可以复原,一个月后什么事没有,删除 运维人员主要关注哪些方面? CPU:对计算机工作速度和效率起决定性作用(intel amd) 内存: 临时存放数据:容量和处理速度 ...

  8. python_如何对实例属性进行类型检查?

    案例: 在某项目中,我们实现了一些类,并希望能像静态语言那样对他们的实例属性进行类型检查 p = Person() p.name = 'xi_xi'         # 必须是str p.age = ...

  9. javaweb后台转码

    为什么需要转码? 客户端向服务器发送请求的四种情况:1.URL方式直接访问;2.页面链接(属于get请求);3.表单get提交;4.表单post提交 1.url(url和页面链接):各大浏览器.各个操 ...

  10. MyEclipse安装插件

    原文地址:http://www.cnblogs.com/pharen/archive/2012/02/08/2343342.html 本文讲解MyEclipse(MyEclipse10)的三种方法,以 ...