广告系统设计

05/07203 浏览开发心得

广告是个得罪人的活儿

做免费手游,绕不开广告变现。但广告天然令人反感——你正沉浸在游戏中,突然蹦出一段 30 秒的视频,心情能好才怪。
更难的是,你还不能不放广告。没有收入,游戏活不下去。

所以问题变成了:怎么让玩家心甘情愿地看广告,甚至觉得"看广告是赚到了"?

答案是——把广告变成"可选的加速通道",看得越多奖励越丰厚,看够了今天直接免广告。
下面讲讲具体是怎么做的。

第一层:统一入口,杜绝野广告

整个游戏只有一个地方能播放广告,就是 AdHelper.ShowRewardAd() 这个函数。
不管是领离线收益、开宝箱、还是加速训练,所有需要看广告的功能都调用它。
为什么要这么做?因为广告播放牵涉的细节很多:SDK 是否可用、广告加载中要不要显示 loading、播放到一半用户退出怎么办、回调触发时游戏状态可能已经变了……如果每个功能各写各的,迟早出 bug。

ShowRewardAd 内部干了这几件事:

先检查"免广卡"。如果玩家当天已经看满了 N 次广告(后面会讲),直接跳过广告、发放奖励、弹一个"免广卡生效"的提示。玩家连等都不用等。
再检查"免广告券"。如果玩家手里有券(里程碑奖励发的),弹窗问一句:"用一张券跳过,还是正常看广告?"选择权交给玩家。
都没有,才真正播放广告。调 SDK 的激励视频接口,播完验证成功后再发奖励。

每一步都用 pcall 包裹。

为什么?因为广告 SDK 的回调是异步的,触发的时候玩家可能已经切了界面、甚至退出了当前关卡,直接调用业务逻辑可能引用到已经不存在的对象。
一个没兜住,游戏就闪退了。线上环境闪退意味着差评,所以宁可多写几行 pcall,也不能让广告回调炸掉整个游戏。

第二层:集中计数,一处记录、多处消费

广告看完之后,AdTracker.Record() 负责计数。它只做两件事:把"今日观看数"和"累计观看数"各加 1,然后触发一连串的 hook。
这些 hook 连接到游戏里各个需要"看广告"进度的系统:
开服好礼的"观看广告"任务进度加 1
每日任务的"观看广告"进度加 1
减负系统的"当日广告计数"加 1(触发里程碑检查)
注意,AdTracker 本身不关心这些系统的具体逻辑。它只是广播一声"广告看完了",各个系统自己决定要不要响应、怎么响应。
这样新增一个"看广告送钻石"的活动,只需要在 AdTracker.Record 里加一行 hook,不用碰广告播放的逻辑。
计数数据存在 HeroData.stats.adTracker 里,跟着存档走。跨天自动重置今日计数,累计数永远不清零。

第三层:里程碑奖励——越看越值

这是整个系统的核心设计。
玩家每天看广告,看到一定次数就解锁一档奖励。有 7 个档位(3 次、6 次、9 次……一直到 N 次)。
里程碑的奖励也是精心搭配的——低档位给小额资源,中间档位加入稀有材料,最高档位是大礼包。
这种设计的意图是让每次广告都不白看。第 3 次有奖,第 6 次又有奖,玩家总觉得"再看几次就能领下一档了"。奖励是实实在在的,而且越往后越丰厚。
关于领取,有个细节值得一提:未领取的里程碑奖励,次日会自动通过邮件发放。
跨天的时候,系统会检查"昨天看够了但没领的档位",逐个生成一封邮件塞进邮箱。
这样做是因为玩家可能忘了点领取就退出了游戏,丢掉辛苦看广告换来的奖励,那他下次就不愿意看了。

第四层:免广卡——看够就"毕业"

看满指定次数,当天自动激活"免广卡"。
免广卡的效果是:当天剩下的所有广告全部自动跳过,直接拿奖励。
实现方式很直接。
ShowRewardAd 的第一步就是检查 IsAdFreeToday()——如果今日已看广告数达到了阈值,直接走"跳过广告 → 发奖励"的逻辑。
不用弹确认框,不用等 SDK 回调。
这个设计给了玩家一种心理预期:“我集中看完 20 个广告,今天后面就都是白嫖了。”
它解决了一个核心矛盾——玩家想要奖励但讨厌看广告。
免广卡把"看广告"变成了一种可以完成的任务,而不是无穷无尽的骚扰。

第五层:连续看广告的加速奖励

这个系统鼓励玩家每天都来看广告,而不只是偶尔看一次。
规则是这样的:每天看满 3 次广告,算"有效的一天"。连续多天有效,挂机加速时长就会增加:
连续 1 天:加速 2 小时
连续 3 天及以上:加速 3 小时
没有连续:保底 1 小时
如果中断了(某天没看够 3 次),加速时长不会直接归零,而是每天减 1 小时,慢慢衰减回基础值。
这比"一天断就归零"要友好得多——玩家偶尔忘了不会受到太大惩罚,这种宽容感让他更愿意保持习惯。
技术上,连续天数的更新在"跨天处理"(DayRollover)里完成。每次读取数据时先检查日期是否变了,变了就结算昨天的状态、更新连续天数和加速倍数、重置今日计数。

第六层:云端行为上报

除了本地计数,每次广告播放还会向云端排行榜上报三种事件:
ad_start_日期:开始播放
ad_done_日期:完整观看
ad_cancel_日期:中途取消
ad_total:累计总观看数(永久递增)
这不是给玩家看的,是给运营看的。通过排行榜接口可以查到每天有多少人开始看广告、多少人看完了、多少人中途退出了。
完播率太低说明广告太长或者时机不对,这些数据帮助调整策略。
用排行榜来做广告统计是个讨巧的办法——不需要额外的后端接口,利用已有的云变量 BatchSet 就能搞定每日分类和永久累计的双重计数。

防御性编程:处处 pcall

纵观整个广告系统,最显眼的特征就是到处都是 pcall。
广告 SDK 回调里套 pcall,onSuccess 回调里套 pcall,AdTracker 的 hook 里套 pcall,里程碑发奖的路由里还是 pcall。
这不是过度防御,而是血泪教训。
广告 SDK 是第三方的,它的回调时机不可控。
回调触发时,玩家可能已经:切换了界面(UI 组件已销毁)、退出了关卡(场景数据已清空)、甚至正在存档(数据处于半写入状态)。
任何一个环节没有 pcall 保护,就可能因为一个 nil 引用导致整个游戏闪退。
而广告闪退的体验极其恶劣——"我好不容易看完了广告,你给我闪退了?奖励呢?"
这种情况一次就够玩家给差评了。
所以原则很简单:广告回调链路上的每一个函数调用,都要假设对方可能不存在或者执行失败。

总结:广告不是敌人

回头看这套系统的设计思路,核心就一句话:让玩家觉得看广告是划算的。
里程碑奖励让每次广告都有回报,而且越看越值。
免广卡给了一个"看完就解脱"的终点。
连续奖励鼓励养成习惯但不惩罚偶尔的遗漏。
统一入口和集中计数保证了技术上的可靠性。
pcall 全覆盖保证了体验上的稳定性。
广告变现和用户体验不是零和博弈。
设计得当的话,玩家甚至会主动找广告看——“今天还差 2 次就能领下一档奖励了”。
这种时候,广告就不再是打断游戏的噪音,而是游戏系统的一部分。
猜你想搜
taptap 制造广告系统设计
11
3
8