路线阶段:Unity WebGL 小游戏实战第 6 章。
本章目标:建立可控商业化系统,而不是“哪里能插广告就硬插”。
学习目标
完成本章后,你应该能做到:
- 划分激励广告与插屏广告的使用场景。
- 设计广告频控、冷却和保护规则,避免体验崩坏。
- 建立奖励发放幂等机制,防止重复领奖。
- 记录收益归因事件,支持后续优化决策。
商业化基础策略
建议先做两条线:
- 激励广告(Rewarded):玩家主动触发,获得明确收益。
- 插屏广告(Interstitial):关卡结束或返回菜单时触发,需低频。
首版禁用:
- 战斗进行中强插。
- 高频弹窗诱导观看。
广告接口抽象
public enum AdType
{
Rewarded = 0,
Interstitial = 1
}
public interface IAdService
{
void Initialize();
bool IsReady(AdType type);
void ShowRewarded(string placement, Action<AdResult> onDone);
void ShowInterstitial(string placement, Action<AdResult> onDone);
}
public struct AdResult
{
public bool Success;
public bool RewardGranted;
public string Placement;
public string Error;
}
触发策略
激励广告触发点
- 失败后“复活一次”。
- 结算页“双倍奖励”。
- 每日商店“免费一次刷新”。
插屏广告触发点
- 关卡结束返回菜单。
- 连续游玩多局后的自然间隙。
频控与冷却
public sealed class AdFrequencyGuard
{
private float _lastInterstitialTime;
private int _interstitialToday;
public bool CanShowInterstitial(float now)
{
if (_interstitialToday >= 8) return false;
if (now - _lastInterstitialTime < 90f) return false;
return true;
}
public void MarkInterstitial(float now)
{
_lastInterstitialTime = now;
_interstitialToday++;
}
}
激励奖励幂等
关键:玩家观看成功后奖励只能发一次。
public sealed class RewardGrantService
{
private readonly HashSet<string> _grantedTokens = new HashSet<string>();
public bool TryGrant(string token, Action grantAction)
{
if (string.IsNullOrEmpty(token)) return false;
if (_grantedTokens.Contains(token)) return false;
_grantedTokens.Add(token);
if (grantAction != null) grantAction();
return true;
}
}
Token 可由:sessionId + placement + adImpressionId 组成。
商业化控制器
public sealed class AdFlowController
{
private readonly IAdService _ads;
private readonly EconomyService _economy;
private readonly RewardGrantService _grant;
private readonly EventBus _eventBus;
public AdFlowController(IAdService ads, EconomyService economy, RewardGrantService grant, EventBus eventBus)
{
_ads = ads;
_economy = economy;
_grant = grant;
_eventBus = eventBus;
}
public void TryShowDoubleReward(int baseReward, string sessionId)
{
const string placement = "result_double";
if (!_ads.IsReady(AdType.Rewarded))
{
_eventBus.Publish("AdUnavailable", placement);
return;
}
_ads.ShowRewarded(placement, delegate(AdResult res)
{
_eventBus.Publish("AdFinished", res);
if (!res.Success || !res.RewardGranted)
{
return;
}
var token = sessionId + "|" + placement;
var ok = _grant.TryGrant(token, delegate
{
_economy.Add(CurrencyType.MetaCrystal, baseReward, "ad_double_reward");
});
if (ok)
{
FoundationLog.Info("Ad", "reward_granted placement=" + placement + " amount=" + baseReward);
}
});
}
}
归因事件
至少记录:
ad_impressionad_clickad_completereward_granted
public struct AdTelemetryEvent
{
public string Name;
public string Placement;
public string AdNetwork;
public string SessionId;
public int StageId;
public long Ts;
}
AB 测试预留
示例实验:
- A组:失败后展示复活广告。
- B组:结算后展示双倍广告。
对比指标:
- 次日留存
- 平均局时长
- 广告完播率
- ARPDAU
WebGL 接入注意点
- 广告 SDK 回调可能从 JS 层异步回 Unity,需要主线程桥接。
- 页面失焦或弹窗拦截会影响回调时序。
- 广告失败要有兜底(直接关闭弹窗,不阻塞流程)。
与前面系统联动
- 流程状态机:广告播放期间进入
AdOverlay输入门控。 - 经济系统:奖励发放统一走货币服务。
- 存档系统:记录当日广告次数与奖励领取状态。
- 回放系统:仅记录奖励发放结果,不回放广告播放本身。
验收清单
- 激励广告成功后奖励发放准确且不重复。
- 插屏广告遵守频控,不在战斗中弹出。
- 广告失败不会卡住流程。
- 关键归因事件可在日志中追踪。
常见坑
坑 1:把奖励发放放在“点击观看”时
应以“广告回调成功且完播”作为唯一条件。
坑 2:缺少频控
短期收益上升,长期留存下降。
坑 3:广告回调直接改 UI 与状态
应通过事件总线中转,避免并发时序问题。
本月作业
实现“失败复活激励广告”完整链路:
- 每局最多复活一次。
- 广告成功后玩家原地半血复活并短暂无敌。
- 回放与日志能区分自然通关与广告复活通关。
下一章进入 Unity WebGL 小游戏实战 07:运营活动系统(限时任务、签到与版本活动开关)。