统计和平台功能怎样不污染业务

人和 AI 一起做游戏 · 第 16 篇

《人和 AI 一起做游戏》第 16 篇:统计和平台功能怎样不污染业务,Kibble Street TD 历史开发画面与流程图。

本篇依据截至 2026 年 8 月 3 日的开发记录,回顾 2026 年 6—7 月及 8 月初的开发过程。正文中的“当前”、数值、画面、候选资源与验证结论均指当时快照,不代表现售版本的状态。

上一篇:广告为什么必须晚于用户同意

游戏接入统计、排行榜、支付和广告以后,代码很容易出现一种变化:原本清楚的业务流程,被越来越多的外部调用切成碎片。启动要等统计,通关要等排行榜,购买结果旁边堆满事件参数,任何一个平台异常都可能改变玩家看到的结果。

《Kibble Street TD》当前处理这类问题的核心,不是把外部能力藏起来,而是先规定一条边界:业务状态由游戏自己决定,统计负责观察,平台能力只在明确授权的范围内同步或展示。

污染业务的不是 SDK,而是职责倒置

统计和平台服务本来都是辅助能力。真正危险的是让它们变成业务前置条件:统计发送失败就不进入首页,排行榜提交失败就撤销通关,或者把平台返回的成绩写回本地存档。

业务主线与旁路能力
业务主线与旁路能力

当前项目把启动、购买、发奖、存档和战斗结算保留在业务主线上。统计从已经发生的节点旁路发送;排行榜在本地通关保存完成后异步提交;广告与内购各自通过平台服务返回明确结果,但不能绕过业务自己的校验和保存。

这种结构有一个简单判断方法:临时拔掉统计,玩家仍应完成原来的操作;临时断开排行榜,本地通关进度仍然成立。只有支付校验、广告奖励许可这类会直接改变资源的外部事实,才进入对应业务合同,而且仍要经过本地发奖与存档。

统计调用必须保证业务不用等待

项目中的所有自定义统计都通过同一个安全入口发送,业务代码不等待统计返回。这个入口并不假设调用一定成功,而是把失败拆成三层:参数生成失败、同步发送失败、异步发送失败。

统计安全发送的三层失败
统计安全发送的三层失败

三层失败都会记录事件名、失败阶段和原始错误,然后立即结束本次统计;业务函数继续执行。即使错误记录器本身异常,也不能把故障重新抛回购买、发奖、存档或战斗流程。

这不是“吞掉错误”。真正的吞错是既不影响流程,也不留下任何证据。旁路隔离要求同时满足两点:外部失败不能改变玩家结果,失败原因必须在本地日志中可追踪。少其中任何一项,都只是把问题藏到了以后。

事件应该描述业务事实,而不是界面热闹

如果只记录按钮点击,报表会很丰富,却很难回答玩家究竟完成了什么。当前事件规范把意图、开始和终态分开:点击重开只表示请求重开;战斗真正开始后才记录关卡开始;购买成功只有在验证、发奖、保存和结束交易全部完成后才成立。

意图、开始与终态的边界
意图、开始与终态的边界

事件名和参数也集中定义。关卡编号固定为字符串,错误只上传规范化的错误域与错误码,不把消息、堆栈、资源标识或整个业务对象塞进统计。Firebase 已经自动采集的首次打开、会话、广告展示和点击等事件,业务层不再重复发送。

这里最费时间的工作不是写一行发送代码,而是确定“成功”发生在哪一刻。AI 可以扫描调用顺序,发现成功事件早于存档或奖励;最终仍要由人决定产品语义,例如一次点击算意图,还是已经算作完成。

测试数据必须先标明自己来自哪里

同一个 Firebase 项目会同时收到开发调试、内测和正式版本的数据。如果没有环境标签,测试购买、模拟广告和开发流程可能与真实玩家行为混在同一张报表里。

统计环境的识别与标记
统计环境的识别与标记

当前原生层会根据应用交易环境识别 xcode、testflight、production 或 unknown,并为每条自定义事件追加 analytics_env。环境解析不会阻塞 Firebase 初始化,因为远程配置还需要及时工作;解析期间最多按顺序暂存 64 条事件,超过 5 秒仍没有结果时,以 unknown 发送并留下日志。

unknown 不是一种可忽略的数据,而是“当前无法证明来源”的明确分类。正式漏斗只读取 production,调试数据通过 DebugView 检查,内测数据单独核对。相比试图把测试流量完全藏起来,先给每条数据标明身份更可靠。

排行榜同步不能夺走本地进度的控制权

Game Center 与统计不同,它会返回登录、提交和排行榜数据,但仍不能成为本地通关的单一事实来源。玩家通关后,游戏先更新并保存最高关卡,再异步提交同一个数值。提交失败只保留待同步进度和错误,不回滚已经完成的通关。

本地进度与平台排行榜的边界
本地进度与平台排行榜的边界

重新认证、再次通关或打开排行榜时,服务会尝试补交最高进度。排行榜只读取全球前 100 名和本地玩家条目,不把平台分数写回存档,也不承担云存档同步。正式环境中,未登录、超时或读取失败会进入明确页面状态,不使用模拟玩家冒充真实结果。

这条链路也经历过一次更底层的返工。接入多个原生模块后,脚本侧只有一个原生回调入口;如果每个模块都直接占用它,后注册的功能会覆盖前一个。现在统一入口按 channel 分发到统计、排行榜、广告和支付,每个请求再用 requestId 对应自己的响应。

排行榜认证回调还曾暴露过原生对象生命周期问题。当前桥接改为 ARC 管理,认证阶段只依据系统认证状态,不急于读取玩家身份;显示名与玩家 ID 延迟到排行榜条目中获取。修复被放在平台边界,而不是让每个业务页面分别捕获同一种崩溃。

自动检查通过不等于后台已经收到数据

当前专项检查已经覆盖统计参数、同步与异步故障注入,并确认这些故障不会中断启动、购买、广告、发奖和战斗结果;排行榜检查也覆盖唯一桥接入口、请求对应、前 100 名范围、本地进度先保存后提交,以及非目标平台不制造假数据。平台权限与基础配置文件能够通过格式检查。

统计与平台能力的验证边界
统计与平台能力的验证边界

但真实设备和后台证据仍未完成:Firebase DebugView 的事件顺序与参数、内测和正式环境标签、两个 Game Center 账号的排序、断网补交、100/101 名边界和切换账号,都需要真机验证。当前全量 TypeScript 检查还存在一处设置页语言类型错误,不能把专项检查通过写成整个工程已通过。

人和 AI 分别负责什么

AI 适合枚举所有事件调用点,核对事件名、参数类型和触发顺序,注入参数构造、同步桥接和异步桥接故障,再确认业务步骤是否仍然完成。它也适合检查多个原生模块有没有争用同一个回调,或请求响应是否可能串线。

人负责定义事实边界:什么算购买成功,排行榜能否覆盖本地进度,哪些错误可以旁路,哪些必须阻断,以及什么设备与后台证据足以进入发布结论。统计口径一旦定义错误,发送得再稳定,也只会得到稳定的错误结论。

如果你想开始

先从一个关键流程入手,按顺序写出“业务改变了什么”和“外部平台只观察或同步什么”。删除所有为了统计而增加的 await,给统计入口补上参数、同步发送和异步发送三类故障检查;平台同步则明确本地与远端谁是事实来源。

最后分别断开统计和排行榜:前者不应改变玩家操作结果,后者不应丢失本地进度;同时日志和页面状态要能够说明失败发生在哪里。能继续运行,也能继续排查,边界才真正成立。