把一款已经上线的微信小游戏当作学习样本,从包体里反推它的Cocos工程结构,这事听起来有点“灰”,但只要你把边界划清楚,它其实是特别高效的进阶训练。我最近就拿一款“切水果跑酷”玩法的上线小游戏复盘了一轮,玩法说白了就是把水果忍者那套滑切交互塞进无尽跑酷里,水果从前面飞过来,玩家在屏幕上划一刀切碎、切中连击、躲开障碍。这种小游戏看起来简单,但工程里牵扯到手势输入、对象池、场景切换、资源分包、图集合图、音效反馈,几乎把Cocos Creator日常开发的模块都覆盖了一遍。这篇文章就完整记录我当时的逆向过程:怎么把微信小游戏包拿到手、怎么解包、怎么从代码和资源产物反推工程目录结构,最后怎么在Cocos Creator里把核心玩法还原出来。适合正在学Cocos、想理解线上项目工程组织方式、或者准备小游戏面试的人参考。
1. 拿一款跑酷水果游戏当样本,到底想学什么
1.1 玩法组合决定了工程复杂度
切水果跑酷这类游戏,比单纯的水果忍者或者单纯的跑酷都要复杂一些。水果忍者核心是“划过屏幕上的水果”,场景基本固定,一屏以内的可交互对象数量有限;跑酷核心是“角色沿路径前进,绕过障碍”,三维坐标、速度、碰撞检测是主轴。把两者组合起来以后,你需要同时维护两套逻辑:一套是角色在跑道上的位置、速度、跳跃/左右换道,另一套是空中水果的生成时机、轨迹、切割判定。更别提连击加分、combo表现、道具效果这些横跨两套系统的玩法机制。
从工程结构角度看,这类游戏通常至少会有这么几个模块:
- 玩家控制模块(跑酷角色的移动、换道、跳跃)
- 手势输入模块(识别屏幕滑动、判断切水果方向)
- 水果生成模块(对象池、批次、路径规划)
- 切割判定模块(切割线包围盒检测、切割回调)
- 计分与连击模块(分数、combo、UI刷新)
- 场景管理模块(主菜单、游戏场景、结算场景切换)
- 资源加载模块(分包、动态加载、图集、音频)
这套模块划分在Cocos工程里怎么落地,不同团队差别挺大。有的团队把所有玩法逻辑塞进一个大场景里的几个脚本组件里,有的团队会拆成 prefab + bundle + 独立脚本的清晰结构。通过逆向读取线上产物,你能看到别人真实项目是怎么组织代码、怎么划分组件、怎么命名资源的,这些经验比看官方demo来得更直接。
1.2 逆向学习能获得哪些不一样的东西
正常的Cocos学习路径,大多是看官方文档、跑教程工程、自己从零写一个Demo。逆向学习则完全反过来:你拿到的是已经构建、压缩、可能混淆过的最终产物,需要自己倒推出原始工程长什么样。这个过程会逼着你把“构建流程”彻底搞懂——为什么发布包里有这些目录、每个文件的作用是什么、资源为什么是uuid命名、场景文件在编译后变成了什么结构。
做完一次逆向,你对下面这些问题的理解会明显加深:
- Cocos Creator从编辑器工程到微信小游戏运行包,中间到底经历了哪些处理
- 场景、预制体、脚本、资源在运行时是怎么被加载和关联的
- 为什么缓存/分包/动态加载要那样设计
- 线上项目遇到贴图报错、资源丢失时,问题大概率出在构建链路的哪一环
另外,如果你正好在准备Cocos面试,这类逆向经验特别加分。面试官问“cocos工程结构”这种话题,大多数人只能背出来“assets、library、temp、settings”,但你能说清楚发布到微信小游戏后代码和资源分别被打到哪个目录、场景资源从哪个json里加载、自定义脚本类型怎么被识别,这个维度完全不同。
1.3 先说清楚什么绝对不能做
“逆向”这个词容易被误解,我在开头必须先交代边界。这次学习和复盘的对象,是我有权在自己设备上查看、且只用于个人技术研究的已上线小游戏资源。你可以把它理解为“读别人的开源项目源码 + 反推设计思路”,而不是“破解、扒皮、抄袭”。
所以实际操作时,有几个原则必须守:
- 不做任何绕过授权、盗取虚拟资产、修改上线游戏数据的行为
- 不把解包出来的资源、代码用于商业项目或公开发布模仿作品
- 不传播解包工具链、不提供特定游戏的完整还原包
- 对提取到的内容仅在本地学习,用完即删
合法合规的学习边界是:研究公开可运行的产物,理解其技术实现,归纳通用的工程结构知识。这篇博文也只讲通用方法和识别思路,不针对某个具体游戏做完整拆解。
2. 微信小游戏包体怎么拿到手、怎么解开
2.1 从本机缓存里把运行包提取出来
微信小游戏跟小程序一样,运行前需要先把代码包下载到本机,所以理论上只要你在自己设备上跑过一次目标小游戏,本地就有一份完整的包体缓存。常见的提取路径有几个:
- Android真机:微信的本地文件一般放在
/data/data/com.tencent.mm/MicroMsg/{hash}/appbrand/pkg/下面,需要root或者借助调试工具才能读取;如果手机没root,也可以用adb backup的方式碰碰运气,但微信对备份通常有保护,实际成功率不高。 - PC端微信:Windows上小游戏缓存通常落在
文档/WeChat Files/Applet/{wxid}/{version}/目录,里面能找到.wxapkg后缀的文件,这个路径对普通使用者可读,提取成本最低。 - 调试工具:微信开发者工具也有缓存目录,但通常只缓存你本地打开过的小程序,拿来看线上别人的游戏不一定适用。
我自己最常用的是PC端缓存路径。把微信打开、玩一下目标小游戏,让包体完整下载,然后去目录里按时间排序找最新生成的.wxapkg文件。注意有的游戏开了分包,你会看到多个.wxapkg,主包加分包都是一份资产,后面要一起解。
2.2 解开wxapkg,理解这个小格式
.wxapkg是微信自己定义的一种打包格式,不是zip,直接改后缀名解压肯定不行。格式本身简单,文件头有固定的魔数(通常是0xBE开头),记录了文件列表、各文件偏移和长度。网上有不少开源脚本可以直接解析,比如用Node.js写的小工具,几行代码就能把包里的文件还原成目录结构。
解包命令大概是这样的思路:
# 用Node.js写的解包脚本(示意) node wxapkg.js game.wxapkg -o output_dir解出来以后,你会看到类似下面的结构(不同版本的Cocos构建产物会略有差异):
game.json:小游戏页面的全局配置,包括页面路径、分包配置、设备方向game.js:小游戏入口逻辑,所有业务代码打包后的入口project.config.json:开发者工具项目配置,能看出构建工具版本assets/:Cocos构建出的资源bundle目录(2.x和3.x有差异)subpackages/:如果有分包,会看到独立目录
拿到这些文件以后,第一件事不是急着翻代码,而是先看game.json和project.config.json这两个小文件,它们能直接暴露引擎版本、项目名称、分包结构等基础信息。
2.3 解包后的第一眼:先识别再细看
解包后目录文件可能上百个,别一头扎进去。我建议按下面这个顺序做初步摸底:
- 打开
project.config.json,看miniprogramRoot、compileType、setting里的字段,确认是小游戏项目 - 打开
game.json,看deviceOrientation(竖屏还是横屏)、subpackages(分包列表)、plugins(有没有用第三方插件) - 打开
game.js开头几十行,看是直接执行还是引用了外部模块,Cocos构建产物通常会把引擎和项目代码一起塞进来 - 在目录里搜索
cocos2d-js、cclegacy、jsb-adapter这些关键字,判断引擎类型
这四步做完,基本上就能判断这个游戏是Cocos Creator 2.x构建、还是3.x构建,甚至能和Unity小游戏产物区分开。比如Unity导出的微信小游戏通常有unity开头的WebGL文件、data.unityweb、wasm文件,这些特征和Cocos完全不同。
3. 从产物反推Cocos工程结构
3.1 先确定引擎版本,版本不对后面全白干
Cocos Creator 2.x 和 3.x 构建出来的微信小游戏产物差异非常大,如果不先确认版本,后面所有分析都会跑偏。
2.x 的典型特征:
game.js里会引入cocos2d-js-min.js或类似的引擎文件- 运行时会挂一个全局
cc对象,大量调用cc.game.run - 目录里能看到
jsb-adapter(微信小游戏适配层) - 脚本和资源入口集中在
src/下面,通常有src/settings.js、src/main.js、src/project.js
3.x 的典型特征:
- 没有单独的
cocos2d-js-min.js,引擎拆成模块系统,通过System.register或require注册 - 代码和资源按bundle组织,常见
assets/main、assets/resources这种结构 - 全局对象是
cclegacy - bundle 目录内有
cc.config.json,记录资源映射、类型、压缩格式等关键信息
确定版本还有一个土办法:直接把game.js里的版权注释字符串搜出来,Cocos引擎文件头部通常会带 “Cocos Creator” 和版本号,或者搜cc.version、CC_JSB这类运行时常量。
拿到版本号以后,去Cocos官方下载对应版本的Creator编辑器,尽量保持小版本一致。你后面重建工程时,资源解析和场景格式能不能直接打开,很大程度上取决于版本是否匹配。
3.2 读懂Cocos构建产物里的目录语义
有了版本概念,再回头看目录结构就清晰多了。以Cocos Creator 3.x为例,解包后的结构通常长这样:
assets/main/:主包资源bundle,里面还能看到cc.config.json、import/、native/三个子目录assets/main/index.js:主包代码入口,包含场景、预制体等信息的索引assets/main/cc.config.json:资源配置清单,列出所有资源的uuid、类型、路径和压缩方式assets/main/import/:序列化后的资源文件(json、二进制),比如场景、预制体、材质、动画都是在这里assets/main/native/:原生文件资源,比如png、jpg、mp3、mp4src/:如果是2.x,这里放脚本和settings
import里的文件命名是uuid,没有任何语义,初看一头雾水。但cc.config.json就是解密这堆乱码的钥匙,里面每一行(或每个条目)都告诉你某个uuid对应什么资源类型、什么原始路径。通过它,你能把整个资源清单梳理出来,甚至还原出策划在编辑器里的目录结构。
3.3 从入口脚本还原运行流程
Cocos项目的运行顺序有固定套路,找到入口脚本里的关键调用,就能还原出启动流程。
3.x典型调用链大致是:
// game.js 里简化的调用链 System.import('./assets/main/index.js') .then(module => module.init()) .then(() => { // 注册系统事件、设置屏幕适配 director.runScene(...) })这里的assets/main/index.js会进一步加载cc.config.json,然后通过assetManager.loadBundle('main')加载主包资源,最后拿到启动场景的uuid,调用director.loadScene进入第一个场景。
启动场景的uuid通常也能从cc.config.json里看出来,一般有一个类型为cc.SceneAsset的资源在列表里特别显眼,比如start-scene、MainScene这种名字。把这个资源的json导出来分析,就能还原第一个场景里有什么节点、挂什么组件。
2.x的流程更简单一些:src/main.js里会设置启动场景、调用cc.game.run,src/settings.js里有scene数组和launchScene字段,直接能定位到首场景。
3.4 项目配置信息散落在哪
除了代码和资源,发布包里还保留了相当多的项目配置痕迹:
- 设计分辨率:2.x在
settings.js里能看到designResolution,3.x在cc.config.json或game.json的windowInfo附近找 - 物理引擎开关:2.x在settings里会有
physics字段 - 模块裁剪:2.x的
module字段列出打包时包含了哪些引擎模块,可以判断项目是否用了物理、spine、粒子等 - 分包策略:看
game.json的subpackages,每个分包会对应一个bundle目录 - 插件引用:
plugins列表里可能有微信原生插件,这会直接影响声音、视频、录屏等能力
这些配置信息组合起来,等于把一个Cocos Creator工程的project settings页面给你画了出来。你重建工程时,照着一项项填回去就行。
4. 资源、场景和预制体的反推技巧
4.1 资源文件在构建后被改成了什么
Cocos编辑器里,资源以.png、.scene、.prefab、.meta的形式存在于项目的assets目录。构建到微信小游戏后,资源会被序列化并改写格式,放进import和native。
- 纹理、音频、视频这类原生二进制资源,会原样或转码后放进
native/ - 场景、预制体、材质、动画、图集配置这类json类资源,会被序列化成紧凑的
.json放进import/ - 插件脚本会被打包进
index.js,跟编辑器的单个文件脚本对应关系不明显,只能靠代码特征反推
所以你在“还原工程”时,不是把文件后缀改回.scene拖进编辑器就能打开,而是要根据这些序列化json的内容,在编辑器中重新创建同名资源和组件树。
4.2 图集与碎图的快速识别
切水果跑酷这种游戏,水果贴图、UI图标、跑道装饰通常会做图集合图。运行时你在native/里能直接看到.png文件和对应的.plist或者.json(需要用纹理图集格式区分)。.plist是Cocos2d-x时代沿用下来的格式,Creator 2.x 之前常见;3.x更倾向于用.json的图集格式。
识别出图集以后,可以用编辑器自带的“碎图自动切分”功能把子图全部切出来,然后在还原工程里重新生成 SpriteFrame。这一步对还原场景特别重要,因为你从场景json里读到的 SpriteFrame uuid,会对应到图集中的某一张子图,如果切分不一致,贴图就会花屏或者错位。
还有个小技巧:native/里的png如果直接打开发现大块透明、但有细微痕迹,大概率不是碎图而是图集。图集通常会带.config信息,别漏看。
4.3 场景和预制体JSON怎么读
找到类型为cc.SceneAsset的json文件以后,打开它,你会看到类似下面的结构(不同版本字段有差,但核心逻辑一致):
{ "__type__": "cc.SceneAsset", "_name": "game", "scene": { "__id__": 1 }, "nodes": [...] }scene指向一个cc.Scene对象,这个对象的_children数组就是场景的根节点列表。每个cc.Node里会有:
_name:节点名,这是还原时最好的人类可读信息_parent:父节点索引_children:子节点索引_components:挂载的组件列表_position、_rotation、_scale:变换信息_layer:节点层级
组件里能看到__type__字段,它指向自定义脚本类名,比如"fruit.PlayerController"。利用这些字段,完全可以写脚本自动生成一棵节点树。实践里我不会要求100%还原所有组件参数,而是先按节点名把层级关系理出来,再根据组件的关键字段(比如哪几个节点挂Sprite、哪几个节点挂Camera)补到编辑器里。
预制体的结构跟场景类似,区别是它会多一个_prefab信息,内部用fileId关联嵌套预制体。还原时先把预制体独立建好,再拖进场景,和编辑器里的工作流一致。
4.4 纹理、音频、视频和Shader的特殊处理
解包出来的原生资源不一定能直接拖进编辑器正常用。最常见的坑:
- 纹理压缩格式:为了减小包体,很多线上项目会把贴图转成etc2/astc这类GPU压缩格式,这些格式在本地复现时很可能打不开或者显示花屏。遇到这种情况,需要从原始png/jpg里重新导入,或者用工具把压缩纹理转回RGBA。开发一款游戏时,如果美术给的贴图在Blender里看着正常、拖进Cocos却报错,也大概率是压缩纹理、颜色空间、非2次幂尺寸这几个原因;逆向阶段你会对这类报错特别敏感。
- 音频格式:微信小游戏对音频格式有限制,产物里可能出现 .mp3、.m4a,少数用 .wav;还原时先确认引擎版本对这些格式的兼容性。
- 视频资源:如果游戏里有剧情动画或广告视频,会在
native/里出现mp4文件。微信小游戏播放视频不能直接走DOM,通常用wx.createVideo或Cocos的VideoPlayer组件;从工程结构上能看出它放在哪个bundle、是否动态加载。之前看到很多Unity小游戏也在研究视频播放方案,Cocos这边套路相对简单,但包体里的资源目录逻辑是共通的。 - Shader与特效:自定义shader会打包成effect资源,在json里能看到
cc.EffectAsset类型。还原时可以根据其名字和属性猜出用途(比如地面扫光、迷雾效果),但精确还原shader源码通常不现实,我会在重建时用一个简化shader替身,保证视觉方向一致就够了。
5. 在Cocos Creator里把核心工程重建出来
5.1 创建一个配对版本的空工程
进入重建阶段后,先在本地创建一个与目标工程版本一致的Cocos Creator项目。版本匹配非常重要,3.8的编辑器没法直接打开用3.6构建产物的场景json,所以尽量精确。
创建完空工程后,我建议先把目录骨架搭出来,不一定跟原版完全一样,但至少保证模块清晰:
assets/ scenes/ scripts/ prefabs/ resources/ textures/ audio/然后把解包后得到的关键信息归档:cc.config.json导出的资源清单、场景json的节点树、脚本模块依赖表、以及从代码里摘出来的自定义组件类名。
5.2 按启动顺序还原场景与预制体
我习惯从启动场景开始还原,因为顺着启动链走,你很快能知道这个游戏的核心玩法在哪几个预制体里。
操作步骤大致如下:
- 在编辑器中新建空场景,重命名为目标场景名
- 根据场景json里的
_name和层级关系,手动创建节点树 - 对每个节点,先挂渲染组件(Sprite、Label、Camera),再挂业务组件(自定义脚本)
- 把解包出来的贴图资源导入项目,给Sprite指定对应的SpriteFrame
- 保存场景,运行看是否报缺组件、缺资源
这里的关键点:首次跑通比精细还原更重要。我自己重建切水果跑酷时,先只还原了三条关键链:角色控制、水果生成、切割判定。场景里那些装饰性的树木、云朵、背景板,直接先用一张占位图替代,等核心逻辑通了再补。
5.3 脚本逻辑还原:先摸依赖,再按模块重写
发布到微信小游戏后的脚本,经过了编译、压缩、可能还有混淆,直接去读它就像看拼合代码。硬啃效率很低。我的办法是分三步走:
- 第一步,建立模块依赖表。在代码里搜所有类名和
require/import语句,列出A脚本引用了哪些B脚本、挂到哪些节点上。2.x的cc._RF.push系统会把模块注册成moduleId,顺着这些id能画出一张依赖图。 - 第二步,按模块阅读关键逻辑。对跑酷玩法来说,我优先读
PlayerController(角色左右移动、跳跃)、SwipeInput(手势识别)、FruitSpawner(对象池生成)、ScoreManager(计分刷新)。这些类名通常在场景json组件的__type__里就能看到,去找对应代码块并不难。 - 第三步,根据理解用自己习惯的方式重写脚本。这一步不是翻译反编译代码,而是“面向行为恢复”——我理解了它的输入、输出、状态流转,然后用合法、干净的代码重新实现一遍。
举个小例子:水果生成模块,我读出它大概率维护一个NodePool,每隔N秒从某个位置生成带随机方向的水果。那我重建时就写一个带NodePool的生成器,定时生成、回收、随机速度方向。行为一致、代码却是自己的,这才是逆向学习的真正产出。
5.4 连点成线:把资源和脚本合起来跑通
当场景树、预制体、核心脚本都建好后,接下来是疯狂调试阶段。微信小游戏运行时与编辑器预览有差异,有几个点要特别注意:
- 屏幕适配:目标游戏的设计分辨率一定要先填对,不然UI布局全乱
- 资源路径:运行时动态加载的资源(比如
resources.load)必须放在resources/目录下,否则线上能加载、编辑器里却白屏 - 输入事件:微信小游戏触摸事件通过
wx.onTouchStart桥接,Cocos编辑器里用node.on(Input.EventType.TOUCH_START),如果还原代码里混用了两者,在编辑器里可能不触发
我重建的版本最后在Cocos Creator里能跑通主流程:主角自动往前跑、滑动屏幕切水果、切中加分、撞障碍结算。虽然美术资源只是占位图,但工程结构、脚本划分和玩法逻辑,已经和线上项目在宏观层面保持一致了。
6. 实际操作中遇到的问题与排查实录
6.1 常见问题速查表
我把逆向学习过程中踩过的坑整理成一张表,按场景分类,方便遇到问题时快速对照。
| 问题现象 | 可能原因 | 处理办法 |
|---|---|---|
| 解包后找不到主场景 | 场景资源被封在分包或bundle里,cc.config.json没导全 | 先看game.json的分包配置,每个分包独立导出,再搜launchScene |
game.js被压缩成一整行 | 构建时默认压缩,可读性差 | 用格式化工具美化,重点搜类名、cc._RF、require |
| 代码被混淆,类名变成短字母 | 线上项目开了代码加密/混淆 | 先不追求完整还原,靠场景json里的__type__反查行为 |
| 导入资源后贴图花屏或报错 | 纹理压缩格式不匹配,或贴图非2次幂 | 从原始native/png重新导入,关闭压缩纹理选项 |
| Spine动画不显示 | 骨骼动画json被序列化进import,贴图没找到 | 检查atlas里的贴图引用,重建atlas资源 |
| 音频播放没声音 | 音频格式平台不兼容,或没走微信适配层 | 转成mp3,确认AudioSource配置 |
| 还原场景后大量组件缺类型 | 自定义脚本还没创建,json里__type__找不到类 | 先建同名的空脚本组件,再补充实现 |
| 小游戏分包内容加载不出来 | 分包bundle没主动加载,或加载路径不对 | 根据game.json里的分包名,用assetManager.loadBundle加载 |
6.2 代码被混淆或加密时的应对思路
如果线上游戏开了代码加密(微信小游戏有代码保护功能,也有的项目自己做了一层加密),解包后你会看到game.js里塞了一堆无法直接阅读的密文,或者所有标识符被替换成 a、b、c、d 这样的短名。
这种情况下,我的第一原则是:不要硬破。加密保护是开发者的合法权利,逆向学习没必要跨过这条线。我会退一步,改从资源侧反推工程结构——因为资源文件(场景、预制体json)里仍然保留了节点名、组件类型名和参数,这些信息完全够用来理解工程模块划分和玩法实现。甚至某种程度上比读代码更直接,因为资源json里的节点命名往往保留了策划和美术的原始意图。
如果只是压缩而不是混淆,那格式化以后还能读。遇到System.register这类模块语法,可以把每个模块的依赖数组提取出来,自动生成 moduleId 与源码位置的映射。这也是建立依赖图的主要手段。
6.3 还原度怎么取舍:先跑通,再精细化
第一次做逆向还原,特别容易陷入“必须100%复刻”的执念。我在前几次尝试中吃过亏:花了大量时间抠装饰节点的旋转角度和背景渐变色,结果核心玩法还没跑起来,热情先被磨没了。
后来我给自己定了三个还原优先级:
- 第一优先:玩法逻辑链路。角色移动、水果生成、切割判定、分数刷新,这四件事不通,整个逆向学习就没意义
- 第二优先:场景层级与组件挂载关系。这是工程结构的核心,决定了你看没看懂别人的项目组织方式
- 第三优先:美术还原和细节调优。贴图精修、动画曲线、特效shader,能还原最好,不能就先放
按这个顺序推进,我在一个周末内就把核心玩法跑通了,后面再慢慢补资源,心态稳定很多。
6.4 从逆向工程延展出去:接入AI与更多工具
这次逆向学习还有一个额外收获:因为把工程结构摸清了,后面在这个骨架上做扩展就特别顺手。现在很多团队在讨论Cocos接入AI能力,不管是做AI计分、AI陪玩对话,还是用大模型做NPC行为,第一步都是把现有代码里的接口边界找清楚。逆向练出来的“拆模块、看依赖、找入口”能力,恰好就是接第三方能力时最重要的一项基本功。
类似地,如果你想把这个学习成果打包成APK发布到安卓市场,理解了小游戏工程的构建产物后,再去对照Cocos原生构建的目录差异,会轻松不少。毕竟微信小游戏这套产物路径和安卓原生的assets、jni结构完全是两套体系,但项目资源、脚本的模块划分是可以平移的,这正是逆向学习带来的通用能力。
最后再分享一个小经验:做完一次逆向学习,千万别把解包目录直接删掉,留一份归档。你后面在Cocos里遇到“有些模型贴图在Blender里看着正常,到Cocos里却报错”这类问题,翻一下解包产物里的纹理配置,大概率能找到答案——因为线上项目为了体积和性能,对贴图做了各种压缩和格式转换,很多报错本质上都是在格式兼容上出了问题。理解了线上工程的取舍,再回头处理自己的工程,视野会开阔很多。