Unity的AssetBundle系统是管理游戏资源的核心机制之一,它允许开发者将资源(如场景、预制体、纹理、音频等)打包成单独的文件,并在运行时动态加载。这种方式不仅优化了内存使用,还支持热更新,提升了游戏的灵活性和可维护性。
AssetBundle 是什么?
AssetBundle 是Unity提供的一种资源打包和加载系统。它将多个资源打包成一个或一组文件(称为AssetBundle文件),这些文件可以在运行时通过代码动态加载,而无需将所有资源直接嵌入游戏包中。
核心概念
- 资源打包:将多个资源(如纹理、模型、音频等)组合成一个AssetBundle文件。
- 动态加载:在游戏运行时按需加载这些文件,而不是一次性加载所有资源。
- 独立性:AssetBundle文件可以独立于主游戏包存在,适合分发和更新。
主要优点
- 按需加载:只加载当前需要的资源,减少初始加载时间和内存占用。
- 热更新:支持在不重新发布游戏的情况下更新内容。
- 资源复用:可以在不同场景或项目中复用相同的AssetBundle。
使用场景
- 大型游戏中分模块加载资源。
- 需要频繁更新内容的在线游戏。
- 跨平台项目中共享资源。
AssetBundle 的打包
在使用AssetBundle之前,开发者需要将资源打包成AssetBundle文件。打包过程通常在Unity编辑器中完成,使用BuildPipeline API。
如何打包
Unity提供了BuildPipeline.BuildAssetBundles方法来完成打包:
BuildPipeline.BuildAssetBundles("Assets/AssetBundles", BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
- 参数一:输出路径,指定AssetBundle文件保存的目录(如"Assets/AssetBundles")。
- 参数二:打包选项,用于控制打包行为(详见下文)。
- 参数三:目标平台,例如BuildTarget.StandaloneWindows(Windows平台)或BuildTarget.Android(Android平台)。
运行这段代码后,Unity会根据资源配置生成AssetBundle文件。
设置资源归属
在Unity编辑器中,开发者需要为每个资源指定它所属的AssetBundle:
- 选中资源(例如一个预制体或纹理)。
- 在Inspector窗口底部,找到"AssetBundle"下拉菜单。
- 输入或选择一个AssetBundle名称(例如"ui"或"characters")。
注意:
- 名称区分大小写,例如"UI"和"ui"会被视为不同的AssetBundle。
- 未指定AssetBundle的资源不会被打包。
打包策略
根据项目需求,可以采用不同的打包策略:
- 按类型打包:将所有纹理打包到一个AssetBundle,所有预制体打包到另一个AssetBundle。
- 按功能打包:将某个UI界面的所有资源打包到一个AssetBundle。
- 按场景打包:将一个场景的所有资源打包到一个独立的AssetBundle。
打包选项(BuildAssetBundleOptions)
BuildAssetBundleOptions枚举提供了多种选项,用于优化打包过程:
- None:默认选项,不使用任何特殊设置。
- UncompressedAssetBundle:不压缩AssetBundle,文件较大但加载速度快,适合本地加载。
- ChunkBasedCompression:使用LZ4压缩,文件较小且加载速度较快,是推荐的默认选择。
- ForceRebuildAssetBundle:强制重新打包,即使资源没有变化,适合调试或确保一致性。
- DisableWriteTypeTree:不写入类型树信息,减小文件大小,但可能影响跨版本兼容性。
示例:
BuildPipeline.BuildAssetBundles("Assets/AssetBundles", BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.Android);
这将生成使用LZ4压缩的AssetBundle,适用于Android平台。
打包结果
打包完成后,Unity会在指定目录生成以下文件:
- 每个AssetBundle文件(例如"ui.bundle")。
- 一个Manifest文件(例如"AssetBundles"),记录所有AssetBundle的元数据。
AssetBundle 的加载
Unity支持多种加载AssetBundle的方式,开发者可以根据需求选择同步或异步加载,以及从文件、内存或网络加载。
同步加载
直接从文件系统中加载AssetBundle:
AssetBundle bundle = AssetBundle.LoadFromFile("Assets/AssetBundles/ui.bundle");
- 优点:简单直接,适合小型AssetBundle。
- 缺点:阻塞主线程,不适合大型文件。
异步加载
使用异步方法加载,避免阻塞主线程:
IEnumerator LoadBundleAsync()
{
AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync("Assets/AssetBundles/ui.bundle");
yield return request; // 等待加载完成
AssetBundle bundle = request.assetBundle;
}
- 优点:不会卡顿,适合大型AssetBundle。
- 使用场景:后台加载或需要显示加载进度的场景。
从内存加载
从字节数组中加载AssetBundle:
byte[] bundleData = File.ReadAllBytes("Assets/AssetBundles/ui.bundle");
AssetBundle bundle = AssetBundle.LoadFromMemory(bundleData);
异步版本:
IEnumerator LoadFromMemoryAsync()
{
byte[] bundleData = File.ReadAllBytes("Assets/AssetBundles/ui.bundle");
AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(bundleData);
yield return request;
AssetBundle bundle = request.assetBundle;
}
适用场景:从网络下载后直接加载字节数据。
从网络加载
通过URL下载并加载AssetBundle:
IEnumerator LoadFromWeb()
{
UnityWebRequest www = UnityWebRequestAssetBundle.GetAssetBundle("http://example.com/ui.bundle");
yield return www.SendWebRequest();
if (www.result == UnityWebRequest.Result.Success)
{
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(www);
}
else
{
Debug.LogError("加载失败: " + www.error);
}
}
适用场景:热更新或在线分发资源
加载AssetBundle中的资源
加载AssetBundle后,可以从中提取具体资源:
GameObject prefab = bundle.LoadAsset<GameObject>("MyPrefab");
异步加载资源:
IEnumerator LoadAssetAsync(AssetBundle bundle)
{
AssetBundleRequest request = bundle.LoadAssetAsync<GameObject>("MyPrefab");
yield return request;
GameObject prefab = request.asset as GameObject;
}
加载所有资源:
UnityEngine.Object[] assets = bundle.LoadAllAssets();
注意:资源名称需与打包时一致,且区分大小写。
依赖管理
AssetBundle之间可能存在依赖关系,例如一个预制体引用了另一个AssetBundle中的纹理。Unity通过Manifest文件管理这些依赖。
Manifest 文件
打包时,Unity会生成一个Manifest文件(例如"AssetBundles"),记录所有AssetBundle的元数据,包括依赖关系、哈希值等。
加载Manifest:
AssetBundle manifestBundle = AssetBundle.LoadFromFile("Assets/AssetBundles/AssetBundles");
AssetBundleManifest manifest = manifestBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
查看依赖关系
通过AssetBundleManifest获取某个AssetBundle的依赖:
string[] dependencies = manifest.GetAllDependencies("ui.bundle");
foreach (string dependency in dependencies)
{
Debug.Log("依赖: " + dependency);
}
加载依赖
在加载目标AssetBundle之前,必须先加载其依赖:
string[] dependencies = manifest.GetAllDependencies("ui.bundle");
foreach (string dependency in dependencies)
{
AssetBundle.LoadFromFile(Path.Combine("Assets/AssetBundles", dependency));
}
AssetBundle uiBundle = AssetBundle.LoadFromFile("Assets/AssetBundles/ui.bundle");
注意:如果依赖未加载,资源可能无法正确显示(例如纹理缺失)。
卸载AssetBundle
为了释放内存,开发者需要在不再使用AssetBundle时卸载它。
卸载方法
使用AssetBundle.Unload:
bundle.Unload(true);
- true:卸载AssetBundle及其中的所有资源。
- false:仅卸载AssetBundle,保留已加载的资源实例。
卸载注意事项
- Unload(true):
- 确保资源不再被场景引用,否则会导致引用丢失(例如场景中的对象变成“粉红色”)。
- Unload(false):
- 已加载的资源(如实例化的预制体)会继续存在,直到没有引用时被垃圾回收。
- 内存管理:
- 使用Unity Profiler检查内存占用,确保卸载生效。
示例:
GameObject prefab = bundle.LoadAsset<GameObject>("MyPrefab");
GameObject instance = Instantiate(prefab);
bundle.Unload(false); // 保留实例
Destroy(instance); // 销毁实例后,资源才会被完全释放
AssetBundle 与热更新
AssetBundle是实现热更新的核心工具,允许开发者更新游戏内容而无需重新发布整个游戏。
热更新流程
- 打包更新资源:将更新的资源打包成新的AssetBundle。
- 上传服务器:将AssetBundle上传到远程服务器。
- 客户端下载:客户端通过网络下载并加载这些AssetBundle。
示例:
IEnumerator UpdateBundle()
{
UnityWebRequest www = UnityWebRequestAssetBundle.GetAssetBundle("http://example.com/new_ui.bundle");
yield return www.SendWebRequest();
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(www);
GameObject prefab = bundle.LoadAsset<GameObject>("NewUIPrefab");
Instantiate(prefab);
}
版本管理
为AssetBundle分配版本号,客户端通过比较版本号决定是否需要更新。Manifest文件中的哈希值可用于版本控制:
string bundleHash = manifest.GetAssetBundleHash("ui.bundle").ToString();
客户端保存本地AssetBundle的哈希值,与服务器对比,若不同则下载更新。
Addressables 系统
Unity推出了Addressable Asset System,它是AssetBundle的高级封装,提供以下改进:
- 更简单的API。
- 自动依赖管理。
- 内置版本控制。 推荐在现代项目中使用Addressables替代手动管理AssetBundle。
AssetBundle 的优缺点
优点
- 动态加载:按需加载资源,优化性能。
- 热更新:支持动态更新游戏内容。
- 资源复用:可以在不同场景或项目中复用。
缺点
- 管理复杂:需要手动处理打包、加载和依赖关系。
- 依赖问题:加载顺序错误可能导致资源缺失。
- 版本兼容性:不同Unity版本生成的AssetBundle可能不兼容。