可以使用两种不同的 API 来加载 AssetBundles。
当从本地存储加载未压缩和块压缩捆绑包(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。例如,您可以使用 AssetBundle.LoadFromMemoryAsync。
可以调用 UnityWebRequestAssetBundle API 来创建用于下载、缓存和加载 AssetBundles 的特殊网络请求。
通常,URL 将是指向文件的“https://”地址,如网络服务所公开。它也可以是“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 值。但在实践中,您的 AssetBundles 的预期 CRC 值会在下载 AssetBundles 之前单独下载或从文件检索。请参阅 AssetBundle 下载完整性和安全性。
注意:为了避免每次调用此代码时都下载整个 AssetBundle 内容,可以为您的 AssetBundle 启用缓存。这可以通过在调用 UnityWebRequestAssetBundle.GetAssetBundle 时传递 AssetBundle 哈希来完成。哈希值可从 AssetBundle Manifest 中获得,将在本节后面介绍。哈希值充当您请求的 AssetBundle 的精确构建的版本标识符。有关缓存和相关压缩注意事项的详细信息,请参阅 AssetBundle 压缩和缓存。
现在您已成功加载 AssetBundle,现在该加载一些资源了。
通用代码片段
T objectFromBundle = bundleObject.LoadAsset<T>(assetName);
T 是您尝试加载的资源的类型。
在决定如何加载资源时,有几个选项。我们有LoadAsset
、LoadAllAssets
以及它们的异步对应物LoadAssetAsync
和LoadAllAssetsAsync
。
这是从 AssetBundles 同步加载资源的方法
加载单个资源(例如预制件的根 GameObject)
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 Manifest 可能非常有用。尤其是在处理 AssetBundle 依赖项时。
要获得可用的 AssetBundleManifest 对象,您需要加载该附加的 AssetBundle(其名称与其所在的文件夹相同)并从其中加载类型为 AssetBundleManifest 的对象。
加载清单本身与从 AssetBundle 加载任何其他资源完全相同
AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
现在,您可以通过上面示例中的清单对象访问AssetBundleManifest
API 调用。从此处,您可以使用清单来获取有关您构建的 AssetBundles 的信息。这些信息包括 AssetBundles 的依赖项数据、哈希数据和变体数据。
有关 AssetBundle 依赖项 的前面部分讨论了如何为了从 AssetBundle 加载资源,您必须先加载该 AssetBundle 依赖的任何 AssetBundles。清单对象使动态查找和加载依赖项成为可能,因此您不必在代码中明确硬编码所有 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));
}
现在您已经加载了 AssetBundles、AssetBundle 依赖项和资源,现在该谈谈管理所有这些已加载的 AssetBundles 了。
注意:Addressables 包提供了一个现成的系统来管理加载 AssetBundles、依赖项和资源。Unity 建议使用 Addressables 而不是自己管理 AssetBundles。
另请参阅:Unity Learn 关于 管理已加载的 AssetBundles 的教程
Unity 不会在对象从活动 场景场景包含游戏的环境和菜单。将每个唯一的场景文件视为一个独特的关卡。在每个场景中,您放置环境、障碍物和装饰,本质上是分段设计和构建游戏。 更多信息
请参阅 术语表 中移除时自动卸载对象。资源清理会在特定时间触发,也可以手动触发。
了解何时加载和卸载 AssetBundle 很重要。不正确地卸载 AssetBundle 会导致内存中出现重复对象或丢失对象。
关于 AssetBundle 管理,最重要的是了解何时调用 AssetBundle.Unload(bool)(或 AssetBundle.UnloadAsync(bool)),以及是否应该在函数调用中传递 true 或 false。Unload 是一个非静态函数,它将通过从内存中移除 AssetBundle 标头和其他数据结构来卸载您的 AssetBundle。该参数指示是否还卸载从该 AssetBundle 实例化的所有对象。
AssetBundle.Unload(true)
会卸载所有从 AssetBundle 加载的 GameObjectsUnity 场景中的基本对象,可以表示角色、道具、场景、摄像机、路点等。GameObject 的功能由附加到它的组件定义。 更多信息
请参阅 术语表(及其依赖项)。这并不包括您已复制到场景中的对象(例如,通过调用Instantiate
),因为该复制数据不属于 AssetBundle。如果场景中的对象引用从 AssetBundle 加载的纹理(并且仍然属于它),则此调用会导致纹理消失,Unity 将其视为丢失纹理。
例如,假设材质 M 从 AssetBundle AB 加载,如以下所示,并用于 预制件一种资源类型,允许您存储一个完整的 GameObject,包括组件和属性。预制件充当模板,您可以从中在场景中创建新的对象实例。 更多信息
请参阅 术语表 P。
如果调用 AB.Unload(true),则活动场景中对象引用的任何 M 实例都将被销毁。
如果您改为调用 AB.Unload(false),则 M 的实例将保留在内存中。它们将成为不链接回原始 AssetBundle 的独立对象。
如果稍后再次加载 AB,Unity 不会将 M 的现有副本重新链接到 AssetBundle 中的材质。
如果您创建了预制件 P 的另一个实例,它将不会使用 M 的现有副本。相反,将加载 M 的第二个副本。
通常,使用AssetBundle.Unload(false)
会导致重复对象和其他问题。大多数项目应使用AssetBundle.Unload(true)
来避免这种情况。有两种常见的策略可以安全地调用AssetBundle.Unload(true)
在应用程序生命周期中定义明确的点,在这些点卸载瞬态 AssetBundles,例如在关卡之间或加载屏幕期间。
为各个对象维护引用计数,并且仅在所有组成对象未被使用时才卸载 AssetBundles。这允许应用程序卸载和重新加载单个对象,而不会重复内存。
如果应用程序必须使用AssetBundle.Unload(false)
,则只能通过两种方式卸载各个对象
消除对不需要的对象的所有引用,例如,确保没有其他已加载的对象包含指向它的字段。完成此操作后,调用 Resources.UnloadUnusedAssets。
非附加地加载场景。这将销毁当前场景中的所有对象并自动调用 Resources.UnloadUnusedAssets。