录屏只能证明现象
Phaser 小游戏上线后,最常见的反馈是几段录屏:角色突然卡住、按钮点了没反应、Boss 血条不掉、第二局开始没有声音。录屏很有价值,但它只能证明现象,不能告诉你内部状态。工程如果只能靠录屏猜,就会把大量时间花在复现上。
我处理过一个线上问题,玩家说“第三关有时开门失败”。录屏里只看到玩家打完怪,门没有开。最初怀疑碰撞触发,后来加了事件日志才发现,怪物死亡事件发了两次,第一次减少计数,第二次因为对象池复用把新怪计数又改错。没有内部日志时,这类问题几乎只能靠运气复现。
Phaser 项目不一定需要重型监控,但需要基本可观测性:开发调试面板、关键事件日志、输入回放、性能指标和错误诊断。目标不是记录一切,而是在问题发生后能回答“当时游戏认为自己处于什么状态”。
flowchart TD
A[玩家操作] --> B[输入采样]
B --> C[游戏状态变化]
C --> D[事件日志]
C --> E[性能采样]
C --> F[错误捕获]
D --> G[本地回放/线上诊断]
E --> G
F --> G
G --> H[复现和定位]
开发调试面板先做小
调试面板不需要一开始很华丽。最有用的信息通常很朴素:当前 Scene、玩家坐标、速度、状态机状态、活动对象数量、FPS、内存估计、当前关卡、资源加载状态、输入状态。把这些显示在游戏角落,就能解决大量“感觉不对”的争论。
面板要能开关,不能影响正常体验。开发环境默认打开,测试包可通过手势或 URL 参数打开,生产包默认关闭。信息刷新频率也要控制,不要每帧生成大量字符串导致自己制造性能问题。
调试面板还要支持命令:跳关、加血、清怪、触发结算、模拟低端档位、清存档、重载配置。命令必须走正式服务接口,而不是直接改内部字段。这样调试时也能覆盖真实业务路径。
事件日志记录语义,不记录噪声
事件日志不是 console.log 的堆积。它应该记录语义事件:level.start、enemy.spawned、enemy.dead、door.opened、reward.claimed、scene.pause、asset.failed、audio.unlocked。每条事件带时间、关卡、Scene、关键参数和 trace id。
不要记录每帧位置,也不要把完整对象塞进日志。日志过多会影响性能,也会让真正有用的信息被淹没。可以保留环形缓冲区,只保存最近 200 条关键事件。玩家反馈问题时,导出这段日志就足够还原很多场景。
事件命名要稳定。今天叫 enemyDead,明天叫 enemy.dead,后天叫 monster.kill,数据就无法分析。小项目也要有事件命名表。日志不仅服务工程,也服务 QA 和运营分析。
输入回放让偶发问题可重复
输入回放的思路是记录玩家输入和关键随机种子,然后在同版本客户端里重放。对于单机玩法,只要模拟确定性足够,很多偶发问题都能复现。即使不能完全确定,输入回放也能把复现成本降很多。
记录输入时,不要只记录 pointer 坐标。还要记录时间 tick、按下/抬起、键盘状态、触控 id、屏幕尺寸、设备档位、随机种子、关卡版本和配置版本。否则换一台设备回放,结果可能不同。
回放模式下,输入系统不读取真实设备,而是按记录喂给游戏。UI 可以显示当前回放进度、暂停、逐帧、加速。对动作、弹幕、解谜类 Phaser 游戏,逐帧回放非常有价值。
随机数要可控
很多小游戏使用 Math.random()。这会让回放和问题复现非常困难。敌人刷点、掉落、暴击、关卡随机块都应该使用可注入的随机数生成器,并记录 seed。这样同一段输入在同一 seed 下更容易得到同样结果。
随机数还要按系统分流。掉落随机、关卡随机、表现随机最好不要共用一个全局序列。否则多播放一个粒子随机,就可能改变掉落结果。可以为玩法规则和纯表现分别使用不同 RNG。表现随机不应该影响规则。
如果涉及联网或奖励,权威随机应该来自服务端或可验证逻辑。客户端随机可以用于表现,但不要决定重要资产。调试回放的目标是复现客户端体验,不是让客户端成为权威。
性能遥测看峰值
FPS 平均值意义有限。玩家抱怨卡顿,通常是某些帧特别慢。性能遥测要记录帧时间分布、最大帧耗时、连续掉帧次数、活跃对象数、子弹数、粒子数、资源加载阶段和 GC 可疑峰值。Phaser 项目可以在本地采样,再按窗口汇总上报。
不要每帧上报。可以每 10 秒汇总一次,或者在发生明显卡顿时记录一次事件。事件里带当前关卡、设备档位、Scene、对象数量和最近操作。这样团队能知道卡顿集中在 Boss 第三阶段,还是集中在某个渠道 WebView。
性能遥测还要关联版本。没有版本,数据无法指导修复。一次优化发布后,要能比较新旧版本的帧时间峰值变化,而不是凭感觉说顺了。
错误捕获和玩家诊断
Phaser 运行时错误、资源加载失败、WebGL context lost、音频解锁失败、存档写入失败,都应该被捕获并转换成可诊断信息。不要让玩家只看到空白页。至少提供错误码、重试、刷新和复制诊断信息。
诊断信息可以包括:游戏版本、资源版本、浏览器 UA、设备像素比、当前 Scene、最近事件、资源失败列表、存档版本、网络状态。注意不要包含隐私数据和敏感 token。诊断信息服务定位,不是把用户信息全收集起来。
对于资源失败,日志要包含 key 和 URL。对于存档失败,要包含错误类型和存储估算。对于 WebGL 丢失,要记录 context lost 时间和是否恢复。问题越具体,修复越快。
一个事件日志骨架
下面示例展示环形事件日志。它简单,但已经比散乱 console.log 强很多。
type GameEventLog = {
at: number;
name: string;
scene: string;
data?: Record<string, unknown>;
};
class EventRecorder {
private logs: GameEventLog[] = [];
constructor(private max = 200) {}
push(event: GameEventLog) {
this.logs.push(event);
if (this.logs.length > this.max) this.logs.shift();
}
snapshot() {
return [...this.logs];
}
}
真实项目可以增加 sessionId、levelId、traceId 和导出按钮。关键是所有系统使用同一个 recorder,而不是各写各的 console。统一日志才能串起玩家路径。
工具不能改变问题本身
调试工具有一个风险:打开工具后问题消失。比如面板每帧访问对象,改变了执行时机;回放模式关闭了真实网络,掩盖了同步问题;debug 绘制增加负载,导致性能问题变严重。工具要尽量低侵入,并明确自己的影响。
可以做分级工具。一级只显示文本状态,低成本;二级显示碰撞和路径,适合本地;三级开启完整日志和回放,适合专项排查。不要让所有玩家测试包默认开启最重工具。
工具代码也要能从生产包裁剪或关闭。否则调试能力本身会成为攻击面和性能负担。尤其是跳关、加资源、清存档等命令,生产环境必须禁用或受严格开关保护。
上线前检查清单
上线前检查:调试面板是否能显示关键状态,事件日志是否有固定命名,输入回放是否记录 seed 和配置版本,性能遥测是否看峰值,资源失败是否记录 key 和 URL,玩家诊断是否可复制,工具是否能关闭,生产环境是否禁用危险命令。
还要做一次真实演练:让测试制造一个门不开、奖励失败或音频异常的问题,只给工程录屏和诊断信息,看能否定位。如果仍然必须远程连接测试机一步步猜,说明可观测性还不够。
数据采样要保护玩家体验
遥测不能为了定位问题而影响游戏本身。每帧上报、同步写日志、把大对象序列化进 localStorage,都会制造新的卡顿。比较稳的方式是本地环形缓存,按事件触发或低频汇总上报。比如发生资源失败、严重卡顿、异常退出、结算失败时,上传最近一小段上下文。
还要给遥测设预算。单局最多上传多少条事件,单条事件最大多大,弱网下是否延后,玩家退出前是否尝试发送。超过预算就采样或丢弃低优先级日志。日志系统如果没有预算,最后会和游戏资源争带宽。
隐私也要明确。诊断信息不应该包含账号 token、手机号、精确定位、聊天内容或未脱敏的用户标识。即使是内部测试包,也要养成脱敏习惯。游戏可观测性服务的是质量,不是无限收集。
回放文件要能跨版本解释
输入回放很有用,但回放文件如果没有版本信息,很快就失效。至少要记录游戏版本、资源 manifest、关卡配置 hash、脚本版本、随机种子、屏幕尺寸和设备档位。旧版本回放在新版本运行,结果不同是正常的,工具应该提示版本不匹配,而不是让工程误判。
对于长期项目,可以保留小型兼容层,让关键回放在一两个版本内仍能打开。比如字段改名时做迁移,缺少新字段时使用默认值。并不是所有回放都要永久可用,但近期线上事故的回放必须能被分析。
回放还可以成为回归测试素材。修复一个难复现 Bug 后,把对应输入记录加入手动或半自动回归清单。下次改输入系统、物理或关卡规则时,先跑这些回放,看问题是否复发。这样线上事故不会只变成一次口头经验,而会沉淀成测试资产。
让 QA 能自己拿证据
调试工具如果只有工程会用,价值会少一半。QA 最需要的是几个明确按钮:复制诊断信息、导出最近事件、保存输入回放、显示碰撞和状态、切换设备档位。按钮文案要清楚,导出的文件名要包含版本、关卡和时间。这样 QA 提 Bug 时能直接附上证据,而不是只写“偶现”。
工具还要支持标记。QA 看到异常时点一下“标记问题”,事件日志里记录 marker,回放文件也记录 marker 时间。工程打开回放后可以直接跳到标记附近,而不是从头看三分钟。这个小功能非常实用,尤其是长关卡和低概率问题。
如果项目有外部测试或渠道验收,诊断导出还要考虑脱敏和体积。外部测试包可以隐藏内部命令,只保留复制诊断和性能状态。不要把跳关、加资源这类命令暴露给不受控环境。工具分级是保护项目,也是保护测试数据。
可观测性也要有人维护
事件名、诊断字段、回放格式、性能指标都会随着项目变化而老化。新增关卡系统后,日志没有关卡版本;新增存档迁移后,诊断没有存档版本;新增 WebGL 效果后,性能指标没有效果档位。这些都会让工具逐渐失效。
因此每次做核心系统改动,都应该顺手更新可观测性。新增一个重要状态,就考虑是否要进调试面板;新增一个失败路径,就考虑是否要进事件日志;新增一个随机系统,就考虑是否要记录 seed。可观测性不是一次性工具,而是和游戏一起生长的工程资产。
最实用的标准是:下次同类问题出现时,是否能少问玩家一句,少复现一次,少猜一个模块。如果答案是否定的,说明这次修复还没有真正沉淀到工具里。
工具维护也要进入迭代定义。修复线上问题后,如果没有新增日志、回放样本或检查项,这个问题很可能会以另一种形式再次出现。
可观测性只有持续维护,才会真的降低下一次排查成本。
结语
Phaser 小游戏也需要可观测性。不是为了把项目做重,而是为了让线上问题有证据。录屏告诉你玩家看到了什么,事件日志、输入回放和遥测告诉你游戏内部发生了什么。两者结合,问题才真正可定位。
调试工具越早建立,后续迭代越快。等线上出现偶发问题再补日志,往往已经错过现场。把可观测性当成客户端基础能力,Phaser 项目即使很轻,也能更稳地面对真实玩家和真实设备。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。