Unity 包体优化实战:从 387MB 压到 118MB,Resources、AssetBundle、Addressables 到底怎么选

🔑 关键词:Unity包体优化,Unity Addressables,Unity AssetBundle,Unity Resources,Unity ASTC压缩

📖 摘要:一个二手项目的包体优化实录:387MB 的 APK 怎么一步步压到 118MB,Resources、AssetBundle、Addressables 三种资源方案的取舍,以及贴图、音频、Mesh、Stripping 的具体参数设置和踩坑记录。

起因:一个 387MB 的包

图片

前年年底接了个二手项目,休闲游戏,玩法不复杂,美术资源也不算多。打包出来一看,Android APK 387MB,iOS 的 IPA 更夸张,快 500MB。Google Play 那边 base 有大小限制,打包完直接被渠道打回,然后就轮到我上场了。

我第一件事不是打开 Player Settings 调参数,是先把包解出来看。APK 本质就是个 zip,改后缀解压,看 assets/bin/Data 下面的文件大小分布。这一步真的很关键,很多人上来就调压缩、删包,结果折腾半天发现大头在自己没想到的地方。

结果发现 Resources 文件夹里 213 个文件,占了 290MB 左右。贴图、音频、Prefab、甚至几个 .cs 里的测试数据都在里面。这个数字是我一个文件夹一个文件夹数出来的,不是拍脑袋估的。

Resources 文件夹:新手最容易躺进去的坑

Resources 这个文件夹的设计意图是「少量、常用、必加载」的资源,比如全局配置、UI 图集。但现实是很多入门教程说「把资源放 Resources 里,Resources.Load 就能读」,于是所有人都往里丢,丢着丢着就成垃圾场了。

图片

它的三个问题我按严重程度排一下:

  1. 全部进包,而且被塞进 resources.assets 这个文件里,压缩率很差。已经压过的 png、ogg 再塞进去几乎不省空间
  2. 无法热更,改了图就得重新发版走审核
  3. 加载没有真正的异步 API。Resources.LoadAsync 是伪异步,主线程还是会被卡,加载一张 2048 的贴图实测能卡 100ms 以上

我当时直接把 Resources 里非必要的全挪走,只留了一个 Loading 界面的 Sprite 和一份全局配置 JSON。包体当场从 387MB 掉到 214MB。这个动作大概花了我两天,是所有优化里性价比最高的。

AssetBundle:能解决问题,但你得当爹

挪出来之后得有个替代方案,最原始的就是 AssetBundle。我用 Unity 2021.3 LTS 做的,那时候 Addressables 已经 1.19、1.20 了,但我还是先老老实实试了原生 AB。

图片

AB 的问题不是难用,是琐碎:

  • 依赖关系要自己维护。A 包的贴图被 B 包的 Prefab 引用,加载顺序错了就丢材质,场景里一片粉色
  • 冗余资源。同一张图集打进两个包,包体反而变大,得靠共享依赖包和 BuildAssetBundleOptions 里的选项去治
  • Manifest 要自己管。版本号、哈希、下载目录、断点续传,一套下来得写不少代码

好处是完全可控。我最后写了个 300 多行的加载器,分了公共包 shared 和模块包,压缩格式用 LZ4(ChunkBasedCompression)。这个压缩比 LZMA 加载快大概 2 到 3 倍,代价是包体多 5% 到 10%。LZMA 那种压缩率好看,但解压慢,移动端加载一个 30MB 的包要等好几秒,用户会以为游戏卡死了。

这一轮下来包体到了 152MB 左右。

Addressables:好用,但别当银弹

图片

后来因为要接热更,把一部分资源换成了 Addressables 1.21。它的好是真好用:

  • 自动处理依赖,Group 里勾一下 Bundle Mode 和 Bundle Naming 基本就能跑
  • 内置 Catalog、远程加载、版本校验,不用自己从零搭
  • 有个 Addressables Event Viewer 能看到每个 bundle 的引用计数,查泄漏挺方便

但它有自己的坑,说我实际遇到的:

  • Group 分太细,bundle 数量爆炸。我一开始按文件夹分 Group,结果 400 多个 bundle,光 Catalog 就 2MB,加载时要发几百次请求。后来改成按「更新频率」分组,压到 30 个左右
  • Play Mode Script 用 Use Asset Database 时跑得飞快,切到 Use Existing Build 才发现有些引用根本没打进包。这个坑我踩了两次
  • 远程 Catalog 的 hash 校验在弱网下容易失败,得自己包一层重试逻辑

图片

最后的方案是混着用:UI 图集、新手引导、基础音效走本地 AB(不热更),关卡资源、活动皮肤走 Addressables 远程。代码里做了一层包装,把两边 API 统一成 LoadAsync 和 Release,上层业务不用关心底下是哪个。

具体到参数,我实际改了这些

写了这么多方案,不如直接列一下动了哪些参数,都是改完有包体收益的:

  • 贴图方面,Android 全改 ASTC 6x6,iOS 用 ASTC 6x6 或 PVRTC 4bpp。原来一堆 RGBA32 未压缩的 1024 贴图,一张就 4MB,这是最大的一块。改完那批图省了大概 60 到 70MB
  • Mesh 方面,Read/Write Enabled 全关掉,既省内存也省一点包体。Mesh Compression 开 Medium,顶点少的模型别开 High,会有肉眼可见的形变
  • 音频方面,BGM 用 Streaming 或 Compressed In Memory + Vorbis,短音效用 Decompress On Load + ADPCM。原来一堆未压缩 WAV,纯粹是浪费
  • Player Settings 里,Managed Stripping Level 设 Medium,Strip Engine Code 勾上。注意这个会误删反射用到的类,要配合 link.xml 白名单
  • 没用的 Package 删掉,TextMeshPro、Cinemachine、Post Processing 其实都挺吃包体的。用不到的 Sprite Atlas 也一起删

最后稳定在 118MB,冷启动加载从 8 秒多降到 3.5 秒左右。

图片

我的观点,可能不太主流

如果团队 5 个人以下,项目也不打算频繁热更,我不建议一上来就上 Addressables。直接用 AssetBundle 加一个自己写的加载器,代码量没那么大,出问题也好查。

Addressables 的抽象层很厚,一个加载失败可能牵扯到 Group 配置、Catalog、远程服务器、Play Mode Script 四五个地方,排查成本对小团队来说不划算。我见过有团队为了修一个加载不出来的问题,折腾了一个礼拜。

Resources 也别一棍子打死。全局配置、Loading 图这种「一定用得上、基本不会改」的资源放里面挺合适。问题在于很多人把「方便」当成了「应该」。

还有一种情况我想说:包体不是越小越好。为了省 10MB 把贴图压成 ASTC 8x8,在低端机上看就是糊的,玩家会截图到评论区骂你。我一般会留 5 到 10MB 的余量给画质,这笔账比省下来的那点下载量值。

🏷️ 标签: