🌻 设计文档小农场 · 野花笔记
← 全部文档/野花笔记/工程

微信小游戏适配

提案 #微信小游戏#工程#优化#发布 docs/野花笔记/微信小游戏适配.md
这一份管什么

把《野花笔记》已经实测过的素材与物理参数换算成微信小游戏的包体、内存、填充率预算, 再逐项对照平台能力给出「能 / 不能 / 需要改」。 玩法本身不动(见 玩法、关卡与解谜、小游戏), 这里只回答一个问题:这套东西搬到微信小游戏里,要改哪几处、改到什么程度。


一、一句话结论#

适合,而且比大多数休闲小游戏更适合——包体和内存根本不是约束,只需要改三处工程细节。 真正决定生死的第四件事不在工程里,在留存里。

判断依据三条,每条都是算出来的,不是感觉:

# 判断 依据 结论
1 包体毫无压力 首包(主包)1.79 MB,占 4 MB 硬上限的 45%;主包 + 全部分包 4.13 MB,占 30 MB 总上限的 14% 不需要为包体做任何素材妥协
2 内存毫无压力 常驻纹理 3.0 MiB,加画布后备缓冲 8.3 MiB,水位约 11.3 MiB,是 iOS 低端机 1 GB 上限的 1.1% 内存不是瓶颈
3 真正的性能风险只有一个 竖屏横版 = 满屏重绘。按"视差 4 层 + 世界条 + 土壤 + 手账"算,每帧要填 4.94 M 像素,60 fps 就是 296 M px/s(见 4.0) 填充率是唯一要认真治的东西

必须改的三件事(都是工程,工作量可控):

  1. HUD 避让刘海与胶囊按钮——现在的 HUD 在原生 y 0~110,会被状态栏和右上角胶囊直接盖住(见 2.1);
  2. WebP 双轨——包一张 PNG 兜底图集,防止老 iOS 解不出 WebP 时首帧是空白兔子(见 2.4);
  3. wx.onHide 落盘 + wx.onShow 无弹窗续跑——"随时能停"必须落成机制,否则它只是一句宣传语(见五、六)。

第四件事不在工程里:图鉴 30 种植物 + 10 件遗物,5~8 天收齐,收齐之后没有回来的理由。 微信小游戏是"用完即走"的生态,它比任何平台都更需要一个"回来看看"的理由。 工程适配全部做完也只是"能上",第 ④ 层循环做不出来就是"上了也没人留"。 这一条的判断标准和取舍已经写在 循环与商业化 第二节,这里只把它列为上线门槛(见八)。


二、适配可行性核对表#

总表先给结论,逐条展开在后面。

# 能力 / 限制 微信小游戏的实际约束 本作怎么做 判定
1 竖屏 9:16 屏幕适配 没有"逻辑分辨率"概念,全屏画布自己算;宽高比跨度 0.43~0.625;刘海屏有 safeArea 宽恒 1080 虚拟单位、高按比例伸缩;地面线距屏底恒定 538 原生 px 需要改(HUD 避让)
2 触摸操作 wx.onTouchStart / Move / End,坐标是 CSS px 只用单指;点击 / 按住 / 滑动三种,无摇杆无虚拟键 ✅ 能
3 音频 mp3 / aac / wav 双端支持;Android 同时最多播 10 个;来电会中断 1 条 BGM + 8 个音效实例池 ✅ 能(要处理中断恢复)
4 帧动画 / WebP Canvas 2D、WebGL 都支持;WebP 解码依赖系统(iOS 14+);高性能模式的压缩纹理只有 ASTC / ETC1 保留 WebP,另出一张 PNG 兜底图集 + 启动自检 需要改(格式双轨)
5 本地存储 单个 key ≤ 1 MB,单账号总上限 10 MB;用户文件目录 200 MB(可申请 1 GB) 存档 < 1 KB(收集态用 bitmask) ✅ 能
6 云开发 小游戏支持云开发(数据库 / 存储 / 云函数),按量计费有免费额度 每账号一条文档,约 0.5 KB ✅ 能
7 分包加载 主包 ≤ 4 MB;主包+分包 ≤ 30 MB;单个普通分包不限大小;单个独立分包 ≤ 4 MB 主包 1.79 MB + 3 个分包 2.34 MB ✅ 能(余量巨大)
8 广告 激励视频 / 插屏 / Banner 都能接;流量主开通需 累计 UV > 500(或游戏圈官方帖 5 篇,或同主体已有变现满 1 季度的小程序) 只接激励视频,只放结算页 ✅ 能(难的是克制)
9 分享与好友 onShareAppMessage 可用;关系链 API(getFriendCloudStorage 等)对个人主体/部分类目已受限 分享"发现" + 带参卡片进好友花园 需要改(不依赖关系链 API)
10 登录 wx.login() 静默换 openid,没有授权弹窗;getUserInfo 已不返回真实昵称头像 全程没有"登录"这个动作 ✅ 能(天然契合)
11 支付 虚拟支付需企业主体 + 版号 P1~P3 不做内购 需要改(资质先行)
12 合规 需 ICP 备案 + 实名 + 防沉迷;未成年人限 周五/六/日及法定节假日 20:00–21:00 照接,无内购无社交攀比风险极低 需要改(接实名)

外部口径来源:分包加载、 本地存储、 文件系统、 音频、 高性能模式(内存限制)、 广告开通条件。

2.1 竖屏 9:16 与屏幕适配(需要改)#

小游戏没有"设计分辨率"这回事,画布就是屏幕。所以第一步是自建一个虚拟坐标系,规则必须是可算的,不能"差不多"。

规则(三条,缺一条就会在某些机型上错位):

  1. 宽度恒为 1080 虚拟单位,高度按屏幕实际比例伸缩: H = 1080 × screenHeight / screenWidth
  2. 兔子地面线距屏幕底部恒为 538 虚拟 px,不按比例算。
  3. 多出来的高度全给天空带(水彩大留白正好吃这个),少掉的高度从天空带顶部裁。

第 2 条是硬要求,理由是物理参数是绝对像素:重力 1800 px/s²、小跳初速 380(跳高 40 px)、 大跳初速 520(跳高 75 px)、台阶一级 22 px。这些数字一旦被缩放,跳跃余量就变了 (大跳过 72 px 的石头只剩 3 px 余量,再缩 5% 就会撞上)。 所以世界条必须是绝对尺寸,只有天空带是可伸缩的。

实测各机型换算(基准 1080×1920):

机型 / 比例 CSS 分辨率 宽高比 虚拟高 H H − 1920 天空带高度 地面线占屏高
iPhone 8 / SE2(16:9) 375×667 0.5625 1921 +1 884 72.0%
安卓 720×1280(16:9) — 0.5625 1920 0 884 72.0%
iPhone 15(19.5:9) 393×852 0.4612 2341 +421 1305 77.0%
iPhone 15 Pro Max(19.5:9) 430×932 0.4614 2340 +420 1304 77.0%
安卓 1080×2340(19.5:9) — 0.4615 2340 +420 1304 77.0%
安卓平板(16:10) — 0.625 1728 −192 692 68.9%
超长屏(21:9) — 0.4286 2520 +600 1484 78.7%

