游戏客户端每日签到日历:把 30 个格子做成可信的状态机

从时间口径、格子状态、领取事务、红点和跨月测试讨论每日签到日历的客户端实现。

签到日历是运营活动,也是客户端状态机

每日签到在需求文档里经常只有几行:展示本月奖励、玩家点击领取、连续签到给额外奖励、漏签可补签。真正落到客户端,问题会变成一串细节:今天到底按服务器时区还是本地时区计算,跨天时界面是否自动刷新,领取按钮点击两次会不会重复弹奖励,离线重连之后日历格子是不是要回滚,补签券不足时是否能预览缺口。

我在项目里更愿意把签到日历当成一个小型交易界面,而不是一张静态 UI。它有服务端事实、有客户端缓存、有动画、有红点、有领奖事务,也有跨天事件。只要把这些状态揉在一个 OnClick 里,月底活动、节假日奖励翻倍、版本热更都会让逻辑变得很难维护。

stateDiagram-v2
    [*] --> Loading
    Loading --> Ready: 拉取服务器日历
    Loading --> CacheOnly: 网络失败且有缓存
    Ready --> Claiming: 点击领取
    Claiming --> Ready: 领取成功并刷新格子
    Claiming --> Ready: 领取失败并恢复按钮
    Ready --> Refreshing: 跨天/重连/活动配置更新
    CacheOnly --> Refreshing: 网络恢复
    Refreshing --> Ready: 对齐服务器状态

先分清三类时间

签到系统最怕“今天”这个词。玩家手机时间、服务器业务日、活动自然日不一定相同。比如服务器按北京时间 5 点刷新,活动在 4 月 3 日 10 点开启,那么 4 月 3 日凌晨 2 点登录的玩家看到的可能还是 4 月 2 日业务日;活动开启之前又不能提前显示领取状态。

客户端应该只把本地时间用于倒计时展示的平滑刷新,不用它决定是否可领。真正的 businessDayactivityIdclaimableIndex 都来自服务端。界面层保存服务器返回的时间戳和本地接收时间,用它们估算“距离下一次刷新还有多久”,但一旦玩家点击领取,仍然以服务器返回为准。

一个实用做法是把日历模型拆成 ActivityConfigServerSnapshotViewClock。配置负责奖励和格子描述,快照负责已领、可领、补签、连续天数,视图时钟只负责倒计时文字。这样配置热更不会覆盖玩家状态,状态刷新也不会重建全部 UI。

格子状态不要只用布尔值

日历格子看起来只有已领和未领,但实际至少有:未开启、错过、可补签、今日可领、已领、未来、活动结束、配置异常、网络待确认。把这些状态压缩成两个布尔值会让视觉和点击逻辑互相猜测。

我通常给每个格子生成一个 CellViewModel:日期、奖励图标、数量、角标、主状态、次状态、点击动作、无障碍文本。UI 只根据模型渲染,不再访问活动原始数据。领取成功后只替换受影响的格子模型,而不是让整个日历面板重开。

这种设计还有一个好处:运营临时加“第 7 天任选箱”“周末双倍”“新服前 3 天额外补贴”时,不用让按钮逻辑判断所有活动规则。规则层计算出格子状态,UI 层只负责稳定呈现。

领取是事务,不是动画

玩家点领取后,客户端当然要播放光效和飞物品动画,但动画不能代表到账。正确顺序是:先锁住按钮,发送领取请求;服务端成功返回后更新状态和背包增量;再播放奖励展示;展示完成后解除锁定并刷新红点。如果失败,按钮要恢复到之前状态,并给出可理解的原因。

这里有一个容易踩的坑:奖励弹窗和日历面板都监听背包变化,导致一次领取弹两次奖励。客户端可以约定签到领取走“活动奖励展示通道”,背包监听只更新数量,不主动弹窗。每个奖励批次带一个 sourcetransactionId,展示系统根据来源决定是否合并。

对弱网场景也要谨慎。玩家点了领取后断线,如果服务端已经记账而客户端没收到响应,下次进入面板应以服务器快照为准,看到格子已领并补发一次“上次奖励已到账”的轻提示即可,不要再主动请求领取。

红点和入口要能解释

签到红点经常被玩家吐槽“有红点但点进去没东西”。原因通常是入口红点用本地日期算,面板用服务器状态算;或者红点缓存没有随领取事务清掉。红点系统应该订阅同一个 ServerSnapshot,只根据“存在可领取或即将过期且可补签”的事实显示。

入口上可以展示剩余天数、今日可领、连续进度,但不要把所有信息都塞到小角标。更重要的是让玩家知道为什么有红点:今日奖励可领、补签券即将过期、连续奖励待领取。客户端调试面板里也应能看到红点来源,否则线上定位会很痛苦。

月末和跨月是测试重点

签到日历到了 4 月 30 日最容易暴露问题:下个月配置是否已预加载,活动是否按自然月切换,连续签到是否跨月保留,补签是否允许补上月,月末 23:59 点击领取是否会被下月刷新打断。测试不能只改手机时间,应该提供服务端模拟时间或测试环境开关。

