Unity AssetBundles加载资源

发表于2018-09-29
评论0 9.2k浏览
在Unity5中,大家可以使用四种不同的API来加载资产包。它们的行为取决于构建资产包时所使用的包加载平台和压缩方法(未压缩、lzma、lz 4)。我们必须使用的四个API是:
  • AssetBundle.LoadFromMemoryAsync
  • AssetBundle.LoadFromFile
  • WWW.LoadfromCacheOrDownload
  • UnityWebRequest’s DownloadHandlerAssetBundle(Unity 5.3 or newer)

AssetBundle.LoadFromMemoryAsync

此函数接受一个包含资产包数据的字节数组。如果您愿意,也可以传入CRC值。如果包是lzma压缩的,它将在加载时解压资产包。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);
    } 
然而,这并不是唯一能够使用loadfromMemy异步的策略,可以用任何获得字节数组的过程来替换File.ReadAllBytes(path)。

AssetBundle.LoadFromFile

当从本地存储加载未压缩的包时,此API是高效的。如果bundle未压缩或块(Lz4)压缩,LoadFromFile将直接从磁盘加载该包。使用此方法加载一个完全压缩(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);
    }
} 
提示:在Unity5.3或更高版本的android设备上,当试图从Streaming Assets路径加载资产包时,该API将失败。这是因为该路径的内容将驻留在一个压缩的。jar文件中。Unity5.4及更高版本可以在Streaming Assets中使用此api调用。

WWW.LoadFromCacheOrDownload

不推荐(使用UnityWebRequest)
此API对于从远程服务器下载资产包或加载本地资产包非常有用。它是UnityWebRequest?API的旧版本,也是不太理想的版本。
从远程位置加载资产包将自动缓存资产包。如果资产包被压缩,工作线程将旋转起来解压包并将其写入缓存。一旦包被解压缩并缓存,它将加载完全类似于assetbundle.loadFrom文件。
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;
    }
}

由于在www对象中缓存资产包字节的内存开销,建议所有使用www.loadfromcacheordownload的开发人员确保其资产包保持较小--最多只有几兆字节。还建议在有限内存平台(如移动设备)上操作的开发人员确保他们的代码一次只下载一个资产包,以避免内存激增。如果缓存文件夹没有缓存其他文件的任何空间,则从cacheordownload加载将迭代地从缓存中删除最近使用最少的资产包,直到有足够的空间存储新的资产包。如果无法腾出空间(因为硬盘已经满了,或者缓存中的所有文件都在使用中),那么loadfromcacheordownload()将绕过缓存,将文件流到内存中,为了强制loadfromcacheordownload,版本参数(第二个参数)需要更改。只有在传递给函数的版本与当前缓存的资产包版本匹配时,资产包才会从缓存中加载。

UnityWebRequest

unitywebrequest有一个特定的API调用来处理资产包。首先,您需要使用unitywebrequest.getassetbundle创建Web请求。在返回请求后,将请求对象传递到?DownloadHandlerAssetBundle.GetContent(UnityWebRequest)。此GetContent调用将返回AssetBundle对象。

您还可以在下载下载包之后在Dowloadhandlerassetbundle类上使用assetbundle属性来加载资产包,从而提高assetbundle.loadfromfile的效率。下面是一个如何加载包含两个游戏对象的资产包并实例化它们的示例。要开始这个过程,我们只需要调用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的优点是它允许开发人员以更灵活的方式处理下载的数据,并可能消除不必要的内存使用。这是比unityEngin.www类更流行和更好的API。

Loading Assets from AssetBundles

现在您已经成功下载了资产包,现在是时候加载一些资产了。通用代码片段:
T objectFromBundle = bundleObject.LoadAsset<T>(assetName);

T是您试图加载的资产的类型,在决定如何加载资产时,有几个选项:我们有loadAsset、LoadAllAssets,以及LoadAssetAsync,LoadAllAssetsAsync
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;

Loading AssetBundle Manifests

加载资产包清单非常有用。特别是在处理资产包依赖关系时。要获得一个可用的assetbundlemanifest对象,您需要加载该附加资产包(与其所在的文件夹命名相同的资产包),并从其中加载一个类型为assetbundlemanest的对象。从资产包加载清单本身的操作与任何其他资产完全相同:
AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
现在,您可以通过上面示例中的清单对象访问AssetBundleManifestAPI调用。从此处,您可以使用清单获取关于您构建的AssetBundle的信息。此信息包括依赖数据、散列数据和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));}

Managing Loaded AssetBundles

当从活动场景中移除对象时,unity不会自动卸载对象。资产清理在特定时间被触发,也可以手动触发。重要的是要知道何时加载和卸载一个资产包。不适当地卸载一个资产包会导致在内存中复制对象或其他不合适的情况,例如缺少纹理。

关于资产包管理,需要了解的最大事情是何时调用assetbundle.unload(Bool);以及是否应该将true或false传递到函数调用中。卸载是一个非静态函数,它将卸载您的资产包。此API卸载正在调用的资产包的头信息。该参数指示是否也卸载从该资产包实例化的所有对象。

AssetBundle.Unload(true)卸载从资产包加载的所有游戏对象(及其依赖项)。这不包括复制的游戏对象(例如实例化的游戏对象),因为它们不再属于资产包。当这种情况发生时,从资产包加载的纹理(并且仍然属于它)从场景中的游戏对象中消失,并且unity将它们视为缺失的纹理。

如果ab.unload(True)被调用。活动场景中的m的任何实例也将被卸载和销毁。
如果要调用ab.unload(False),则会破坏m和ab当前实例的链。

如果ab稍后再次加载并调用ab.loadAsset(),那么统一将不会将现有的m副本重新链接到新加载的材料上,而是会有两个m加载的副本。

通常,使用assetbundle.unload(False)并不会导致理想的情况。大多数项目应该使用assetbundle.unload(True)来防止内存中的对象复制。大多数项目应该使用assetbundle.unload(True),并采用一种方法来确保对象不被复制:

在应用程序的生命周期中具有定义良好的点,在此期间卸载临时资产包,例如在级别之间或加载场景期间;维护单个对象的引用计数,并且只有当所有组成对象都被卸载时才能卸载资产包。这允许应用程序卸载。

如果应用程序必须使用assetbundle.unload(False),那么只能通过两种方式卸载单个对象:在场景和代码中消除对不想要的对象的所有引用,这样做之后,调用resources.unloadunusedassets;非附加加载场景,这会摧毁所有在当前场景中的对象,并自动调用resources.unloadunusedassets。

如果您不愿意自己管理加载资产Bundes、依赖项和资产,您可能会发现自己需要资产包管理器。
来自:https://blog.csdn.net/qq_27880427/article/details/79369127

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引