看最后两列就能得出结论:地面线的屏幕百分比在 68.9% ~ 78.7% 之间浮动, 但距屏底永远是 538。所以:

  • ✅ 地面线、地形剖面、手账条:距底锚定,绝对尺寸(538 / 153 / 269)
  • ✅ 天空带:吸收全部差额,高度区间 692 ~ 1484 px
  • ⚠️ 天空带的内容必须能在 692 px 内成立(16:10 是最坏情况):云、远山、树线、灌木 从下往上排——树线贴世界条上沿,远山在其上,云在最上面(可被裁)。 云的铺法按密度而不是固定数量,否则窄天空里会挤成一堆。

刘海屏与胶囊按钮(这是"需要改"的具体所在):

障碍 实测位置(iPhone 15,CSS 393×852) 换算到 1080 虚拟坐标 影响
状态栏 / 刘海 safeArea.top ≈ 59 CSS px ≈ 162 虚拟 px HUD 原定 y 0~110,整条被盖住
底部指示条 safeArea.bottom ≈ 34 CSS px ≈ 93 虚拟 px 手账条最下 93 px 不能放文字
右上角胶囊按钮 wx.getMenuButtonBoundingClientRect(),约 top 50 / 高 32 CSS px y ≈ 137225,**x ≈ 8801070** HUD 右上的速度档正好压在胶囊上
三条必须改的
  1. HUD 顶边距:top = max(110, safeArea.top × 1080 / screenWidth + 24) → iPhone 15 上从 0 变成 186;
  2. 速度档从右上角挪到左上或 HUD 中部——右上角永远让给胶囊按钮,这是小游戏里唯一不能碰的死区;
  3. 手账条文字底边距增加 93 虚拟 px(safeArea.bottom 换算值),别让收集格名压在 home 指示条上。

safeArea 从基础库 2.7.0 才有,且"部分机型没有安全区域概念、不返回该字段", 代码里必须有默认值兜底(用 screenHeight − 84 之类的保守估计 + wx.getMenuButtonBoundingClientRect() 交叉校验)。

2.2 触摸操作(✅ 能)#

本项目只有三种手势,没有摇杆、没有虚拟按键、没有多点,是触摸适配里最省事的一档。

手势 用在哪 实现 判据
单击 岔路口选出口 onTouchStart 记点 → onTouchEnd 结算 位移 < 10 CSS px 且时长 < 300 ms
按住 小游戏蓄力(跳铃铛等 12 种) onTouchStart 记 t0,onTouchMove 更新蓄力条,onTouchEnd 结算 蓄力 = clamp((t1−t0)/800ms, 0, 1)
滑动 手账收集格横滑 onTouchMove 的 dx 驱动内容偏移,松手做惯性 惯性每帧 ×0.94,< 30 px/s 停

实现要点四条:

  1. 坐标一律换算到 1080 虚拟系:vx = clientX × 1080 / screenWidth。 触摸事件给的是 CSS px,不要拿它和虚拟坐标直接比,否则高 DPR 机型上点不准。
  2. 热区最小 88×88 虚拟 px(≈ 44×44 CSS px,微信/苹果的最小可点尺寸)。 岔路出口牌给 240×240 虚拟 px——它要能被拇指随便点中,因为这是玩家唯一的操作。
  3. 触摸按 30 Hz 读取(每 2 帧一次)就够。蓄力条不需要 60 Hz,省下来的 CPU 留给渲染。
  4. 不做"滑动返回"拦截。小游戏里从左边缘右滑不会触发系统返回(没有页面栈), 但 Android 物理返回键会直接退出小游戏——这不是要拦的东西,因为本项目 任何一秒退出都不丢东西(见五)。

2.3 音频(✅ 能)#

项 平台约束(官方口径) 本作
格式 mp3 / aac / wav iOS 与 Android 都支持;flac / amr / wma / mp4 只有 Android 支持 统一 mp3,96 kbps 单声道(不用 wav,不用 flac)
并发 Android 上最多同时播放 10 个音频,超出部分做有损处理,开发者不感知 音效实例池固定 8 个(留 2 个余量),BGM 独立 1 个
中断 来电 / 闹钟 / 切后台会中断播放 必须监听 wx.onAudioInterruptionBegin / onAudioInterruptionEnd + wx.onShow 重建实例
静音键 iOS 静音键是否影响播放需真机核对(InnerAudioContext.obeyMuteSwitch) 首屏给可见的声音开关,默认跟随系统;同时满足合规上"提供关闭声音的能力"

体积账(按 96 kbps 单声道 = 12 KB/秒):

音频 时长 体积 放哪
音效 12 个(跳跃 / 落地 / 摘花 / 翻页 / 铃铛 / 结算 …) 平均 1.0 秒 144 KB 主包
BGM 1 首环境循环 45 秒 540 KB 主包
季节 BGM 2 首 各 45 秒 1 080 KB 分包 C
音频是"经典坑",不是难点

三件事一定会遇到,都要提前写:① 用户第一次进入时 iOS 不允许自动播(要等第一次触摸)—— 本作的第一次触摸是岔路口选出口,所以 BGM 从第 30 秒才开始,这个延迟要接受,别做成"卡住不出声"; ② 切后台再回来 BGM 静音且状态错乱 → onShow 时销毁重建而不是 play(); ③ 音效不要每次 createInnerAudioContext()——池化 8 个实例反复 stop() + play(), 否则 Android 上很快超过 10 个并发上限。

2.4 帧动画与 WebP(需要改)#

三个必须分清的概念,混起来会做出错误决策:

概念 WebP 有优势吗 数字
包体 / 下载量 ✅ 优势巨大 5 张精灵表 42 帧 = 243 KB;同样内容 PNG 预估 0.5~0.8 MB(水彩半透明边缘 PNG 压缩很差)
运行时纹理内存 ❌ 完全没优势 WebP 解码后是 RGBA8888,42 帧 × 128×128 × 4 B = 2.63 MiB,和 PNG 一模一样
GPU 压缩纹理 ❌ 不支持 iOS 高性能模式只支持 ASTC / ETC1,WebP 不是 GPU 纹理格式
所以:**不要换 PNG,要加 PNG 兜底**

换 PNG 只会把包体从 243 KB 涨到 0.5~0.8 MB,一个字节的纹理内存都省不下来。 真正要做的是"格式双轨":

  1. 主轨:WebP 图集(243 KB)。iOS 14+ / Android 全部支持;
  2. 兜底轨:一张 PNG 版图集(预估 1.4 MB),放分包 A、按需下载, 只有自检失败时才去拉。不放进主包,避免给 99% 的用户多下 800 KB;
  3. 启动自检:用 1 个 4 KB 的 1×1 WebP 做解码探测,失败 → 切 PNG 轨。 自检成本 4 KB + 一次 1 帧异步解码,可以忽略。

WebP 还有两个本项目特有的坑:

  • 水彩半透明边缘的"灰边":有损 WebP 的 RGB 与 alpha 是分开压的,透明区域的 RGB 值会被邻近像素污染, 合成后边缘出现一圈脏色。本项目整套风格都是半透明水彩边缘,这个问题一定看得见。 做法:导出前做颜色外扩(alpha bleeding / dilate)——把边缘像素的 RGB 往透明区扩 2~4 px 再压缩;
  • 不要为了"顺手"把 5 组动作合进一张大图。理由见 4.5(兔子同一时刻只播一组动作,合图集基本零收益, 反而多占 14% 纹理)。

