先打个比方:一个搬家用的大箱子
把游戏装进玩家手机,就像寄出一个大箱子,里面大致分四类东西:
| 箱子里的东西 | 对应包体内容 |
|---|---|
| 说明书和工人:知道怎么干活 | 代码:Unity 引擎、游戏逻辑、第三方 SDK |
| 家具和物品:真正占地方的东西 | 资源:贴图、模型、音频、动画、场景、Shader、字体 |
| 物品清单和摆放说明 | 引擎数据和配置:场景列表、项目设置、资源索引 |
| 箱子外壳、快递单、封条 | 平台外壳:图标、清单文件、签名 |
🎯 对大多数 MMO 来说,资源通常占包体的绝大部分,代码和外壳只占一小部分。所以包体优化的重点几乎总是在资源上。
一、Android 包(APK / AAB)里有什么
APK 本质上是一个 zip 压缩包,改后缀解压就能看到:
xxx.apk ├─ AndroidManifest.xml ← 应用清单:包名、权限、入口 Activity ├─ classes.dex ← Java/Kotlin 代码:Unity 的 Android 外壳 + 各种 SDK ├─ resources.arsc / res/ ← Android 原生资源:图标、启动图、字符串 ├─ META-INF/ ← 签名信息 ├─ lib/ │ ├─ arm64-v8a/ │ │ ├─ libunity.so ← Unity 引擎本体(C++) │ │ ├─ libil2cpp.so ← 你的 C# 代码经 IL2CPP 转成 C++ 后编译的结果 │ │ ├─ libmain.so ← 启动入口 │ │ └─ libxxx.so ← 第三方 SDK 的原生库 │ └─ armeabi-v7a/ ← 32 位版本(如果勾选了,会再来一份) └─ assets/ ├─ bin/Data/ │ ├─ globalgamemanagers ← 项目设置、场景列表等全局数据 │ ├─ level0, level1 … ← Build Settings 中的场景 │ ├─ sharedassets0.assets … ← 场景引用到的资源 │ ├─ resources.assets ← Resources 文件夹里的资源 │ ├─ *.resS / *.resource ← 大块数据:贴图、网格数据、音频 │ ├─ unity default resources ← Unity 内置资源 │ └─ Managed/Metadata/global-metadata.dat ← IL2CPP 元数据 ├─ aa/ ← Addressables 打进包内的资源(如果使用) └─ (StreamingAssets 的内容) ← 原样拷贝,例如 AssetBundle、配置表、视频💡 如果用Mono而不是 IL2CPP,C# 代码会以
Managed/*.dll的形式出现,而不是libil2cpp.so。上架 Google Play 要求 64 位,实际项目基本都用 IL2CPP。
AAB 与 APK 的区别
上架 Google Play 用的是AAB:商店会根据玩家设备只下发对应的 CPU 架构、屏幕密度等部分,所以玩家实际下载的比 AAB 文件本身小。超出基础包大小限制的资源,可以用Play Asset Delivery拆成资源包另行下发(具体大小限制以 Google 最新政策为准)。
二、iOS 包(IPA)里有什么
xxx.ipa(也是 zip) └─ Payload/xxx.app/ ├─ xxx(主可执行文件) ← 启动入口 ├─ Frameworks/ │ ├─ UnityFramework.framework ← 引擎 + IL2CPP 编译出的游戏代码 │ └─ 第三方 SDK 的 framework ├─ Data/ ← 与 Android 的 bin/Data 基本相同 │ └─ Raw/ ← StreamingAssets ├─ Info.plist ← 应用清单 ├─ Assets.car ← 图标、启动图 ├─ embedded.mobileprovision ← 描述文件 └─ _CodeSignature/ ← 签名iOS 需要注意的两点:
| 要点 | 说明 |
|---|---|
| 下载大小 ≠ 安装大小 | App Store 会对可执行文件加密,加密后几乎无法再压缩,所以代码体积对下载大小的影响比你想象的大 |
| App Thinning | 商店按设备裁剪,可用 Xcode 导出时的App Thinning Size Report查看各机型真实大小 |
三、按类别看:哪些东西占地方
| 类别 | 内容 | 在 MMO 中的典型情况 |
|---|---|---|
| 贴图 | 角色、场景、UI、特效贴图,光照贴图 | 🔴 通常第一大户。压缩格式、最大尺寸、是否带 Mipmap 影响巨大 |
| 音频 | 背景音乐、音效、语音 | 🔴 语音和多语言配音很容易上百 MB |
| 网格与动画 | 模型、骨骼动画 | 🟠 动画片段多时不小 |
| Shader | 着色器及其变体 | 🟠 变体数量失控时可达几十 MB,还会拖慢加载 |
| 字体 | 中文字体 | 🟠 一个完整中文字体可能 10MB 以上,多个字体更多 |
| 场景 | 场景本身和其中烘焙的数据 | 🟠 光照贴图、地形数据、导航数据 |
| 视频 | 开场 CG、过场动画 | 🔴 一段就可能几十到上百 MB |
| 游戏代码 | IL2CPP 生成的原生库 + 元数据 | 🟡 几十 MB 常见,泛型多、第三方库多会更大 |
| 引擎本体 | libunity / UnityFramework | 🟡 相对固定,可通过剥离未用模块减少一点 |
| 第三方 SDK | 登录、支付、广告、统计、语音、推送 | 🟡 国内渠道 SDK 堆叠起来不容小觑 |
| 配置表 | 策划数据表 | 🟢 一般不大,但 JSON 等文本格式可能意外膨胀 |
四、资源是怎么"跑进"包里的
这一点最容易出问题:很多资源进包,并不是因为你用了它,而是因为它满足了某条规则。
| 进包途径 | 规则 | 常见坑 |
|---|---|---|
| Build Settings 里的场景 | 场景本身及其直接和间接引用的所有资源 | 场景里藏着一个禁用的物体,引用了一大堆资源,照样进包 |
| Resources 文件夹 | 里面的所有资源无条件进包,不管代码有没有用到 | 历史遗留的大量废弃资源;多个插件各自带 Resources 文件夹 |
| StreamingAssets | 原样拷贝,不做任何处理 | 测试用的 AssetBundle、旧版本资源没删 |
| 包内 AssetBundle / Addressables | 被设为本地组或打进包内的资源 | 本应远程下载的组被设成了本地 |
| Always Included Shaders / Preloaded Assets | 项目设置中强制包含 | 列表里塞了大量不需要的 Shader |
| 重复打包 | 同一资源被多个 AssetBundle 依赖,但没有单独成包 | 一张贴图在包里出现好几份 |
五、MMO 的常见做法:首包 + 下载
手游 MMO 的总资源往往有好几个 GB,不可能全部塞进安装包。通常分成三层:
① 安装包(首包):引擎 + 代码 + 登录、新手引导等最少量资源 ▼ 首次启动 ② 基础资源下载:主城、常用角色、通用 UI 等 ▼ 游玩过程中 ③ 按需下载:新地图、副本、高清资源包、语音包| 好处 | 说明 |
|---|---|
| 下载转化率高 | 首包越小,玩家越愿意点"安装",渠道和商店对大包也有限制 |
| 支持热更新 | 资源(以及通过 HybridCLR、Lua 等方案实现的部分逻辑)可以不经过商店审核更新 |
| 按需占用空间 | 不玩的模式不必下载 |
实现上通常使用AssetBundle 或 Addressables(远程组),配合版本清单和断点续传。
六、怎么分析自己的包
| 工具 | 用途 |
|---|---|
| Editor.log中的构建报告 | 构建完成后会列出各类别占比,以及按大小排序的资源列表 |
| Build Report Inspector(Unity 官方包) | 图形化查看构建报告 |
| Android Studio → Analyze APK | 查看 APK 中各文件的原始大小和下载大小 |
| 直接解压 APK / IPA | 最直观地看哪个文件最大 |
| Xcode App Thinning Size Report | 查看 iOS 各机型的下载和安装大小 |
| AssetBundle 分析工具(如 Unity 的 AssetBundle Browser、第三方 Asset Graph 类工具) | 查重复依赖、各包大小 |
七、常见瘦身手段速查
| 对象 | 手段 |
|---|---|
| 贴图 | 移动端用ASTC(老设备兼容可用 ETC2),按用途设置 Max Size;UI 贴图关闭 Mipmap;合理打图集 |
| 音频 | 背景音乐用 Vorbis 并调低质量;音效转单声道、降采样率;长音频用 Streaming 加载 |
| 网格 / 动画 | 开启 Mesh Compression;动画启用 Keyframe Reduction / Optimal 压缩;去掉不需要的缩放曲线 |
| Shader | 剥离不用的变体(Shader Variant Stripping),清理 Always Included 列表 |
| 字体 | 中文字体裁剪子集,只保留常用字;或使用 TextMeshPro 的字体图集 |
| 视频 | 降低码率、分辨率,或改为下载内容 |
| 代码 | Managed Stripping Level调高;精简第三方库;只打arm64 |
| 引擎 | Strip Engine Code;移除 Package Manager 中不用的模块 |
| 规则层面 | 清理 Resources 文件夹;清理 StreamingAssets;解决 AssetBundle 重复依赖 |
| 架构层面 | 首包最小化,其余资源走远程下载 |
一句话总结
📦 Unity 游戏包体 =代码(引擎 + IL2CPP 游戏代码 + SDK) +资源(贴图、音频、模型、动画、Shader、字体、场景、视频) +引擎数据(场景列表、设置、资源索引) +平台外壳(清单、图标、签名)。
🔍 其中资源通常是绝对大头,而资源进包往往是因为"被场景引用、放在 Resources、放在 StreamingAssets"这几条规则,而不是因为真的用到了。
🛠️ MMO 的标准做法是:首包只放必需品,其余资源远程按需下载,再配合贴图、音频、Shader 变体、字体等专项压缩。