可以使用两种不同的 API 来加载 AssetBundle。
从本地存储空间加载未压缩和块压缩捆绑包 (LZ4) 时,此 API 非常高效,因为它可以直接从磁盘增量式地读取文件内容。对于使用全文件压缩 (LZMA) 进行压缩的文件,使用此方法时加载效率较低,因为它要求首先将文件完全解压缩到内存中。
如何使用 LoadFromFile 的一个示例:
using System.IO;
using UnityEngine;
public class LoadFromFileExample : MonoBehaviour
{
void 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);
}
}
还可以使用异步版本 AssetBundle.LoadFromFileAsync,而不是在加载过程中阻止应用程序。
如果 AssetBundle 未实际存储在本地文件中,那么还有其他几种加载方法可用。例如,可以使用 AssetBundle.LoadFromMemoryAsync。
可以调用 UnityWebRequestAssetBundle API 来创建用于下载、缓存和加载 AssetBundle 的特殊 Web 请求。
通常,URL 将是 ‘https://’ 地址,指向 Web 服务公开的文件。在不支持直接文件系统访问的平台上,访问本地数据的也可能是 ‘file://’。
此请求可以传入 DownloadHandlerAssetBundle.GetContent(UnityWebRequestAssetBundle) 以获取加载的 AssetBundle 的 AssetBundle 对象。
例如
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
public class DownloadExample : MonoBehaviour
{
IEnumerator Start()
{
string uri = "https://myserver/myBundles/bundle123";
uint crc = 1098980; // Expected content CRC
UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(uri, crc);
yield return request.SendWebRequest();
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
var loadAsset = bundle.LoadAssetAsync<GameObject>("Assets/Players/MainPlayer.prefab");
yield return loadAsset;
Instantiate(loadAsset.asset);
}
}
或从 2023.1 开始使用 Await 支持:
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
public class DownloadExample : MonoBehaviour
{
async void Start()
{
string uri = "https://myserver/myBundles/bundle123";
uint crc = 1098980; // Expected content CRC
UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(uri, crc);
await request.SendWebRequest();
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
var loadAsset = bundle.LoadAssetAsync<GameObject>("Assets/Players/MainPlayer.prefab");
await loadAsset;
Instantiate(loadAsset.asset);
}
}
为简单起见,示例显示了在代码中硬编码的 CRC 值。但实际上,在下载 AssetBundle 之前,将单独下载 AssetBundle 的预期 CRC 值,或者从文件中获取这些值。请参阅 AssetBundle 下载的完整性和安全性。
注意:为了避免每次调用此代码时下载整个 AssetBundle 内容,可以为 AssetBundle 启用缓存。方法是在调用 UnityWebRequestAssetBundle.GetAssetBundle 时传递 AssetBundle 哈希。哈希值可从 AssetBundle Manifest 中获取,本节稍后将对此进行介绍。哈希用作所请求的 AssetBundle 精确构建的版本标识符。请参阅 AssetBundle 压缩和缓存,详细了解缓存及相关压缩注意事项。
现在已经成功加载 AssetBundle,终于该加载一些资源了。
通用代码片段:
T objectFromBundle = bundleObject.LoadAsset<T>(assetName);
T 是尝试加载的资源类型。
决定如何加载资源时有几个选项。我们有 LoadAsset、LoadAllAssets 及其各自的异步对应选项 LoadAssetAsync 和 LoadAllAssetsAsync。
同步从 AssetBundle 加载资源的方法如下:
要加载单个资源(例如预制件的根游戏对象):
GameObject gameObject = loadedAssetBundle.LoadAsset<GameObject>(assetName);
加载所有资源:
Unity.Object[] objectArray = loadedAssetBundle.LoadAllAssets();
这将返回一个数组,其中包含每个资源的所有根对象。
现在,前文显示的方法将返回正加载的对象类型或对象数组,异步方法返回将 AssetBundleRequest。在访问资源之前,需要等待此操作完成。异步加载资源的方法如下:
AssetBundleRequest request = loadedAssetBundleObject.LoadAssetAsync<GameObject>(assetName);
yield return request; // or await request;
var loadedAsset = request.asset;
以及
AssetBundleRequest request = loadedAssetBundle.LoadAllAssetsAsync();
yield return request; // or await request;
var loadedAssets = request.allAssets;
加载资源后,就可以开始了!可以像使用 Unity 中的任何对象一样使用加载的对象。
加载 AssetBundle 清单可能非常有用。特别是在处理 AssetBundle 依赖关系时。
要获得可用的 AssetBundleManifest 对象,需要加载另外的 AssetBundle(与其所在的文件夹名称相同的那个)并从中加载 AssetBundleManifest 类型的对象。
加载清单本身的操作方法与 AssetBundle 中的任何其他资源完全相同:
AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
现在,可以通过上面示例中的清单对象访问 AssetBundleManifest API 调用。从这里,可以使用清单获取所构建的 AssetBundle 的相关信息。此信息包括 AssetBundle 的依赖项数据、哈希数据和变体数据。
之前关于 AssetBundle 依赖项的小节讨论了如何从 AssetBundle 按顺序加载资源,必须先加载 AssetBundle 依赖的 AssetBundle。清单对象使动态查找和加载依赖项成为可能,因此不必在代码中显式编码所有 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));
}
现在已经加载 AssetBundle、AssetBundle 依赖项和资源,因此是时候讨论如何管理所有这些已加载的 AssetBundle 了。
##管理已加载的 AssetBundle
注意:Addressables 包提供了一个现成的系统来管理加载 AssetBundle、依赖项和资源。Unity 建议使用 Addressables 而不是自己管理 AssetBundles。
另请参阅:关于管理已加载的 AssetBundle的 Unity 学习教程
从活动场景中删除对象时,Unity 不会自动卸载对象。资源清理在特定时间触发,也可以手动触发。
了解何时加载和卸载 AssetBundle 非常重要。不正确地卸载 AssetBundle 会导致在内存中复制对象或缺少对象。
关于管理 AssetBundle,最需要了解的是何时调用AssetBundle.Unload(bool)– 或者AssetBundle.UnloadAsync(bool) – 以及应该将 true 还是 false 传递到函数调用中。Unload 是一个非静态函数,它会从内存中删除 AssetBundle 头文件和其他数据结构,从而卸载 AssetBundle。该参数指示是否还要卸载通过此 AssetBundle 实例化的所有对象。
AssetBundle.Unload(true) 卸载从 AssetBundle 加载的所有游戏对象(及其依赖项)。这不包括已复制到场景中的对象(例如,通过调用 Instantiate),因为该复制的数据不属于 AssetBundle。如果场景中的对象引用了从 AssetBundle 加载的纹理(仍然属于该纹理),则此调用将导致纹理消失,Unity 会将其视为缺失纹理。
举例而言,假设 Material M 是如下所示从 AssetBundle AB 加载并在 Prefab P 中使用。
如果调用 AB.Unload(true),活动场景中对象引用的任何 M 实例也将被销毁。
如果改为调用 AB.Unload(false),则 M 的实例将保留在内存中。它们将成为不链接回原始 AssetBundle 的独立对象。
如果稍后再次加载 AB,则 Unity 不会将现有 M 副本重新链接到 AssetBundle 中的材质。
如果创建另一个 Prefab P 的实例,它将不会使用 M 的现有副本。而是加载 M 的第二个副本。
通常,使用 AssetBundle.Unload(false) 可能会导致对象重复和其他问题。大多数项目应该使用 AssetBundle.Unload(true) 来避免这种情况。安全调用 AssetBundle.Unload(true) 的常见策略有两种:
在应用程序生命周期中具有明确定义的卸载瞬态 AssetBundle 的时间点,例如在关卡之间或在加载屏幕期间。
维护单个对象的引用次数,仅当未使用所有组成对象时才卸载 AssetBundle。这允许应用程序卸载和重新加载单个对象,而无需复制内存。
如果应用程序必须使用 AssetBundle.Unload(false),则只能以两种方式卸载单个对象:
消除对不需要的对象的所有引用,例如,确保没有其他加载的对象具有指向它的字段。完成后,调用 Resources.UnloadUnusedAssets。
以非附加方式加载场景。这样会销毁当前场景中的所有对象并自动调用Resources.UnloadUnusedAssets。