2.5 本地存储与云开发(✅ 能)#

项 上限 本作用量 余量
storage 单 key 1 MB < 1 KB 1000 倍
storage 单账号总量 10 MB < 1 KB 1 万倍
用户文件目录(wx.env.USER_DATA_PATH) 200 MB(可申请 1 GB) 季节素材按需下载,约 2 MB 100 倍

存档小是因为收集态用 bitmask:30 种植物 + 10 件遗物 = 40 格 = 5 字节, base64 之后 8 个字符。整个存档(收集位图 + 花园状态 + 版本号 + 时间戳)约 0.5 KB。

这个尺寸带来一个重要结论:每次收集都同步落盘是可行的。 一次 < 1 KB 的 setStorageSync 耗时 < 1 ms,一局最多 40 次收集,累积开销可以忽略。 不需要做"攒起来定时写",也就不会出现"闪退丢最后几格"。

云开发不是必须的,但它是"跨设备"的唯一干净解法

单账号 storage 是设备维度的(同一微信账号换手机读不到),云开发是账号维度的。 本作没有"账号"概念(wx.login 静默拿 openid),所以云端存档天然跟着微信号走, 玩家不需要做任何"同步"动作——这正好和零操作一致。

2.6 分包加载(✅ 能,且不需要妥协)#

限制 官方数额 本作 占用
主包 ≤ 4 MB 1.79 MB 45%
主包 + 全部分包 ≤ 30 MB 4.13 MB 14%
单个独立分包 ≤ 4 MB 最大 1.34 MB 34%
关于"20 MB"还是"30 MB":**已核实,是 30 MB**

早期任务书给的口径是总包 20 MB,那个数字是错的(属于旧口径)。 从 官方现行文档 抓到的原文是:

整个小游戏所有主包+分包大小不超过 30M;主包不超过 4M; 单个普通分包不限制大小;单个独立分包不超过 4M。

本文统一按 30 MB 编预算(此前文档里一处按 30 MB、一处按 20 MB,自相矛盾,已统一)。 顺带说:按哪个口径都不影响任何决策——我们总共用 4.51 MB, 占 30 MB 的 15%、占 20 MB 的 23%,余量都在五倍以上。

另外"单个普通分包不限制大小"这条比总包上限更有用: 它意味着季节内容(分包 C,1.34 MB)将来还能继续长,不必为了分摊而硬拆。

详细的分包方案见第三节。

2.7 广告接入点(✅ 能,难在克制)#

广告形式 接不接 为什么
激励视频(wx.createRewardedVideoAd) ✅ 只接这一种,只在关卡结算页 结算页本来就是停顿点,插在这里不打断节奏;而且玩家是主动点的
插屏(InterstitialAd) ❌ 不接 会打断"看风景"这个核心动作。这是最伤的一种
Banner(BannerAd) ❌ 不接 竖屏底部就是手账条(拇指自然落点),Banner 会永久压住画面 1/9

本作唯一成立的激励视频位(详细论证见 循环与商业化 3.2):

位置 玩家换到什么 频次上限
关卡结算界面 "这一关有 3 种花没摘到,重跑这一段" 每关 1 次(一局 34 关 → 每天最多 34 次)

技术要点:激励视频实例必须复用单例(createRewardedVideoAd 返回单例), onClose 里用 res.isEnded 判断是否完整观看; show() 失败要能静默退回(不弹错误、不阻塞流程),这是零失败原则在广告上的延伸。 广告加载失败时不提示、不降级成别的广告,直接当"这个按钮今天不在"。

判断一个广告位能不能加,只问一句

"不看这个广告,我会不会觉得亏?" 会 → 这是强迫,迟早把玩家赶走。 不会 → 可以加。「重跑这一段」属于后者(不看就是不重跑,本来也没这功能); 「双倍收获」属于前者。这条标准比"能不能接"更重要。

2.8 分享与好友(需要改)#

本项目不做排行榜、不做数值攀比,社交只做两件 (这条不只是设计偏好,也是合规上的低风险选择,见七):

玩法 怎么做 技术落点
分享一朵花 手账里每种花生成一张卡片(一张花 + 一句观察,如"苔藓长在北面") wx.shareAppMessage({ imageUrl, title }),分享图不要带"点击领取"这类诱导文案
花园互访 好友点分享卡进来看"他的花园里有什么花、住了哪只小动物" ⚠️ 不能依赖关系链 API,改走带参分享卡片
赠一株花 送好友一株(自己手账里那株不会消失) 云端写一条 gift 记录,好友下次进来自动收到,不要求回赠
这里有一个必须绕开的平台限制

wx.getFriendCloudStorage / getGroupCloudStorage 这类关系链开放数据域能力, 对个人主体和部分类目已经不可用。如果"花园互访"设计成"打开好友列表挨个看", 这个功能做不出来。

可行做法:把"互访"降级为"看到朋友分享的花园"—— 分享卡片带一个花园快照 token,好友点进来渲染的就是分享那一刻的花园状态。 单向、无需授权、无需关系链,而且恰好更符合本作"不攀比"的气质: 你看到的是一张明信片,不是一个排行榜条目。


三、包体预算表#

3.1 素材清单与实测体积#

素材 数量 单件规格 磁盘体积 数据来源
兔子序列帧 5 组 / 42 帧 128×128 / 帧 243 KB 实测(stand 8 / hop 10 / leap 12 / land 6 / look 6)
场景物件 17 个 54×38 ~ 150×210 111 KB 实测(草 3 / 花 5 / 树 4 / 石 3 / 灌木 2)
植物 30 种 128×128 179 KB 实测(进分包,不进主包)
自包含演示页 1 内联全部素材 498 KB 实测(仅开发期用,不进包)
这一节的数字**整表重算过**,因为色板换了

原来的 604 KB / 671 KB 是旧色板时期的实测值。这一轮按新参考图重做了整套色板 (见 美术规范 第二节),全部素材重新生成,体积跟着变了:

素材 旧值 现值 变化
兔子序列帧 5 张表 604 KB 243 KB −60%
场景物件 17 个 145 KB 111 KB −23%
自包含演示页 671 KB 498 KB −26%

变小是合理的:新色板的暗部有颜色(色度 12~22), 而旧色板的暗部是一片中性灰、相邻像素差更小 —— 相邻像素差越小,WebP 越压不动。 换句话说,"颜色更准"顺带把包体压小了 60%。

结论没变(包体依然远不是约束,主包反而从 54% 降到 45%), 但表里的数必须是现在的数 —— 拿旧实测值去规划预算,是"文档和实物对不上"的典型。

3.2 主包预算(首包)#

主包 = 首次启动必须有、且不能等 的东西。