客户端自动化可以覆盖几个关键用例:首次进入、领取成功、领取失败、跨天刷新、跨月刷新、弱网重试、活动结束、配置缺失。每个用例只要截取日历模型快照,不一定非要跑完整 UI 动画。模型快照稳定,UI 问题再单独用截图验证。

动画要服务节奏

签到是低频界面,动画可以比战斗 UI 慢一点,但不能拖。玩家每天进来可能只想领完就走。第一个奖励展示最好在 300 毫秒内有反馈,完整飞行动画可跳过。连续签到奖励可以做得更隆重,但必须允许快速关闭。

一套舒服的节奏是:按钮按下立即缩放,成功后格子点亮,奖励图标从格子飞向背包或弹窗,红点在弹窗关闭后消失。失败时不要播放“半成功”光效,直接恢复按钮并给出提示。动画系统只消费事务结果,不反向驱动状态。

配置校验比界面更重要

签到配置由运营频繁调整,客户端至少要校验日期数量、奖励 ID、奖励数量、补签规则、连续奖励索引、活动时区、互斥活动关系。发现配置异常时,宁愿隐藏异常格子并上报,也不要显示空白奖励让玩家点击。

上线前可以生成一份“日历展开表”:每一天是什么奖励、是否可补签、累计奖励在哪一天、客户端资源是否存在。策划、运营、QA 都看同一份表,很多错误会在发布前被发现。

小结

签到日历的难点不在画 30 个格子,而在时间、状态、事务和红点一致性。客户端把它做成可解释的状态机,后续无论加补签券、节日翻倍还是新服专属奖励,都不会把界面逻辑拖进混乱。一个玩家每天只看十几秒的页面,也值得用严谨的工程方式维护,因为它直接影响留存和信任。
实际落地时,我会要求每个活动面板都能打印当前活动 ID、服务器业务日、快照版本号和红点来源。这个调试信息不面向玩家,却能让测试在录屏里直接说明问题,也能让客户端和服务端少花很多时间互相猜测。

实际落地时,我会要求每个活动面板都能打印当前活动 ID、服务器业务日、快照版本号和红点来源。这个调试信息不面向玩家,却能让测试在录屏里直接说明问题,也能让客户端和服务端少花很多时间互相猜测。

实际落地时,我会要求每个活动面板都能打印当前活动 ID、服务器业务日、快照版本号和红点来源。这个调试信息不面向玩家,却能让测试在录屏里直接说明问题,也能让客户端和服务端少花很多时间互相猜测。

实际落地时,我会要求每个活动面板都能打印当前活动 ID、服务器业务日、快照版本号和红点来源。这个调试信息不面向玩家,却能让测试在录屏里直接说明问题,也能让客户端和服务端少花很多时间互相猜测。

实际落地时,我会要求每个活动面板都能打印当前活动 ID、服务器业务日、快照版本号和红点来源。这个调试信息不面向玩家,却能让测试在录屏里直接说明问题,也能让客户端和服务端少花很多时间互相猜测。

实际落地时,我会要求每个活动面板都能打印当前活动 ID、服务器业务日、快照版本号和红点来源。这个调试信息不面向玩家,却能让测试在录屏里直接说明问题,也能让客户端和服务端少花很多时间互相猜测。

实际落地时,我会要求每个活动面板都能打印当前活动 ID、服务器业务日、快照版本号和红点来源。这个调试信息不面向玩家,却能让测试在录屏里直接说明问题,也能让客户端和服务端少花很多时间互相猜测。

实际落地时,我会要求每个活动面板都能打印当前活动 ID、服务器业务日、快照版本号和红点来源。这个调试信息不面向玩家,却能让测试在录屏里直接说明问题,也能让客户端和服务端少花很多时间互相猜测。

实际落地时,我会要求每个活动面板都能打印当前活动 ID、服务器业务日、快照版本号和红点来源。这个调试信息不面向玩家,却能让测试在录屏里直接说明问题,也能让客户端和服务端少花很多时间互相猜测。

实际落地时,我会要求每个活动面板都能打印当前活动 ID、服务器业务日、快照版本号和红点来源。这个调试信息不面向玩家,却能让测试在录屏里直接说明问题,也能让客户端和服务端少花很多时间互相猜测。

实际落地时,我会要求每个活动面板都能打印当前活动 ID、服务器业务日、快照版本号和红点来源。这个调试信息不面向玩家,却能让测试在录屏里直接说明问题,也能让客户端和服务端少花很多时间互相猜测。

实际落地时,我会要求每个活动面板都能打印当前活动 ID、服务器业务日、快照版本号和红点来源。这个调试信息不面向玩家,却能让测试在录屏里直接说明问题,也能让客户端和服务端少花很多时间互相猜测。

实际落地时,我会要求每个活动面板都能打印当前活动 ID、服务器业务日、快照版本号和红点来源。这个调试信息不面向玩家,却能让测试在录屏里直接说明问题,也能让客户端和服务端少花很多时间互相猜测。

实际落地时,我会要求每个活动面板都能打印当前活动 ID、服务器业务日、快照版本号和红点来源。这个调试信息不面向玩家,却能让测试在录屏里直接说明问题,也能让客户端和服务端少花很多时间互相猜测。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页