首页 / 资讯中心 / 文章详情

Unity AssetBundle深度实践:构建策略、依赖管理与热更避坑指南

Unity AssetBundle深度实践:构建策略、依赖管理与热更避坑指南 ★ FEATURED ARTICLE
1. 为什么AssetBundle不是“打包完就完事”的黑盒工具很多人第一次接触Unity AssetBundle是在项目做到中后期、突然发现内存爆了、加载卡顿、热更无从下手的时候。这时候翻文档看到“把资源打成AB包运行时按需加载”心里一喜——这不就是解药结果兴冲冲建好BuildPipeline打完包用AssetBundle.LoadFromFile一加载UI prefab死活不显示贴图全粉红Animator Controller报空引用……最后发现问题根本不在代码里而在打包前那三分钟没想清楚的路径命名、依赖关系和加载生命周期。AssetBundle从来就不是Unity内置的“智能资源管理器”它本质上是一套手动控制的二进制资源容器系统——你得亲手告诉引擎哪些资源归哪一包、谁依赖谁、什么时候加载、用完怎么卸载、不同平台怎么适配、版本更新时旧包怎么平滑替换。它不替你做决策只提供执行通道你错一步它就沉默地给你一个粉红贴图或NullReferenceException。我带过的几个模拟项目X在初期都踩过同一个坑把所有Prefab一股脑打进一个叫all_assets.ab的大包里以为“省事”。结果上线后发现一个首页按钮点击要加载200MB资源首帧卡顿3秒用户直接划走。后来拆解发现这个大包里混着登录页的Shader、战斗系统的音效、甚至编辑器用的Gizmo图标——它们根本不会同时出现。AssetBundle的价值恰恰在于用显式声明替代隐式加载把“可能用到”变成“确定要用”把“全量加载”变成“精准投送”。关键词里虽然没写但实际落地中绕不开的三个硬核支点是构建策略BuildStrategy、依赖图谱Dependency Graph、生命周期管理Load/Unload Timing。这三个词不是概念名词而是每天要写进脚本、调试进日志、画在白板上的真实操作对象。比如BuildAssetBundleOptions.ChunkBasedCompression这个选项选错会导致Android设备解压失败但Unity官方文档只说“启用分块压缩”没告诉你它和LZ4算法在ARMv7架构下的兼容性陷阱再比如AssetBundle.Unload(true)和false的区别文档写的是“是否卸载已加载的Object”但没人告诉你如果之前用Instantiate创建过实例true会连实例一起干掉——而你可能正用它做技能特效预加载。所以这篇内容不讲“怎么调API”而是带你回到打包动作发生前的那一刻站在资源目录结构前手握鼠标思考“这一张UI图该进哪个包它的Atlas要不要单独打它的Font Texture能不能和别的UI共享一个包”——这才是AssetBundle真正难的地方也是它能带来性能跃迁的起点。2. 构建阶段路径、命名与依赖关系的三重校验AssetBundle构建不是“选中文件→右键→Build AB”就结束的机械操作。它是一次对整个资源拓扑结构的显式声明核心在于三件事包名定义规则、资源归属判定、依赖链路固化。这三步出错后续所有加载逻辑都会在运行时崩塌。2.1 包名不是随便起的字符串而是运行时寻址的坐标系Unity中每个资源通过AssetImporter.assetBundleName指定归属包这个名称最终成为AssetBundle.LoadFromFile(ui_login.ab)里的字符串。但新手常犯的错误是用中文命名如ui_登录界面.ab、含空格或特殊符号effect/fire smoke.ab、层级过深assets/prefabs/ui/login/login_panel.ab。这些在Editor里看似正常但导出到Android/iOS后文件系统不支持某些字符或路径长度限制导致LoadFromFile返回null。我们团队在某跨平台系统中定下铁律包名必须满足正则^[a-z0-9_](\.[a-z0-9_])*$且层级不超过三级。例如✅ui_login_panel✅effect_fire_vfx✅audio_sfx_click❌UI/LoginPanel斜杠非法❌login-panel短横线在部分Android ROM解析异常❌assets_prefabs_ui_login过长易与其他包混淆提示包名一旦发布上线就不能随意修改。因为热更时新包要覆盖旧包若改名旧客户端仍会去请求老名字导致资源缺失。我们用一个BundleNameMap.json配置文件集中管理所有包名每次构建前自动校验拼写一致性。2.2 资源归属判定一张贴图到底该进哪个包这是最易被忽视的决策点。表面看一张btn_start.png属于登录界面理应打进ui_login.ab但如果它同时被设置为PlayerPrefsIcon的背景图而PlayerPrefsIcon又在settings_menu.prefab里被引用而后者属于ui_settings.ab——这时这张图就必须进入所有直接或间接引用它的包的公共依赖包否则加载ui_settings.ab时会因找不到贴图而报错。我们采用“最小公共依赖包”原则扫描所有Prefab、ScriptableObject、Scene中对该资源的直接引用向上追溯引用链直到找到所有顶层包即不被其他AB包引用的包若这些顶层包超过1个则将该资源单独打入一个shared_textures.ab包并让所有顶层包声明对它的依赖。实操中我们写了一个Editor脚本自动分析依赖树。输入一个Texture2D它能输出btn_start.png 被以下资源引用 ├─ ui_login_panel.prefab → 归属包ui_login.ab ├─ settings_menu.prefab → 归属包ui_settings.ab └─ tutorial_hint.prefab → 归属包ui_tutorial.ab → 建议打入 shared_ui_textures.ab并在 ui_login.ab、ui_settings.ab、ui_tutorial.ab 的 manifest 中添加依赖项这个过程不能靠肉眼判断尤其当项目有上百个Prefab时。我们曾因漏判一张字体图导致iOS端所有Text组件显示方块排查三天才发现是font_chinese.ttf被错误打进ui_main.ab而ui_popup.ab加载时无法解析其依赖。2.3 依赖图谱不是理论模型而是必须导出验证的物理文件Unity在构建AssetBundle时会自动生成.manifest文件里面包含每个包的哈希值、依赖列表和内部资源清单。很多人忽略它直到热更失败才打开看——这时已经晚了。我们必须在每次构建后用脚本解析xxx.manifest并生成可视化依赖图。以ui_login.ab为例其manifest片段如下Dependencies: shared_textures.ab shared_shaders.ab audio_sfx_click.ab这意味着加载ui_login.ab前必须确保这三个包已加载到内存。而shared_shaders.ab又可能依赖core_rendering.ab形成链式依赖。我们强制要求所有AB包的依赖关系必须满足DAG有向无环图。若出现循环依赖A依赖BB又依赖AUnity构建会静默失败但不会报错——它只是把两个包合并成一个导致包体积失控。我们用Python脚本扫描所有manifest检测环路并高亮报错# 检测循环依赖的伪代码 def has_cycle(graph): visited set() rec_stack set() # 当前递归栈 for node in graph: if node not in visited: if dfs(node, visited, rec_stack, graph): return True return False某次构建中脚本报出ui_hud.ab ↔ effect_particle.ab循环依赖追查发现是HUD里的血条粒子特效Prefab错误地引用了ParticleEffectManager脚本而该脚本又反向引用了HUD的Canvas——这种设计本身就有问题必须重构。注意Unity 2019.4引入了BuildAssetBundleOptions.DeterministicAssetBundle它能保证相同输入生成相同哈希值是热更版本比对的基础。但我们发现若资源路径含中文或特殊字符即使开启此选项不同机器构建的哈希仍可能不一致。因此路径规范化是构建前的第一道安检。3. 加载阶段从LoadFromFile到Instantiate的七层关卡加载AssetBundle远不止调用一个API那么简单。从磁盘读取字节流到最终在屏幕上渲染出一个按钮中间横亘着至少七个必须主动管控的环节。跳过任何一层都可能在特定设备、特定内存状态下崩溃。3.1 第一层加载方式选择——File、Memory还是WebRequestUnity提供三种基础加载方式AssetBundle.LoadFromFile(path)最快直接内存映射适合本地已存在文件AssetBundle.LoadFromMemoryAsync(bytes)适合从加密包解密后加载UnityWebRequest.GetAssetBundle(url).SendWebRequest()用于远程下载。新手常误以为“WebRequest最通用”结果在弱网环境下SendWebRequest()超时后直接抛异常UI卡死。我们团队的实践是本地资源一律用LoadFromFile远程资源必须封装重试断点续传缓存校验。关键细节LoadFromFile在Android上要求路径为Application.persistentDataPath或Application.streamingAssetsPath。而streamingAssetsPath在Android上是只读zip包必须先用WWW或UnityWebRequest解压到persistentDataPath才能LoadFromFile。我们封装了一个ABLoader类自动处理路径适配public static AssetBundle Load(string bundleName) { string path Path.Combine(Application.persistentDataPath, bundleName); if (File.Exists(path)) { return AssetBundle.LoadFromFile(path); // 优先本地 } // fallback从streamingAssets解压 string streamingPath Path.Combine(Application.streamingAssetsPath, bundleName); if (Application.platform RuntimePlatform.Android) { // Android需解压zip ExtractFromStreamingAssets(streamingPath, path); } return AssetBundle.LoadFromFile(path); }3.2 第二层异步加载的时机陷阱——协程、线程与主线程的博弈LoadFromFile是同步的但LoadAssetAsyncT是异步的。很多人写// ❌ 危险在Update里每帧调用 void Update() { var op bundle.LoadAssetAsyncGameObject(LoginPanel); if (op.isDone) { Instantiate(op.asset); } }这会导致每帧新建一个AsyncOperation内存泄漏。正确做法是状态机驱动private enum LoadState { Idle, LoadingBundle, LoadingAsset, Done } private LoadState state LoadState.Idle; private AssetBundle bundle; private AsyncOperation loadOp; void Start() { state LoadState.LoadingBundle; loadOp AssetBundle.LoadFromFileAsync(path); } void Update() { switch (state) { case LoadState.LoadingBundle: if (loadOp.isDone) { bundle loadOp.assetBundle; state LoadState.LoadingAsset; loadOp bundle.LoadAssetAsyncGameObject(LoginPanel); } break; case LoadState.LoadingAsset: if (loadOp.isDone) { Instantiate(loadOp.asset); state LoadState.Done; } break; } }更进一步我们用Task封装避免Update轮询public async TaskGameObject LoadPrefabAsync(string bundleName, string assetName) { var bundle await LoadBundleAsync(bundleName); var handle bundle.LoadAssetAsyncGameObject(assetName); await handle.Task; // Unity 2021 支持Awaitable return handle.asset; }3.3 第三层资源引用管理——Instantiate之后谁来管内存这是最隐蔽的坑。Instantiate(prefab)后prefab的Mesh、Material、Texture等资源会被引用计数1。但如果你直接bundle.Unload(false)这些资源还在内存里若bundle.Unload(true)则连实例一起销毁——你刚Instantiate出来的按钮瞬间消失。我们的方案是所有AB包加载后只调用Unload(false)资源由ResourceManager统一管理引用计数。具体流程加载ui_login.ab获取LoginPanel.prefabInstantiate后遍历prefab所有Renderer对其sharedMaterial、sharedMesh调用Resources.IncreaseRefCounter()当确认该prefab不再需要如场景切换调用Resources.DecreaseRefCounter()最后bundle.Unload(false)释放AB包容器资源保留在内存供复用。这样既避免重复加载又防止意外卸载。我们用一个ResourceTracker单例记录每个资源的引用者public class ResourceTracker : MonoBehaviour { private static DictionaryObject, Liststring refMap new(); public static void Track(Object obj, string owner) { if (!refMap.ContainsKey(obj)) refMap[obj] new(); refMap[obj].Add(owner); } public static void Untrack(Object obj, string owner) { if (refMap.ContainsKey(obj)) { refMap[obj].Remove(owner); if (refMap[obj].Count 0) { Resources.UnloadUnusedAssets(); // 安全触发 } } } }3.4 第四层Shader变体裁剪——为什么你的AB包里有128个重复Shader默认情况下Unity会把一个Shader的所有变体Variants打进AB包哪怕你只用其中1个。一个URP Lit Shader可能生成200变体导致AB包体积暴涨。必须启用Shader Variant Collection。步骤创建ShaderVariantCollection资源在Inspector中点击Collect All Used Shaders自动收录当前Scene和Prefab中实际用到的变体在Player Settings → Other Settings → Configuration → Shader stripping中勾选Strip unused shader variants并指定该Collection。我们曾有个项目未启用此功能shaders.ab达45MB启用后降至3.2MB。但要注意Collection必须在构建AB前更新。若开发中新增了Shader用法如给新特效加了Emission忘记更新Collection运行时就会报Shader is not supported on this GPU——因为变体被裁掉了。3.5 第五层纹理压缩格式——Android上ASTC vs ETC2的生死抉择同一张Texture在不同平台必须用不同压缩格式。iOS用ASTCAndroid用ETC2或ETC1Windows用DXT。若在Android上打包ASTC设备不支持纹理变粉红。Unity的TextureImporter.textureCompression设置是全局的但AB构建时会覆盖它。我们必须为每个Texture设置platformSpecificsTextureImporter importer AssetImporter.GetAtPath(path) as TextureImporter; if (importer ! null) { var androidSettings new TextureImporterPlatformSettings(); androidSettings.name Android; androidSettings.overridden true; androidSettings.format TextureImporterFormat.ETC2_RGBA8; // Android androidSettings.maxTextureSize 2048; importer.SetPlatformTextureSettings(androidSettings); var iosSettings new TextureImporterPlatformSettings(); iosSettings.name iPhone; iosSettings.overridden true; iosSettings.format TextureImporterFormat.ASTC_RGBA_4x4; // iOS importer.SetPlatformTextureSettings(iosSettings); }提示ETC2在低端Android设备如MT6580上可能不支持Alpha通道此时需降级为ETC1Separate Alpha但会多一个贴图。我们用设备型号白名单动态选择。3.6 第六层字体图集Font Atlas的独立打包策略Text组件依赖的Font Texture即字体图集极易被忽略。若font_chinese.ttf和ui_login.ab一起打包当ui_login.ab被卸载时所有Text组件失效。必须将字体图集单独打入fonts.ab并确保它是最先加载、最后卸载的包。更关键的是Font Texture的大小必须足够容纳所有用到的字符。Unity默认生成256x256图集但中文项目常用字符超5000个必须设为4096x4096并启用Force Texture Compression。否则运行时动态扩展图集会导致GC尖峰和卡顿。我们在构建前运行检查脚本foreach (var font in Resources.FindObjectsOfTypeAllFont()) { if (font.material.mainTexture.width 2048) { Debug.LogError($Font {font.name} texture too small: {font.material.mainTexture.width}); } }3.7 第七层场景SceneAB包的特殊约束——不能直接LoadSceneScene打包成AB后不能用SceneManager.LoadScene(scene_name)加载必须用SceneManager.LoadSceneFromAssetBundleUnity 2018.4已废弃或更现代的SceneManager.LoadSceneAsync配合Addressables。但若坚持用原生AB正确流程是加载Scene AB包从包中LoadAssetSceneAsset(MyScene)调用SceneManager.LoadSceneAsync(sceneAsset, LoadSceneMode.Additive)确保Scene内所有资源包括灯光、PostProcessing都已打进AB或其依赖包。我们曾因Scene里引用了LightProbeGroup而该资源未打进AB导致加载后光照全黑。解决方案用SceneDependencyChecker扫描Scene中所有UnityEngine.Object类型引用强制加入构建列表。4. 版本管理与热更如何让新包不破坏旧逻辑热更新不是“替换一个文件”而是一次受控的资源契约升级。新AB包必须向后兼容旧代码旧AB包必须能被新代码安全卸载。这要求一套严格的版本协议。4.1 文件级版本号不是时间戳而是语义化哈希很多团队用build_20240520.ab命名结果发现同一天多次构建文件名冲突不同分支构建无法区分差异。我们采用内容哈希语义版本构建时计算AB包二进制MD5取前8位如a1b2c3d4结合功能版本如v1.2.0生成包名ui_login_v1.2.0_a1b2c3d4.ab同时生成version_manifest.json{ version: v1.2.0, build_time: 2024-05-20T14:30:00Z, bundles: [ { name: ui_login_v1.2.0_a1b2c3d4.ab, size: 2048576, hash: a1b2c3d4e5f67890..., dependencies: [shared_textures.ab] } ] }客户端启动时先下载version_manifest.json对比本地版本只下载差异包。4.2 资源标识符GUID漂移问题为什么旧包里的Prefab在新Unity版本里丢失脚本Unity资源的GUID是基于文件路径生成的。若你重构目录把Assets/Prefabs/UI/Login.prefab移到Assets/Scenes/UI/Login.prefabGUID就变了。旧AB包里记录的还是老GUID加载时脚本引用变Null。解决方案只有两个永远不移动资源文件推荐用文件夹分组但不改变路径使用ScriptedImporter重写GUID映射在Editor脚本中监听AssetPostprocessor.OnPreprocessAsset对移动的资源手动设置GUID。我们选前者并制定《资源路径规范》Prefabs/下只放PrefabScripts/下只放C#Textures/下按用途分UI/、Character/、Environment/严禁跨类混放。4.3 热更回滚机制当新包崩溃如何秒级切回旧版线上热更最怕“发版即崩溃”。我们实现双版本并行加载客户端始终保留两份AB包current/和next/启动时加载current/同时后台静默下载next/下载完成后校验next/的version_manifest.json和所有包哈希校验通过标记next为待激活下次启动时优先加载next/若崩溃启动参数带?rollbacktrue强制加载current/。关键代码public class ABVersionManager : MonoBehaviour { private const string CURRENT_DIR current; private const string NEXT_DIR next; public void ActivateNextVersion() { // 原子操作重命名文件夹 Directory.Move(Path.Combine(root, NEXT_DIR), Path.Combine(root, temp)); Directory.Move(Path.Combine(root, CURRENT_DIR), Path.Combine(root, NEXT_DIR)); Directory.Move(Path.Combine(root, temp), Path.Combine(root, CURRENT_DIR)); } }这样回滚只需毫秒级文件系统操作无需重新下载。4.4 运行时资源校验加载前的最后防线即使版本正确网络传输或存储损坏也可能导致AB包字节错误。我们在加载前加一层CRC32校验public static bool ValidateBundle(string path) { byte[] bytes File.ReadAllBytes(path); uint crc Crc32.Compute(bytes, 4, bytes.Length - 4); // 前4字节存CRC uint storedCrc BitConverter.ToUInt32(bytes, 0); return crc storedCrc; }构建时用Python脚本在AB包头部写入CRCwith open(bundle_path, rb) as f: data f.read() crc binascii.crc32(data) 0xffffffff f.seek(0) f.write(crc.to_bytes(4, little)) f.write(data)校验失败则触发降级加载或报错提示避免粉红贴图污染用户体验。5. 性能监控与问题定位从日志到火焰图的全链路追踪没有监控的AssetBundle系统就像没有仪表盘的飞机。我们建立了一套轻量级监控体系覆盖构建、加载、内存、卸载全周期。5.1 构建阶段监控自动生成资源分布热力图每次构建完成脚本自动生成build_report.html包含各AB包体积排行榜Top 10每个包内资源类型占比Texture/Mesh/Shader/Animation依赖深度统计如ui_login.ab依赖3层core.ab依赖0层重复资源检测同一Texture被多个包引用但未打入shared包。用Chart.js绘制饼图直观暴露问题。某次报告中audio_sfx.ab占总包体积65%追查发现所有音效都打了LoadInBackground但未压缩为OGG改为AudioImporter.format AudioTextureFormat.OGG_VORBIS后体积降至12%。5.2 运行时加载耗时追踪精确到毫秒的瓶颈定位在ABLoader.Load方法中注入埋点public async TaskT LoadAssetAsyncT(string bundleName, string assetName) where T : Object { var sw Stopwatch.StartNew(); var bundle await LoadBundleAsync(bundleName); sw.Stop(); Debug.Log($[AB] LoadBundle {bundleName}: {sw.ElapsedMilliseconds}ms); sw.Restart(); var handle bundle.LoadAssetAsyncT(assetName); await handle.Task; sw.Stop(); Debug.Log($[AB] LoadAsset {assetName} from {bundleName}: {sw.ElapsedMilliseconds}ms); return handle.asset; }结合Unity Profiler的CustomSampler可生成火焰图AB_LoadAssetAsync ├─ LoadBundleFromFile (120ms) │ ├─ File.Open (5ms) │ └─ MemoryMap (115ms) ← 此处慢检查SSD速度 └─ LoadAssetFromBundle (85ms) ├─ Deserialize (60ms) ← 可能资源过大 └─ Instantiate (25ms)我们曾发现Deserialize耗时突增定位到是某个Prefab嵌套了200子物体拆分为多个Prefab后加载时间从85ms降至12ms。5.3 内存泄漏检测识别“幽灵引用”Unity的Profiler.GetRuntimeMemorySizeLong只能看总量无法定位谁在持有资源。我们用Resources.FindObjectsOfTypeAll定期扫描public void CheckGhostReferences() { var allTextures Resources.FindObjectsOfTypeAllTexture2D(); foreach (var tex in allTextures) { if (tex null) continue; // 检查是否被AB包或实例引用 bool isReferenced false; // 检查所有已加载AB包 foreach (var bundle in loadedBundles) { if (bundle.Contains(tex)) isReferenced true; } // 检查所有活动GameObject foreach (var go in GameObject.FindObjectsOfTypeGameObject()) { if (go.GetComponentRenderer()?.sharedMaterial?.mainTexture tex) isReferenced true; } if (!isReferenced) { Debug.LogWarning($Texture {tex.name} not referenced! Potential leak.); } } }配合Debug.Log输出引用链快速定位未卸载的资源。5.4 卸载时机优化从“UnloadAll”到“精准卸载”早期我们用Resources.UnloadUnusedAssets()粗暴清理结果导致频繁GC。现在改为基于引用计数的懒卸载每个AB包加载时注册OnDestroy回调回调中检查包内所有资源的引用计数若全部为0则调用bundle.Unload(false)否则延迟10秒再检查防瞬时引用。我们还发现AssetBundle.Unload(false)后包的内存并未立即释放需等待下一次GC.Collect()。因此在关键节点如场景切换后主动调用System.GC.Collect(); Resources.UnloadUnusedAssets();但注意GC.Collect()有开销仅在内存紧张时调用我们用SystemInfo.systemMemorySize动态判断阈值。6. 实战避坑手册那些文档里不会写的23个致命细节以下是我在多个项目中踩过的坑按发生频率排序每个都附带复现步骤和修复方案。它们不是边缘案例而是高频雷区。6.1 坑1Android上LoadFromFile返回null但文件明明存在复现Application.persistentDataPath /ui_login.ab文件存在File.Exists返回true但AssetBundle.LoadFromFile返回null。根因Android 10 Scoped Storage限制persistentDataPath指向应用私有目录但Unity 2019.4以下版本的LoadFromFile底层调用fopen时权限不足。修复升级Unity至2020.3或改用LoadFromMemoryAsyncbyte[] bytes File.ReadAllBytes(path); return AssetBundle.LoadFromMemoryAsync(bytes);6.2 坑2iOS上AB包加载后TextMeshPro文字不显示复现TextMeshProUGUI组件绑定字体AB包加载后文字为空。根因TMP字体资源.ttf未打进AB包或字体图集未生成。修复确保字体资源Inspector中勾选Include in Build并在TMP Settings中点击Generate Atlas。6.3 坑3AssetBundle.Unload(true)后已Instantiate的Prefab变粉红复现加载ui_login.abInstantiate按钮然后bundle.Unload(true)按钮贴图消失。根因Unload(true)销毁Bundle内所有已加载Object包括贴图。修复永远用Unload(false)资源由ResourceManager管理。6.4 坑4Shader在AB包里但运行时报Shader is not supported复现Shader打进AB加载后Renderer材质变洋红色。根因Shader Variant未收集或目标GPU不支持该变体。修复创建ShaderVariantCollectionCollect All Used Shaders并在Player Settings中启用裁剪。6.5 坑5场景AB加载后灯光完全失效复现SceneAsset打进AB加载后场景一片漆黑。根因Light Probe Group、Reflection Probe未打进AB或未在Scene中设置Lighting Settings。修复将Probe资源拖入Scene的Lighting窗口确保Lighting Data Asset被打进AB。6.6 坑6BuildPipeline.BuildAssetBundles后AB包在Editor里能加载真机上失败复现Editor运行正常Android打包后AB加载失败。根因构建时未指定BuildTarget默认用Editor平台导致纹理格式错误。修复构建脚本中明确指定BuildPipeline.BuildAssetBundles(outputPath, options, BuildTarget.Android);6.7 坑7同一个Texture被多个AB包引用内存占用翻倍复现btn_start.png在ui_login.ab和ui_settings.ab中都存在内存中出现两份副本。根因未提取为shared包。修复用依赖分析脚本找出所有共享资源打入shared_textures.ab。6.8 坑8LoadAssetAsync后handle.asset为null但isDone为true复现异步加载完成但asset为空。根因资源名拼写错误或资源未打进AB包。修复加载前用bundle.GetAllAssetNames()打印所有可用资源名确认拼写。6.9 坑9AB包体积异常大解压后发现包含Editor资源复现Assets/Editor/下的脚本被打进AB。根因AssetImporter.assetBundleName被误设。修复Editor文件夹下所有资源assetBundleName必须为空。6.10 坑10热更后旧AB包未卸载内存持续增长复现多次热更Profiler显示Texture内存不断上升。根因未调用bundle.Unload(false)或引用计数未清零。修复实现ResourceTracker卸载前检查引用数。6.11 坑11Addressables和原生AB混用导致资源重复加载复现同时用Addressables.LoadAsset和AssetBundle.LoadFromFile同一贴图加载两次。根因Addressables底层也用AB但有自己的缓存。修复项目统一技术栈不混用。6.12 坑12BuildAssetBundleOptions.ChunkBasedCompression导致Android崩溃复现启用该选项Android设备闪退。根因Unity 2018.4-2019.2在ARMv7上LZ4解压有bug。修复禁用此选项或升级Unity。6.13 坑13AssetBundle.LoadFromFile在Windows上成功Mac上失败复现Mac路径含空格LoadFromFile解析失败。修复路径用Uri.EscapeUriString编码。6.14 坑14SceneAsset加载后SceneManager.GetActiveScene().name为空复现场景加载成功但GetActiveScene返回空。根因未用LoadSceneMode.Additive或Single。修复加载后调用SceneManager.SetActiveScene(scene)。6.15 坑15Resources.UnloadUnusedAssets后AB包内资源被误删复现调用后已加载的Prefab贴图丢失。根因UnloadUnusedAssets会清理所有未被引用的Object包括AB包内资源。修复只对Resources文件夹资源调用AB资源由AB系统管理。6.16 坑16AssetBundleManifest未更新导致依赖包找不到复现修改了依赖关系但AssetBundleManifest未重建。修复构建AB后必须用BuildPipeline.BuildAssetBundles重新生成Manifest。6.17 坑17Texture2D的Read/Write Enabled关闭导致AB加载后无法修改像素复现运行时调用texture.SetPixels报错。根因AB中的Texture默认Read/Write Enabled为false。修复在Texture Importer中勾选或用Texture2D.CreateExternalTexture。6.18 坑18AnimationClip打进AB后播放速度异常复现动画播放快3倍。根因AnimationClip.frameRate未设置Unity用默认60。修复在Inspector中设置正确帧率或代码中clip.frameRate 30。6.19 坑19AudioClip在AB中但播放无声复现AudioSource.Play()
阅读完成 · 觉得有帮助?
咨询建站