# 模块 内容 大小 依据
1 逻辑代码 原生 Canvas 2D 自研运行时(不引第三方引擎)+ 自动跑跳 + 岔路解谜 + 手账 + 花园 250 KB 演示页同构逻辑的上限估计
2 兔子序列帧 5 组 42 帧,128×128,WebP 243 KB 实测(色板重做后重算)
3 常驻场景物件 17 个 WebP 111 KB 实测(色板重做后重算)
4 UI 图集 手账条 / 线索卡 / HUD / 结算 / 按钮,合 1 张 1024×1024 WebP 320 KB 按 UI 面积估算
5 音效 12 个 × 12 KB(mp3 96 kbps 单声道,平均 1.0 秒) 144 KB 12 KB/秒
6 BGM 1 首环境循环 45 秒 540 KB 12 KB/秒
7 配表与文本 levels.json + 30 种植物 / 10 件遗物的中文文案 40 KB 约 12 万字节 UTF-8 的一半
8 启动图 / 图标 / 分享图 3 张 180 KB 按分辨率估算
主包合计 1 828 KB ≈ 1.79 MB 占 4 MB 上限的 45%

不放进主包的东西:30 种花的素材、10 件遗物、12 种小游戏、季节内容。 理由:第一次启动时玩家只看得到 5 种花和 1 只兔子,其余全部可以边跑边下。

冷启动下载时间估算(主包必须启动前下完):

网络 下行速度 1.79 MB 耗时
4G 良好 2 MB/s ≈ 1.1 秒
4G 一般 1 MB/s ≈ 2.2 秒
弱网 / 地铁 0.5 MB/s ≈ 4.4 秒

对比"把主包塞满 4 MB"的情况,弱网下省掉约 4.4 秒。这是"不引引擎"带来的直接收益。

3.3 分包方案#

分包 装什么 什么时候加载 大小
主包 见 3.2 启动前 1 828 KB
分包 A · 花园与图鉴 30 种植物世界图(60×90,≈150 KB)+ 30 张手账图标 96×96(≈90 KB)+ 10 件遗物(≈50 KB)+ 花园背景 1080×800(≈120 KB)+ 1 套花园装饰(≈150 KB) 第一关结算后(进花园前)后台预下载 560 KB
分包 B · 小游戏 12 种 12 种小游戏的专属素材(铃铛 / 水车 / 风车 …,12 × 30 KB = 360 KB)+ 12 段逻辑代码(≈140 KB) 跑第一关时后台预下载(第一关的小游戏是基础版,已在主包) 500 KB
分包 C · 季节内容 换季场景图集(2 个季节 × 400 KB = 800 KB)+ 季节 BGM 2 首(1 080 KB) 首次换季时(第 8 天以后) 1 340 KB
PNG 兜底图集(条件分包) 序列帧 + 物件的 PNG 版 仅 WebP 自检失败时 ≈1 400 KB
分包合计(常用) 2 400 KB ≈ 2.34 MB

总计(主包 + 常用分包)= 4 623 KB ≈ 4.51 MB,占 30 MB 上限的 15%。 加上 PNG 兜底轨也只有 5.88 MB。

为什么小游戏的素材要单独分包,而不是塞主包

12 种小游戏每种只玩 10~20 秒、每关出现 0~1 次—— 它们是"插曲",不是"必经之路"。全塞进主包会让每个玩家为了可能不做的小游戏 多下 500 KB。分包之后只有真的玩到才加载,而加载发生在"兔子在跑"的 60~90 秒里, 玩家完全感知不到。

不再需要 CDN 的理由:季节内容已经进了分包 C。剩下的边角(未来的第 3 季、限定外观) 再考虑走 wx.downloadFile 落到 wx.env.USER_DATA_PATH(200 MB 空间), 那是上线半年后的事,现在不需要为它设计。

3.4 纹理内存估算#

这是包体之外的另一本账:磁盘体积 ≠ 内存占用。图片进内存后统一是 RGBA8888(4 字节/像素)。

项 像素数 纹理内存(RGBA8888)
兔子序列帧 5 张精灵表(1024×128 / 1280×128 / 1536×128 / 768×128 / 768×128) 688 128 px 2.63 MiB
常驻场景物件 17 张 159 012 px 0.61 MiB
UI 图集 1024×1024 1 048 576 px 4.00 MiB
常驻纹理小计 7.24 MiB
画布后备缓冲(1080×1920 × 4 B,60 fps 满屏重绘) 2 073 600 px 7.91 MiB
内存水位合计 ≈ 15.2 MiB

对照官方口径:iOS 高性能模式下,2 GB RAM 机型(6s / 7 / 8)内存限制 1 GB,3 GB 以上机型 1.4 GB。

本作水位 ≈ 15.2 MiB  /  1000 MiB  =  1.5%
结论:内存完全不是约束

15.2 MiB 是低端机上限的 1.5%。序列帧 42 帧全驻留只要 2.63 MiB,这个交换太划算了 (用 2.63 MiB 换掉每帧的贴图上传),不要为了省内存去做异步流式加载序列帧。

唯一的红线是画布尺寸:UI 图集开 1024×1024 = 4 MiB,开 2048×2048 = 16 MiB, 开 4096×4096 = 64 MiB。永远不要创建超过 2048×2048 的 RGBA 画布—— 一张 4096² 就能吃掉低端机预算的 6%。"图集开大一点更整齐"是本项目最可能踩的内存坑。


四、性能优化清单(按收益排序)#

4.0 先把成本算出来(不然排序是瞎排)#

竖屏横版是满屏重绘:每一帧整个屏幕都要重画。按 1080×1920 的虚拟坐标算每帧填充量:

区域 面积(虚拟 px) 层数 每帧填充
天空 + 远景(云 / 远山 / 树线 / 灌木) 1080 × 884 ×4 3 818 880
世界条(地形 + 物件 + 兔子) 1080 × 614 ×1 663 120
地形剖面(土壤) 1080 × 153 ×1 165 240
手账条 1080 × 269 ×1 290 520
合计 4 937 760 ≈ 4.94 M px/帧

60 fps → 296 M px/s。而且水彩画风是大面积半透明混合(alpha blending), GPU 实际成本还要再乘一个系数。这就是本作唯一真实的性能风险所在—— 不是对象多,不是帧动画,是"一个屏幕上叠了太多层"。

同一项目此前在无 GPU 的测试机上量出的成本模型是 耗时(ms) ≈ 0.0221 × 实例数 + 14.8 × 百万像素——像素项的权重远大于实例项。 (那台机器是软件光栅化,绝对值不能直接用,但"往像素上使劲"这个方向是对的。)

所以下面按减少填充量排序,而不是按"看起来优雅"排序。


① 背景视差从 4 层降到 2 层,预烘焙成可平铺位图(收益最大)#

做法:把「云 + 远山」预渲染成一张 2160×884 的离屏位图(2160 = 两屏宽,保证无缝平移), 「树线 + 灌木」再一张。每帧只做 drawImage 平移 blit,不再每帧走矢量路径画云和山。

收益:

方案 天空带填充 全屏填充 相对现状
现状(4 层逐层绘制) 3 818 880 4.94 M px/帧(296 M px/s) —
合成 2 层(保视差) 1 909 440 3.03 M px/帧(182 M px/s) −39%
合成 1 层(弃视差) 954 720 2.07 M px/帧(124 M px/s) −58%

推荐 2 层:保住视差("风景即线索"需要纵深线索),拿到 −39%; 低端机档位再降到 1 层拿满 −58%。

