从做微信小游戏第一天起,4M就像一个紧箍咒。CocosCreator项目还没加多少玩法,随手扔进去几张UI大图、两段音频,构建完一看包体,好家伙,5.3M。上传微信开发者工具,直接弹红字“主包大小超过4M”,那一刻的心情,相信做过小游戏的人都懂。这篇文章不绕弯子,就把我在CocosCreator 2.x和3.x上对付这个4M限制的整套方案聊透,从体积定位、代码裁剪、资源压缩到远程加载和分包策略,全是实际项目里反复验证过的东西。
1. 先搞清楚4M限制到底卡在哪
1.1 限制的真相:不是整个游戏只能4M
很多第一次接触小游戏的开发者会误以为整个游戏资源加起来不能超过4M,其实不是。微信小游戏平台限制的是“主包”体积,也就是游戏首次启动时必须下载的那部分代码和资源。超过这个体积,工具会拒绝上传,线上也发不了版本。
这里有个关键认知:限制的只是启动时必须在那里的东西。如果你能把资源拆出去,让游戏跑起来之后再按需下载,那整个游戏大几十M甚至上百M都没问题。CocosCreator里的Asset Bundle机制,本质上就是为这个需求设计的。
另外要注意,微信的4M限制在不同时间点有过调整,某些平台类目、某些灰度环境可能放宽到8M甚至更多,但作为开发者,依然要按4M这个最严格标准来做,这样最稳妥。
1.2 CocosCreator 2.x和3.x的包体差异
我刚从2.4.x升级到3.8.x的时候,第一反应是“这包体怎么又胖了”。3.x的引擎底子更像一个通用渲染器,基础模块本身就比2.x大一些,加上3.x默认使用更完整的资源管线,构建产物里会多一些基础配置。
2.x项目里,构建完的web-mobile目录结构大概是:
src/里放着project.js、settings.js等引擎和项目脚本assets/里放着纹理、音频、预制体等资源res/里是调试模式下的原始资源(release构建一般不存在)
3.x项目构建之后,代码和资源的分区更清晰,但体积大头依然是assets和src这两块。不管哪个版本,压缩包体的基本思路都一样:把大纹理压小、把不用的代码删掉、把重资源挪出去。
1.3 包体超限时的典型表现
超限不一定是构建时报错,很多时候构建一切正常,是你上传微信开发者工具时才看到提示。有些项目在小游戏工具里能预览,但点击“上传”时提示包体积超出限制,或者打开“真机调试”时下载很慢,甚至黑屏,这些其实都跟包体过大有关。
我遇到过一个比较隐蔽的情况:微信开发者工具里显示主包只有3.9M,但一上传依然报超限。查了半天,发现是构建时勾选了“MD5 Cache”,文件名被加上了hash,但工具统计时把“分包内引用了主包资源”也算进了主包体积。这种事不实际踩坑,光看文档是想不到的。
2. 精准定位体积消耗点,别凭感觉瞎砍
2.1 用构建产物反推体积分布
拿到一个超标的包,先别急着删资源。我会直接从构建目录下手,看哪个文件夹最占地方。命令行跑一条最简单的命令:
du -sh *在build/web-mobile或者build/wechatgame目录下执行,瞬间就能把体积大户列出来。正常情况下你会看到assets和src占了90%以上。
下一步是往资产目录里钻,找找是哪些子目录大:
du -sh assets/*这时候你会发现,通常问题就出在那么几个文件夹上——某个UI界面的大图、某段没压缩的音频、或者一堆误导入的psd源文件。
2.2 微信开发者工具的依赖分析
微信开发者工具的调试器里有一个“代码依赖分析”面板,可以看到每个JS模块被哪些文件引用、各自体积是多少。这个工具对排查哪些代码白白进了包体特别有用。
实际使用中我发现,CocosCreator 3.x 构建后生成的cocos.js和main.js有时候会包含一些根本没用到的引擎模块。就是你在Cocos编辑器里创建项目时默认勾选了一堆模块,后来代码里根本没用上,但构建时它们依然会被打包进去。这种模块级的浪费,依赖分析面板一眼就能看出来。
2.3 Bundle细分定位法
如果项目已经做了Bundle拆资源,但主包还是超了,那就在每个Bundle里做个“资源占地”埋点。做法很简单,在场景加载后,用资源管理器遍历Bundle内的资源,统计每类资源的总大小。
我在3.x项目里写过这么一段临时统计代码:
// 遍历bundle里已加载资源的体积 bundle.getDependencies().forEach(uuid => { const asset = bundle.get(uuid); if (asset && asset._nativeAsset) { const size = asset._nativeAsset.byteLength || 0; totalMap[asset.constructor.name] = (totalMap[asset.constructor.name] || 0) + size; } }); console.table(totalMap);打印出来,哪个类别的资源占地最多一目了然。比起猜测,这种数据驱动的方式要可靠得多。做完这步,你才知道该往哪个方向用力。
3. 代码层的瘦身实战
3.1 裁剪引擎模块:最容易被忽略的第一步
CocosCreator从2.x开始就支持模块裁剪,但很多人压根没用过。在“项目设置” -> “功能裁剪”(2.x叫“模块设置”)里,能看到一长串引擎模块列表,默认全部打勾。
我在一个小游戏项目里做过一次对比:不做任何业务代码删除,只把3D物理、Spine、DragonBones、VideoPlayer这些完全用不到的模块取消勾选,光这一步就减少了大约1.2M的代码体积。因为引擎模块最终是要编译进包里的,每个模块都有对应的源码和资源,关了就是实实在在的瘦身。
不过有一点要小心:模块之间存在依赖关系。比如你用了UI,那UI、Widget、Layout这些都得开;用了碰撞检测,Physics相关模块也要保留。建议裁剪后立马构建跑一遍,看看有没有报“xxx is not defined”这种运行时报错。
3.2 第三方库的按需引入和去重
现在做小游戏基本都会引第三方库,最常见的是protobufjs、socket.io、各种SDK。这里踩过的坑是:有些库在package.json里写了一大堆依赖,但实际你只用其中一个接口,打包时它会把所有依赖一起带进来。
我的做法是:
- 能用npm模块的,不手动拷贝源码。npm模块在构建时才能被正确tree-shaking掉无用代码。
- 引入SDK时,只引入你用得到的API对应的文件。比如protobufjs,如果用静态生成代码,就完全不引解析器,体积差距非常明显。
- 检查重复代码。用了两个插件,结果都内置了一份相同的工具函数库,这种情况太常见了。
一个实际的例子:项目里同时引了一个老版本SDK和一套新版本工具库,两者都带了自己的md5实现,差点白送几KB,最后删掉了多余的。
3.3 代码压缩混淆别省
CocosCreator构建时,“压缩”和“混淆”选项直接影响代码体积。微信小游戏上传之后,平台还会对JS再做一层压缩,但那是后话,构建阶段该做的还是要做。
在构建面板里,2.x要确认为“release”模式,3.x要开启“压缩”选项。3.x里还多了一个“Source Maps”选项,这个默认应该关掉,它会把原始映射打进包里,体积能涨不少。我见过一个项目,构建完的包里面main.js.map就占了几百KB,关掉立马瘦身。
如果项目用了WebAssembly或者特殊插件,要注意有些库在压缩后不能正常运行,这时候需要对特定文件做“排除压缩”处理,Cocos的构建配置里可以配置,否则精确踩坑。
3.4 代码分包:把启动时代码量降下来
如果主包代码本身就很大,比如网络框架、UI框架、战斗逻辑都在主包,那就要考虑把部分逻辑拆到分包里。微信小游戏支持“分包加载”,CocosCreator的Asset Bundle配合微信分包,可以让主包代码尽量精简。
操作方法:
- 在CocosCreator里把某些模块配置为Bundle。
- 在微信开发者工具里,通过
wx.loadSubpackage或者Cocos的assetManager加载分包。
这里的关键是入口场景和首屏UI的依赖链要短。首屏不需要的界面、弹窗、商店页,尽量放进分包,等玩家点到了再加载。
4. 资源层优化:大头都在这里
4.1 贴图优化:从压缩格式到尺寸控制
资源体积的大头通常就是贴图。一个2048x2048的RGBA压缩格式贴图,不管放在哪个平台都是好几MB的存在。
CocosCreator里对贴图的优化手段,从性价比高到低依次是:
- 使用压缩纹理:微信小游戏支持
ETC2、ASTC等压缩纹理格式。在3.x的编辑器里,选中图片资源,Texture的type选择压缩纹理,构建设置里配置多个平台格式,包体可以减少到原来的四分之一甚至更低。 - PNG转JPG:没有透明通道的UI背景图、场景图,完全可以用JPG。我遇到不少项目全用PNG,一张背景图就2M,转成JPG后直接降到300KB,肉眼几乎无差别。
- 控制单图尺寸:UI图不需要超清。在手机上展示,大部分图不超过1024宽就没问题。那种2560宽的全屏大图,纯属浪费。
另外,如果图片放在远程服务器上再动态加载,那主包里的尺寸压力直接归零,后续会详细说。
4.2 图集合并与重复资源清理
图集的好处不只是减少DrawCall,它在包体优化上也有价值。合并后的图集能打掉图片文件头部的冗余信息,而且引擎加载一张大图和加载几百张小图的IO开销差异巨大。
这里的实操技巧是:所有UI贴图尽量进图集。CocosCreator的自动图集配置在“项目设置” -> “资源管理”里,开启“自动图集”并设置最大尺寸,构建时会自动把散图合并。
我还遇到过一种情况:同一个图标,策划从UI素材库里拖了三次,分别放在不同目录,构建时压根没去重,白白占了三次体积。后来写了个资源检查脚本,按MD5扫描assets目录,自动找出重复文件,一次性清理掉,包体降了400多KB。
4.3 音频和视频的压缩
音频是体积刺客亚军。很多项目的音频都是320kbps的MP3或者WAV,一首1分钟的BGM就2M多了。
我的标准做法:
- BGM用MP3或M4A,码率压到128kbps以下,人耳环境音效不受影响。
- 音效用OGG或MP3,码率96kbps足够。
- 不要从网上下载现成的高码率音效后直接拖进工程,先转码再导入。
视频更狠,一个几百MB的视频如果拖进包里,那基本没救。无论如何视频都应该走远程加载,不占主包。如果你必须本地放视频,我建议做启动引导时用“小尺寸+低清晰度”,其他一律远程。
4.4 清理内置的初始场景和多余预制体
新建CocosCreator项目默认会有一些示例场景、默认资源。这些如果不处理,会直接进包。很多人项目做完了,assets/resources下面还躺着几个早年测试用的预制体和场景。
清理逻辑很简单:
- 删除未引用的资源和场景。
- 把
resources目录里的“必要图片、配表、音频”之外的资源移出去。 - 确保首场景里没有引用到无关的Prefab。
最稳妥的办法是:在构建前,把resources目录里的东西全部清空,然后启动游戏逐个功能点,看哪些资源报错,再加回来。有几次我就是靠这种方式把几个没用的大体量预制体“请”出了包。
5. 远程资源方案:让4M限制彻底失效
5.1 什么样的资源适合放远程
远程资源不是什么都往服务器扔,要按使用频率和时机来分。首屏启动必须用到的、UI框架、底层逻辑、启动场景,这些留在本地。而“进入某功能才用到的图片、音频、3D模型、新手教学视频”这些,基本都适合放远程。
另一个判断维度是更新频率。如果某个资源每周都可能改(比如运营活动图),放到远程会非常方便,不用因为改一张图重新提审。我一个朋友的项目就是这样,每次活动上新图只更新服务器资源,版本完全不动。
5.2 用Asset Bundle加载远程包的实操流程
CocosCreator里有一套完整机制来管理远程Bundle。核心流程三步:
第一步,在编辑器里把某块资源标记为Bundle,然后在构建配置中把它的“Bundle配置”设为“远程包”。
第二步,构建后相关的Bundle不会进主包,而是生成在remote目录下,你需要把它整体上传到自己的服务器目录。
第三步,游戏运行时用代码加载:
// 远程bundle加载示例 assetManager.loadBundle('http://your.server.com/remote/activity', { version: 'v1.0.4' }, (err, bundle) => { if (err) { console.error('远程bundle加载失败', err); return; } bundle.load('activityPrefab', Prefab, (err2, prefab) => { // 场景里实例化这个预制体 }); });有一点非常重要:远程资源必须要有版本控制。如果你更新了服务器上的资源,但客户端还缓存着旧版本,那就会出各种诡异bug。CocosCreator里,version参数就是干这个用的,可以配置成一个版本号,也可以把资源打包后的hash拼进URL里。
5.3 服务器配置和缓存策略
我自己的做法是给远程资源单独准备一个静态目录,走CDN。在Nginx或者对象存储上配置好,关键要给cache-control设置好:
- 对于带版本号的资源(比如
bundle_v1.0.4.zip),cache-control可以设成max-age=31536000,一年不失效。 - 对于不带版本号、会被覆盖的资源,设成
no-cache,保证每次都回源验证。
这个配置看似简单,但在实际项目里,光缓存就导致过两次线上事故。一次是更新图片资源后,玩家手机上还是旧图;另一次是服务器文件已经删了,但CDN还在吐旧缓存,玩家客户端直接加载失败。
微信小游戏的远程资源还有一层限制:必须配置合法域名。你在开发模式下可以勾选“不校验合法域名”,但上线时必须在小程序后台配置好downloadFile合法域名,并且服务器要支持HTTPS。这一步常常被忽略,结果代码写好了,远程加载在真机上就是不行。
5.4 首包分割的粒度划分
远程资源方案不是把所有东西都扔出去就完事,关键是分割粒度。我在一个3.x项目里,把启动流程理清楚后,分了这么几层:
- 主包(留在本地):启动场景、进入游戏的逻辑、UI框架、基础网络层。
- 首屏Bundle(本地分包):主城界面、角色基础显示。
- 功能Bundle(远程):商店、背包、活动、战斗结算等。
这样做的好处是,启动包连本地分包加在一起在3M左右,实测冷启动下载只有几百KB的增量,首屏秒开,后面每开一个功能模块才去拉对应资源。整体体验提升非常明显。
6. 微信分包:本地资源怎么继续压缩
6.1 理解微信分包与Cocos Bundle的关系
很多开发者分不清“微信分包”和“CocosCreator的Asset Bundle”,这两者不是一回事,但可以配合使用。
- 微信分包:微信小游戏平台的机制,把代码和资源划分成多个包,主包和分包分别限制,但所有包都在本地(相当于打进了小游戏安装包里)。
- CocosCreator Asset Bundle:Cocos引擎的资源包机制,构建时可以把资源组合成多个Bundle,这些Bundle可以留在本地,也可以标记为远程。
两者的配合方式是:系统启动时用到的最小集合放主包,次要用到的功能做成Cocos Bundle,再映射为微信分包;更大块的资源就走远程Bundle。
在微信开发者工具的game.json里,分包大概长这样:
{ "subpackages": [ { "name": "stage1", "root": "stage1/" } ] }CocosCreator构建时,如果你勾选了“配置为分包”,构建产物会自动生成对应的分包目录。
6.2 主包与分包的体积平衡
微信平台对分包总大小也有限制(目前常见的是总包不超过20M,主包不超过4M)。实际操作中我的习惯是:
- 主包控制在3.5M以内,留出余量。
- 本地分包每个控制在4M以内。
- 超过1M的资源,优先考虑远程。
如果你想让主包尽可能小,那原则就是:首屏依赖以外的场景全部丢到分包或远程。比如你的游戏是“加载即进入大厅(自然语言),大厅里有多个玩法入口”,那么大厅、公共UI、网络层必须在主包,而每个玩法入口的完整场景可以直接做成一个本地分包或远程Bundle。
6.3 分包加载的进度体验
分包加载本身有耗时,特别是网络环境差的时候。这里有一个体验上的关键点:加载分包时一定要给玩家视觉反馈,别让人干等。
我在项目里会做一个loading界面,展示当前的加载进度和提示语。微信的wx.loadSubpackage自带进度回调:
const task = wx.loadSubpackage({ name: 'stage1', success: () => {}, fail: () => {}, complete: () => {} }); task.onProgressUpdate(res => { // res.progress 0-100 console.log('分包加载进度', res.progress); });如果用的是Cocos的assetManager加载Bundle,也有对应的onProgress回调,可以实时更新进度条。
这个细节虽然不影响包体大小,但直接影响玩家对游戏性能的感知。
7. 一个完整项目优化复盘:从6.2M压到3.7M
7.1 开局数据
最近优化的一款合成类小游戏,CocosCreator 3.8.2,上线前构建包体6.2M,主要构成:
- 引擎代码 + 项目JS:2.1M
- UI贴图(主要是各个合成物品图标和背景):2.6M
- 音频(10首BGM + 60个音效):1.3M
- 配置表和Prefab:0.2M
微信上传直接爆红。
7.2 逐项优化过程
第一步,先开功能裁剪。项目不用3D物理,不用Spine,不用粒子特效,全部关掉。构建后再看,引擎代码降到了1.5M左右。
第二步,清理未使用的第三方库。项目里挂了一个万能的事件广播库,其实Cocos自带的EventTarget就够了,删掉后代码体积又减了200KB。
第三步,处理贴图。UI合成物品图标一共两百多个,都挤在若干大图集里。检查后发现很多图标是PNG格式,而且带透明通道,实际上完全可以合并到一张大图集上,直接用压缩纹理。改用TexturePacker重新打图集之后,贴图总体积从2.6M压到了1.1M。
第四步,BGM全部转成96kbps的M4A,音效统一压成64kbps的MP3。1.3M直接降到500KB。这个步骤对玩家来说几乎不可感知,但包体少了一半多。
第五步,把完整的主城界面和商店资源标记成两个远程Bundle,上传到CDN。主包体积进一步缩减。
最终结果,主包3.7M,本地分包1.4M,远程资源约2M。构建后上传微信开发者工具,数据终于绿了。
7.3 复盘的重点心得
回看这个项目,我最大的体会是:优化包体不是某一个技巧的功劳,而是把每个环节都压一遍的累计结果。代码省一点,贴图省一点,音频省一点,最后加起来可能就刚好过了那根线。
还有一个容易被忽视的地方是构建的历史残留。之前构建过一些废弃资源,如果不主动清理构建目录,体积会越来越高。后来我在CI脚本里加了一步,每次构建前先把build目录整体清空,确保产出干净。
8. 常见问题速查与避坑记录
8.1 高频问题汇总
| 问题现象 | 原因 | 解决方案 |
|---|---|---|
| 构建后包体依然大,改无可改 | 可能有重复资源未被清理,或者resources目录塞了太多资源 | 扫描重复文件,精简resources,将非首屏资源移出 |
| 主包内容没变,但上传超限 | 勾选了MD5 Cache,引用关系变化导致统计口径不同 | 关闭MD5 Cache,或把被主包引用的资源也挪进主包 |
| 远程Bundle加载一直失败 | 域名未配置合法,或HTTPS证书无效 | 在小程序后台配置合法域名,检查CDN证书 |
| 分包加载后场景是黑屏 | 分包里有用到主包资源,加载时序不对 | 确保分包内的依赖资源也在分包中,或者先加载依赖再加载场景 |
| 压缩后代码运行报错 | 某个库不支持压缩混淆 | 在构建设置中对该文件排除压缩 |
| 引擎模块裁剪后UI显示异常 | UI相关模块没有完整勾选 | 重新检查模块依赖,保留UI、Widget、Layout等基础模块 |
8.2 三个值得留意的坑
第一个坑是微信的缓存。远程资源更新后,部分真机上会出现旧资源不清理的情况。后来我在每次远程资源加载失败时增加了一次缓存清理兜底逻辑,才把线上问题控制住。
第二个坑是构建压缩的源映射。3.x的构建面板里“Source Maps”默认关闭,但有时候升级引擎版本会把它重置,我就在一次升级后忘了重新检查,导致主包莫名多了500KB。
第三个坑是不要图省事把整张超大图集塞进远程包。远程包可以很大,但如果玩家进了某个玩法突然要下载5M的图集,网速慢的时候体验是灾难级的。大图集要切小块,按功能模块拆Bundle,做到按需加载。
8.3 优化动作的优先级建议
如果预算时间有限,照着这个优先级做性价比最高:
- 开功能裁剪,这是零成本、收益最大的。
- 清理冗余资源和重复文件,如果项目很久没人维护,这一步往往能直接减掉10%-20%体积。
- 把大贴图转压缩纹理,收益巨大但需要调整UI渲染表现。
- 音频统一转码降码率,对玩家几乎无感,收益很直接。
- 远程Bundle拆出去,这是对架构改动最大的操作,建议在版本迭代期做。
9. 一些长期维护的建议
CocosCreator 2.x和3.x在项目维护上有一个共同点:包体不会自己变小,只会越来越大。如果团队没有专门的人盯着体积数据,每次版本发布前都必须跑一遍体积检查流程。
我的习惯是让CI阶段自动做一次体积报告。构建完包后,用脚本解析构建目录,自动输出主包、分包、远程Bundle的体积列表,再和上一版比较,超了阈值就报警。别觉得这功能复杂,就是几分钟就能写出来的脚本,但能在早期帮你拦住问题。
团队协作时还需要一条规定:所有新增的大尺寸资源必须经过体积评审。一旦有人往工程里拖了一个2M的视频或者一组没有压缩的图,后面所有人都在给这个错误买单。有了这个流程,优化压力会小很多。
另外,如果你的项目周期长,建议定期把引擎版本升级一下。新版本的引擎在代码体积和资源管理上往往会偷偷优化。比如3.6到3.8之间,默认的资源加载逻辑就改善了不少,构建产物也小了一点。
结尾
我在实际项目中最大的感受是:4M不是一道过不去的坎,而是一道逼着你想清楚“什么东西是启动必需、什么东西可以后置”的过滤器。这个思考过程本身,比省下那几兆字节更有价值。按照文章里的顺序,从功能裁剪做起,再逐步拆解资源、上远程Bundle,你会发现自己项目的包体会比想象中更好瘦。