一、为什么打包策略是重中之重打包策略 内存/性能/热更的源头设计 打包时的分组决定了 ├── 运行时内存冗余多不多 ├── 加载性能IO次数、粒度 ├── 热更下载量改一点要下多少 └── 依赖复杂度好不好维护 ⚠️ 打包策略错了后期运行时怎么优化都是补救二、核心矛盾粒度Granularity打包策略的本质是回答一个问题多少资源打成一个包太细 太粗 ┌──────────────┐ ┌──────────────┐ │ 1资源1包 │ │ 全部1个大包 │ └──────────────┘ └──────────────┘ 问题 问题 - 包数量爆炸 - 加载1个小图要读整包 - 索引开销大 - 内存浪费 - IO次数多 - 热更改1处下全部 - 加载频繁 - 无法按需加载 ↓ 平衡点 ↓ ┌──────────────────────┐ │ 按逻辑合理分组 │ └──────────────────────┘粒度对比表粒度包数量单包大小加载灵活性内存热更量适用极细(1对1)极多极小高索引开销大精准❌不推荐合理分组适中适中好优适中✅推荐极粗(全打包)少巨大差浪费巨大❌不推荐三、四大分组维度组合使用维度1按逻辑模块分组按游戏功能模块划分最直观 ab_ui_login.bundle → 登录界面相关 ab_ui_main.bundle → 主界面 ab_ui_battle.bundle → 战斗UI ab_character_hero.bundle → 主角资源 ab_character_enemy.bundle→ 敌人资源 ab_scene_forest.bundle → 森林场景 ab_audio_bgm.bundle → 背景音乐 ab_effect_skill.bundle → 技能特效✅优点结构清晰按模块加载/卸载好维护维度2按使用频率分组冷热分离根据资源多久用一次划分 【常驻包】(Resident) - 全程需要加载后不卸载 ab_common_ui.bundle → 通用按钮、图标 ab_common_shader.bundle→ 公共shader ab_font.bundle → 字体 【高频包】(Hot) - 经常用缓存优先级高 ab_battle_common.bundle→ 战斗通用资源 【低频包】(Cold) - 偶尔用用完即卸 ab_boss_special.bundle → 特殊Boss(打完就卸) ab_cutscene.bundle → 过场动画内存策略常驻包 → 一直在内存不卸载 低频包 → 用完立即 Unload(true) 释放✅优点内存精准管理冷资源及时释放维度3按更新频率分组热更优化关键根据资源多久会改一次划分直接影响热更下载量 【稳定包】几乎不改 ab_font.bundle → 字体(基本不动) ab_common_shader.bundle→ shader(稳定) 【易变包】经常调整 ab_config.bundle → 数值配置(频繁改) ab_ui_activity.bundle → 活动界面(每次活动改)为什么重要看热更下载量❌ 错误稳定资源和易变资源打一个包 ab_all.bundle (100MB含字体配置) ↓ 只改了1个配置数值 ↓ 客户端要重新下载整个 100MB ✅ 正确分离 ab_font.bundle (50MB不变) ← 不用下 ab_config.bundle (1MB改了) ← 只下这个 ↓ 客户端只下 1MB核心原则易变的和稳定的分开打把改动影响范围缩到最小。维度4按资源类型分组相同类型资源打一起利于统一管理和压缩 ab_textures.bundle → 贴图集合 ab_meshes.bundle → 网格集合 ab_audios.bundle → 音频集合 ab_prefabs.bundle → 预制体集合⚠️ 通常不单独用类型分组而是结合模块如UI模块的贴图四、依赖管理 —— 消除资源冗余最重要问题隐式依赖导致冗余场景ab_hero 和 ab_enemy 都用了 common_shader 不做处理 ┌─────────────────┐ ┌─────────────────┐ │ ab_hero.bundle │ │ ab_enemy.bundle │ │ ├─hero.prefab │ │ ├─enemy.prefab │ │ └─common.shader│ │ └─common.shader│ ← 各存一份 └─────────────────┘ └─────────────────┘ 内存里 common.shader 有【2份】 冗余 10个角色都引用 → 冗余10份 → 内存爆炸解决公共资源独立打包把共享资源单独打成共享包 ┌──────────────────┐ │ ab_shared.bundle │ │ └─common.shader │ ← 唯一一份 └──────────────────┘ ↑ 依赖 ↑ 依赖 ┌───────────┐ ┌────────────┐ │ ab_hero │ │ ab_enemy │ │ (依赖shared)│ │ (依赖shared)│ └───────────┘ └────────────┘ 内存里 common.shader 只有【1份】✅如何识别需要分离的公共资源1. 被多个AB引用的资源 → 提取到共享包 常见shader、公共贴图、字体、通用材质 2. Unity打包时会生成依赖清单(Manifest) 查看 .manifest 文件看哪些资源被重复引用依赖加载顺序代码// 必须先加载依赖包再加载主包AssetBundleManifestmanifestLoadManifest();// 获取某个AB的所有依赖string[]depsmanifest.GetAllDependencies(ab_hero);// 先加载所有依赖foreach(stringdepindeps){LoadBundle(dep);// 如 ab_shared}// 再加载主包LoadBundle(ab_hero);Addressables 自动处理依赖不用手动管理顺序。五、压缩格式策略// 三种压缩方式BuildAssetBundleOptions.None// LZMA(默认)BuildAssetBundleOptions.ChunkBasedCompression// LZ4BuildAssetBundleOptions.UncompressedAssetBundle// 不压缩三种格式对比格式包体大小加载速度内存占用特点LZMA最小⭐慢(全量解压)高流式压缩包最小LZ4中等快⭐(块解压)低⭐按需解压运行时优不压缩最大最快高空间换时间最佳实践策略分场景选择 【首包/下载资源】用 LZMA → 包体最小省下载流量和存储 【本地运行】用 LZ4 → 加载快内存低块解压不用全量 【最优方案】下载LZMA 本地转LZ4 1. 服务器存LZMA包(省带宽) 2. 客户端下载后 3. 重新压缩成LZ4缓存到本地(省内存快加载)// 下载后转码为LZ4缓存AssetBundle.RecompressAssetBundleAsync(srcPath,// LZMA源文件dstPath,// LZ4目标BuildCompression.LZ4Runtime);六、命名与版本管理策略1. 命名规范推荐模块_类型_名称.bundle (小写下划线) ab_ui_login.bundle ab_character_hero.bundle ab_scene_forest_01.bundle 避免 ❌ 中文名(部分平台有问题) ❌ 大写(某些平台大小写敏感) ❌ 特殊字符2. 用哈希名做版本管理热更友好方案A文件名带哈希 hero_a3f5c8.bundle ← 内容变了哈希就变 优点天然版本区分CDN缓存友好 方案B目录分版本 1.0.0/hero.bundle 1.0.1/hero.bundle// 打包时用AppendHashBuildAssetBundleOptions.AppendHashToAssetBundleName// 生成hero_a3f5c8def.bundle七、场景资源打包策略场景比较特殊单独说1. 场景单独打包 ab_scene_battle.bundle → 只含 battle.unity 2. 场景依赖的资源分离 场景引用的模型/贴图 → 打到资源包 避免场景包过大 3. 加载场景AB后用SceneManager加载// 加载场景ABAssetBundleabAssetBundle.LoadFromFile(ab_scene_battle);// 场景AB加载后场景名可用于SceneManagerSceneManager.LoadScene(battle);八、常见打包问题与解决问题原因解决资源冗余(内存多份)公共资源没分离提取共享包热更下载量巨大稳定/易变资源混打按更新频率分离加载卡顿/IO频繁粒度太细包太多合理合并单包过大占内存粒度太粗拆分模块依赖丢失(粉红)没加载依赖包先加载Manifest的依赖包体过大压缩格式不对下载用LZMA平台加载失败中文/大写命名规范命名九、打包自动化工程化// 编辑器脚本自动打包usingUnityEditor;publicclassABBuilder{[MenuItem(Build/BuildAssetBundles)]staticvoidBuild(){stringoutputPathAssetBundles/GetPlatform();BuildPipeline.BuildAssetBundles(outputPath,BuildAssetBundleOptions.ChunkBasedCompression|// LZ4BuildAssetBundleOptions.AppendHashToAssetBundleName,EditorUserBuildSettings.activeBuildTarget);}}分组配置驱动推荐用配置表定义打包规则避免手动设AssetBundleName 打包配置.json: { groups: [ { bundleName: ab_ui_common, assets: [Assets/UI/Common/*], compression: LZ4 }, { bundleName: ab_shared, assets: [Assets/Shaders/*, Assets/Fonts/*] } ] } 打包脚本读配置 → 自动设置 → 打包十、Addressables 的分组策略现代方案Addressables 用Group管理可视化配置Addressables Groups 配置 ┌─────────────────────────────┐ │ Group: Common (常驻) │ │ ├─ Bundle Mode: Pack Together│ │ └─ Compression: LZ4 │ ├─────────────────────────────┤ │ Group: Battle (按需) │ │ ├─ Bundle Mode: Pack Together│ │ └─ 用完可Release │ ├─────────────────────────────┤ │ Group: Remote (热更) │ │ └─ 走远程CDN │ └─────────────────────────────┘Addressables 分组模式Bundle Mode ├── Pack Together组内所有资源打1个包 ├── Pack Separately每个资源单独打包 └── Pack Together By Label按标签分包 优势 - 可视化分组不用改meta - 自动依赖处理 - 内置热更支持(Remote/Local)十一、打包策略 Checklist【粒度设计】 ☐ 不过细(避免包爆炸)也不过粗(避免大包) ☐ 单包大小合理(经验值几百KB~几MB) 【分组维度】 ☐ 按逻辑模块分组(UI/角色/场景) ☐ 冷热分离(常驻 vs 临时) ☐ 按更新频率分离(稳定 vs 易变) ← 热更关键 ☐ 公共资源提取共享包 ← 冗余关键 【依赖管理】 ☐ 识别多引用资源提取共享包 ☐ 加载时先加载依赖 ☐ 定期检查Manifest的依赖关系 【压缩】 ☐ 下载用LZMA(省流量) ☐ 运行用LZ4(省内存快加载) ☐ 考虑下载后转码 【命名】 ☐ 小写下划线规范 ☐ 无中文无特殊字符 ☐ 考虑哈希名做版本 【工程化】 ☐ 自动化打包脚本 ☐ 配置驱动分组 ☐ 新项目考虑Addressables核心要点1. 打包策略是源头决定内存/性能/热更上限 2. 核心矛盾是粒度不过细不过粗合理分组 3. 四大分组维度组合用 - 逻辑模块(清晰) - 使用频率(内存管理) - 更新频率(热更下载量) ★ - 资源类型(辅助) 4. 公共资源必须分离 → 消除冗余(内存最大杀手)★ 5. 依赖要正确处理先加载依赖包 6. 压缩下载LZMA 运行LZ4 7. 易变和稳定分开打 → 热更下载量最小 ★ 8. 新项目直接用Addressables可视化分组自动依赖