微信小游戏开发实战完全指南
概述
微信小游戏是运行在微信客户端内的HTML5游戏,无需下载安装,即点即玩。凭借微信庞大的用户基础和便捷的分享机制,微信小游戏已成为移动游戏市场的重要组成部分。
本文将详细介绍微信小游戏的开发流程,从基础配置到高级功能实现,帮助开发者快速掌握微信小游戏开发。
一、开发环境配置
1.1 开发者工具
下载安装
- 访问微信开发者工具下载页面
- 下载对应系统版本
- 安装并登录微信扫码
常见问题:服务端口已关闭
错误提示:
解决方案:
- 打开微信开发者工具
- 点击 设置(齿轮图标)
- 选择 安全设置
- 开启 服务端口
1 2
| 设置路径: 微信开发者工具 -> 设置 -> 安全设置 -> 服务端口 -> 开启
|
1.2 开发者账号注册
- 访问微信公众平台
- 注册小程序/小游戏账号
- 完成邮箱验证和主体认证
- 获取AppID
1.3 Cocos Creator配置
构建发布
二、服务器域名配置
2.1 配置要求
微信小游戏只能与指定的服务器域名进行网络通信,包括:
- 普通HTTPS请求(
wx.request)
- 上传文件(
wx.uploadFile)
- 下载文件(
wx.downloadFile)
- WebSocket通信(
wx.connectSocket)
2.2 配置流程
配置路径:
1
| 小程序后台 -> 开发 -> 开发设置 -> 服务器域名
|
配置规则:
| 协议 |
说明 |
示例 |
| https |
用于request/upload/download |
https://api.example.com |
| wss |
用于WebSocket |
wss://ws.example.com |
注意事项:
- 必须使用HTTPS/WSS协议
- 不能使用IP地址(小程序的局域网IP除外)
- 不能使用localhost
- 域名必须经过ICP备案
- 不支持配置父域名,只能配置子域名
2.3 端口配置
| 协议 |
端口配置 |
说明 |
| HTTPS |
可选配置 |
配置后只能访问该端口 |
| WSS |
无需配置 |
默认允许所有端口 |
示例:
1 2 3 4 5 6 7 8
| 配置:https://api.example.com:8080 请求:https://api.example.com:8080 成功 请求:https://api.example.com 失败 请求:https://api.example.com:443 失败
配置:https://api.example.com 请求:https://api.example.com 成功 请求:https://api.example.com:443 失败(即使443是默认端口)
|
2.4 开发环境跳过校验
在微信开发者工具中,可以临时跳过服务器域名校验:
1 2
| 开发者工具 -> 详情 -> 本地设置 -> 勾选"不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书"
|
注意:此设置只在开发工具中有效,真机调试仍需配置域名。
三、用户登录与OpenID获取
3.1 登录流程
1 2 3 4 5 6 7 8 9 10
| ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 小游戏 │ --> │ 微信服务器 │ --> │ 开发者服务器│ │ wx.login() │ │ 返回code │ │ code2Session│ └─────────────┘ └─────────────┘ └──────┬──────┘ │ v ┌─────────────┐ │ 返回OpenID │ │ session_key │ └─────────────┘
|
3.2 客户端登录代码
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
| function wxLogin() { return new Promise((resolve, reject) => { wx.login({ success: (res) => { if (res.code) { console.log("登录code:", res.code); requestOpenId(res.code) .then(resolve) .catch(reject); } else { console.log("登录失败:", res.errMsg); reject(res.errMsg); } }, fail: reject }); }); }
async function requestOpenId(code) { try { const response = await wx.request({ url: 'https://your-domain.com/api/login', method: 'POST', data: { code: code } });
if (response.data.openid) { wx.setStorageSync('openid', response.data.openid); return response.data; } } catch (error) { console.error("获取OpenID失败:", error); throw error; } }
|
3.3 服务端获取OpenID
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
| const axios = require('axios');
async function getOpenId(code) { const appId = 'YOUR_APP_ID'; const secret = 'YOUR_APP_SECRET';
const url = `https://api.weixin.qq.com/sns/jscode2session`;
try { const response = await axios.get(url, { params: { appid: appId, secret: secret, js_code: code, grant_type: 'authorization_code' } });
const data = response.data;
if (data.openid) { return { openid: data.openid, session_key: data.session_key, unionid: data.unionid }; } else { throw new Error(data.errmsg); } } catch (error) { console.error("获取OpenID失败:", error); throw error; } }
|
3.4 返回数据示例
1 2 3 4 5 6 7
| { "openid": "oN_9x5O5WQ2xxxxxxxxxx", "session_key": "xxxxx+xxxxxxxxxx==", "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL", "errcode": 0, "errmsg": "ok" }
|
| 字段 |
说明 |
| openid |
用户在该小程序的唯一标识 |
| session_key |
会话密钥,用于数据加密 |
| unionid |
用户在微信开放平台的唯一标识(需绑定) |
四、用户信息获取
4.1 获取方式对比
| 方式 |
适用场景 |
需要用户授权 |
| wx.getUserInfo |
已废弃,不建议使用 |
是 |
| wx.getUserProfile |
获取用户信息 |
是 |
| Button.open-type=”getUserInfo” |
按钮触发 |
是 |
| 开放数据校验 |
服务端验证 |
否 |
4.2 使用按钮获取用户信息
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
| function createUserInfoButton() { const button = wx.createUserInfoButton({ type: 'text', text: '获取用户信息', style: { left: 10, top: 76, width: 200, height: 40, lineHeight: 40, backgroundColor: '#ff0000', color: '#ffffff', textAlign: 'center', fontSize: 16, borderRadius: 4 } });
button.onTap((res) => { if (res.errMsg.indexOf(':ok') > -1 && res.rawData) { console.log("用户信息:", res.userInfo); console.log("加密数据:", res.encryptedData); console.log("IV:", res.iv);
decryptUserData(res.encryptedData, res.iv); } });
return button; }
|
4.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 26 27 28 29 30 31 32
| wx.getSetting({ success: (res) => { if (res.authSetting['scope.userInfo'] === true) { wx.getUserInfo({ success: (res) => { console.log("用户信息:", res.userInfo); } }); } else { const button = wx.createUserInfoButton({ type: "image", image: "images/login.png", style: { left: 100, top: 100, width: 100, height: 100 } });
button.onTap((res) => { if (res.errMsg.indexOf(':ok') > -1 && res.rawData) { console.log("用户信息:", res.userInfo); button.destroy(); } }); } } });
|
4.4 服务端数据解密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const crypto = require('crypto');
function decryptData(sessionKey, encryptedData, iv) { const sessionKeyBuffer = Buffer.from(sessionKey, 'base64'); const encryptedBuffer = Buffer.from(encryptedData, 'base64'); const ivBuffer = Buffer.from(iv, 'base64');
try { const decipher = crypto.createDecipheriv( 'aes-128-cbc', sessionKeyBuffer, ivBuffer ); decipher.setAutoPadding(true);
let decoded = decipher.update(encryptedBuffer, 'binary', 'utf8'); decoded += decipher.final('utf8');
return JSON.parse(decoded); } catch (err) { throw new Error('解密失败'); } }
|
五、广告接入
5.1 开通条件
| 条件 |
要求 |
| 独立访客(UV) |
累计不低于1000 |
| 违规记录 |
无严重违规 |
5.2 开通方法
1
| 小程序后台 -> 推广 -> 流量主 -> 申请开通
|
5.3 广告类型
| 类型 |
特点 |
eCPM |
| Banner广告 |
常驻显示,占用空间 |
较低 |
| 激励视频广告 |
用户主动观看,收益高 |
高 |
| 插屏广告 |
全屏展示,打断性强 |
中 |
5.4 Banner广告
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
| let bannerAd = null;
function createBannerAd() { if (wx.createBannerAd) { bannerAd = wx.createBannerAd({ adUnitId: 'your-ad-unit-id', style: { left: 10, top: 76, width: 320 } });
bannerAd.onLoad(() => { console.log('Banner广告加载成功'); });
bannerAd.onError((err) => { console.error('Banner广告加载失败:', err); });
bannerAd.onResize((res) => { console.log('Banner尺寸:', res.width, res.height); }); } }
function showBannerAd() { if (bannerAd) { bannerAd.show(); } }
function hideBannerAd() { if (bannerAd) { bannerAd.hide(); } }
function destroyBannerAd() { if (bannerAd) { bannerAd.destroy(); bannerAd = null; } }
|
5.5 激励视频广告
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
| let rewardedVideoAd = null;
function initRewardedVideoAd() { if (wx.createRewardedVideoAd) { rewardedVideoAd = wx.createRewardedVideoAd({ adUnitId: 'your-ad-unit-id' });
rewardedVideoAd.onLoad(() => { console.log('激励视频广告加载成功'); });
rewardedVideoAd.onError((err) => { console.error('激励视频广告加载失败:', err); });
rewardedVideoAd.onClose((res) => { if (res && res.isEnded) { console.log('广告播放完成,发放奖励'); grantReward(); } else { console.log('广告未播放完成'); } }); } }
function showRewardedVideoAd() { return new Promise((resolve, reject) => { if (!rewardedVideoAd) { reject('广告未初始化'); return; }
rewardedVideoAd.show().catch(() => { rewardedVideoAd.load().then(() => { rewardedVideoAd.show(); }).catch(err => { console.error('激励视频广告显示失败:', err); reject(err); }); }); }); }
|
5.6 插屏广告
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
| let interstitialAd = null;
function initInterstitialAd() { if (wx.createInterstitialAd) { interstitialAd = wx.createInterstitialAd({ adUnitId: 'your-ad-unit-id' });
interstitialAd.onLoad(() => { console.log('插屏广告加载成功'); });
interstitialAd.onError((err) => { console.error('插屏广告加载失败:', err); });
interstitialAd.onClose(() => { console.log('插屏广告关闭'); }); } }
function showInterstitialAd() { if (interstitialAd) { interstitialAd.show().catch((err) => { console.error('插屏广告显示失败:', err); }); } }
|
5.7 广告最佳实践
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
| class AdManager { constructor() { this.rewardedAd = null; this.bannerAd = null; this.init(); }
init() { this.initRewardedAd(); this.initBannerAd(); }
preloadRewardedAd() { if (this.rewardedAd) { this.rewardedAd.load().catch(console.error); } }
showRewardedAd(onReward) { if (!this.rewardedAd) { onReward(false); return; }
this.onRewardCallback = onReward;
this.rewardedAd.show().catch(() => { this.rewardedAd.load().then(() => { this.rewardedAd.show(); }).catch(() => { onReward(false); }); }); }
onLevelComplete(level) { if (level % 3 === 0) { this.showInterstitialAd(); } } }
const adManager = new AdManager();
function onRevive() { adManager.showRewardedAd((success) => { if (success) { revivePlayer(); } else { showToast('广告加载失败,请重试'); } }); }
|
六、性能优化
6.1 包体优化
1 2
| 主包大小限制:4MB 总包大小限制:20MB(通过分包)
|
优化建议:
- 图片压缩:使用TinyPNG等工具
- 代码压缩:开启代码混淆
- 资源分包:将非必需资源放在分包
- 音频压缩:使用合适格式和采样率
6.2 性能监控
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const performance = wx.getPerformance();
wx.onMemoryWarning((res) => { console.log('内存警告级别:', res.level); });
const fpsMonitor = wx.createCameraFrameListener({ success: (res) => { console.log('当前帧率:', res.fps); } });
|
七、发布流程
7.1 提审前检查清单
7.2 提审流程
1 2 3 4 5
| 1. 开发者工具上传代码 2. 小程序后台提交审核 3. 填写版本信息 4. 等待审核(1-7个工作日) 5. 审核通过后发布
|
八、常见问题
8.1 网络请求失败
问题:request:fail url not in domain list
解决方案:
- 检查域名是否在服务器域名配置中
- 检查域名是否已完成ICP备案
- 开发环境可开启”不校验域名”选项
8.2 用户拒绝授权
1 2 3 4 5 6 7 8 9 10 11 12
| wx.getSetting({ success: (res) => { if (!res.authSetting['scope.userInfo']) { wx.openSetting({ success: (res) => { console.log(res.authSetting); } }); } } });
|
8.3 真机调试
总结
本文详细介绍了微信小游戏的开发要点:
- 环境配置:开发者工具、服务端口、Cocos Creator
- 域名配置:HTTPS/WSS协议要求、端口配置
- 用户系统:登录流程、OpenID获取、用户信息
- 广告变现:Banner、激励视频、插屏广告
- 性能优化:包体大小、内存管理
- 发布流程:提审检查、审核流程
- 常见问题:网络请求、授权问题
微信小游戏具有庞大的用户基础和便捷的传播机制,是游戏开发者不可忽视的平台。建议持续关注微信官方文档更新,及时适配新功能和政策变化。
参考资源: