故事是这样的。
我做了一款微信小游戏,叫《就差一对》。
玩法不复杂,记住牌面,翻开,配对,把散落在关卡里的片光一点点找回来,再让画里的人重新有颜色。
我很喜欢游戏里的一句话。
如果你还记得我,那么我便一直存在。
听起来挺温柔的,对吧。
然后,这款温柔的小游戏,给我上了一堂一点都不温柔的工程课。
就因为一个好友排行榜。
前后4个版本。
事情最荒诞的地方在于,排行榜在开发版里是好的。
好友头像能出来,进度能排序,几个榜单也能切换。开发者工具正常,开发版真机也正常。可一旦走完提审,到了发布环境,排行榜中间只剩一大片空白。
没有好友。
没有报错。
甚至连一句像样的线索都没有。
就是一块安安静静的米黄色空地,仿佛这个功能从来没有存在过。
如果你做过微信小游戏,可能已经开始替我头疼了。
这种问题最难受的地方,不是它报错,而是它只在真正发布以后才报错。你本地改完,开发版测完,体验版再跑一遍,看起来都好好的。然后提交,等待审核,灰度发布,拿起手机一看。
还空着。
再改,再提,再等。
一个普通的 bug,突然有了版本审核的回合制冷却时间。
说真的,我前面也走过一个很像正确答案的弯路。
微信好友排行榜不是普通排行榜。为了保护好友关系数据,微信把它放在一个独立的开放数据域里。主游戏不能直接读取好友列表,只有开放数据域能调用好友云数据接口。子域把内容画到一张离屏的 sharedCanvas 上,主域再把这张画布贴到游戏界面里。
你可以把它想成一家只能隔着玻璃传菜的厨房。
主域负责点单和摆桌,子域负责接触好友数据并做菜,sharedCanvas 是唯一的传菜口。
任何一环没接上,玩家看到的都不是错误,而是一张空桌子。
排行榜第一次在真机上出问题时,还真是权限配置导致的。微信后台没有配好隐私保护指引,好友数据接口直接失败。把后台条目补齐以后,开发版跑通了。
这反而埋下了一个误导。
因为当发布版再次空白时,人很容易顺着上一次的经验继续怀疑授权、好友数据、云存储,甚至怀疑是不是测试账号压根没有同玩好友。
但这次不是。
v1.0.3,我做的第一件事,不是继续猜,而是给这条链路装仪表盘。
我把排行榜拆成了几层,主域有没有读到授权,自己的排行数据有没有成功写进 rank,开放数据域有没有启动,主域有没有创建显示桥,分页和刷新消息有没有发出去,子域有没有真的开始拉好友数据。
结果灰度日志很直接。
SubContextView 动态创建失败。
也就是说,排行榜还没走到好友授权,也没走到子域取数,主域在创建 Cocos 的开放数据域显示组件时就已经倒下了。
第一层剥开了。
于是 v1.0.4,我绕开动态构造的 SubContextView,自己做了一个更轻的 RankCanvasView。它只干一件事,把 sharedCanvas 刷成 Cocos 可以显示的纹理。
开发版正常。
提审,过审,灰度。
还是空白。
这一次日志又往前走了一点。wx.getOpenDataContext 明明存在,新的画布桥代码也执行了,可 OpenDataContext.canvas 取不到。画布桥没有创建,tab 和 refresh 也没有发给子域。
问题已经从「组件建不起来」,缩到了「开放数据画布没有在预期时间出现」。
坦率地讲,走到这里时,最自然的修法就是等。
画布没准备好,那就晚一点再拿。
所以 v1.0.5 里,我显式请求离屏画布,增加唤醒消息,再给它一段有限重试窗口。不是无限等,而是在大约5.7秒里最多尝试7次。理论上,只要是初始化稍慢,总有一次能接住。
又一次,开发环境正常。
然后 v1.0.5 发布。
诊断面板上清清楚楚写着,尝试7次,画布仍然不存在。
不是哥们,7次都不行???
这张日志很重要。
它告诉我的不是「再多等一会」,而是「你等待的东西根本不会从这条路径回来」。
重试只能解决时间问题,解决不了生命周期问题。
回头看旧实现,每次挂载、重试、发消息,都可能在项目层或独立引擎适配层重新调用 getOpenDataContext。在普通开发环境里,它返回的对象看起来没问题。到了 Release 的独立引擎环境,同样的调用可能经过代理和包装,项目代码拿到的是能发消息、却不一定能暴露安全画布的对象。
我一直在同一扇打不开的门上,多敲几次。
门当然不会因为我敲了7次,就突然变成另一扇门。
真正的修法,发生在 v1.0.6。
不再等 Cocos、Web Adapter 和独立引擎把环境包装完以后,才去项目层申请开放数据域。
而是在微信启动模板最早的位置,只创建一次 OpenDataContext。
一次。
拿到以后立刻缓存到全局,后面的画布挂载、分页、刷新和失败重试全部复用这一个对象,谁也不许再偷偷创建第二份。
大概是这样。
微信启动模板
↓
创建唯一的 OpenDataContext
↓
缓存到全局
↓
主域显示、消息发送、有限重试全部复用
还有一个改动也很关键。
以前只有画布挂载成功,主域才会发送分页和刷新消息。画布没上屏,子域也收不到正式取数命令。显示失败和取数失败被绑成了一个死结。
v1.0.6 里,这两件事彻底拆开。
玩家进入排行榜,diagMode、limit、tab、refresh 立刻发给开放数据域。子域先读取好友数据、先把榜单画出来。主域的画布如果晚一点准备好,就晚一点上屏,但不能再阻止子域工作。
反过来,子域冷启动时也不再抢着拉好友数据。它只准备画布、监听消息,等玩家真的进入排行榜以后再刷新。这样既不抢跑好友授权,也不白白占掉接口的刷新冷却。
这次重新构建,上传体验版。
诊断信息变了。
bootstrap
canvas=Y
attempts=1
画布桥已创建,分页和刷新都已经发送。
然后好友列表真的出现了。
到2026年8月6日,v1.0.6 正式发布,排行榜表现正常。
那个空了好几个版本的区域,终于有人了。
我回头整理这段经历时,发现它留下来的东西其实比一个修复提交要多。
我自己也还在摸索,不敢把这些叫成什么高深的方法论。但如果你也在做小游戏,或者在查那种「开发版正常、正式版发疯」的问题,下面几件事可能真能帮你少发一两个版本。
1,先画链路,别急着改代码。
好友榜至少有授权、主域写排行、子域读好友、子域绘制、主域贴图、主子域通信这几层。页面空白只是一种表象。没有把链路拆开以前,改授权、换组件、加重试都像蒙着眼睛扔飞镖。
你今天就能做的动作很简单,把功能从用户点击开始,一直画到最终像素出现在屏幕上。每一段标出输入、输出和失败后玩家会看到什么。
2,日志要能排除问题,不只是描述问题。
加载失败 这种日志几乎没有价值。画布桥未创建,消息未发送,好友数据尚未读取 就有价值,因为它一次排除了授权、好友数量、排序和云存储格式。
后来我的诊断页甚至单独记录 context 来源、canvas 是否存在、挂载尝试次数、分页发送时间、刷新发送时间和排行同步状态。
不是为了让日志看起来专业。
是为了让每一次昂贵的灰度发布,都至少换回来一个确定结论。
3,开发版成功,只能证明开发版成功。
这话听着像废话,但平台型功能最容易在这里坑人。开放数据域、广告、登录、云存储、独立引擎、分包和远程资源,都可能在开发者工具、开发版、体验版、审核版、正式版之间出现行为差异。
我的感受是,只要功能依赖平台原生能力,就要提前设计 Release 诊断。别等正式版坏了,才发现日志只活在电脑控制台里。
4,重试不是万能药。
重试适合偶发失败、网络抖动和资源晚到。可如果对象拿错了,调用层级错了,或者生命周期已经过去,重试100次只是更稳定地失败100次。
v1.0.5 的7次失败,反而是整个排查最有价值的一次结果。
它逼着我停止调参数,回去重新审视对象到底在什么时候创建,又经过了谁的包装。
5,修故障时,先守住不该动的东西。
这几轮里,我始终没有改排行榜的 rank KV,没有清玩家数据,没有改榜单排序、周周期和前20名规则,也没有关闭独立引擎去赌另一个未知问题。
每次只推进一层。
这样就算新版本仍失败,旧结论也不会被一锅端掉。等最终修好,老玩家的排行数据还能原样接回来。
顺着这件事再聊聊做小游戏。
以前我总觉得,把玩法做出来、把 bug 修完、把审核过掉,游戏就算完成了大半。
现在越来越觉得,不是。
开发可能只是一半。
另一半是怎么让别人知道它,怎么把一次踩坑变成对其他开发者有用的经验,怎么把一个陌生玩家带进来,再让他愿意多翻一对牌、多记住一幅画、多和一个好友比一次进度。
小游戏市场确实很卷。大厂有成熟买量,有素材团队,有数据团队。个人开发者很难在同一张牌桌上拼预算。
但个人开发者也有自己的东西。
我们离产品很近。
一个按钮为什么放这里,一句文案为什么让人焦虑,一张立绘为什么要先灰着、再一点点恢复颜色,一次构建为什么会在 Windows 上被 openDataContext 的旧缓存卡住,这些细节都是真的发生在手里的。
这些过程,本身就是内容。
所以这篇文章也算一个开始。
后面我想继续分享《就差一对》的真实开发记录。30MB 包体是怎么被逼到只剩18KB,后来又怎样把音乐和后期画面迁到腾讯云 COS。一个记忆翻牌游戏为什么会做「画中客」和「拾光茶馆」。手机发热怎么查。新手为什么会在第一关流失。个人主体做小游戏,又有哪些看起来能做、实际上最好别碰的合规坑。
不保证每次都有漂亮答案。
有些可能只是一次失败,一张日志,或者一个我寻思了半天也没完全寻思明白的问题。
但都是真的。
如果你想看看这次折腾出来的排行榜,也想体验一下这款正在一边开发、一边学着运营的小东西,可以在微信里搜索《就差一对》。
它是一款记忆翻牌配对小游戏,也是一间慢慢坐满画中客的拾光茶馆。
如果你进去玩了几关,排行榜里也许就会多一个名字。
这大概是对这篇文章最合适的结尾。
游戏叫《就差一对》。
而这次排行榜真正差的,不是第8次重试。
是那个在正确时间,只该被创建一次的 OpenDataContext。
以上,既然看到这里了,如果觉得不错,随手点个赞、在看、转发三连吧。如果你也做过小游戏,或者遇到过「开发版正常、发布版不正常」的离谱问题,也欢迎留言聊聊。
谢谢你看我的文章,我们,下次再见。