微信小游戏开发
用生活化的比喻,让你掌握微信小游戏的架构差异、关键 API 和优化策略
前置知识:JavaScript 基础、4_1_game-architecture(游戏架构)
阅读指南(初学者必看)
为什么你需要学习微信小游戏开发?
微信小游戏是国内最大的 H5 游戏分发平台,月活用户超过 10 亿。如果你要在中国市场做 H5 游戏,微信小游戏几乎是必经之路。但微信小游戏和普通 H5 有很多差异——没有 DOM、首包 4MB 限制、特殊的 API 体系。理解这些差异,是开发小游戏的第一步。
学完本章,你能回答:
- 微信小游戏和普通 H5 有什么本质区别?
- 微信小游戏的关键 API 怎么使用(登录、分享、支付、广告)?
- 如何优化包体、内存和启动速度?
本文结构
第一部分:微信小游戏架构(与普通H5的差异)
第二部分:关键 API(登录/分享/支付/广告/开放数据域)
第三部分:小游戏优化(包体/内存/启动)
第四部分:发布上线
一、微信小游戏架构
微信小游戏 vs 普通H5:
- 没有DOM(不能用document/window)
- 没有BOM(用wx.request代替XMLHttpRequest)
- 有微信API(支付/分享/广告)
- 首包限制4MB
- 内存限制(iOS约1GB)
1.1 运行环境对比
生活类比:普通 H5 像是在"自由市场"做生意,什么都能用;微信小游戏像是在"商场"里开店,场地有限,但客流巨大,还有商场提供的支付和推广服务。
| 维度 | 普通 H5 | 微信小游戏 |
|---|---|---|
| DOM | ✅ 完整支持 | ❌ 没有 |
| BOM | ✅ window/navigator等 | ❌ 用 wx API 替代 |
| 网络 | XMLHttpRequest/Fetch | wx.request |
| 存储 | localStorage | wx.setStorageSync |
| 音频 | Web Audio API | wx.createInnerAudioContext |
| Canvas | ✅ | ✅(核心渲染方式) |
| WebGL | ✅ | ✅ |
| 包体大小 | 无限制 | 首包 ≤ 4MB,总包 ≤ 20MB |
| 内存 | 无明确限制 | iOS ~1GB,Android 设备各异 |
1.2 小游戏启动流程
小游戏启动流程:
用户点击 ──▶ 下载首包(<4MB) ──▶ 初始化运行环境
│
▼
加载游戏代码(Canvas渲染)
│
▼
显示首屏(<3秒目标)
│
▼
按需下载子包资源
│
▼
完整游戏体验
关键时间节点:
- 冷启动 → 首屏展示:< 3秒
- 首包下载 → 代码执行:< 1秒
- 子包下载:后台进行,不阻塞游戏
1.3 项目结构
my-game/
├── game.js # 游戏入口
├── game.json # 游戏配置
├── project.config.json # 项目配置
├── images/ # 图片资源
├── audio/ # 音频资源
└── js/ # JavaScript代码
// game.json
{
"deviceOrientation": "portrait",
"showStatusBar": false,
"networkTimeout": {
"request": 5000,
"connectSocket": 5000,
"uploadFile": 5000,
"downloadFile": 5000
}
}
// game.js 入口
const canvas = wx.createCanvas();
const ctx = canvas.getContext('2d');
canvas.width = wx.getSystemInfoSync().windowWidth;
canvas.height = wx.getSystemInfoSync().windowHeight;
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#fff';
ctx.font = '20px Arial';
ctx.fillText('Hello WeChat Mini Game', 50, 50);
requestAnimationFrame(gameLoop);
}
gameLoop();
二、关键 API
2.1 登录
// 登录
wx.login({ success: res => { /* res.code 换 openid */ } });
微信登录流程:
1. 前端调用 wx.login() 获取 code
2. 将 code 发送到你的服务器
3. 服务器用 code + appId + appSecret 向微信服务器换 openid
4. 服务器生成自定义登录态返回前端
5. 前端保存登录态,后续请求带上
┌──────┐ wx.login(code) ┌──────┐ code+secret ┌──────┐
│ 前端 │────────────────────▶│ 服务端│───────────────▶│ 微信 │
│ │◀────────────────────│ │◀───────────────│ │
└──────┘ 自定义登录态 └──────┘ openid+session └──────┘
2.2 用户信息
// 创建用户信息按钮(新版API)
let button = wx.createUserInfoButton({
type: 'text',
text: '获取用户信息',
style: {
left: 100, top: 200, width: 200, height: 40,
lineHeight: 40, backgroundColor: '#07C160',
color: '#ffffff', textAlign: 'center',
fontSize: 16, borderRadius: 4
}
});
button.onTap((res) => {
console.log('用户信息', res.userInfo);
button.destroy();
});
2.3 分享
// 主动分享
wx.shareAppMessage({ title: '来玩!', query: 'inviter=123' });
// 监听右上角分享按钮
wx.onShareAppMessage(() => {
return { title: '快来玩这个游戏!', imageUrl: 'images/share.png', query: 'from=share&id=123' };
});
分享机制:
- 主动分享:玩家点击分享按钮触发
- 被动分享:游戏结束时"分享复活"等
- 分享到聊天:单聊/群聊
- 分享到朋友圈:仅支持小程序,小游戏暂不支持
关键参数:
- title:分享卡片标题
- query:分享参数(用于追踪来源)
- imageUrl:分享卡片图片(可选自定义)
2.4 支付与广告
// 虚拟支付
wx.requestMidasPayment({ offerId: 'xxx', purchaseQuantity: 10 });
// 激励视频广告
const ad = wx.createRewardedVideoAd({ adUnitId: 'xxx' });
ad.show().then(() => { /* 发放奖励 */ });
小游戏变现模式:
┌──────────────────────────────────────────────┐
│ 小游戏变现方式 │
├──────────────┬───────────────────────────────┤
│ 内购(IAP) │ 广告 │
│ ├─ 虚拟货币 │ ├─ 激励视频(最赚钱) │
│ ├─ 皮肤/角色 │ ├─ 插屏广告 │
│ └─ 通行证 │ ├─ Banner广告 │
│ │ └─ 原生广告 │
└──────────────┴───────────────────────────────┘
激励视频广告流程:
1. 玩家点击"看广告获得奖励"
2. 加载广告 → 展示30秒视频
3. 玩家看完 → 服务端验证 → 发放奖励
4. 如果广告加载失败 → 降级方案(直接发放或重试)
2.5 本地存储
// 同步存储
wx.setStorageSync('key', 'value');
let value = wx.getStorageSync('key');
// 异步存储
wx.setStorage({ key: 'key', data: 'value', success: () => console.log('存储成功') });
wx.getStorage({ key: 'key', success: (res) => console.log('获取成功', res.data) });
// 删除存储
wx.removeStorageSync('key');
wx.clearStorageSync();
2.6 网络请求
// 发起请求
wx.request({
url: 'https://api.game.com/user',
method: 'GET',
data: { id: 123 },
success: (res) => console.log('请求成功', res.data),
fail: (err) => console.error('请求失败', err)
});
// 下载文件
wx.downloadFile({
url: 'https://game.com/image.png',
success: (res) => console.log('下载成功', res.tempFilePath)
});
2.7 开放数据域
// 开放数据域(好友排行)
wx.getOpenDataContext().postMessage({ command: 'getFriendRanking' });
开放数据域架构:
┌──────────────────────┐ postMessage ┌──────────────────────┐
│ 主域(游戏逻辑) │────────────────▶│ 开放数据域(好友数据) │
│ - 不能访问好友数据 │ │ - 只能访问好友数据 │
│ - 可以渲染Canvas │◀────────────────│ - 不能联网 │
│ - 可以调用所有API │ 共享Canvas │ - 只能渲染到共享Canvas │
└──────────────────────┘ └──────────────────────┘
为什么要有开放数据域?
- 安全:防止游戏获取好友隐私数据传到自己服务器
- 隔离:好友数据只能在开放数据域中使用
- 共享:通过共享Canvas把排行榜渲染结果传给主域
三、小游戏优化
3.1 包体优化
包体优化(首包<4MB):
1. 代码:Tree Shaking + 压缩 + 分包
2. 资源:图片/纹理/音频压缩 + 分包下载
3. 分包策略:首包只放启动+登录,子包按需下载
分包策略示例:
┌──────────────────────────────┐
│ 首包(<4MB) │
│ ├─ 游戏代码(压缩后) │
│ ├─ 启动页资源 │
│ └─ 登录UI资源 │
├──────────────────────────────┤
│ 子包1:主城场景(按需下载) │
│ ├─ 城市贴图 │
│ └─ NPC模型 │
├──────────────────────────────┤
│ 子包2:战斗场景(按需下载) │
│ ├─ 战斗特效 │
│ └─ 怪物模型 │
└──────────────────────────────┘
// game.json 分包配置
{
"subpackages": [
{ "name": "level1", "root": "subpackages/level1/" },
{ "name": "level2", "root": "subpackages/level2/" }
]
}
// 加载分包
wx.loadSubpackage({
name: 'level1',
success: () => console.log('分包加载成功'),
fail: (err) => console.error('分包加载失败', err)
});
3.2 内存优化
内存优化:
- 纹理压缩(ETC2/ASTC减少显存)
- 对象池(减少GC)
- 资源释放(切场景时主动释放)
- LRU缓存
纹理压缩对比:
| 格式 | 平台 | 压缩比 | 质量 |
|------|------|--------|------|
| PNG | 通用 | 无压缩 | 完美 |
| JPEG | 通用 | 10:1 | 有损 |
| ETC2 | Android | 4:1 | 有损 |
| ASTC | iOS/部分Android | 4:1~8:1 | 可调 |
| WebP | 通用 | 2:1~3:1 | 有损/无损 |
对象池实现:
class ObjectPool {
constructor(factory, initialSize = 50) {
this.factory = factory;
this.pool = [];
for (let i = 0; i < initialSize; i++) this.pool.push(factory());
}
get() { return this.pool.pop() || this.factory(); }
release(obj) { this.pool.push(obj); }
}
3.3 启动优化
启动优化:
- 代码预加载
- 首屏<3秒
- 骨架屏
启动优化时间线:
0ms ──── 用户点击 ──── 下载首包 ──── 解析代码 ──── 初始化引擎 ──── 渲染首屏
│ │
500ms 3000ms
代码开始执行 看到画面
优化手段:
1. 减少首包大小(Tree Shaking、代码压缩)
2. 预加载关键资源(启动时就开始下载子包资源)
3. 骨架屏(先显示UI框架,数据异步加载)
4. 延迟初始化(非关键模块在首屏后初始化)
四、发布上线
4.1 版本管理
// 检查更新
const updateManager = wx.getUpdateManager();
updateManager.onCheckForUpdate((res) => {
console.log('是否有新版本', res.hasUpdate);
});
updateManager.onUpdateReady(() => {
wx.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success: (res) => { if (res.confirm) updateManager.applyUpdate(); }
});
});
updateManager.onUpdateFailed(() => {
console.error('新版本下载失败');
});
4.2 发布流程
1. 开发调试
- 使用微信开发者工具
- 真机调试
2. 上传代码
- 点击"上传"按钮
- 填写版本号和备注
3. 提交审核
- 登录微信公众平台
- 提交审核(通常3~7天)
4. 发布上线
- 审核通过后发布
4.3 注意事项
1. 隐私协议
- 必须提供隐私协议
- 收集用户信息需要授权
2. 内容审核
- 不能有违规内容
- 不能有诱导分享
3. 性能要求
- 启动时间 < 3秒
- 内存占用 < 300MB
- CPU占用 < 50%
4. 兼容性
- 测试不同机型
- 测试不同微信版本
实践:
- 发布一个微信小游戏
- 实现分包加载(首包<4MB)
- 接入微信支付和广告
自问自答
Q:微信小游戏和微信小程序有什么区别? A:小游戏是小程序的一种特殊类型。小程序有 DOM(WXML/WXSS),小游戏没有 DOM,只能用 Canvas 渲染。小游戏更接近游戏引擎的运行方式,小程序更接近传统 Web 开发。
Q:首包 4MB 够用吗? A:对于纯代码来说够用了,关键是不要把资源放首包。策略:首包只放代码 + 启动必需的最少资源,其余资源放子包或 CDN 按需下载。一个成熟的小游戏首包通常在 2~3MB。
Q:开放数据域为什么要单独一个环境? A:为了隐私安全。如果主域能直接读取好友数据,游戏可以把好友信息发到自己服务器,造成隐私泄露。开放数据域限制了好友数据只能在隔离环境中使用。
Q:激励视频广告和插屏广告哪个更赚钱? A:激励视频。因为玩家主动选择看广告,完播率高,广告主愿意出更高的价格。插屏广告打扰体验,完播率低。最佳实践:把激励视频和游戏机制结合(看广告复活/获得道具),玩家不反感,收入也高。
Q:微信小游戏代码能直接在浏览器运行吗? A:大部分可以,但需要适配:window对象 → wx对象,DOM操作 → Canvas操作,网络请求 → wx.request。建议在开发时先写Web版,再适配到微信小游戏。
实践任务
- 任务1:注册微信小游戏开发者账号,创建第一个小游戏项目并真机预览
- 任务2:实现分包加载——首包只放启动代码和登录页,主游戏内容放子包
- 任务3:接入微信登录流程(前端 wx.login + 后端 code2session)
- 任务4:接入激励视频广告,实现"看广告获得金币"功能
- 任务5:优化小游戏启动速度——使用骨架屏 + 延迟初始化,目标冷启动 < 3秒
- 任务6:接入版本更新管理器,实现热更新提示功能
与其他章节的关联
| 本章内容 | 关联章节 | 关联点 |
|---|---|---|
| 首包优化 | 第01章 WebAssembly深度 | Wasm 二进制体积小,适合首包 |
| 内存优化 | 2_3_browser-memory-mastery | 小游戏内存限制更严,优化方法通用 |
| Canvas渲染 | 2_2_h5-rendering-mastery | 小游戏只能用Canvas渲染,GPU优化方法适用 |
| 广告/支付 | 第06章 多平台小游戏适配 | 不同平台的广告/支付API不同 |
| 分包加载 | 4_1_game-architecture | 资源管理和分包策略是架构设计的一部分 |
上一章:04-图形学前沿 | 下一章:06-多平台小游戏适配