H5游戏性能优化与广告接入踩坑记录

做了不少H5游戏项目,这里整理一下性能优化和广告接入过程中踩过的坑。

H5游戏性能优化

图片资源优化

H5游戏对加载速度要求高,图片优化是第一步。

压缩工具对比

工具 压缩比 效果 适用场景
FireWorks PNG8 有明显纹路 非精细图像
TinyPNG 中高 效果好 推荐首选
ImageOptim 无损压缩 Mac开发

TinyPNG使用

操作步骤:

  1. 访问 https://tinypng.com/
  2. 将图片拖拽到网页中(支持批量上传)
  3. 等待自动压缩完成
  4. 打包下载所有压缩后的图片

压缩效果示例:

1
2
3
原图: background.png (2.5MB)
压缩后: background.png (580KB)
压缩率: 76.8%

图片格式选择

格式 特点 适用场景
PNG 无损,支持透明 UI元素、图标
JPG 有损,压缩率高 背景图、照片
WebP 高压缩率 现代浏览器首选

资源打包优化

单HTML打包方案

将游戏打包成单个HTML文件,减少HTTP请求:

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
┌─────────────────────────────────────────────┐
│ 单文件HTML结构 │
├─────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────┐ │
│ │ HTML Head │ │
│ │ - Meta标签 │ │
│ │ - CSS样式(内联) │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Base64资源区 │ │
│ │ - window.resMap = { │ │
│ │ "res/bg.png": "data:image/...", │ │
│ │ "res/bgm.mp3": "data:audio/..." │ │
│ │ } │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ JavaScript代码 │ │
│ │ - 引擎代码 │ │
│ │ - 游戏逻辑 │ │
│ │ - 启动脚本 │ │
│ └──────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────┘

Python打包脚本:

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import json
import base64

class H5Packer:
def __init__(self):
self.file_types = {
'.png': 'data:image/png;base64,',
'.jpg': 'data:image/jpeg;base64,',
'.jpeg': 'data:image/jpeg;base64,',
'.mp3': 'data:audio/mpeg;base64,',
'.ogg': 'data:audio/ogg;base64,'
}
self.res_map = {}

def encode_file(self, file_path):
"""文件转Base64"""
ext = os.path.splitext(file_path)[1].lower()
if ext not in self.file_types:
return None

with open(file_path, 'rb') as f:
encoded = base64.b64encode(f.read()).decode('utf-8')
return self.file_types[ext] + encoded

def scan_resources(self, res_dir):
"""扫描资源目录"""
for root, dirs, files in os.walk(res_dir):
for file in files:
file_path = os.path.join(root, file)
rel_path = os.path.relpath(file_path, res_dir)
rel_path = 'res/' + rel_path.replace('\\', '/')

data = self.encode_file(file_path)
if data:
self.res_map[rel_path] = data
print(f"Encoded: {rel_path}")

def generate_html(self, template_path, output_path):
"""生成打包后的HTML"""
with open(template_path, 'r', encoding='utf-8') as f:
html = f.read()

# 注入资源映射
res_script = f"window.resMap = {json.dumps(self.res_map)};"
html = html.replace('{#resMap}', res_script)

# 注入其他脚本...

with open(output_path, 'w', encoding='utf-8') as f:
f.write(html)

print(f"Packaged: {output_path}")

# 使用示例
if __name__ == '__main__':
packer = H5Packer()
packer.scan_resources('./build/web-mobile/res')
packer.generate_html('./index_template.html', './output/game.html')

音频资源优化

格式选择

1
2
3
4
5
6
7
8
// 根据平台选择音频格式
function getAudioUrl(baseName) {
if (cc.sys.os === cc.sys.OS_IOS) {
return `res/audio/${baseName}.mp3`; // iOS用MP3
} else {
return `res/audio/${baseName}.ogg`; // 其他用OGG
}
}

音频预加载策略

1
2
3
4
5
6
7
8
9
10
11
12
// 动态加载音频资源
cc.loader.loadResDir('audio', cc.AudioClip, (err, clips) => {
if (err) {
cc.error('Audio load error:', err);
return;
}

// 缓存音频资源
clips.forEach(clip => {
this.audioCache[clip.name] = clip;
});
});

Facebook Playable广告

Playable广告简介

Facebook Playable广告是交互式试玩广告,用户可以直接在广告里体验游戏。

技术要求:

  • 单个HTML文件
  • 不超过2MB
  • 不能有外部网络请求

打包流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────────────┐
│ Playable广告打包流程 │
├─────────────────────────────────────────────────┤
│ │
│ 1. Cocos Creator构建 │
│ └── 构建web-mobile平台 │
│ │
│ 2. 资源Base64编码 │
│ ├── 图片 -> data:image/png;base64,... │
│ ├── 音频 -> data:audio/mp3;base64,... │
│ └── 生成resMap对象 │
│ │
│ 3. 合并HTML文件 │
│ ├── index.html │
│ ├── cocos2d-js-min.js │
│ ├── main.js(修改加载器) │
│ └── project.js │
│ │
│ 4. 添加CTA回调 │
│ └── FbPlayableAd.onCTAClick() │
│ │
└─────────────────────────────────────────────────┘

自定义资源加载器

修改main.js实现Base64资源加载:

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
(function () {
function boot() {
// ... 原有初始化代码

// Base64转Blob
function base64toBlob(base64, type) {
var bstr = atob(base64);
var n = bstr.length;
var u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: type });
}

// Base64转ArrayBuffer
function base64toArray(base64) {
var bstr = atob(base64);
var n = bstr.length;
var u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return u8arr;
}

// 加载DOM音频
function loadDomAudio(item, callback) {
var dom = document.createElement('audio');
dom.muted = true;
dom.muted = false;

var data = window.resMap[item.url.split("?")[0]];
data = base64toBlob(data, "audio/mpeg");

if (window.URL) {
dom.src = window.URL.createObjectURL(data);
} else {
dom.src = data;
}

var timer = setTimeout(function () {
if (dom.readyState === 0)
callback('load timeout', item.url);
else
callback(null, item.url);
}, 8000);

dom.addEventListener("canplaythrough", function () {
clearTimeout(timer);
item.element = dom;
callback(null, item.url);
}, false);

dom.addEventListener("error", function () {
clearTimeout(timer);
callback('load error', item.url);
}, false);
}

// 加载Web音频
function loadWebAudio(item, callback) {
var context = cc.sys.__audioSupport.context;
if (!context) {
callback(new Error('No web audio context'));
return;
}

var data = window.resMap[item.url];
var arrayBuffer = base64toArray(data).buffer;

context.decodeAudioData(arrayBuffer,
function(buffer) {
item.buffer = buffer;
callback(null, buffer);
},
function() {
callback('decode error', item.url);
}
);
}

// 图片加载器
var imageBufferHandler = (item, callback) => {
var data = window.resMap[item.url];
var img = new Image();

img.onload = function () {
callback(null, img);
};

img.onerror = function () {
callback(new Error('Load image failed: ' + item.url));
};

img.src = data;
};

// JSON加载器
var jsonBufferHandler = (item, callback) => {
var str = window.resMap[item.url];
callback(null, JSON.parse(str));
};

// 音频加载器
var audioBufferHandler = (item, callback) => {
var formatSupport = cc.sys.__audioSupport.format;
if (formatSupport.length === 0) {
return new Error('Audio not supported');
}

if (!cc.sys.__audioSupport.WEB_AUDIO) {
loadDomAudio(item, callback);
} else {
loadWebAudio(item, callback);
}
};

// 注册自定义加载器
cc.loader.addDownloadHandlers({
'png': imageBufferHandler,
'jpg': imageBufferHandler,
'jpeg': imageBufferHandler
});

cc.loader.addDownloadHandlers({
'json': jsonBufferHandler
});

cc.loader.addDownloadHandlers({
'mp3': audioBufferHandler,
'ogg': audioBufferHandler,
'wav': audioBufferHandler
});

// 启动游戏
boot();
}

if (window.document) {
boot();
}
})();

CTA按钮集成

Playable广告必须调用Facebook的CTA回调:

1
2
3
4
5
6
7
8
9
10
11
// 游戏结束界面显示"Play More"按钮
onGameOver() {
this.showPlayMoreButton();
},

onPlayMoreClicked() {
// 调用Facebook CTA
if (typeof FbPlayableAd !== 'undefined') {
FbPlayableAd.onCTAClick();
}
}

CTA按钮实践建议:

  • 按钮文案使用”Play Now”、”Play More”等行动号召
  • 按钮放在游戏完成后的结算界面
  • 确保按钮清晰可见,易于点击

Google AdSense接入

申请流程

申请步骤:

  1. 准备网站

    • 拥有完整内容的网站
    • 遵守AdSense内容政策
    • 网站运行稳定
  2. 提交申请

    • 访问 Google AdSense
    • 填写网站信息和联系方式
    • 提交申请等待审核
  3. 添加广告代码

    • 获取AdSense提供的广告代码
    • 将代码添加到网站页面
  4. 等待审核

    • 通常需要1-2周
    • 期间持续更新网站内容
    • 确保网站有稳定访问量

注意事项

Banner广告规范

规则 说明
禁止自动刷新 用户未要求不得自动刷新广告
禁止新窗口 不得在新窗口打开广告
位置规范 不能压在游戏内容上,应放在屏幕底部

正确放置:

1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────┐
│ │
│ 游戏区域 │
│ │
│ ┌─────────────┐ │
│ │ 游戏内容 │ │
│ │ │ │
│ └─────────────┘ │
│ │
├─────────────────────────────────┤
│ Banner广告区域 │ ← 底部固定
└─────────────────────────────────┘

激励视频广告

注意: H5游戏暂不支持AdSense的激励视频形式,用现有激励位置可能违规。

插屏广告

ad_type参数:

ad_type值 说明
video_text_image 允许所有形式(推荐)
text 仅文字广告
image 仅图片广告

推荐配置:

1
2
// 允许所有广告形式,价高者得
ad_type: 'video_text_image'

防止无效流量

关键措施:

  1. 禁止自动刷新

    • 不得在用户未操作的情况下刷新广告
    • 不得设置定时器自动刷新
  2. 禁止激励点击

    • 不得鼓励用户点击广告
    • 不得将点击广告作为游戏奖励条件
  3. 内容合规

    • 确保网站内容符合AdSense政策
    • 定期检查和更新内容

申请通过心得

成功经验:

  1. 准备充分

    • 按照官网要求填写申请
    • 确保网站内容丰富、原创
  2. 遵守规则

    • 仔细阅读AdSense政策
    • 主动剔除违规内容
  3. 持续更新

    • 申请期间持续添加新内容
    • 保持网站活跃度
  4. 推广引流

    • 分享网站到各个渠道
    • 保证一定的IP访问量
  5. 关注反馈

    • 经常查看关联的Gmail邮箱
    • 及时根据反馈调整

投放数据分析

核心指标说明

基础数据表格

指标 计算方式 说明
CPA 广告投入 / 安装量 每次行动成本,越低越好
新增CPA 广告投入 / 新增用户 获取新用户成本
展示频次 广告展示次数 / DAU 每用户平均看到广告次数
ECPM 广告收益 / 展示次数 * 1000 千次展示收益
ROI 收益 / 投入 投入产出比,>100%盈利

数据分析示例

样本数据:

日期 广告投入 安装量 CPA 新增 DAU 展示次数 收益 ECPM 总ROI
周日 993 1233 0.81 1045 1714 48356 353 7.30 38.37%
周六 455 492 0.92 467 1988 37863 336 8.89 38.72%
周五 532 643 0.83 618 1508 33027 263 7.98 36.58%
周四 930 907 1.03 946 1693 33400 283 8.49 35.58%

指标分析

CPA分析

1
2
3
4
5
6
7
8
计算公式: CPA = 广告投入 / 安装量

示例: 周日 CPA = 993 / 1233 = 0.81

分析:
- CPA < 1.0: 表现优秀
- CPA 1.0-1.5: 表现良好
- CPA > 1.5: 需要优化

展示频次优化

1
2
3
4
5
6
7
8
9
10
11
计算公式: 展示频次 = 广告展示次数 / DAU

示例: 周日频次 = 48356 / 1714 = 28.21

建议范围:
- 休闲游戏: 15-25次/日
- 中度游戏: 25-35次/日
- 重度游戏: 35-50次/日

过高: 影响用户体验
过低: 广告收益不足

ECPM分析

1
2
3
4
5
6
7
计算公式: ECPM = (广告收益 / 广告展示次数) * 1000

示例: 周日 ECPM = (353 / 48356) * 1000 = 7.30

行业参考:
- 发达国家: $5-15
- 发展中国家: $1-5

ROI分析

1
2
3
4
5
6
7
8
计算公式: ROI = 收益 / 投入 * 100%

示例: 周日 ROI = 353 / 993 = 35.58%

判断标准:
- ROI > 100%: 盈利
- ROI < 100%: 亏损
- ROI 目标: > 150%(考虑其他成本)

数据分析策略

周数据分析:

1
2
3
4
5
6
7
8
9
10
11
12
// 计算周平均值
function calculateWeeklyAverage(data) {
const totalCost = data.reduce((sum, d) => sum + d.cost, 0);
const totalInstalls = data.reduce((sum, d) => sum + d.installs, 0);
const totalRevenue = data.reduce((sum, d) => sum + d.revenue, 0);

return {
avgCPA: totalCost / totalInstalls,
avgECPM: (totalRevenue / data.reduce((s, d) => s + d.impressions, 0)) * 1000,
totalROI: (totalRevenue / totalCost) * 100
};
}

优化建议:

情况 CPA ECPM 建议操作
高价值 加大投放
低价值 暂停投放
高留存 优化素材
高收益 优化频次

H5游戏发布检查清单

发布前检查

  • 图片已压缩
  • 音频格式兼容iOS和Android
  • 首屏加载 < 3秒
  • 总包体 < 5MB
  • 兼容主流浏览器
  • 响应式布局适配

广告集成检查

  • AdSense代码正确嵌入
  • Banner位置符合规范(底部)
  • 无自动刷新广告逻辑
  • CTA按钮正常工作
  • 广告频次设置合理

数据分析检查

  • 数据埋点完整
  • 转化漏斗配置正确
  • 数据上报无延迟
  • 关键指标监控告警

总结一下

H5游戏要成功,技术和变现都得抓。图片压缩、单文件打包、广告规范接入、数据分析,这些环节都要做好。

核心要点:

  1. 性能优化:TinyPNG压缩、Base64资源打包、音频格式适配
  2. Playable广告:单HTML打包、自定义加载器、CTA回调
  3. AdSense规范:Banner放置、禁止自动刷新、内容合规
  4. 数据分析:CPA/ECPM/ROI指标监控

参考: