> 技术文档 > Unity中AssetBundle使用整理(一)

Unity中AssetBundle使用整理(一)


一、AssetBundle 概述

AssetBundle 是 Unity 用于存储和加载游戏资源(如模型、纹理、预制体、音频等)的一种文件格式。它允许开发者将游戏资源打包成独立的文件,在运行时动态加载,从而实现资源的按需加载、更新以及减小初始安装包大小等功能。

用途:

减小初始安装包大小:将部分资源(如非首发场景资源、后期更新资源)以 AssetBundle 形式打包,不在初始安装包中包含,降低安装包体积。
资源动态更新:在游戏发布后,可以通过下载新的 AssetBundle 文件来更新游戏资源,无需发布新的游戏版本,实现快速迭代和内容更新。
资源复用:多个场景或项目可以共享相同的 AssetBundle 资源,提高资源利用效率。

二、AssetBundle 的创建

1. 资源标记

1.在 Unity 编辑器中,通过 Inspector 面板为需要打包进 AssetBundle 的资源设置 AssetBundle 名称和变体(Variant)。变体可用于区分不同平台、画质等级等版本的资源。
2.通过脚本方式设置。
代码:使用AssetImporter设置ab包名和变体名,可以设置没有提前配置的包名和变体名

public static class AssetBundleSetter{ [MenuItem(\"Tools/Set AssetBundle Name\")] static void SetAssetBundleName() { // 获取资源路径 string assetPath = \"Assets/Res/buffIcon_Speed.png\"; // 获取资源的 AssetImporter AssetImporter importer = AssetImporter.GetAtPath(assetPath); if (importer != null) { // 设置 AssetBundle 名称和变体 importer.assetBundleName = \"obj1\"; // 主名称 importer.assetBundleVariant = \"unity3d\"; // 变体后缀 // 保存设置 importer.SaveAndReimport(); Debug.Log($\"已设置: {assetPath} -> {importer.assetBundleName}.{importer.assetBundleVariant}\"); } else { Debug.LogError($\"资源不存在: {assetPath}\"); } AssetDatabase.Refresh(); // 刷新数据库 }}

注:变体(Variant)是什么?
变体是与其一起存储的 AssetBundle 的选项或子类,允许同一组资源的不同变体(如高清/标清材质、多语言文本)共享相同的加载逻辑,运行时动态选择合适版本,同一Bundle名称下,要么全部资源使用变体,要么全部不使用变体,否则会导致资源冲突或加载错误。

2.构建 AssetBundle

使用BuildPipeline.BuildAssetBundles方法来构建 AssetBundle。此方法需要指定输出路径、构建选项和目标平台。

BuildAssetBundleOptions的主要参数信息如下:

BuildAssetBundleOptions.None 此选项使用 LZMA 格式压缩,这是一个压缩的 LZMA 序列化数据文件流。LZMA 压缩要求在使用捆绑包之前对整个捆绑包进行解压缩。此压缩使文件大小尽可能小,但由于需要解压缩,加载时间略长。 BuildAssetBundleOptions.UncompressedAssetBundle 此选项采用使数据完全未压缩的方式构建捆绑包。未压缩的缺点是文件下载大小增大。但是,下载后的加载时间会快得多。未压缩的 AssetBundles 是 16 字节对齐的。 BuildAssetBundleOptions.ChunkBasedCompression

此选项使用称为 LZ4 的压缩方法,因此压缩文件大小比 LZMA 更大,但不像 LZMA 那样需要解压缩整个包才能使用捆绑包。LZ4 使用基于块的算法,允许按段或“块”加载 AssetBundle。解压缩单个块即可使用包含的资源,即使 AssetBundle 的其他块未解压缩也不影响。

 更多选项信息:BuildAssetBundleOptions - Unity 脚本 API

注:
​​LZMA 压缩(BuildAssetBundleOptions.None)​​
​​特点​​:文件体积最小,但使用资源前需解压整个包,适用于首次远程下载,首次解压完成后,将使用 LZ4 压缩技术在磁盘上重新压缩ab包,并存入本地缓存目录中,后续再加载,都会加载LZ4压缩的ab包。
​​使用场景​​:资源包高度关联且需整体加载(如完整场景、角色资源)。
​​LZ4 压缩(缓存与本地优化)​​
​​特点​​:按需加载资源无需全包解压,性能更快。
​​自动转换​​:通过 UnityWebRequestAssetBundle 下载的 LZMA 包会转为 LZ4 并本地缓存。
​​手动转换​​:其他方式下载的 LZMA 包需调用 AssetBundle.RecompressAssetBundleAsync 转 LZ4。
​​核心原则​​:​​远程用 LZMA(省带宽)​​,​​本地用 LZ4(提性能)​​。

BuildTarget的参数信息如下:

BuildTarget.StandaloneWindows 构建Windows平台 BuildTarget.iOS 构建ios平台 BuildTarget.Android 构建安卓平台

更多选项信息:BuildTarget - Unity 脚本 API

注:EditorUserBuildSettings.activeBuildTarget是获取当前的在BuildSetting中设置的平台,利用此API可以在打包时直接打此设置平台的ab包。
代码:

public class BuildAssetBundle : MonoBehaviour{ [MenuItem(\"Tools/Build AssetBundles\")] static void BuildAllAssetBundles() { string assetBundleDirectory = \"Assets/AssetBundles\"; if (!Directory.Exists(assetBundleDirectory)) { Directory.CreateDirectory(assetBundleDirectory); } BuildPipeline.BuildAssetBundles(assetBundleDirectory,  BuildAssetBundleOptions.None,  BuildTarget.StandaloneWindows); }}

注:以上Assetbundle操作的脚本需放在Editor文件夹,否则打包时会报错。 

准备资源并标记包名和变体名(可选)如下:

执行打包,打包完成后,除了定义的每个 AssetBundle 文件(如 scene.bundle)及其对应的 .manifest 文件外,​​还会生成两个全局管理文件,分别是​主清单文件(.bundle) 文件和一个 元数据文件(.manifest) 文件,(清单ab包以其所在的目录(构建 AssetBundle 的目录)命名)。

注:
​​主清单文件(.bundle)​​:
是 Unity 引擎能直接识别的二进制格式,包含 AssetBundleManifest 对象,存储所有 AssetBundle 的依赖关系哈希值
必须通过 AssetBundle.LoadFromFile 加载,用于运行时依赖解析。

​​元数据文件(.manifest)​​:
纯文本文件,记录所有 AssetBundle 的名称、依赖关系和哈希值,方便开发者手动查看内容。
​​不参与运行时逻辑​​,仅用于调试热更新时的版本对比

3.资源分组 

1.逻辑实体分组

逻辑实体分组是指根据资源所代表的项目功能部分将资源分配给 AssetBundle。这包括各种不同部分,比如用户界面、角色、环境以及在应用程序整个生命周期中可能经常出现的任何其他内容。非常适合于可下载内容 (DLC),因为通过这种方式将所有内容隔离后,可以对单个实体进行更改,而无需下载其他未更改的资源。

逻辑实体分组例如:
1.用户界面屏幕的所有纹理和布局数据。
2.一个/一组角色的所有模型和动画。
3.在多个关卡之间共享的景物的纹理和模型。

2.类型分组

相似类型的资源(例如音频轨道或语言本地化文件)分配到单个 AssetBundle。

3.并发内容分组

需要同时加载和使用的资源放到一个AssetBundle。可以将这些类型的捆绑包用于基于关卡的游戏(其中每个关卡包含完全独特的角色、纹理、音乐等)。最常见的用例是针对基于场景的AssetBundle包。在此分配策略中,每个场景AssetBundle包应包含大部分或全部场景依赖项。

注:
按更新频率分组​​
频繁更新资源稳定资源拆分为独立ab包,减少冗余下载。
​​按加载关联性分组​​
同时加载的资源(如模型+纹理+动画)合并到同一个ab包。
​​依赖管理​​
多ab包共享的依赖项,独立为共享ab包​​,避免重复加载。
​​按需分包​​
不可能同时加载的资源(如标清/高清资源)拆分到不同ab包。
​​拆分冗余内容​​
若 AssetBundle 中超过 50% 的资源不常同时使用,拆分成更小包。
​​合并小型高频包​​
多个小型(5~10 个资源以内)且常共用的ab包合并,减少加载次数。
​​变体处理多版本​​
同一资源的不同版本(如多语言、设备适配)使用 ​​AssetBundle 变体​​ 管理。

核心原则​​
​​减少冗余​​(依赖、版本、低频资源)
​​提升加载效率​​(按需加载、合并高频包)
​​简化维护​​(变体、分组策略

三、AssetBundle 的加载与卸载

准备资源

创建一个Cube、一个材质球、一张纹理贴图

场景中显示如下,使用这些资源来测试加载卸载的API

1.加载本地ab包

1.同步加载:

1.LoadFromFile:从磁盘上的文件同步加载 AssetBundle,是加载 AssetBundle 的最快方法, 在lzma压缩的情况下,数据将被解压缩到内存中,可以直接从磁盘读取未压缩和块压缩的ab包。
此API有三个重载,

参数的含义:

path 文件在磁盘上的路径。 crc 未压缩内容的可选 CRC-32 校验和。如果该值不为零,则在加载内容之前,会将内容与校验和进行比较,如果不匹配,则给出错误。 offset 可选的字节偏移量。此值指定从何处开始读取 AssetBundle。

代码:

 void ABLoadFromFile() { var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.dataPath, \"AssetBundles/obj1\")); if (myLoadedAssetBundle == null) { Debug.Log(\"加载AB包失败!\"); } else { Debug.Log(\"加载AB包成功!\"); } }

结果:

注:

1.crc参数如何使用
如果校验失败,抛出的错误会带有计算的crc32的值,如下:

将此数值在加载时传入,

 var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.dataPath, \"AssetBundles/obj1\"), 0x6789); myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.dataPath, \"AssetBundles/obj1\"), 0xa56b8935); if (myLoadedAssetBundle == null) { Debug.Log(\"加载AB包失败!\"); } else { Debug.Log(\"加载AB包成功!\"); }

结果:

2.offset参数如何使用
offset 是 ​​从文件起始位置跳过的字节数​​,用于指定 AssetBundle 数据在文件中的起始位置。它的核心作用是处理 ​​非标准文件结构​​,即当 AssetBundle 数据并不位于文件开头时,通过 offset 跳过无关数据,直接定位到 AssetBundle 的真实数据位置。使用场景如下:
1.文件包含自定义头信息
2.需要从复合文件中提取特定 AssetBundle。
3.处理加密或分段存储的 AssetBundle。
例:读取加密AB包
[加密头(64字节)][加密的AssetBundle数据]

string path = \"combined.bundle\";ulong offset = 1024; // Bundle1 的结束位置AssetBundle bundle = AssetBundle.LoadFromFile(path, 0, offset);

2.LoadFromMemory
此函数有两个重载

参数含义:

binary 包含 AssetBundle 数据的字节数组。 crc 未压缩内容的可选 CRC-32 校验和。如果该值不为零,则在加载内容之前,会将内容与校验和进行比较,如果不匹配,则给出错误。

代码:

 void ABLoadFromMemory() { // 读取本地文件到字节数组 string path = Path.Combine(Application.dataPath, \"AssetBundles/obj1\"); byte[] data = File.ReadAllBytes(path); // 加载 AssetBundle var myLoadedAssetBundle = AssetBundle.LoadFromMemory(data); if (myLoadedAssetBundle == null) {  Debug.Log(\"加载AB包失败!\"); } else {  Debug.Log(\"加载AB包成功!\"); } }

结果:

3.LoadFromStream:从托管 Stream 同步加载 AssetBundle,lzma 压缩数据被解压缩到内存中,而未压缩和块压缩的ab包则直接从 Stream 中读取。
此API有三个重载

参数含义:

stream 托管的 Stream 对象。Unity 调用此对象上的 Read()、Seek() 和 Length 属性来加载 AssetBundle 数据。 CRC 未压缩内容的可选 CRC-32 校验和。 managedReadBufferSize 可以使用它来覆盖 Unity 在加载数据时使用的读取缓冲区的大小。默认大小为 32KB。

代码:

 void LoadFromStream() { var fileStream = new FileStream(Path.Combine(Application.dataPath, \"AssetBundles/obj1\"), FileMode.Open, FileAccess.Read); var myLoadedAssetBundle = AssetBundle.LoadFromStream(fileStream); if (myLoadedAssetBundle == null) { Debug.Log(\"加载AB包失败!\"); } else { Debug.Log(\"加载AB包成功!\"); } }}

结果:

注:

1.managedReadBufferSize怎么设置
代码:

 // 设置缓冲区为 64KB(通常默认值即可) using (FileStream fs = new FileStream(Path.Combine(Application.dataPath, \"AssetBundles/obj1\"), FileMode.Open,FileAccess.Read)) { AssetBundle myLoadedAssetBundle = AssetBundle.LoadFromStream(fs, 0, 65536); // 64KB 缓冲区 if (myLoadedAssetBundle == null) { Debug.Log(\"加载AB包失败!\"); } else { Debug.Log(\"加载AB包成功!\"); } }

结果:

2.优化 AssetBundle 数据加载,Stream 对象强制要求​​如下
1.​​数据起始位置​​:AssetBundle 数据必须从Stream的 Position = 0 开始,Unity 在加载 AssetBundle 数据之前会自动将Stream位置重置为 0
2.​​基础能力​​:Unity 假定Stream中的读取位置不会被任何其他进程更改。这允许 Unity 进程从Stream中读取数据,而无需在每次读取之前调用 Seek()。Stream必须支持 CanRead(可读)和 CanSeek(可定位)。
​​3.线程安全​​:流的 Seek() 和 Read() 需支持多线程调用(Unity 可能从非主线程访问)。
4.越界处理​​:读取超过 AssetBundle 数据末尾时,必须返回实际读取字节数不抛出异常)。
数据末尾读取时返回 0 字节不报错

3.为了减少从本机代码到托管代码的调用次数,使用缓冲区大小为 managedReadBufferSize 的缓冲读取器从 Stream 中读取数据。

4.缓冲区(managedReadBufferSize)优化
​​1.性能影响​​:缓冲区大小直接影响加载效率(尤其移动端),需根据项目实测调整。
​​2.推荐值范围​​:测试 8KB/16KB/32KB/64KB/128KB,观察性能变化。
3.​​大缓冲区适用场景​​:
压缩的 AssetBundle、Bundle 含大型资源(如场景、高清纹理)、资源按顺序加载
4.​​小缓冲区适用场景​​:
未压缩的 AssetBundle、Bundle 含大量小资源(如图标、配置表)、资源随机加载(需频繁定位)。

2.异步加载:

1.LoadFromFileAsnyc:与LoadFromFile类似,但此函数是异步的。

代码:

 IEnumerator ABLoadFromFileAsync() { var bundleLoadRequest = AssetBundle.LoadFromFileAsync(Path.Combine(Application.dataPath, \"AssetBundles/obj1\")); yield return bundleLoadRequest; var myLoadedAssetBundle = bundleLoadRequest.assetBundle; if (myLoadedAssetBundle == null) { Debug.Log(\"加载AB包失败!\"); } else { Debug.Log(\"加载AB包成功!\"); } yield break; }

结果:

2.LoadFromMemoryAsnyc:与LoadFromMemory类似,但此函数是异步的。

代码:

 IEnumerator ABLoadFromMemoryAsync() { // 读取本地文件到字节数组 string path = Path.Combine(Application.dataPath, \"AssetBundles/obj1\"); byte[] data = File.ReadAllBytes(path); var bundleLoadRequest = AssetBundle.LoadFromMemoryAsync(data); yield return bundleLoadRequest; var myLoadedAssetBundle = bundleLoadRequest.assetBundle; if (myLoadedAssetBundle == null) { Debug.Log(\"加载AB包失败!\"); } else { Debug.Log(\"加载AB包成功!\"); } yield break; }

结果:

3.LoadFromStreamAsnyc:与LoadFromStream类似,但此函数是异步的。

代码:

 IEnumerator LoadFromStreamAsync() { // 创建可读可定位的流 using (FileStream fs = new FileStream(Path.Combine(Application.dataPath, \"AssetBundles/obj1\"), FileMode.Open, FileAccess.Read)) {  // 异步加载  AssetBundleCreateRequest bundleLoadRequest = AssetBundle.LoadFromStreamAsync(fs);  // 等待加载完成  yield return bundleLoadRequest;  var myLoadedAssetBundle = bundleLoadRequest.assetBundle;  if (myLoadedAssetBundle == null)  {  Debug.Log(\"加载AB包失败!\");  }  else  {  Debug.Log(\"加载AB包成功!\");  } } }

结果:

2.加载Web端ab包

1.基础

​获取 AssetBundle​​:
​​方法1​​:GetAssetBundle(string url, int version)
通过 url(本地路径或远程 URL)和版本号下载指定 AssetBundle。
​​方法2​​:UnityWebRequestAssetBundle + DownloadHandlerAssetBundle
使用专用处理器异步下载并解析 AssetBundle。
​​核心流程​​:​​URL 请求 → 下载 → 解析为 AssetBundle

2.使用

首先搭建一个用于测试的web服务器,步骤如下:
使用PhpStudy搭建Web测试服务器-CSDN博客​​​​​​
然后将ab包放到web服务器的文件夹中,

最后将ab包下载到本地并加载,代码如下:

 IEnumerator ABLoadUnityRequest() { string url = \"http://localhost:88/Test/obj1\"; var request = UnityEngine.Networking.UnityWebRequestAssetBundle.GetAssetBundle(url, 0); yield return request.SendWebRequest(); AssetBundle bundle = UnityEngine.Networking.DownloadHandlerAssetBundle.GetContent(request); if (bundle == null) { Debug.Log(\"加载AB包失败!\"); } else { Debug.Log(\"加载AB包成功!\"); } }

结果:

3.从AssetBundle加载资源

1.​​加载资源​​:​​

操作​​:从已加载的 AssetBundle 对象调用 LoadAsset(string assetName)。
T:资源类型(如 GameObject、Texture)。
assetName:资源在包内的名称(区分大小写)。

(1).LoadAsset:加载单个游戏对象

GameObject heroPrefab = myLoadedAssetBundle.LoadAsset(\"obj1\");if (heroPrefab == null){ Debug.Log(\"加载资产失败!\");}else{ Debug.Log(\"加载资产成功!\");}

结果:

(2).LoadAllAssets:加载所有资源
代码:

 // 加载全部资源 GameObject[] heroPrefab = myLoadedAssetBundle.LoadAllAssets(); if (heroPrefab == null) { Debug.Log(\"加载资产失败!\"); } else { Debug.Log(\"加载资产成功!\"); }

结果:

(3).LoadAssetAsync
代码:

 IEnumerator AssetLoadAssetAsync() { var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.dataPath, \"AssetBundles/obj1\")); if (myLoadedAssetBundle != null) { AssetBundleRequest request = myLoadedAssetBundle.LoadAssetAsync(\"obj1\"); yield return request; var loadedAsset = request.asset; if (loadedAsset == null) { Debug.Log(\"加载资产失败!\"); } else { Debug.Log(\"加载资产成功!\"); } } }

结果:

(4).LoadAllAssetsAsync
代码:

 IEnumerator AssetLoadAllAssetsAsync() { var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.dataPath, \"AssetBundles/obj1\")); if (myLoadedAssetBundle != null) { // 异步加载全部资源 AssetBundleRequest request = myLoadedAssetBundle.LoadAllAssetsAsync(); yield return request; var loadedAssets = request.allAssets; if (loadedAssets == null) {  Debug.Log(\"加载资产失败!\"); } else {  Debug.Log(\"加载资产成功!\"); } } }

结果:

2.使用资源​​:

加载后的对象与 Unity 常规资源一致,可直接操作。
如:使用Instantiate ( gameObjectFromAssetBundle) 实例化到场景。

代码:

 // 加载资源(如名为 \"obj1\" 的预制体) GameObject heroPrefab = myLoadedAssetBundle.LoadAsset(\"obj1\"); Instantiate(heroPrefab);

结果:

4.加载AssetBundle资源清单

1.加载清单

代码:

 void LoadManifest() { string manifestFilePath = Path.Combine(Application.dataPath, \"AssetBundles/AssetBundles\"); AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath); AssetBundleManifest manifest = assetBundle.LoadAsset(\"AssetBundleManifest\"); if (manifest == null) { Debug.Log(\"加载清单失败!\"); } else { Debug.Log(\"加载清单成功!\"); } }

结果:

2.通过清单加载依赖

将obj1包中的材质,命名为obj2

这样打包之后,清单文件里就有依赖了

代码:

 string manifestFilePath = Path.Combine(Application.dataPath, \"AssetBundles/AssetBundles\"); AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath); AssetBundleManifest manifest = assetBundle.LoadAsset(\"AssetBundleManifest\"); string[] dependencies = manifest.GetAllDependencies(\"obj1\"); //传递想要依赖项的捆绑包的名称。 foreach (string dependency in dependencies) { AssetBundle assetBundle2 = AssetBundle.LoadFromFile(Path.Combine(Application.dataPath, \"AssetBundles/\" + dependency)); if (assetBundle2 == null) { Debug.Log(\"加载资源失败!\"); } else { Debug.Log(\"加载清单成功!\"); } }

结果: 

5.管理已加载的 AssetBundle

1.核心问题

资源不会自动卸载​​:Unity 不会主动清理已加载的 AssetBundle 资源,需手动管理卸载时机。
​​错误卸载后果​​:内存泄漏(残留未卸载资源)、资源丢失(如纹理变粉红或消失)。

2.AssetBundle.Unload(bool) 参数区别

参数​​ ​​行为​​ ​​适用场景​​ ​​风险​​ Unload(true)​ 卸载 ​​AssetBundle 及其加载的所有资源​​(如材质、纹理)。 明确不再需要 AssetBundle 内容时(如切换关卡)。 若场景中有引用残留(如实例化对象),会导致资源丢失(如材质变粉)。 Unload(false)​ 仅卸载 ​​AssetBundle 头信息​​,保留已加载资源。 需保留资源但需释放 AssetBundle 句柄。 资源引用链断裂,需手动管理残留资源(易内存泄漏)。

​​​优先使用 Unload(true)​​
1.​​明确卸载时机​​:在关卡切换、加载界面时统一卸载。
​​2.引用计数​​:记录资源引用次数,仅当所有依赖对象不再使用时卸载。
​​避免 Unload(false) 的陷阱:
1.手动清理残留引用​

 //清理GameAsset内存,包括材质、贴图、模型、声音、已实例化的Prefab等, Resources.UnloadAsset(gameAssetName); //清理GameObject内存,包括未实例化的Prefab,Object.Destroy可以删除层级视图面板上的游戏对象, //在托管堆中将其置未null,在GC时只会回收托管堆中的对象,并没有释放实际的Native中的内存, Resources.UnloadUnusedAssets(prefabName);

2.非附加式加载场景​​:

// 销毁旧场景并清理资源,Unity会在每次切换场景时在底层进行Native无用内存回收SceneManager.LoadScene(\"NewScene\", LoadSceneMode.Single); 

四、AssetBundle 的依赖管理

AssetBundle的依赖管理是确保资源正确加载和避免冗余的关键机制。

​​​​1. 依赖关系的自动记录​​

当资源 A(如场景)引用了资源 B(如材质或贴图),且 A 和 B 分别被打包到不同的 AssetBundle 中,Unity 会自动记录这种依赖关系。
示例​​:
scene.bundle(场景)引用了 materials.bundle(材质)。
Unity 会记录 scene.bundle 依赖 materials.bundle。

2.自定义配置文件记录依赖关系

使用json格式定义配置文件

[ \"charactersbundle\": { \"md5\": \"e10adc3949ba59abbe56e057f20f883e\", \"dependencies\": [\"materials.bundle\"] }]

3.运行时加载依赖的流程

(1)加载自定义配置文件

public CustomManifest LoadCustomManifest(string jsonPath){ string json = File.ReadAllText(jsonPath); return JsonUtility.FromJson(json);}

(2)递归加载依赖项

string[] dependencies = LoadCustomManifest(\"characters.bundle\").charactersbundle.dependencies;foreach (string dep in dependencies) { AssetBundle.LoadFromFile(Path.Combine(path, dep));}

(3)加载目标ab包

AssetBundle sceneBundle = AssetBundle.LoadFromFile(\"scene.bundle\");GameObject scene = sceneBundle.LoadAsset(\"MainScene\");

4. 依赖管理的优化策略​

​​共享依赖包​​
将高频引用的资源(如通用材质、Shader)打包到独立 AssetBundle(如 common.bundle),供多个主 AssetBundle 共享。优点​​:避免重复加载,减少内存占用。
​​变体(Variants)​​:
同一资源的不同版本(如高清/标清贴图)使用变体后缀(如 textures.hd.bundle 和 textures.sd.bundle),通过代码动态切换。

// 当前激活的变体类型private static string activeVariant = \"hd\";// 单个Bundle加载private static IEnumerator LoadBundle(string bundleName){ string path = GetBundlePath(bundleName); using (UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(path)) { yield return request.SendWebRequest();  if (request.result == UnityWebRequest.Result.Success) { AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request); loadedBundles[bundleName] = bundle; } }}// 获取带变体的完整路径private static string GetBundlePath(string bundleName){ #if UNITY_EDITOR return $\"file://{Application.dataPath}/../AssetBundles/{bundleName}.{activeVariant}\"; #else return $\"{Application.streamingAssetsPath}/{bundleName}.{activeVariant}\"; #endif}

按需分块​​:
将大型资源(如场景)拆分为多个 AssetBundle,按需加载。

五、AssetBundle 的版本控制 

版本控制是确保资源热更新和客户端同步的关键机制。

1. MD5校验​

生成机制:Unity 在打包 AssetBundle 时,为每个包生成唯一的 MD5码,保存在自定义配置文件中。
​远程校验​​:将本地MD5码与服务器上的最新MD5码对比,判断是否需要更新。

2. 版本号管理​

​​版本标识​​:使用本地版本号与服务器版本号对比,确定是否更新
​增量更新:仅下载MD5码变化的 AssetBundle,减少流量消耗。

3.依赖链版本控制​

递归更新​​:若依赖的ab包(如 textures.bundle)更新,需同时更新此包依赖及其依赖链。

4.本地版本缓存​

校验更新​​:启动时加载本地缓存,与服务器版本对比。

5.错误处理与回退​

​​下载失败​​:
记录失败次数,达到阈值后回退到旧版本或提示用户重试。
​​版本冲突​​:
若更新后依赖关系不兼容(如父包未更新),回滚到上一稳定版本。

参考:

《Unity3D游戏开发第三版》

AssetBundle - Unity 手册

AssetBundle 简介 - 2019.4 - Unity Learn

资源、资源和 AssetBundle - Unity Learn

AssetBundle 简介 - 2019.4 - Unity Learn