zoukankan      html  css  js  c++  java
  • 【Unity3D技术文档翻译】第1.5篇 本地使用 AssetBundles

    Unity3D技术文档翻译

    上一章:【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 的联系。

    使用 AB.Unload(false) 后

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

    AssetBundle 再次被加载后

    再次加载预设后

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

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

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

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

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

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

  • 相关阅读:
    笔试题系列001
    算法系列001---dfs|多叉|解空间树理解
    leetcode--014 Gas station
    leetcode--012 single number I
    leetcode--011 copy list with random pointer
    leetcode--010 Linked List Cycle II
    leetcode--009 Linked List Cycle I
    leetcode--007 word break I
    leetcode-006 detect cycle
    alex鸡汤回信
  • 原文地址:https://www.cnblogs.com/hearthstone/p/7807803.html
Copyright © 2011-2022 走看看