微信小游戏构建实战

4.1 从编辑器到微信开发者工具,一条能走通的路

Skyline Courier 第一次出微信小游戏包,我卡在「构建完了但开发者工具打不开」上整整一个下午。回头看,路径其实只有四步,每一步都有明确的落点。

先装工具,再告诉编辑器工具在哪。 微信开发者工具从微信官方文档页下载安装,然后到编辑器的 Cocos Creator/File -> 偏好设置 -> 外部程序 里把它的路径填进去。这一栏不填,构建面板右下角的「运行」按钮点了没反应。

开构建面板,选平台。 菜单栏 项目 -> 构建发布,在「发布平台」下拉里选「微信小游戏」。选完之后面板下方会多出一组微信专属选项,这是 3.8 的机制:各平台的构建选项是以扩展形式注入面板的,你在 扩展 -> 扩展管理器 -> 内置 里能看到它们。

填选项,点构建。 构建结束后点「构建任务」左下角的文件夹图标,发布包就在项目的 build 目录下,生成一个 wechatgame 文件夹(以构建任务名为准)。这个文件夹里已经带上了微信环境的两个配置文件:game.json 和 project.config.json。

点运行。 构建任务卡片右下角有「运行」按钮,点它会唤起微信开发者工具并导入这个目录。

这里有个坑我踩过:如果之前从没手动运行过微信开发者工具,点「运行」会报 Please ensure that the IDE has been properly installed。这不是构建失败,是工具链的握手没建立,手动打开一次微信开发者工具,登录,然后回到 Creator 再点「运行」就通了。

4.2 构建面板上要填的那些格子

面板上的选项分三层:通用构建选项、平台专属选项、引擎设置。通用选项每个平台都有,平台专属的才是微信专属。

选项 字段名 在 Skyline Courier 里的取值与理由
发布路径 — 保持项目下 build 文件夹,不勾选「与项目相对路径」以外的复杂配置
构建任务名 — 用 wechatgame-prod 这类带后缀的名字,方便区分灰度包和正式包
初始场景 — Boot.scene,负责加载首场景和远程资源
参与构建的场景 — 手动勾掉不需要发布的关卡场景,能明显瘦包
MD5 缓存 — 正式包必开,远程资源靠它做版本失效
调试模式 — 正式包不勾,勾了 UUID 不压缩、脚本不混淆
资源服务器地址 remoteServerAddress 填 CDN 域名,构建后把 remote 目录整个传上去
配置主包为远程包 — 主包太大时才勾,勾了主包会进 remote
设备方向 orientation Landscape,写入 game.json
AppID appid 面板默认的 wx6ac3f5090a6b99c5 仅供测试,正式包必须换,写入 project.config.json
生成开放数据域工程模板 buildOpenDataContextTemplate 要做排行榜才勾
引擎原生代码分包 — 勾了能把 WASM/Asm.js 挪进子包,主包小一截
分离引擎 separateEngine 走微信小游戏引擎插件时用

有个细节容易忽略:「设备方向」和「AppID」这两个字段名不是编辑器里显示的中文,它们是命令行构建时的参数名。3.8 里任何选项都能在面板右上角点「导出构建配置」,导出一份 JSON,里面用的就是这些英文字段名。做 CI 的时候直接改这份 JSON,不用手敲参数。

关于引擎设置那一块,3.8 把引擎相关的选项从平台选项里拆出来单独成组了。里面有一项叫 CLEANUP_IMAGE_CACHE,含义是「纹理数据上传 GPU 后是否删除内存数据」。删了能省内存,但动态合图功能会失效,我一开始为了省内存开了它,结果图集合并没有掉,drawcall 反而涨了一截。后来把它关掉,合图才恢复正常。

再一个常被忽略的选项是「物理系统」。如果项目设置里的物理后端选了 bullet(ammo.js),微信小游戏构建时才会生效 Wasm 3D 物理系统 这项。默认开启意味着会打包 wasm 模式的物理代码,包体会涨。真机上如果报 wasm 相关错误,先查这里。

4.3 产物目录里到底有什么

构建完打开 build/wechatgame,里面大致是这些东西:

路径 内容
game.json 设备方向、窗口配置
project.config.json appid 等工程信息
src/ 引擎脚本 + project.js
assets/ 各 Asset Bundle 的 import / native 目录
remote/ 勾了「配置为远程包」的 bundle,待上传 CDN
subpackages/ 压缩类型设为「小游戏分包」的 bundle

目录结构和两个配置文件的具体内容,在 settings.json 和 config.json 里能看全。settings.json 记录整个游戏包的基本配置,包括 launchScene、designResolution、BundleVers(各 bundle 的 md5)、subpackages、remoteBundles、server 这些字段。config.json 是每个 bundle 一份,记录该 bundle 的资源清单、依赖的其他 bundle、场景信息、md5 版本表。

排障的时候这两个文件比日志更有用。 我有一次远程资源加载 404,翻了半小时日志没头绪,最后是打开 settings.json 发现 server 字段是空的,构建面板里「资源服务器地址」忘了填,本地把 bundle 删了之后就没得下了。

关于包体,微信官方《分包加载》文档的口径是:主包不超过 4M,主包加所有分包的总和不超过 30M,单个普通分包不限大小,单个独立分包不超过 4M。Cocos 的《小游戏分包》文档对「单个分包」的措辞略有不同,但主包 4M 和总量 30M 这两条是一致的。具体数字以微信官方公告为准,平台政策会调整,我写的是我在 3.8 文档和微信开发者文档里核对到的版本。

分包的做法是把要分出去的文件夹配置成 Asset Bundle,「目标平台」选对应的小游戏平台,「压缩类型」选「小游戏分包」,同时构建面板里的「主包压缩类型」也要设成「小游戏分包」。产物会落在 subpackages/ 下,每个子目录是一个 bundle。分包只能放本地,「配置为远程包」会被锁住不可勾。

4.4 那些让人卡住的报错

现象:构建成功,运行时资源加载 404,日志里是一串 UUID。

原因是三种可能:脚本里动态加载了没放进任何 Bundle 的资源(只有直接放在 Bundle 目录下的资源 url 才会写进 config.json);资源导入有问题,没能正常生成数据到 library;或者依赖资源被误删。修法对应三种:在资源管理器里找到该 UUID,检查依赖资源是否正常,右键「重新导入资源」,再用代码编辑器搜这个 UUID 被谁引用。

现象:构建日志里报脚本相关错误,但不知道是哪个脚本。

引擎在反序列化之前会先加载所有用户脚本,脚本不合法会直接构建失败。报错信息里带着脚本的 UUID,去资源管理器里搜这个 UUID 就能定位到具体文件。

现象:构建完成了,但游戏里某个资源加载不出来,路径看着是对的。

十有八九是 MD5 缓存的锅。开启后所有资源文件名都会带上 MD5 信息,如果有第三方资源没走 assetManager 加载,路径就对不上了。转换一下就行:

const uuid = assetManager.utils.getUuidFromURL(url);
const realUrl = assetManager.utils.getUrlWithUuid(uuid);

现象:release 包里找不到某个 UUID。

release 模式下 config.json 里的 UUID 是压缩过的。点主菜单 开发者 -> 打开构建调试工具,在控制台里用 Build.Utils.decompressUuid 就能还原。

现象:微信开发者工具里一切正常,真机上一片黑屏。

先确认是不是缓存了旧资源。升级引擎版本后留在本地的缓存还是老版本的,渲染会出错。在真机上调 assetManager.cacheManager.clearCache();在微信开发者工具里走菜单 工具 -> 清除缓存 -> 全部清除。另外开发者工具不限制缓存大小,所以「缓存满了导致下载失败」这类问题在工具里永远测不出来,必须真机测。

构建过程本身跑在单独的 worker 里,想看完整日志和调用栈,也要点 开发者 -> 打开构建调试工具。

4.5 开放数据域什么时候才需要

微信、抖音、百度这些小游戏平台为了保护社交关系链,引入了开放数据域,一个跟主游戏完全隔离的独立执行环境,只有在里面才能通过平台接口拿到好友关系数据。

Skyline Courier 做了个「本周配送里程排行」,需要展示玩家头像和好友的分数,这就必须上开放数据域。如果你的游戏不需要好友关系、排行榜、开放数据相关的任何展示,那就别碰它,它带来的构建复杂度、跨域消息通信、额外的渲染开销都不小。

要用的话,流程是四步:场景里放一个容器节点并挂上 SubContextView 组件;在构建面板勾选「生成开放数据域工程模板」;构建后 build/wechatgame/openDataContext 就是模板工程,你基于它定制;再次构建时如果这个目录已经存在会被跳过,不会覆盖你的改动。

SubContextView 组件有两个属性要注意。「设计分辨率」设成 640 × 960 的话,sharedCanvas 的尺寸就是 640 × 960,你在 style.js 里写的标签样式最大也只能到这个尺寸,超出部分渲染不出来。「FPS」属性控制主域把 sharedCanvas 刷到组件上的频率,设太高白白浪费性能,排行榜这种一屏不变的界面设低一点没影响。

跨域通信有个坑:主域发到开放数据域的消息里建议带上 type 字段标记来源,否则开放数据域会误处理:

wx.onMessage(res => {
    if (!(res && res.type === 'engine')) {
        console.log('处理来自主域的业务消息');
    }
});

4.6 相关阅读