H5游戏广告接入完全指南:AdSense for Games (AFG) 实战

引言

HTML5 游戏凭借其跨平台特性和无需安装的优势,越来越受到开发者和玩家的青睐。而广告变现是 H5 游戏的主要收入来源之一。Google 的 AdSense for Games (AFG) 提供了专门针对游戏的广告解决方案,支持横幅、插屏、激励视频等多种广告形式。本文将详细介绍如何在 Cocos Creator 等 H5 游戏引擎中接入 AFG 广告。

AdSense for Games 概述

广告形式

类型 说明 适用场景 收益潜力
横幅广告 底部/顶部固定展示 游戏进行中的持续展示 中等
插屏广告 全屏展示,可跳过 关卡切换、暂停菜单 较高
激励视频 观看视频获得奖励 复活、双倍奖励、道具 最高

接入要求

  1. 域名要求:需要拥有合法的网站域名
  2. 游戏质量:内容健康,无版权问题
  3. 流量要求:满足 AdSense 最低流量要求
  4. 合规性:遵守 Google AdSense 政策

环境准备

HTML 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>H5 Game</title>
<!-- 游戏样式 -->
<style>
#GameDiv {
position: relative;
width: 100%;
height: 100%;
}

#advertise {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
visibility: hidden;
z-index: 1000;
}

#adContainerDiv {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<!-- 游戏容器 -->
<div id="GameDiv"></div>

<!-- 游戏脚本 -->
<script src="game.js"></script>
</body>
</html>

广告管理类实现

核心 AdSense 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// AdSense.js
let AdSense = cc.Class({
properties: {
preloadedRewardedVideoCallbackObj: null,
adsManager: null,
adsLoader: null,
adDisplayContainer: null,
intervalTimer: null,
adTagUrl: null,
linearAdSlotWidth: 0,
linearAdSlotHeight: 0,
nonLinearAdSlotWidth: 0,
nonLinearAdSlotHeight: 0,
advertiseDiv: null,
videoContent: null,
adContainerDiv: null,
},

/**
* 初始化广告系统
*/
init() {
// 初始化 Google IMA SDK
window['googletag'] = window['googletag'] || {};
let googletag = window['googletag'];
googletag.cmd = googletag.cmd || [];

// 加载 IMA SDK
this._loadIMASDK();

// 创建广告容器
this._createAdContainer();
},

/**
* 加载 IMA SDK
*/
_loadIMASDK() {
let gads = document.createElement('script');
gads.type = 'text/javascript';
gads.src = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js';

let node = document.getElementsByTagName('script')[0];
node.parentNode.insertBefore(gads, node);
},

/**
* 创建广告容器
*/
_createAdContainer() {
// 获取或创建广告容器
this.advertiseDiv = document.getElementById('advertise');

if (this.advertiseDiv == null) {
this.advertiseDiv = document.createElement('div');
this.advertiseDiv.id = 'advertise';
this.advertiseDiv.style.cssText = `
border: 0;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
visibility: hidden;
z-index: 1000;
`;

let gameDiv = document.getElementById('GameDiv');
gameDiv.appendChild(this.advertiseDiv);
}

// 创建视频内容元素(IMA SDK 需要)
this.videoContent = document.createElement('video');
this.videoContent.style.display = 'none';

let contentDiv = document.createElement('div');
contentDiv.id = 'contentDiv';
contentDiv.appendChild(this.videoContent);

// 创建广告容器
this.adContainerDiv = document.createElement('div');
this.adContainerDiv.id = 'adContainerDiv';
this.adContainerDiv.style.cssText = `
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
`;

this.advertiseDiv.appendChild(contentDiv);
this.advertiseDiv.appendChild(this.adContainerDiv);
},

/**
* 显示广告
* @param {number} type - 广告类型: 1=横幅, 2=插屏, 3=激励视频
* @param {Object} callbackObj - 回调对象
*/
show(type, callbackObj) {
let size = cc.view.getFrameSize();
let width = size.width;
let height = size.height;

// 根据类型设置广告参数
switch (type) {
case 1: // 横幅广告
this.adTagUrl = game.setting.adsense.text;
height = 64; // 横幅高度
break;
case 2: // 插屏广告
this.adTagUrl = game.setting.adsense.image;
break;
case 3: // 激励视频
this.adTagUrl = game.setting.adsense.video;
this.preloadedRewardedVideoCallbackObj = callbackObj;
break;
default:
console.error('Unknown ad type:', type);
return;
}

this.linearAdSlotWidth = width;
this.linearAdSlotHeight = height;
this.nonLinearAdSlotWidth = width;
this.nonLinearAdSlotHeight = height;

// 等待 IMA SDK 加载完成
this._waitForIMA(callbackObj);
},

/**
* 等待 IMA SDK 加载
*/
_waitForIMA(callbackObj) {
let checkCount = 0;
let maxChecks = 30; // 最多等待30秒

this.scheduleTimer = setInterval(() => {
checkCount++;

if (window['google'] && window['google']['ima']) {
clearInterval(this.scheduleTimer);
this.requestAds();
} else if (checkCount >= maxChecks) {
clearInterval(this.scheduleTimer);
console.error('IMA SDK load timeout');
if (callbackObj && callbackObj.onError) {
callbackObj.onError('SDK load timeout');
}
}
}, 1000);
},

广告请求与展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
* 请求广告
*/
requestAds() {
// 清空之前的广告
this.adContainerDiv.innerHTML = '';

// 创建广告显示容器
this.adDisplayContainer = new google.ima.AdDisplayContainer(
this.adContainerDiv,
this.videoContent
);

// 初始化容器(移动端需要在用户交互后调用)
this.adDisplayContainer.initialize();

// 创建广告加载器
this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer);

// 添加事件监听
this._addAdsLoaderListeners();

// 创建广告请求
let adsRequest = new google.ima.AdsRequest();
adsRequest.adTagUrl = this.adTagUrl;
adsRequest.linearAdSlotWidth = this.linearAdSlotWidth;
adsRequest.linearAdSlotHeight = this.linearAdSlotHeight;
adsRequest.nonLinearAdSlotWidth = this.nonLinearAdSlotWidth;
adsRequest.nonLinearAdSlotHeight = this.nonLinearAdSlotHeight;

// 可选:强制非线性广告全屏
// adsRequest.forceNonLinearFullSlot = true;

// 发送广告请求
this.adsLoader.requestAds(adsRequest);
},

/**
* 添加广告加载器事件监听
*/
_addAdsLoaderListeners() {
// 广告管理器加载完成
this.adsLoader.addEventListener(
google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
this.onAdsManagerLoaded.bind(this),
false
);

// 广告错误
this.adsLoader.addEventListener(
google.ima.AdErrorEvent.Type.AD_ERROR,
this.onAdError.bind(this),
false
);
},

/**
* 广告管理器加载完成回调
*/
onAdsManagerLoaded(adsManagerLoadedEvent) {
// 获取广告管理器
this.adsManager = adsManagerLoadedEvent.getAdsManager(this.videoContent);

// 添加广告事件监听
this._addAdsManagerListeners();

// 初始化广告管理器
try {
this.adsManager.init(
this.linearAdSlotWidth,
this.linearAdSlotHeight,
google.ima.ViewMode.NORMAL
);

// 开始播放广告
this.adsManager.start();
} catch (adError) {
console.error('AdsManager init error:', adError);
this.onAdError(adError);
}
},

/**
* 添加广告管理器事件监听
*/
_addAdsManagerListeners() {
// 错误处理
this.adsManager.addEventListener(
google.ima.AdErrorEvent.Type.AD_ERROR,
this.onAdError.bind(this)
);

// 内容暂停请求(开始播放广告)
this.adsManager.addEventListener(
google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED,
this.onContentPauseRequested.bind(this)
);

// 内容恢复请求(广告结束)
this.adsManager.addEventListener(
google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED,
this.onContentResumeRequested.bind(this)
);

// 所有广告完成
this.adsManager.addEventListener(
google.ima.AdEvent.Type.ALL_ADS_COMPLETED,
this.onAdEvent.bind(this)
);

// 广告加载完成
this.adsManager.addEventListener(
google.ima.AdEvent.Type.LOADED,
this.onAdEvent.bind(this)
);

// 广告开始
this.adsManager.addEventListener(
google.ima.AdEvent.Type.STARTED,
this.onAdEvent.bind(this)
);

// 广告完成
this.adsManager.addEventListener(
google.ima.AdEvent.Type.COMPLETE,
this.onAdEvent.bind(this)
);

// 用户跳过广告
this.adsManager.addEventListener(
google.ima.AdEvent.Type.SKIPPED,
this.onAdEvent.bind(this)
);
},

广告事件处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
    /**
* 广告事件处理
*/
onAdEvent(adEvent) {
let ad = adEvent.getAd();

switch (adEvent.type) {
case google.ima.AdEvent.Type.LOADED:
console.log('Ad loaded, content type:', ad.getContentType());
this.setupUIForAds(ad.getContentType());
break;

case google.ima.AdEvent.Type.STARTED:
console.log('Ad started');
if (ad.isLinear()) {
// 启动计时器更新剩余时间
this.intervalTimer = setInterval(() => {
let remainingTime = this.adsManager.getRemainingTime();
console.log('Ad remaining time:', remainingTime);
}, 300);
}
break;

case google.ima.AdEvent.Type.COMPLETE:
console.log('Ad completed');
if (ad.isLinear()) {
clearInterval(this.intervalTimer);
}
this.setupUIForContent();
break;

case google.ima.AdEvent.Type.SKIPPED:
console.log('Ad skipped');
if (ad.isLinear()) {
clearInterval(this.intervalTimer);
}
this.setupUIForContent();
break;

case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:
console.log('All ads completed');
this.setupUIForContent();
break;
}
},

/**
* 内容暂停请求(广告即将播放)
*/
onContentPauseRequested() {
console.log('Content pause requested');
// 暂停游戏
cc.director.pause();
this.setupUIForAds();
},

/**
* 内容恢复请求(广告结束)
*/
onContentResumeRequested() {
console.log('Content resume requested');

// 激励视频回调
if (this.preloadedRewardedVideoCallbackObj) {
this.preloadedRewardedVideoCallbackObj.onComplete();
this.preloadedRewardedVideoCallbackObj = null;
}

// 恢复游戏
cc.director.resume();
this.setupUIForContent();
},

/**
* 广告错误处理
*/
onAdError(adErrorEvent) {
console.error('Ad error:', adErrorEvent.getError());

// 错误回调
if (this.preloadedRewardedVideoCallbackObj) {
this.preloadedRewardedVideoCallbackObj.onError(adErrorEvent.getError());
this.preloadedRewardedVideoCallbackObj = null;
}

// 恢复游戏界面
this.setupUIForContent();
cc.director.resume();
},

/**
* 设置广告展示 UI
*/
setupUIForAds(contentType) {
// 根据内容类型调整广告容器
if (contentType === 'text') {
// 文字广告,调整高度
this.advertiseDiv.style.height = '63px';
this.advertiseDiv.style.top = 'auto';
this.advertiseDiv.style.bottom = '0';
} else {
// 图片/视频广告,全屏
this.advertiseDiv.style.height = '';
this.advertiseDiv.style.top = '0';
this.advertiseDiv.style.bottom = '0';
}

this.advertiseDiv.style.visibility = 'visible';
},

/**
* 设置内容展示 UI(隐藏广告)
*/
setupUIForContent() {
this.advertiseDiv.style.visibility = 'hidden';

// 清理广告管理器
if (this.adsManager) {
this.adsManager.destroy();
this.adsManager = null;
}
}
});

// 导出单例
game.adSense = new AdSense();
module.exports = game.adSense;

在游戏中的使用

初始化与预加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Game.js
const AdSense = require('AdSense');

cc.Class({
extends: cc.Component,

onLoad() {
// 初始化广告系统
AdSense.init();

// 预加载广告(可选)
this.preloadAds();
},

preloadAds() {
// 提前加载广告,减少展示延迟
// 在游戏启动时或关卡加载时预加载
console.log('Preloading ads...');
}
});

展示横幅广告

1
2
3
4
5
6
7
8
9
// 在需要展示横幅广告的地方调用
showBannerAd() {
// 通常在游戏主界面或关卡选择界面展示
AdSense.show(1, {
onError: (error) => {
console.log('Banner ad error:', error);
}
});
}

展示插屏广告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 在关卡切换时展示插屏广告
onLevelComplete() {
// 暂停游戏逻辑
this.pauseGame();

// 展示插屏广告
AdSense.show(2, {
onError: (error) => {
console.log('Interstitial ad error:', error);
// 继续下一关
this.nextLevel();
}
});
}

展示激励视频广告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 激励视频广告 - 用户观看广告后获得奖励
onDoubleRewardButtonClick() {
let callbackObj = {
onComplete: () => {
// 用户完整观看了广告,发放奖励
this.grantDoubleReward();
cc.log('Reward granted!');
},
onError: (error) => {
// 广告加载或播放失败
this.showToast('广告加载失败,请稍后重试');
cc.log('Ad error:', error);
}
};

// 显示激励视频
AdSense.show(3, callbackObj);
}

// 复活功能
onReviveButtonClick() {
AdSense.show(3, {
onComplete: () => {
// 复活玩家
this.revivePlayer();
cc.log('Player revived!');
},
onError: (error) => {
this.showToast('无法加载视频,请检查网络');
}
});
}

最佳实践

1. 广告展示频率控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// AdFrequencyController.js
class AdFrequencyController {
constructor() {
this.lastInterstitialTime = 0;
this.interstitialInterval = 3 * 60 * 1000; // 最少间隔3分钟
this.dailyAdCount = 0;
this.maxDailyAds = 20;
}

canShowInterstitial() {
let now = Date.now();
let timeSinceLast = now - this.lastInterstitialTime;

if (timeSinceLast < this.interstitialInterval) {
console.log('Interstitial cooldown');
return false;
}

if (this.dailyAdCount >= this.maxDailyAds) {
console.log('Daily ad limit reached');
return false;
}

return true;
}

recordInterstitialShown() {
this.lastInterstitialTime = Date.now();
this.dailyAdCount++;
}
}

2. 用户体优化

1
2
3
4
5
6
7
8
9
10
// 在用户操作后展示广告(避免自动弹出)
onUserClickShowAd() {
// 好的做法:用户主动点击按钮后展示
this.showAdButton.on('click', () => {
AdSense.show(3, callbackObj);
});
}

// 避免的做法:自动弹出广告
// ❌ 游戏进行中突然弹出广告

3. 错误处理与降级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
showAdWithFallback(adType, callback) {
let attempts = 0;
let maxAttempts = 3;

let tryShowAd = () => {
AdSense.show(adType, {
onComplete: () => {
callback.onComplete();
},
onError: (error) => {
attempts++;
if (attempts < maxAttempts) {
console.log(`Retrying ad (${attempts}/${maxAttempts})...`);
setTimeout(tryShowAd, 2000);
} else {
console.log('Ad failed after max attempts');
// 降级处理:直接给予奖励或继续游戏
callback.onFallback();
}
}
});
};

tryShowAd();
}

常见问题

Q1: 广告加载失败怎么办?

原因:

  • 网络连接问题
  • 广告填充率问题
  • 配置错误

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 设置超时和重试机制
loadAdWithTimeout(timeout = 10000) {
return new Promise((resolve, reject) => {
let timer = setTimeout(() => {
reject(new Error('Ad load timeout'));
}, timeout);

AdSense.show(3, {
onComplete: () => {
clearTimeout(timer);
resolve('success');
},
onError: (error) => {
clearTimeout(timer);
reject(error);
}
});
});
}

Q2: 如何提高广告填充率?

建议:

  1. 确保使用正确的广告单元 ID
  2. 检查地理位置限制
  3. 使用多种广告类型
  4. 与 Google 支持联系

Q3: 激励视频用户跳过怎么办?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
onAdEvent(adEvent) {
switch (adEvent.type) {
case google.ima.AdEvent.Type.SKIPPED:
// 用户跳过了广告,不发放奖励
if (this.preloadedRewardedVideoCallbackObj) {
this.preloadedRewardedVideoCallbackObj.onSkipped();
}
break;

case google.ima.AdEvent.Type.COMPLETE:
// 用户完整观看,发放奖励
if (this.preloadedRewardedVideoCallbackObj) {
this.preloadedRewardedVideoCallbackObj.onComplete();
}
break;
}
}

总结

H5 游戏广告接入要点:

  1. 选择合适的广告形式:横幅持续展示、插屏关卡切换、激励视频奖励
  2. 用户体验优先:控制广告频率、避免自动弹出、提供跳过选项
  3. 错误处理完善:超时重试、降级方案、友好提示
  4. 遵守政策规定:不诱导点击、不干扰游戏、内容合规

通过合理接入广告,H5 游戏可以实现良好的商业变现,同时保持良好的用户体验。