背景:激励视频广告为什么会变成真实问题
免费游戏里,激励视频看似简单:玩家点“看广告领体力”,广告播放完就发奖励。真正接入后才发现状态多得多:广告未加载、加载中、展示失败、玩家中途关闭、SDK 回调顺序异常、服务端奖励超时、前后台切换、同一按钮重复点击。某次测试里,玩家快速点两次按钮,SDK 只展示了一次广告,客户端却发了两次奖励请求。这个问题让我们重新设计了广告接入边界。
广告 SDK 是平台侧能力,Godot 客户端不应该把业务奖励逻辑直接绑在 SDK 回调上。激励视频的可靠流程应该有明确状态机:请求广告、展示、播放完成、服务端确认奖励、客户端刷新 UI。广告回调只能证明“SDK 认为播放满足条件”,不能单独证明奖励应该入账。尤其是涉及付费替代、体力、抽奖券等有价值道具时,奖励发放必须由服务端校验请求幂等性。客户端负责体验、状态、兜底提示和防重复操作。
stateDiagram-v2
[*] --> Idle
Idle --> Loading: preload
Loading --> Ready: loaded
Loading --> Failed: load_error
Ready --> Showing: user_click
Showing --> ClosedNoReward: closed_early
Showing --> Completed: rewarded_callback
Completed --> Claiming: request_server_reward
Claiming --> Granted: server_ok
Claiming --> ClaimFailed: timeout_or_error
Granted --> Idle
Failed --> Idle
ClosedNoReward --> Idle
ClaimFailed --> Idle
广告状态不要散在按钮脚本里
广告按钮只应该关心当前是否可点和点击后显示什么提示,不应该直接操作 SDK。我们做了 AdsService Autoload,内部维护每个广告位的状态。按钮订阅状态变化:Ready 显示可领取,Loading 显示加载中,Failed 显示稍后再试,Showing 禁用点击。这样大厅、体力页、复活页使用同一个广告位时,不会各自发起加载和展示请求。状态集中后,日志也能准确记录广告从加载到奖励的完整链路。
在实际执行里,我会把这一点写进开发任务,而不是只留在口头约定里。负责功能的人需要说明输入数据从哪里来、失败时 UI 怎么退回、日志里能看到哪些字段、测试要如何复现。激励视频广告相关的问题通常不会在第一天爆发,它会在几轮需求叠加后变成难以定位的偶现缺陷。提前把规则落到代码和检查清单里,成本很低,收益却会在后续版本里持续出现。
奖励发放必须幂等
激励视频最怕重复发奖。客户端点击时生成一次 reward_attempt_id,展示广告、收到完成回调、请求服务端奖励都带这个 ID。服务端保证同一个 ID 只发一次。客户端本地也记录 pending 状态,防止按钮重复点击。即使 SDK 回调两次,或者玩家在请求奖励时切后台再回来,服务端也只认第一笔。对于离线单机游戏,也要在本地存一个已处理 ID 集合,至少避免同一轮流程重复入账。
在实际执行里,我会把这一点写进开发任务,而不是只留在口头约定里。负责功能的人需要说明输入数据从哪里来、失败时 UI 怎么退回、日志里能看到哪些字段、测试要如何复现。激励视频广告相关的问题通常不会在第一天爆发,它会在几轮需求叠加后变成难以定位的偶现缺陷。提前把规则落到代码和检查清单里,成本很低,收益却会在后续版本里持续出现。
展示失败不是玩家错误
广告加载和展示失败很常见,可能是填充率、网络、SDK 内部错误或平台限制。提示文案不要责怪玩家,也不要只说“失败”。我们会区分可重试和不可重试:加载失败可以提示稍后再试;展示中断如果是玩家主动关闭,就明确没有获得奖励;SDK 错误则可以恢复按钮并记录错误码。对于关键复活场景,如果广告失败会严重影响体验,可以提供替代路径,例如消耗少量道具或等待几秒免费复活。
在实际执行里,我会把这一点写进开发任务,而不是只留在口头约定里。负责功能的人需要说明输入数据从哪里来、失败时 UI 怎么退回、日志里能看到哪些字段、测试要如何复现。激励视频广告相关的问题通常不会在第一天爆发,它会在几轮需求叠加后变成难以定位的偶现缺陷。提前把规则落到代码和检查清单里,成本很低,收益却会在后续版本里持续出现。
前后台切换要保守处理
移动端看广告时经常触发前后台切换。玩家可能点广告跳到商店,也可能系统弹权限框。Godot 层需要监听应用 pause/resume,但不要自行推断广告是否完成。只有 SDK 的 rewarded 回调或服务端确认才改变奖励状态。resume 后如果处于 Showing 状态但长时间没有回调,可以进入未知状态,提示玩家广告未完成或正在确认。不要因为应用回到前台就发奖励,也不要因为 pause 就取消流程。
在实际执行里,我会把这一点写进开发任务,而不是只留在口头约定里。负责功能的人需要说明输入数据从哪里来、失败时 UI 怎么退回、日志里能看到哪些字段、测试要如何复现。激励视频广告相关的问题通常不会在第一天爆发,它会在几轮需求叠加后变成难以定位的偶现缺陷。提前把规则落到代码和检查清单里,成本很低,收益却会在后续版本里持续出现。
聚合 SDK 要隔离平台差异
Godot 项目接广告通常需要原生插件或第三方模块。不同平台 SDK 回调名称、错误码、初始化流程不同。我们在原生层或桥接层统一成少量事件:initialized、loaded、load_failed、show_failed、opened、closed、rewarded。Godot 业务不直接依赖具体广告平台。将来切聚合平台或加渠道 SDK 时,只改适配层。这个隔离还能让编辑器里用 FakeAdsService 模拟成功、失败、延迟和重复回调,方便测试 UI。
在实际执行里,我会把这一点写进开发任务,而不是只留在口头约定里。负责功能的人需要说明输入数据从哪里来、失败时 UI 怎么退回、日志里能看到哪些字段、测试要如何复现。激励视频广告相关的问题通常不会在第一天爆发,它会在几轮需求叠加后变成难以定位的偶现缺陷。提前把规则落到代码和检查清单里,成本很低,收益却会在后续版本里持续出现。
广告体验要纳入埋点和 QA
广告接入不能只看功能通不通。需要埋点加载成功率、展示成功率、播放完成率、奖励领取成功率、平均等待时间和失败错误码。QA 用例要覆盖快速连点、无网、弱网、切后台、SDK 未初始化、服务端超时、同一广告位多入口打开。激励视频是商业化功能,也是客户端状态机能力的考验。把奖励边界守住,用户体验才不会被 SDK 的不确定性拖垮。
在实际执行里,我会把这一点写进开发任务,而不是只留在口头约定里。负责功能的人需要说明输入数据从哪里来、失败时 UI 怎么退回、日志里能看到哪些字段、测试要如何复现。激励视频广告相关的问题通常不会在第一天爆发,它会在几轮需求叠加后变成难以定位的偶现缺陷。提前把规则落到代码和检查清单里,成本很低,收益却会在后续版本里持续出现。
广告位配置要从代码里拿出来
广告位 ID、冷却时间、预加载策略、是否允许跳过、奖励类型,不应该散落在按钮脚本里。我们会维护一份广告位配置,例如 energy_reward、revive_reward、double_loot。每个广告位声明平台 ID、业务场景、奖励请求接口、每日上限和失败提示。客户端启动后根据平台和渠道选择对应配置。这样运营调整广告位或渠道切换时,不需要改每个页面。
配置也要支持灰度。某些渠道没有广告填充,就隐藏相关入口;某些地区法规要求限制激励广告,就降低展示频率。Godot 业务层只问 AdsService 当前广告位是否 available,具体原因由服务处理。按钮根据原因显示“稍后再试”“今日已达上限”或直接隐藏。
预加载不能无限并发
激励视频通常需要提前加载,否则玩家点击后等待很久。但预加载过多会占网络和 SDK 资源。我们按场景做预加载:进入大厅加载体力广告,进入战斗失败界面前预热复活广告,进入结算页预热双倍奖励广告。离开场景后,如果广告位不再可能使用,可以停止刷新。AdsService 维护有限并发,避免三个页面同时要求加载。
预加载状态要有过期时间。广告加载成功后,如果玩家十几分钟后才点击,SDK 里的广告可能已经失效。Ready 状态应该带 timestamp,超过阈值就重新加载。按钮显示也要跟着变化,不要一直显示可领取,点击后才失败。对玩家来说,点击那一刻的反馈最重要。
服务端奖励确认要设计重试
广告播放完成后,请求服务端发奖可能失败。此时玩家已经付出了观看时间,不能简单提示失败并结束。我们会把奖励请求放入 pending 队列,带 attempt_id、ad_unit、reward_type、播放完成时间。网络失败时提示“奖励确认中”,后台重试;玩家重启游戏后仍然尝试补发。服务端如果最终拒绝,也要返回可解释原因,例如广告凭证无效或已领取。
如果广告平台支持服务端回调验证,可以让服务端以平台回调为准;客户端只是通知和刷新 UI。如果暂时没有服务端验证,也至少要用幂等 ID 和账号状态保护。不要让客户端本地直接加钻石、体力再稍后同步,这会让作弊和重复发奖都变得难处理。
编辑器和自动测试需要假广告服务
真实广告 SDK 不适合在编辑器和 CI 里跑。我们实现 FakeAdsService,可以模拟加载成功、加载失败、播放完成、中途关闭、重复 rewarded 回调、展示后无回调、服务端超时。业务 UI 对接的是同一套接口,因此测试能覆盖大部分状态机。没有 Fake 服务时,很多广告问题只能靠真机手点,效率很低。
Fake 服务也帮助产品评审体验。比如复活界面点击广告后等待 2 秒再完成,按钮是否有 pending 状态;广告失败后替代路径是否清晰;奖励确认中离开页面再回来是否恢复。把这些状态在开发期看清楚,上线后就少很多争议。
合规和玩家信任
激励视频必须让玩家知道看完能得到什么,不能用误导按钮。奖励数量、条件和失败情况要写清楚。儿童、隐私、地区法规可能影响广告展示,客户端要尊重平台和服务端配置。玩家关闭个性化广告或拒绝相关权限时,游戏不能崩,也不能反复诱导。
信任还体现在奖励一致性上。玩家看完广告却没拿到奖励,是非常伤体验的事件。即使最终需要人工客服处理,客户端也应该能提供 attempt_id 和时间,让客服查得到。广告系统表面是商业化,底层其实是状态机、平台适配、服务端契约和用户信任的组合。
广告入口要和经济系统对账
激励视频发放的奖励通常接入经济系统。客户端展示按钮前,应该知道玩家是否还能领取、每日次数是否用完、背包是否已满、当前玩法是否允许广告复活。不要等广告看完再发现奖励无法发。AdsService 可以和 EconomyService 或服务端接口配合,先拿到 eligibility,再展示入口。入口状态包括可看、次数用完、条件不满足、广告不可用。
对账也包括 UI 刷新。服务端发奖成功后,客户端不要只更新当前页面的数字,还要让全局货币栏、任务进度、红点系统收到同一份变更。否则玩家看完广告,弹窗显示成功,货币栏却没变,会怀疑奖励丢了。奖励流程应该复用正常道具入账通道,而不是广告页面自己加显示值。
错误码要翻译成行动
广告 SDK 的错误码很多,直接展示给玩家没有意义,但对排查很有价值。我们把错误分成 no_fill、network、not_initialized、show_in_progress、internal、platform_restricted。玩家看到的是行动建议:暂无广告、检查网络、稍后再试;日志里保留原始平台、广告位、错误码和状态。这样客服和开发可以查,玩家也不会被技术细节困住。
同一个错误如果连续出现,入口可以短暂冷却,避免玩家反复点击。比如 no_fill 后 30 秒内不再请求同广告位,network 错误则等网络状态变化后再尝试。冷却策略既保护 SDK,也减少无效挫败感。
结语
这类系统在 Godot 里往往不是“某个 API 会不会用”的问题,而是边界有没有提前说清楚。节点、资源、平台能力和业务状态都很灵活,灵活就意味着团队需要给它们加上可维护的秩序。我的经验是,先把生命周期、输入输出、失败路径和调试信息写明,再去追求抽象优雅。这样项目进入频繁迭代期时,新增需求不会把旧功能挤得变形,排查问题的人也能从日志、结构和约定里找到线索。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。