去年九月接了个外包,Unity 2021.3.18f1,Android 端休闲项目,包是别人打好的。对方原话是「能跑,就是包大了点」。我在 Redmi Note 11(天玑 810,6GB RAM,MIUI 13)上装了一下:APK 412MB,冷启动到主界面 7.8 秒,进战斗场景 Profiler 里 PSS 大概 380MB。
运营一开始说没事,反正用户都是 WiFi 下载。我说 Google Play 对 AAB 的初始下载大小有 200MB 的上限,你这 412MB 的 APK 拆出来也超了,上架会被卡,而且首包体验很差。这事后来就没人说话了。
下面按性价比排序,写我实际改了什么。不聊渲染管线,只聊包体和内存,每一步都有数字。
一、Resources 文件夹:先删它,别急着动贴图
Assets 下任何叫 Resources 的文件夹,里面的所有资源会被无条件打进包,跟你有没有 Resources.Load 过一点关系都没有。这规则很多人知道,但知道和真去扫描一遍是两回事——我见过的大部分项目,都有至少一个「祖传」Resources 文件夹。
这个项目有三个:一个装 2019 年做原型时留下的 87 张废弃贴图和 12 个旧版模型,一个装配置表 JSON,还有一个塞了一个 4MB 的中文字体。加起来用 AssetDatabase 扫出来是 163MB。
改法:配置表迁到 StreamingAssets(或者干脆改成 ScriptableObject 走 Addressables);废弃资源让他们先 git branch 备份再删,我不敢直接删别人的美术成果;字体换成子集化后的,从 4MB 降到 780KB。
还有一个容易忽略的隐性成本:Resources 文件夹会在启动时被索引一次。具体占多少时间我没法精确测,但这 163MB 挪走之后,冷启动从 7.8 秒掉到 6.4 秒,我能确定的只有这个结果。
二、纹理压缩格式:Android 上别偷懒用 Default
很多项目建完就忘了 Texture 的 Platform Override,全走 Default。一张 2048×2048 的 RGBA32 未压缩贴图,内存占用是 2048 × 2048 × 4 = 16,777,216 字节,也就是 16MB;如果带 mipmap 还要再乘 1.33 左右。
改成 ASTC 之后体积差多少?ASTC 是按块算的,每个块固定占 16 字节:
- 4×4 块 = 16 像素 → 1 字节/像素
- 5×5 块 = 25 像素 → 0.64 字节/像素
- 6×6 块 = 36 像素 → 0.444 字节/像素
- 8×8 块 = 64 像素 → 0.25 字节/像素
同一张 2048×2048 用 6×6,就是 4,194,304 × 0.444 ≈ 1.86MB,大概是原始的九分之一。
这里有个我自己的习惯,不一定是标准:UI 图(带文字的那种)走 4×4,因为 6×6 会让小字糊边;3D 场景的 Diffuse 走 6×6;法线贴图走 5×5,纯属个人经验,没有做过 A/B 对比测试,别当成行业规范。
操作路径是 Project Settings → Player → Android → Texture Compression 选 ASTC,然后每张图的 Inspector 里勾 Override for Android 单独设块大小。要批量改就写个 AssetPostprocessor,不然几百张图点到手断。
三、音频:Decompress On Load 的隐性账单
Unity 音频导入的默认设置是 Decompress On Load,这个默认值在移动端挺要命的。算一下:44.1kHz、立体声、16bit 的 PCM,每秒是 44100 × 2 × 2 = 176,400 字节。一首 60 秒的 BGM 加载到内存就是 176,400 × 60 ≈ 10.1MB——而它在包里的 Vorbis 压缩文件可能只有 1.5MB。
这个项目有 14 首 BGM 加 200 多个音效,全默认。进战斗场景内存直接拉到 380MB 的 PSS,我怀疑一大半是音频。
我按长度分类改了:
- BGM 和超过 10 秒的环境音,全部改 Streaming,内存里只留解码缓冲
- 1 秒以内的点击、按钮音,保留 Decompress On Load,反正小
- 中间那些战斗音效、技能音,改成 Compressed In Memory,压缩格式从 Vorbis 换 ADPCM(解码快,体积略大,短音效划算)
- Force To Mono 打开(这些音效本来就是单声道素材,之前被误设成双声道)
改完战斗场景 PSS 从 380MB 掉到 265MB。BGM 那部分省下来的最多。
四、说点可能被骂的
写到这里我得说个自己的观点:大部分 Unity 项目的包体和内存问题,根因不是「优化做得不够」,而是资产管线从第一天起就没有规则。
我见过的团队基本都是这样——美术直接往 Assets 里拖东西,程序随手 Resources.Load,没人管 PSD 源文件是不是也躺在工程里,没人管一张 UI 图是 4096 还是 512。等到包体炸了再找人优化,这时候你要做的是删东西,而这些东西是别人加班做出来的,删一个要开三次会。
真正省事的做法是项目第一天就把规矩定死:禁止用 Resources、统一走 Addressables;所有贴图必须有 Android Platform Override 走 ASTC;音频导入做成 Preset 锁住,美术改不了;资源命名和存放路径统一。这四条定下来,能省掉后面八成所谓的「优化工作」。
我知道这话听起来像废话,但你去看那些包体失控的项目,没有一个是把这四条做到的。
最终数字
- 包体:412MB → 88MB(Google Play Console 上显示的下载大小是 74MB,因为 AAB 会按 ABI 和屏幕密度拆分)
- 冷启动:7.8 秒 → 2.6 秒(同一台 Redmi Note 11,清后台冷启三次取中间值)
- 战斗场景 PSS:380MB → 205MB
还有一个当时没做但值得做的:把 armv7 去掉只留 arm64。Unity 默认两个 ABI 都打,如果你们的 minSdkVersion 和目标机型允许,砍掉 armv7 能省下一大块 so 体积。我当时没动是因为发行方要求兼容老机型,后来想想其实可以谈。
最后提醒一句,上面所有数字都是在特定工程、特定设备上测的,不要直接拿去做预算表。优化的第一步永远是先在自己的 Profiler 里跑一遍,别人的数字只能用来判断「这个方向大概有多大空间」。