额外收益:矢量绘制 → 位图 blit,省掉每帧的路径计算与渐变重算。 如果现在的云和远山是代码画的(本项目灰蓝水彩天空极可能是渐变 + 路径), 这一项的收益可能比 39% 还大,因为它省的不只是填充,还有 CPU 侧的绘制命令构建。

注意:预烘焙位图要按虚拟高度分段(884 / 1304 / 1484 三档), 为每档出一张,而不是用一张拉伸——拉伸会把水彩的纸纹和线条拉变形,破坏 美术规范 的验收标准。


② 低端机三档降级 + 自适应(性价比第二)#

三个旋钮的收益都是乘性的,叠加起来非常猛:

旋钮 动作 收益
渲染分辨率 DPR 2 → 1(1080×1920 → 540×960) 填充率 −75%(像素数 ∝ DPR²)
视差层数 4 层 → 2 层 → 1 层 −39% / −58%
帧率 60 → 30 每帧预算不变,但每秒总量 −50%
序列帧烘焙尺寸 128×128 → 96×96 → 64×64 纹理内存 −44% / −75%,WebP −40% / −75%

三项叠加(DPR 1 + 1 层 + 30 fps)= 高端的 1/16。

分档规则:

档 触发条件 DPR 帧率 视差层 序列帧 粒子
高 iOS A12+ ;或 benchmarkLevel ≥ 20 2 60 4 128×128 全帧率 全开
中 10 ≤ benchmarkLevel < 20 1.5 60 3 128×128 减半
低 benchmarkLevel < 10;或首 60 帧滑动均值 > 22 ms 1 30 1 64×64 重烘焙 关闭

检测:wx.getSystemInfoSync().benchmarkLevel 只在 Android 有效 (取值 -2 / 0 = 该设备无法运行小游戏,-1 = 未知,≥1 越大越好)。 iOS 上恒为 -1,所以 iOS 必须靠首 60 帧的帧时间滑动平均自适应:

若 滑动均值 > 22 ms → 降一档(有 10 秒冷却,避免抖动)
只降不升(升档会来回横跳,体验比一直低画质更差)

顺带一提:低端机降 DPR 到 1 时,画布变成 540×960, 这时候 64×64 的序列帧和 128×128 在屏幕上是一样清楚的—— 所以"降 DPR"和"降烘焙尺寸"要同时降,这是本作最省的一刀(一次改动,填充率和纹理内存双降)。


③ 对象池:横向滚动不要每帧 new(收益在 p99,不在均值)#

要池化的东西:世界条里循环滚动的所有物件。

池容量怎么算(不要拍脑袋):

最密情况:世界条宽 1080 虚拟 px ÷ 最小物件间距 60 px = 18 个/层
地形有 6 个高度层(−2 ~ 3),但同屏不重复铺满 → 有效层数取 4
物件池 = 18 × 4 = 72 → 取 80
粒子池(摘花飞出、落地尘土) = 32
兔子 + 阴影 = 2
────────────────────────────
池总容量 = 114 个实例,启动时一次分配,之后永不 new / 永不 delete

回收策略(关键):物件移出屏幕左侧时不是销毁,而是"重新绑定":

if (obj.x < -80) { obj.x += 世界条总长; 重新绑定物种/尺寸/随机相位; }

收益:GC 触发次数从"每帧若干次"降到 0 次。 这项的收益不体现在平均帧时间上,而在 p99 抖动: 16.7 ms 的预算下,一次 5 ms 的 GC 就是一次可见掉帧。 验收方式不是看平均 fps,而是看帧时间 p99 ≤ 20 ms。

顺带:粒子、飘字、收集格高亮这些反馈全部用代码画,零素材 (这是 玩法 已经写死的约定),所以它们的池化成本只是几个浮点数组。


④ 地形高度图的渲染优化#

数据结构:地形高度层 −2~3 共 6 层,一级 22 px,总落差 66 − (−44) = 110 px。 跳跃水平距离是 127 px(小跳)/ 173 px(大跳),所以高度采样间距取 8 px 足够—— 一屏世界条宽 1080 → 135 个采样点。

三条优化:

  1. 等高段合并(RLE):渲染前把高度数组压成"连续等高的段", 典型一屏只有 5~15 段,地形绘制调用从 135 次降到 ≤15 次(−89%)。
  2. 草皮顶边预渲染成 6 张切片位图(每层一张,宽 128 可横向平铺), 拼接绘制。不再每帧重画草叶的锯齿边——这些细节每帧都在变的话,肉眼看是"抖"的。
  3. 土壤剖面(153 px 高)用一张可平铺的纹理位图,每帧只 blit 一次。

收益:地形绘制耗时预计 −60~75%,且顺带把画面稳定性提上来。

这一节有一条不能违反的规矩

渲染必须从物理读的那一份高度数组派生,不许各算一遍。 渲染侧和物理侧各维护一份高度图,迟早会出现"看起来踩在地面上、其实悬空 4 px", 而这种 bug 在自动跳跃的游戏里极其难查(因为跳跃是全自动的,玩家不会触发异常操作)。 一份数据,两个消费者。小跳只跳 40 px、大跳过 72 px 的石头只剩 3 px 余量—— 4 px 的渲染偏差就足以让"看起来跳过了"变成"其实撞上了"。


⑤ 帧动画图集合并(收益小,但顺手就做)#

这一条要如实说,不能为了显得"优化了"就把它排到前面:

事实 数字
兔子同一时刻只播一组动作 纹理切换只发生在动作切换的那一帧,不是每帧
5 组动作合成一张 1536×640 的图集 纹理 2.63 MiB → 3.00 MiB(+14%),因为有 294 912 px 的空白
省下的 每帧最多 4 次纹理绑定,约 20~80 μs,占 16.7 ms 的 0.5% 以下

结论:兔子不要合图集,合了反而多占 14% 纹理内存。

真正值得合的是常驻场景物件的 17 张——它们是同屏同时出现的:

分开 合 1 张 512×512
纹理内存 0.61 MiB 1.00 MiB(+64%)
每帧纹理绑定 17 次 1 次
省下 — 16 次绑定,约 80 μs/帧

该不该做?做。 理由是它不增加包体(合并后 WebP 通常更小,因为共享调色板), 纹理 +0.39 MiB 在 15.2 MiB 的水位里毫无感知,而 bindTexture 的次数从 17 降到 1 对低端机的驱动开销是有意义的(移动 GPU 的纹理切换比桌面贵得多)。

但不要为了这一项把图集开到 2048×2048(16 MiB)——用 512×512 紧排, 浪费 19% 空间换取一整档内存,比"图集整齐"重要得多。


⑥ 首帧与冷启动#

阶段 目标 怎么做
冷启动到首帧 ≤ 100 ms 首帧只画静态层:预烘焙的天空位图 + 手账条 + 地形,全部是位图 blit,同步可完成
兔子出场 ≤ 1.5 s(中端机) 序列帧图集异步解码完成后再放兔子。宁可先给一个空风景,也不要给一只白兔子
进游戏(可交互) ≤ 2.5 s 主包 1.79 MB + 首帧渲染
首帧为什么要"先风景后兔子"

本作开局就是零操作——玩家在头 3 秒做的事就是"看"。 如果这 3 秒看到的是资源没加载完的空白,他对这个游戏的第一印象就是"卡"。 而如果先给一幅完整的静态风景(天空 + 远山 + 草地 + 手账条), 兔子晚 1 秒从左边跑进来,反而像"它从远处跑过来了",是加分不是减分。


⑦ 音频池(顺带)#

固定 8 个 InnerAudioContext 实例复用(stop() + play(),不重建), BGM 独立 1 个。原因见 2.3:Android 同时播放上限是 10 个, 每次新建实例的写法在连续收集时会迅速撞上限,表现是"音效时有时无",非常难查。


优化优先级排序(一页版)#

序 做什么 预计收益 工作量
1 背景视差 4 层 → 2 层预烘焙位图 填充 −39%(≈ 296 → 182 M px/s) 小
2 低端机三档降级(DPR / 层数 / 帧率 / 烘焙尺寸) 低端机填充最高 −94% 中
3 世界条对象池(80 物件 + 32 粒子 + 2 角色) GC p99 抖动归零(均值不变) 小
4 地形 RLE 合并 + 顶边切片位图 地形绘制 −60~75% 中
5 常驻物件 17 张 → 1 张 512×512 图集 纹理绑定 17 → 1(≈80 μs/帧) 小
— 兔子 5 组动作合图集 不做(+14% 纹理,收益 < 0.5%) —

五、操作与交互适配#

5.1 三种手势的落地#

本作没有摇杆、没有虚拟按键、没有多点,三种手势各管一件事,互不重叠:

手势 唯一用途 实现细节
单击 岔路口选出口(每关 2~3 次) 出口牌热区 240×240 虚拟 px;判据:位移 < 10 CSS px 且 < 300 ms
按住 小游戏蓄力起跳(每关 0~1 次,12 种小游戏共用这一种操作) 蓄力值 = clamp((t1−t0)/800ms, 0, 1);松手必然命中(零失败)
滑动 手账收集格横滑(看已经收了多少) onTouchMove 的 dx 驱动偏移,松手惯性每帧 ×0.94

三条实现约束:

  1. 所有触摸坐标换算到 1080 虚拟系(vx = clientX × 1080 / screenWidth);
  2. 最小热区 88×88 虚拟 px,出口牌 240×240——因为这是玩家唯一的手部操作, 点不中会让整个游戏变成"我在跟 UI 较劲";
  3. 触摸按 30 Hz 读取,不需要 60 Hz。

5.2 "随时能停"怎么从特性变成优势#

微信的核心场景是**"用完即走":秒开、随时放下、下次再来。 大多数游戏和这个场景是对抗**的(体力、冷却、连续登录、限时活动,都是在阻止你走), 本作是唯一不需要对抗的——它把"停下来"的成本做到了 0:

本作的设计 直接消灭的成本
零操作(跑跳全自动) 任何一秒放下都不会"输"——没有正在进行的操作被打断
岔路口主动停下且不计时(每 30~45 秒一次) 天然暂停点。玩家不用找"安全的地方"停下来
零失败(选错只是折返,还给一样东西) 回来后没有要补救的东西——没有心理债
无体力 / 无门票 / 无冷却 不会因为"今天玩过了"被拒之门外
不做每日签到 不会因为"断签了"而不敢回来

这四条合起来的效果:打开这个游戏的心理成本 ≈ 0,所以启动次数会高。 而微信生态里,"一天打开 3 次 × 4 分钟"比"一周打开 1 次 × 12 分钟"健康得多。

技术上必须落成机制,否则以上全是宣传语:

时机 做什么 硬指标
wx.onHide(切后台 / 下拉 / 锁屏) 立即同步落盘:兔子世界坐标 x、当前动作与帧号、当前段落种子、手账增量、花园时间戳 存档 < 1 KB,同步写入 < 1 ms
wx.onShow(回来) 什么都不弹,直接从存档续跑。玩家看到的是"兔子还在那儿" 禁止"欢迎回来"弹窗、禁止"点击继续"——那都是一次操作
wx.onAudioInterruptionEnd 销毁重建 BGM 实例(不是 play()) BGM 恢复率 100%
关卡内进度 离线不推进(回来还是原来那一步) 避免"离线跑完半关"这种失控感
花园进度 按绝对时间戳推进(见六) 与是否在线无关
把"随时能停"做成看得见的东西

不要在 HUD 上加"暂停"按钮——按钮 = 一次操作,而且它会暗示"你需要暂停"。 让兔子在岔路口自己停下来看牌子这个动作承担暂停语义就够了: 画面还在动、BGM 还在放、兔子有待机小动作(look 6 帧),但它在等你。 这是本作最优雅的一处:"停下来"看起来是剧情,不是功能。

5.3 上线后指标要按"碎片化"来定#

指标 目标 为什么是这个数
平均单次时长 3~5 分钟 一局 4~6 分钟且可中途停。不追求 10 分钟——追求长时长会诱导我们去做"让人停不下来"的设计,那是反向的
日启动次数 ≥ 2.5 次 "随时能停"的真实度量。启动次数上不去,说明"打开成本"还是太高
首关完成率 ≥ 85% 零操作应该接近 100%。低于 85% 说明第一次进入有卡点(大概率是加载或 HUD 遮挡)
岔路口停留时长 中位数 8~20 秒 太短(< 5 秒)说明线索太直白、解谜是假的;太长(> 40 秒)说明线索看不懂

六、登录与存档#

6.1 登录:本作没有"登录"这个动作#

项 做法
身份 wx.login() 静默拿 code → 云函数换 openid。没有授权弹窗(小游戏的 login 本来就是静默的)
昵称 / 头像 不要。getUserInfo 早已不返回真实昵称头像,而本作也不需要——兔子才是主角
游客模式 首启动没有账号概念。openid 静默拿,本地和云端是同一份档案;没网就纯本地
玩家感知 全程零登录动作。这是"零操作"在账号层的直接体现
这一点比看起来重要

主流小游戏的第一步是"授权头像昵称",那一次点击就是一次流失。 本作不需要昵称的原因是世界观里根本没有"玩家"这个角色—— 你是一只兔子的旁观者,旁观者不需要报名。

6.2 存档结构(约 0.5 KB)#

字段 类型 大小 说明
collectMask uint64 → base64 8 B → 12 字符 40 格(30 植物 + 10 遗物)bitmask
gardenStage uint8 1 B 花园成长阶段(0~5,对应离线时段)
lastVisitTs uint32 4 B 上次访问的绝对时间戳(秒)
decoId / petId uint8 × 2 2 B 花园装饰 / 小动物
seed uint32 4 B 本季地形与谜题种子
ver uint8 1 B 存档版本号(必须留,改结构时靠它迁移)
≈ 30 B 序列化后连同 JSON 外壳 < 500 B

6.3 三级存档与同步策略#

级 存储 时机 失败时
1 内存 每一帧 —
2 本地 storage 每次收集立即同步写(< 1 ms) 不可能失败(写失败也继续玩)
3 云开发数据库 每次关卡结算异步写(一局 3~4 次) 静默重试,不提示、不阻塞

冲突合并:取并集。 collectMask = localMask | cloudMask,gardenStage = max(local, cloud), lastVisitTs = max(local, cloud)。

这是"零失败"在存储层的延伸,也是本作最大的工程红利

因为本作的数据没有任何一项会减少(收集只增不减,花园只长不退), 所以两边存档合并永远不会丢东西,不需要时间戳覆盖、不需要"哪边更新"的判断、 更不需要弹"存档冲突"对话框。

对比之下,一个数值有涨有跌的游戏必须处理冲突覆盖,而任何覆盖策略都可能让玩家丢东西。 本作靠"只增不减"这条设计铁律,直接消掉了整个冲突处理模块。

6.4 "7 天不登录花园不会枯萎"怎么落地#

做法:绝对时间戳 + 分段函数,不模拟。

elapsed = now − lastVisitTs
stage   = f( min(elapsed, 7 天) )     ← 封顶在 7 天
离线时长 花园发生什么 玩家看到
2 小时 已收进手账的植物从灰线轮廓变成上色 图鉴里灰的变彩的
8 小时 长出一格 "?"新芽 一个新格子,点开才知道是什么
1 天 来一只小动物住下(兔子 / 鸟 / 刺猬) 花园里多一个会动的东西
3 天 换一个景(石头长苔、溪水改道) 看得出"它自己过了几天"
7 天 封顶,之后不再变化 —

为什么封顶在 7 天,两条理由都成立:

  1. 不封顶 → 离线 30 天回来一口气跳过全部内容,内容被浪费,玩家也没看到过程;
  2. 不封顶 → 反过来就得做"退化"(太久不来就枯萎),那就直接违反零失败铁律。

封顶函数是单调不减的:f(7天) = f(30天) = f(365天)。 离线一年回来,花园停在第 5 阶段,不会更差,也不会更满。

三条实现细节:

  • 不需要"领取"。离线长出来的东西直接就在那里,玩家打开就能看见。 一旦需要点一下领,它就从"发现"变成"打卡"——那就变回每日签到了;
  • 离线不推进关卡。花园按时间走,关卡进度冻结(回来还是原来那一步);
  • 不做每日签到(设计铁律,见 循环与商业化 第五节)。 花园自己长已经提供了"回来"的理由,而且不需要玩家做任何事。
唯一需要防的越线

"花园离线成长"和"每日签到"只差一步:要不要玩家点一下。 判据写死在这里:如果实现里出现"收取""领取""确认"这三个词中的任何一个,就是做错了。


七、发布与合规清单#

7.1 主体与类目#

项 建议 理由
主体 个人主体先上(无内购版本),企业主体留给后续 个人主体可上线无虚拟支付的小游戏;开通虚拟支付必须企业主体 + 版号
类目 休闲 / 益智解谜 避开"角色扮演 / 卡牌"——那些类目会额外触及抽卡概率公示、虚拟货币等要求,而本作本来就没有这些东西
分级说明 全年龄、无暴力无恐怖 灰蓝水彩、无敌人无战斗(玩法 第七节已明确"敌人/战斗/BOSS"不许加)

7.2 资质与门槛清单#

事项 要求 本作状态 周期
ICP 备案 小程序 / 小游戏上线前必须完成工信部备案 需要,提前 2~4 周启动 2~4 周
版号 内购(虚拟支付)必须;纯免费 + 广告变现按官方流量主开通条件执行 P1~P3 不做内购 → 不阻塞上线 数月(要内购才启动)
流量主(广告) 三条满足其一即可:累计独立访客 UV > 500 / 开通游戏圈并发官方帖 5 篇 / 同主体已有小程序变现满 1 季度 上线后 1~2 周可达 500 UV 1~2 周(资格次日刷新)
实名 + 防沉迷 游戏类必须接入;未成年人仅可在周五、周六、周日及法定节假日 20:00–21:00 游玩 必须接,没有内购也要接 接入 1 周
隐私协议 声明收集的信息。本作只取 openid 一份短协议,上线前必须挂 1 天
两个容易搞反的认知
  1. "没有内购就不用版号"≠"什么都不用":ICP 备案是硬门槛,没备案不能上架, 而备案要 2~4 周。这件事要第一周就去办,不能等游戏做完。
  2. "广告变现是否要版号"要按官方口径确认:公开的流量主开通条件列的是 UV / 游戏圈 / 同主体三条, 没有把版号列为前置。但这个口径会变,上线前必须以后台实际提示为准, 不要拿本文当法务依据。

7.3 内容合规注意点#

项 本作的情况 风险
内购 / 抽卡 / 赌博 全无。无抽卡、无概率获得、无限时折扣 极低
社交攀比 不做排行榜、不做分数榜、不做战力对比 极低
UGC 无玩家文本输入 极低(无内容审核压力)
分享文案 只放"我发现了一种花"这类观察,不放"点击领取""集齐 X 个领奖励" 低,但要卡死——诱导分享是审核高发区
广告 只做激励视频、只在结算页;拒绝插屏与 Banner 低
未成年人 零失败、无内购、无社交、随时能停 极低,且"随时能停"应写进审核说明作为加分项
合规上本作有一个别人没有的优势

"随时能停"是可以写进审核材料的。 一个明确不做体力、不做签到、不做排行榜、不诱导连续在线的游戏, 在"未成年人保护"这一维度上是主动合规的,不需要靠补丁去救。

7.4 分享技术的合规边界(和 2.8 呼应)#

能做 不能做
onShareAppMessage 自定义分享图与标题 分享图里放"点击领取""拉好友得奖励"
分享卡片带花园快照 token,好友点进来看到一张"明信片" 读取好友列表 / 关系链(个人主体已受限,且涉及隐私)
单向赠花(自己那份不消失) 互赠 / 互帮 / 组队(会产生"欠人情"的社交压力,且触碰更多合规面)

八、上线节奏建议#

8.1 五个阶段#

阶段 版本 做什么 周期 准入门槛
P0 内部体验版 已有 30 项自动化验收(无头 Chrome CDP)全部保留;另加 6 台真机验证 1 周 真机 60 fps 达标率 ≥ 80%;零崩溃;帧时间 p99 ≤ 20 ms
P1 纯体验版(无广告、无内购) 上线,不推广。只为拿真实数据 2~4 周 次留 ≥ 30%、7 日留存 ≥ 12%;平均单次 3~5 分钟
P2 加第 ④ 层循环 花园离线成长(6.4 那张表全部实现) 3~4 周 7 日留存 ≥ 15%;日启动 ≥ 2.5 次
P3 加变现 达 500 UV 后开流量主;结算页上激励视频;试 2 套外观 4 周 激励视频渗透率 15~30%;留存不低于 P2
P4 增长 分享卡片 + 花园"明信片"互访 持续 分享率 ≥ 8%(分享 UV / DAU)

8.2 什么叫"成功了"(每个数字都要能埋点算出来)#

指标 目标值 不达标说明什么
次日留存 ≥ 35% < 25% → 第一次进入的体验有问题(加载、HUD 遮挡、第一关不够好看)
7 日留存 ≥ 15% < 10% → 第 ④ 层循环没做对。此时绝对不要加商业化,加了只是把一个留不住的游戏更快地消耗掉
平均单次时长 3~5 分钟 > 10 分钟说明玩家在"硬玩",要重新审视节奏;< 2 分钟说明没看到值得看的东西
日启动次数 ≥ 2.5 次 < 1.5 次说明"随时能停"没变成"随时能打开"
首关完成率 ≥ 85% 零操作的游戏低于这个数一定是技术问题,不是玩法问题
分享率 ≥ 8% 分享是这套设计里唯一的自然增长手段(没有排行榜可炫耀)。< 5% 说明分享卡片做得不像"一封明信片"
收集进度 第 3 天中位数 ≥ 12/40 格 太快(3 天就 30 格)说明内容太薄;太慢说明流程太长
收齐后的次日留存(单独埋点 collection_complete) ≥ 25% 这是全项目最该盯的一个数。它直接度量"第 ④ 层循环到底有没有用"

8.3 30 项自动化验收怎么搬过来#

现有的 30 项验收跑在无头 Chrome CDP 上,小游戏里没有等价的成熟工具链。不要赌,拆成两层:

层 验收什么 怎么跑 数量
纯逻辑层(保留无头) 自动跳跃的三档判定、起跳点、缺口贴边、岔路判对判错、折返、收集、存档序列化 / 并集合并 把物理与规则抽成不依赖渲染的纯函数模块,在 Node + 无头 Chrome 里跑现有 30 项 30 项原样保留,加约 10 项新增(存档合并、bitmask)
渲染与输入层(真机) 首帧时间、帧率、帧时间 p99、内存峰值、触摸坐标换算、HUD 避让、音频中断恢复 微信开发者工具 + 6 台真机,逐台跑截图对比 + 帧时间断言 6 台 × 5 项 = 30 项
为什么这样拆是对的

本作最容易出错的逻辑全是像素级的绝对量(小跳 40 px / 大跳 75 px / 台阶 22 px / 余量 3~23 px), 这些不需要 GPU 就能验,而且必须在 CI 里每次提交都验。 渲染层的验收需要真机、无法高频跑,所以只验"是不是真的画出来了、有没有掉帧"。 把两者的频次分开,比强行统一到一套工具上更可靠。


九、风险清单#

# 风险 具体后果 缓解
1 真机性能未知 开发机没有 GPU(软件光栅化),此前所有性能数字只能看趋势。竖屏横版满屏重绘、每帧 4.94 M px,低端机上极可能掉到 30 fps 以下 ① 优化 ①②④ 全做(目标把每帧填充压到 2.07 M px 以下);② 6 台真机(含 2 台 2 GB RAM 老安卓)跑每帧强制同步的 profiler;③ 首 60 帧自适应降档兜底
2 WebP 在部分 iOS 上解不出来 WebP 依赖系统解码器(iOS 14+),老机型上首帧是空白兔子,而这是玩家看到的第一件事 ① PNG 兜底图集放条件分包(1.4 MB,只对失败机型下载);② 启动时用 4 KB 的 1×1 WebP 做解码自检;③ 自检失败静默切轨,不弹任何提示
3 第 ④ 层循环是空的(最致命,且不是工程问题) 图鉴 40 格 5~8 天收齐,之后没有理由再打开。微信是"用完即走"生态,没有"回来的理由"就等于没有留存 ① P2 必须把"花园离线成长"做出来(6.4);② 把 7 日留存 ≥ 15% 设为 P3 商业化的准入条件,不达标就不加广告;③ 单独埋点 collection_complete 后的次日留存,这是全项目最该盯的指标
4 好友关系链 API 不可用 getFriendCloudStorage 等能力对个人主体 / 部分类目已受限,"花园互访"若依赖它会直接做不出来 ① 改走带参分享卡片(花园快照 token),好友看到的是"明信片";② 把"互访"定义为"看到朋友分享的花园",不读关系链;③ P0 阶段就先验证分享链路能不能跑通,不要等做到 P4
5 备案与版号的周期撞车 ICP 备案 2~4 周;版号数月。若 P3 的外观直购要动虚拟支付,整条变现路线要往后推一个季度 ① 第一周就启动备案,不等游戏做完;② P1~P3 全部走"无内购"版本;③ 外观直购若被资质卡住,降级为"看激励视频解锁 24 小时试用"(不违反零操作,也不违反无内购)
6 音频中断后不恢复(经典坑,一定会遇到) 来电 / 闹钟 / 切后台之后 BGM 静音且状态错乱;连续收集时音效时有时无(Android 并发上限 10 个) ① 8 实例音效池,不重建;② onAudioInterruptionEnd + onShow 时销毁重建 BGM 实例;③ 首屏给可见声音开关;④ P0 阶段专门测"来一次电话再回来"
7 被误判为"低交互 / 不是游戏" 零操作 + 零失败,审核方或首批玩家可能觉得"这不算游戏",评价翻车 ① 前 30 秒必须发生三件事:兔子自己跳过一块石头、花自动飞进手账、手账格子亮起来——把"看"的价值前置;② 第一关第 40 秒就放一次小游戏(按住蓄力),让"手"尽早参与一次;③ 审核说明里主动写清"零操作"是设计选择,并附"随时能停 / 无内购 / 无排行榜"三条
8 HUD 被刘海与胶囊按钮盖住 右上角速度档正好压在胶囊上(x ≈ 8801070、y ≈ 137225 虚拟 px),顶部整条 HUD 被状态栏覆盖——首帧就是坏的 ① 顶边距 max(110, safeArea.top + 24);② 速度档挪出右上角,右上永远让给胶囊;③ safeArea 可能不返回,必须有默认兜底;④ 6 台真机逐台截图核对(已列入 8.3 的 30 项)

十、下一步工单(按依赖排序)#

序 工单 依赖 预估
1 启动 ICP 备案(周期最长,不阻塞开发) — 2~4 周(等待期)
2 抽离纯逻辑模块(物理 / 规则 / 存档),把现有 30 项验收迁过来 — 3 天
3 屏幕适配层:虚拟坐标系 + 地面线距底 538 + safeArea / 胶囊避让 — 2 天
4 背景视差 4 层 → 2 层预烘焙(收益第 1 的优化) 3 2 天
5 对象池(80 物件 + 32 粒子)+ 地形 RLE 合并 3 3 天
6 低端机三档降级 + 自适应(含 64×64 序列帧重烘焙) 4、5 3 天
7 存档层:bitmask + 本地即时落盘 + 云端并集合并 2 2 天
8 WebP 自检 + PNG 兜底轨(构建期出双份图集) 4 1 天
9 6 台真机验收(帧时间 p99、内存峰值、首帧、HUD、音频中断) 6、8 3 天
10 接实名 + 防沉迷、挂隐私协议 1 1 周
11 上线 P1 纯体验版,只埋点不推广 9、10 —
最后一句

这份文档里所有"必须改"的东西加起来大约 3 周。 真正需要提前动手的只有一件:ICP 备案——它不占开发人力,但占日历时间, 而它不到手,前面所有工作都上不了线。


相关文档#

  • 玩法 —— 自动跑跳的物理参数、竖屏三段式布局
  • 关卡与解谜 —— 岔路谜题与选错的兜底
  • 小游戏 —— 12 种小游戏与"只有一种操作"的准入标准
  • 收集手册 —— 30 种植物、10 件遗物、花园成长
  • 循环与商业化 —— 第 ④ 层循环、广告位取舍、版号路线
  • 美术规范 —— 色板、线条规则、序列帧规范与验收标准