主题切换
07 微信小游戏 API 与开放能力
预计阅读 30 分钟
版本信息
最后更新:2026-07-09 · 适用基础库:≥ 3.9.0(最低兼容 ≥ 2.30.0)· 微信开发者工具:稳定版 1.06+
本章全面讲解微信小游戏的核心 API 和开放能力,包含登录、支付、分享、排行榜、云开发、广告等关键模块。
7.1 登录与用户体系
javascript
// 微信登录流程
wx.login({
success(res) {
if (res.code) {
// 小游戏环境没有标准 fetch,使用 wx.request 将 code 发送到后端
wx.request({
url: 'https://your-domain.com/api/login',
method: 'POST',
data: { code: res.code },
success(serverRes) {
// 服务端返回 openid / 自定义登录态 token
wx.setStorageSync('token', serverRes.data.token)
},
fail(err) {
console.error('登录失败', err)
},
})
}
},
})获取用户信息
重要更新:
wx.getUserProfile和wx.getUserInfo自基础库 2.27.1 起已完全废弃,返回的均为匿名信息(默认灰色头像 + "微信用户"昵称),不再能获取真实用户资料。
当前(基础库 ≥ 2.21.2)唯一合规的用户信息获取方案是让用户主动填写:
- 头像:使用
<button open-type="chooseAvatar">让用户选择头像,获取临时 URL 后立即上传到自己的服务器(临时链接约 2 小时内失效)。 - 昵称:使用
<input type="nickname">让用户点击键盘上方的微信昵称条填入。 - 自定义 UI + 服务端保存:由用户自行上传头像、输入昵称,服务端保存后与自己的账号体系关联。
javascript
// 小游戏中获取头像昵称的正确方式:
// 注意:WXML(<button>/<input>)属于小程序 API,小游戏不支持!
// 小游戏中应使用 wx.createUserInfoButton 创建原生按钮
// 方案一:wx.createUserInfoButton(推荐,小游戏原生方法)
const button = wx.createUserInfoButton({
type: 'text',
text: '获取头像昵称',
style: {
left: 10,
top: 76,
width: 200,
height: 40,
lineHeight: 40,
backgroundColor: '#ff7f50',
color: '#ffffff',
textAlign: 'center',
fontSize: 16,
borderRadius: 4,
},
})
button.onTap((res) => {
// res.userInfo.avatarUrl 为头像临时 URL(约 2 小时有效)
// res.userInfo.nickName 为用户昵称
const { avatarUrl, nickName } = res.userInfo
// ⚠️ 必须立即将头像上传到自己的服务器
console.log('用户信息:', nickName, avatarUrl)
button.destroy() // 获取完成后销毁按钮
})javascript
// 方案二:小程序 WXML 中的头像/昵称组件(仅适用于小程序,小游戏请使用 wx.createUserInfoButton)
// 头像选择回调
function onChooseAvatar(e) {
const avatarUrl = e.detail.avatarUrl
// ⚠️ 临时链接约 2 小时有效,必须立即上传到自己的服务器
wx.uploadFile({
url: 'https://your-server.com/upload',
filePath: avatarUrl,
name: 'file',
success(res) {
const permanentUrl = res.data.url // 拿到永久 URL 后存储
wx.setStorageSync('userAvatar', permanentUrl)
},
})
}
// 昵称输入回调
function onNicknameBlur(e) {
const nickname = e.detail.value
// 用户主动点击键盘顶部昵称条才会填入微信昵称
if (nickname) {
wx.setStorageSync('userNickname', nickname)
// 发送到服务端保存
wx.request({
url: 'https://your-server.com/api/user/profile',
method: 'POST',
data: { nickname },
})
}
}小游戏与小程序的区别
- 上述
open-type="chooseAvatar"和type="nickname"是小程序 WXML 组件,小游戏没有 WXML,无法直接使用。 - 小游戏中获取用户头像、昵称,应使用
wx.createUserInfoButton创建原生按钮引导用户点击授权。 - 开放数据域(如排行榜)中只能使用
wx.getUserInfo({ openIdList: ['selfOpenId'] })等开放数据接口读取匿名化的好友数据,不能主动获取真实用户信息。
废弃 API 警示
以下 API 请勿在新项目中使用(即使不报错,也只会返回匿名数据):
wx.getUserInfo— 返回匿名头像和 "微信用户"wx.getUserProfile— 同上,且需要用户点击触发
安全提醒
session_key是敏感信息,不要在前端保存或传输。- 登录态建议由服务端生成自定义 token,前端仅保存 token。
- 使用
wx.checkSession判断登录态是否过期,避免频繁调用wx.login。
前端对比:Web API vs 微信 API
| 前端 Web | 微信小游戏 | 区别 |
|---|---|---|
fetch() / axios | wx.request() | 微信 API 自带 session 管理 |
localStorage | wx.setStorageSync() | 同步/异步双模式,上限 10MB |
navigator.geolocation | 需用户授权 + 隐私配置 | 微信更严格的隐私管控 |
new Audio() | wx.createInnerAudioContext() | 微信有并发数和格式限制 |
| OAuth 2.0 跳转 | wx.login() → code 交换 | 无需跳转,体验更顺畅 |
| Cookie/Session | 自定义 session_key + 服务端 | 需要自己管理登录态 |
适应技巧: 微信 API 的设计哲学是"能力封装 + 隐私优先",很多 Web 端随手可用的 API(如获取位置、读取剪贴板)在小游戏中需要用户授权并声明使用目的。这更接近移动端原生开发的思维方式。
7.2 分享系统
javascript
// 主动分享
wx.shareAppMessage({
title: '快来玩这个超好玩的小游戏!',
imageUrl: 'https://cdn.example.com/share.png',
query: 'inviteCode=ABC123', // 用于追踪分享来源
})
// 监听分享进入
wx.onShow((res) => {
if (res.query.inviteCode) {
// 通过邀请码进入,发放奖励
}
})7.3 排行榜 — 开放数据域
开放数据域是一个独立的 JS 运行环境,与主域隔离,专用于处理好友关系链数据。
javascript
// 主域:向开放数据域发送消息
const openDataContext = wx.getOpenDataContext()
openDataContext.postMessage({
type: 'showRanking',
key: 'score',
})
// 开放数据域:接收并渲染排行榜
wx.onMessage((data) => {
if (data.type === 'showRanking') {
wx.getFriendCloudStorage({
keyList: [data.key],
success: (res) => {
// 排序并渲染排行榜
const sorted = res.data.sort((a, b) => {
const aScore = a.KVDataList.find((kv) => kv.key === data.key)
const bScore = b.KVDataList.find((kv) => kv.key === data.key)
return (bScore?.value || 0) - (aScore?.value || 0)
})
renderRanking(sorted)
},
})
}
})7.4 广告变现
| 广告类型 | 适用场景 | eCPM 参考 | 用户体验影响 |
|---|---|---|---|
| Banner 广告 | 游戏底部常驻 | 低 | 低 |
| 激励视频广告 | 复活/双倍奖励/续命 | 高 | 中(用户主动选择) |
| 插屏广告 | 关卡切换/暂停 | 中 | 高 |
| 原生模板广告 | 灵活嵌入 UI | 中 | 低 |
javascript
// 激励视频广告示例
const videoAd = wx.createRewardedVideoAd({
adUnitId: 'xxxx',
})
videoAd.onLoad(() => console.log('广告加载成功'))
videoAd.onClose((res) => {
if (res.isEnded) {
// 用户看完广告,发放奖励
grantReward()
}
})
videoAd.show().catch(() => {
// 广告未准备好,重新加载
videoAd.load().then(() => videoAd.show())
})7.5 小游戏虚拟支付
小游戏内的虚拟道具购买必须使用 米大师虚拟支付(wx.requestMidasPayment),而不是小程序的 wx.requestPayment。使用错误接口会导致审核驳回。
javascript
// 米大师虚拟支付示例
// 注意:platform 的有效值仅为 'android',iOS 需使用其他支付方式
// 注意:outTradeNo 为业务订单号(必填),每笔订单号唯一
// 生成唯一订单号(实际应从服务端获取)
const outTradeNo = `order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
wx.requestMidasPayment({
mode: 'game',
env: 0, // 0 正式环境,1 沙箱环境
offerId: 'your-offer-id',
currencyType: 'CNY',
platform: 'android', // ⚠️ 仅支持 'android',不是 sysInfo.platform!
outTradeNo: outTradeNo, // 必填:业务订单号,每笔唯一
buyQuantity: 1,
zoneId: '1',
success(res) {
// 支付成功,建议再次向服务端确认订单状态后发放道具
verifyOrderAndGrantReward(outTradeNo, res)
},
fail(err) {
console.error('支付失败', err)
},
})虚拟支付注意事项
- 需先在微信公众平台申请「小游戏虚拟支付」能力,且仅企业主体可申请。
offerId在米大师后台配置,对应具体的商品/礼包。- iOS 虚拟支付:iOS 端小游戏虚拟支付长期受限;截至 2026-07,具体方案以微信官方最新公告为准。在正式上线前,建议按平台做功能开关或提示,并关注微信官方接入文档。
- 沙箱环境(
env: 1)用于开发联调,上线前务必切换为正式环境。 - 支付结果以服务端回调为准,前端
success仅作 UI 提示,发放道具前必须校验订单。
7.6 云开发
javascript
// 初始化云开发
wx.cloud.init({ env: 'your-env-id' })
// 云数据库
const db = wx.cloud.database()
db.collection('scores').add({
data: { score: 9999, timestamp: Date.now() },
})
// 云函数
wx.cloud.callFunction({
name: 'getRanking',
data: { top: 100 },
})7.7 API 调用最佳实践
API 使用原则
- 异步优先 — 所有 API 调用使用 Promise/async-await 包装
- 错误处理 — 每个 API 调用必须处理 fail 回调,做好降级方案
- 权限最小化 — 仅在必要时请求用户授权
- 缓存策略 — 用户信息、配置数据本地缓存,减少 API 调用
- 版本兼容 — 优先使用
wx.canIUse()做能力检测;如需读取版本号,使用wx.getAppBaseInfo().SDKVersion,避免字符串直接比较 - 分包意识 — 非核心功能可以通过分包延迟加载
将回调式 API 包装为 Promise
微信小游戏的大部分 API 同时支持回调和 Promise。推荐在项目中统一封装一个 promisify 工具,使代码更易于阅读和错误处理:
typescript
// utils/wx-promise.ts
function promisify<T = any>(api: (...args: any[]) => any): (...args: any[]) => Promise<T> {
return (...args: any[]) =>
new Promise((resolve, reject) => {
// 微信 API 的最后一个参数通常为 options 对象
const options = args[args.length - 1] || {}
const { success: userSuccess, fail: userFail, ...rest } = options
api(...args.slice(0, -1), {
...rest,
success: (res: T) => {
// 优先调用用户传入的 success,再 resolve Promise
userSuccess?.(res)
resolve(res)
},
fail: (err: any) => {
// 优先调用用户传入的 fail,再 reject Promise
userFail?.(err)
reject(err)
},
})
})
}
// 常用 API 的 Promise 版本
export const wxLogin = promisify<WechatMinigame.LoginSuccessCallbackResult>(wx.login)
export const wxRequest = promisify<WechatMinigame.RequestSuccessCallbackResult>(wx.request)
// 注意:wx.getUserProfile 已废弃,不再推荐使用
// export const wxGetUserProfile = promisify<WechatMinigame.GetUserProfileSuccessCallbackResult>(wx.getUserProfile)typescript
// 使用示例
async function fetchUserScore(openid: string) {
try {
const res = await wxRequest({
url: 'https://your-server.com/api/score',
method: 'POST',
data: { openid },
})
return res.data
} catch (err) {
console.error('获取分数失败', err)
return null
}
}说明:本章部分示例为节省篇幅仍使用回调写法。实际项目建议统一使用上述 Promise 风格,尤其是在需要链式调用多个微信 API 的场景。
能力检测与版本比较
微信基础库版本众多,调用新 API 前必须检测兼容性。
typescript
// 检测单个 API 是否可用
if (wx.canIUse('createWebAudioContext')) {
const ctx = wx.createWebAudioContext()
} else {
// 降级到 InnerAudioContext
}
// 检测 API 的某个参数或返回值是否支持
// 注意:getSystemInfoSync 已不推荐使用,建议使用 getWindowInfo/getDeviceInfo 等新 API
if (wx.canIUse('getWindowInfo')) {
const { safeArea } = wx.getWindowInfo()
}比较版本号时,不要直接比较字符串('3.10.0' < '3.9.0' 在字符串比较下会出错),应拆分为数字数组:
typescript
function compareVersion(v1: string, v2: string): number {
const a = v1.split('.').map(Number)
const b = v2.split('.').map(Number)
for (let i = 0; i < Math.max(a.length, b.length); i++) {
const x = a[i] || 0
const y = b[i] || 0
if (x > y) return 1
if (x < y) return -1
}
return 0
}
const sdk = wx.getAppBaseInfo?.().SDKVersion ?? '2.0.0'
if (compareVersion(sdk, '3.9.0') >= 0) {
// 使用 3.9.0+ 才支持的能力
}7.8 WebSocket 实时通信
微信小游戏通过 wx.connectSocket 实现 WebSocket 实时通信,常用于多人对战、实时排行榜、聊天等功能。
基础连接与通信
javascript
// 创建 WebSocket 连接
const socket = wx.connectSocket({
url: 'wss://your-server.com/game',
header: {},
protocols: ['protocol-game-v1'], // 可选子协议
tcpNoDelay: true, // 关闭 Nagle 算法,降低延迟(实时游戏推荐开启)
})
// 连接生命周期
socket.onOpen(() => {
console.log('WebSocket 已连接')
// 连接成功后发送鉴权信息
socket.send({
data: JSON.stringify({ type: 'auth', token: getToken() }),
})
})
socket.onMessage((res) => {
const msg = JSON.parse(res.data)
handleGameMessage(msg)
})
socket.onError((err) => {
console.error('WebSocket 错误', err)
// 错误通常由底层网络引起,无需手动处理,onClose 会触发
})
socket.onClose((res) => {
console.log('WebSocket 已断开', res.code, res.reason)
// 触发重连逻辑
})心跳保活机制
微信 WebSocket 在长时间无数据传输时可能被中间路由断开。心跳机制是保持连接的必需手段:
javascript
class HeartbeatWS {
constructor(url) {
this.url = url
this.socket = null
this.heartbeatTimer = null
this.serverTimeoutId = null
this.heartbeatTimeoutMs = 30000 // 30s 未收到心跳响应则重连
this.pingInterval = 15000 // 15s 发送一次心跳
}
connect() {
this.socket = wx.connectSocket({ url: this.url })
this.socket.onOpen(() => {
this.startHeartbeat()
this.onReady()
})
this.socket.onMessage((res) => {
const data = JSON.parse(res.data)
if (data.type === 'pong') {
// 收到心跳响应,重置超时
this.resetServerTimeout()
return
}
this.onMessage(data)
})
this.socket.onClose(() => {
this.stopHeartbeat()
this.reconnect()
})
}
startHeartbeat() {
// 先发送一次心跳并启动超时检测
this.sendPing()
this.heartbeatTimer = setInterval(() => {
this.sendPing()
}, this.pingInterval)
}
sendPing() {
this.socket.send({ data: JSON.stringify({ type: 'ping' }) })
// 每次发送 ping 后重新启动超时检测
this.resetServerTimeout()
this.serverTimeoutId = setTimeout(() => {
console.warn('心跳超时,主动断开重连')
this.socket.close({ code: 4001, reason: 'heartbeat timeout' })
}, this.heartbeatTimeoutMs)
}
stopHeartbeat() {
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = null
this.resetServerTimeout()
}
resetServerTimeout() {
if (this.serverTimeoutId) {
clearTimeout(this.serverTimeoutId)
this.serverTimeoutId = null
}
}
reconnect() {
// 指数退避重连逻辑
}
onReady() {}
onMessage(data) {}
}断线重连策略
javascript
// 指数退避 + 随机抖动的重连策略
function createReconnectStrategy(maxAttempts = 10) {
let attempts = 0
const baseDelay = 1000 // 基础延迟 1s
const maxDelay = 30000 // 最大延迟 30s
return {
getDelay() {
if (attempts >= maxAttempts) return -1 // 达到上限,停止重连
// 指数退避:1s → 2s → 4s → 8s → 16s → 30s...
const exponentialDelay = Math.min(baseDelay * Math.pow(2, attempts), maxDelay)
// 随机抖动:避免大量客户端同时重连
const jitter = exponentialDelay * (0.5 + Math.random() * 0.5)
attempts++
return jitter
},
reset() {
attempts = 0
},
get attempts() {
return attempts
},
}
}
// 使用示例
const strategy = createReconnectStrategy(10)
function reconnect() {
const delay = strategy.getDelay()
if (delay < 0) {
console.error('已达最大重连次数')
wx.showModal({
title: '连接失败',
content: '网络不稳定,请检查网络后重试',
showCancel: false,
})
return
}
setTimeout(() => {
console.log(`正在重连...(第 ${strategy.attempts} 次)`)
socket.connect()
}, delay)
}多人对战通信架构
javascript
// 帧同步 vs 状态同步的选择
class MultiplayerManager {
constructor(mode) {
this.mode = mode // 'frame-sync' | 'state-sync'
}
// 帧同步:每帧发送操作指令(适用于实时动作类游戏)
sendFrameAction(frameNo, action) {
this.socket.send({
data: JSON.stringify({
type: 'frame',
frameNo,
action, // { move: 'left', jump: true, skill: 1 }
timestamp: Date.now(),
}),
})
}
// 状态同步:仅在状态变化时同步(适用于回合制/慢节奏游戏)
sendStateUpdate(partialState) {
// 差值同步:只发送变化的部分
this.socket.send({
data: JSON.stringify({
type: 'state-delta',
delta: partialState, // { score: 101, position: {x: 320, y: 240} }
seq: this.getNextSeq(),
}),
})
}
// 重连后状态恢复
requestFullState() {
this.socket.send({ data: JSON.stringify({ type: 'sync-full-state' }) })
}
// 延迟补偿(客户端预测)
applyClientPrediction(action) {
// 先本地执行,后续用服务端权威状态修正
this.localState = this.simulate(this.localState, action)
this.pendingInputs.push({ action, frameNo: this.frameNo })
}
}帧同步 vs 状态同步
| 维度 | 帧同步 | 状态同步 |
|---|---|---|
| 适用场景 | 格斗、MOBA、FPS | 回合制、MMO、卡牌 |
| 延迟要求 | 极低(< 50ms) | 中等(< 200ms) |
| 流量消耗 | 低(仅操作指令) | 中(状态数据) |
| 实现复杂度 | 高(确定性逻辑) | 中 |
| 微信小游戏推荐 | 休闲对战可用 | 更通用 |
7.9 订阅消息
订阅消息是小游戏与用户保持触达的核心渠道,用于召回用户、活动通知、奖励提醒等场景。
基础配置
javascript
// 订阅消息的完整流程
// Step 1: 请求用户订阅
wx.requestSubscribeMessage({
tmplIds: [
'模板ID1', // 活动通知模板
'模板ID2', // 奖励到账模板
],
success(res) {
// res 格式: { '模板ID1': 'accept', '模板ID2': 'accept' }
// accept: 用户同意; reject: 用户拒绝; ban: 已被后台封禁
const accepted = Object.entries(res).filter(([, status]) => status === 'accept')
console.log('用户订阅了', accepted.length, '个模板')
// 将订阅结果发送到服务端记录
wx.request({
url: 'https://your-server.com/api/subscribe',
method: 'POST',
data: { subscriptions: res },
})
},
fail(err) {
// 用户可能关闭了订阅弹窗或系统设置中关闭了通知
console.log('订阅失败', err)
},
})
// Step 2: 服务端通过微信服务器接口发送订阅消息
// POST https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN
// Body:
// {
// "touser": "OPENID",
// "template_id": "模板ID",
// "page": "index", // 点击消息跳转的小游戏页面
// "data": {
// "thing1": { "value": "新活动上线了!" },
// "time2": { "value": "2026-07-08 20:00" },
// "amount3": { "value": "100金币" }
// }
// }订阅消息最佳实践
订阅消息使用指南
- 合理的订阅时机 — 在用户获得奖励、完成关卡、参与活动等积极情绪时刻弹出订阅请求,转化率远高于进入游戏时直接弹出。
- 明确告知价值 — 订阅弹窗前用自定义 UI 说明"订阅后可获得 XX 奖励"或"不错过限时活动",降低拒绝率。
- 注意频次限制 — 每个模板消息每月有发送次数上限,避免在低价值场景滥用。
- 模板内容合规 — 模板消息不能包含广告、营销内容,必须是服务通知性质。违规可能被封禁模板消息能力。
- 处理拒绝场景 — 用户拒绝后不要反复弹出,应提供手动开启入口(
wx.openSetting)。 - 一次订阅多次发送 — 用户订阅一次后,服务端可在有效期内多次发送该模板消息。
应对模板消息限制的策略
小游戏没有 tabBar,因此不能调用小程序专用的 wx.setTabBarBadge。当模板消息额度用完或用户未订阅时,可使用以下降级方案:
javascript
class NotificationStrategy {
private noticeBadge: any | null = null // 游戏内自定义红点组件
// 方案1:游戏内红点通知
showInGameNotice() {
this.noticeBadge?.show('new')
}
// 方案2:利用微信客服消息(需用户主动发起对话后 48h 内可回复)
sendCustomerServiceMessage(openid, content) {
// 服务端调用客服消息接口
}
// 方案3:通过分享裂变间接通知
async shareActivityUpdate() {
const shareTitle = '好友都在玩新关卡,快来挑战!'
return wx.shareAppMessage({ title: shareTitle })
}
}7.10 微信运动、蓝牙与定位
微信运动数据
微信运动能力可将用户步数融入游戏玩法(如计步游戏、健康养成类游戏):
javascript
// 获取用户微信运动数据
wx.getWeRunData({
success(res) {
// res.encryptedData 是加密的步数数据
// res.iv 是解密所需的初始向量
wx.request({
url: 'https://your-server.com/api/decrypt-werun',
method: 'POST',
data: {
encryptedData: res.encryptedData,
iv: res.iv,
// ⚠️ session_key 属于服务端敏感信息,应由服务端使用自己保存的 session_key 解密
// 绝不要从客户端传入 sessionKey
},
success(decryptRes) {
// 解密后的数据格式: { stepInfoList: [{ timestamp, step }] }
const todaySteps = decryptRes.data.stepInfoList.slice(-1)[0].step
console.log('今日步数:', todaySteps)
// 将步数转换为游戏内体力或金币
convertStepsToGameReward(todaySteps)
},
})
},
fail(err) {
console.error('获取微信运动数据失败', err)
},
})微信运动使用限制
- 用户需先关注「微信运动」公众号并授权才能获取数据
- 数据为加密格式,需在服务端使用
session_key解密 - 仅能获取最近 30 天的步数数据
- 适合作为辅助玩法机制,不建议作为核心玩法依赖
蓝牙(BLE)通信
蓝牙 API 可用于连接外设(游戏手柄、手环等)或近距离设备间通信:
javascript
// 蓝牙完整使用流程
class BLEController {
constructor() {
this.connectedDevice = null
this._adapterStateHandler = null
this._deviceFoundHandler = null
this._valueChangeHandler = null
}
// Step 1: 初始化蓝牙适配器
async init() {
try {
await wx.openBluetoothAdapter()
console.log('蓝牙适配器已初始化')
} catch (err) {
console.error('蓝牙适配器初始化失败', err)
throw err
}
// 监听蓝牙适配器状态变化
this._adapterStateHandler = (res) => {
if (!res.available) {
console.warn('蓝牙已关闭')
this.connectedDevice = null
}
}
wx.onBluetoothAdapterStateChange(this._adapterStateHandler)
}
// Step 2: 扫描蓝牙设备
async startScan() {
try {
// 注意:Android 6.0+ 需要定位权限才能扫描蓝牙
await wx.startBluetoothDevicesDiscovery({
services: [], // 为空扫描所有设备,指定 service UUID 可加速
allowDuplicatesKey: false,
interval: 0,
})
} catch (err) {
console.error('启动蓝牙扫描失败', err)
return
}
this._deviceFoundHandler = (res) => {
res.devices.forEach((device) => {
// 过滤目标设备(通过名称或 service UUID)
if (device.name && device.name.startsWith('GamePad')) {
console.log('发现游戏手柄:', device.name, device.RSSI)
this.connectDevice(device.deviceId)
}
})
}
wx.onBluetoothDeviceFound(this._deviceFoundHandler)
}
// Step 3: 连接设备
async connectDevice(deviceId) {
try {
await wx.createBLEConnection({ deviceId })
this.connectedDevice = deviceId
console.log('蓝牙设备已连接')
} catch (err) {
console.error('连接蓝牙设备失败', err)
return
}
try {
// 获取设备所有服务
const { services } = await wx.getBLEDeviceServices({ deviceId })
// 找到目标服务(需查阅设备协议文档)
const targetService = services.find((s) => s.uuid.includes('FFF0'))
if (!targetService) {
console.warn('未找到目标服务')
return
}
// 获取服务中的特征值
const { characteristics } = await wx.getBLEDeviceCharacteristics({
deviceId,
serviceId: targetService.uuid,
})
// 订阅特征值通知(接收设备数据)
const notifyChar = characteristics.find((c) => c.properties.notify)
if (notifyChar) {
await wx.notifyBLECharacteristicValueChange({
deviceId,
serviceId: targetService.uuid,
characteristicId: notifyChar.uuid,
state: true,
})
this._valueChangeHandler = (res) => {
// 解析设备发送的数据
const data = new Uint8Array(res.value)
this.handleDeviceInput(data) // 例如:将蓝牙手柄按键映射为游戏操作
}
wx.onBLECharacteristicValueChange(this._valueChangeHandler)
}
} catch (err) {
console.error('蓝牙服务发现失败', err)
}
}
handleDeviceInput(data) {
// 解析蓝牙手柄按键数据,映射到游戏操作
// 例如:data[0] = 0x01 表示按下 A 键 → 触发跳跃
}
// 清理:断开连接、关闭适配器、移除监听
async close() {
try {
if (this.connectedDevice) {
await wx.closeBLEConnection({ deviceId: this.connectedDevice })
this.connectedDevice = null
}
await wx.closeBluetoothAdapter()
} catch (err) {
console.error('关闭蓝牙失败', err)
}
if (this._adapterStateHandler) {
wx.offBluetoothAdapterStateChange(this._adapterStateHandler)
this._adapterStateHandler = null
}
if (this._deviceFoundHandler) {
wx.offBluetoothDeviceFound(this._deviceFoundHandler)
this._deviceFoundHandler = null
}
if (this._valueChangeHandler) {
wx.offBLECharacteristicValueChange(this._valueChangeHandler)
this._valueChangeHandler = null
}
}
}地理位置
javascript
// 获取用户位置(LBS 游戏、AR 游戏核心能力)
// 注意:基础库 3.0.1 起 wx.getLocation 已不再维护,请使用 wx.getFuzzyLocation
// 需在 game.json(不是 app.json!)中声明: "requiredPrivateInfos": ["getFuzzyLocation"]
async function getUserLocation() {
try {
const res = await wx.getFuzzyLocation({
type: 'wgs84', // wgs84 或 gcj02
})
const { latitude, longitude, speed } = res
// 游戏中的位置应用:
// 1. 附近玩家匹配
findNearbyPlayers(latitude, longitude)
// 2. 基于地理的玩法(如打卡、占领地盘)
checkNearbyLandmarks(latitude, longitude)
// 3. 运动速度计算(骑行/跑步类游戏)。注意:speed 字段可能不存在或不可靠
if (typeof speed === 'number' && speed > 0) {
updateMovementSpeed(speed)
}
return { latitude, longitude }
} catch (err) {
if (err.errMsg.includes('auth deny')) {
// 用户拒绝授权,引导开启
wx.showModal({
title: '需要位置权限',
content: '开启位置权限后可使用附近功能',
success(modalRes) {
if (modalRes.confirm) {
wx.openSetting()
}
},
})
}
}
}能力使用前提
这些 API 使用时需注意:
- 微信运动:需用户授权,且加密数据需要服务端解密
- 蓝牙:需
scope.bluetooth授权,Android 还需定位权限 - 地理位置:需在
game.json中声明requiredPrivateInfos: ["getFuzzyLocation"],并在地图组件的配置中填写用途说明 - 使用前务必用
wx.canIUse()做能力检测,低版本基础库可能不支持
📝 课后练习
练习 1:登录流程排查
题目: 用户反馈登录后数据未同步。列出从 wx.login 到服务端返回 token 的完整链路中可能出错的环节。
参考答案:
wx.login获取 code 失败(网络问题)wx.request发送 code 超时或被防火墙拦截- 服务端用 code 换取 session_key 时 appid/secret 错误
- 服务端生成 token 时加密算法异常
- 前端
wx.setStorageSync写入 token 失败(存储满) - 后续请求未携带 token 或 token 已过期
练习 2:广告变现设计
题目: 为一款消除类游戏设计 3 个合适的广告插入点,说明每个点的广告类型和触发逻辑。
参考答案:
- 通关后的双倍奖励(激励视频广告)— 通关后弹出"看广告获得双倍金币",用户主动选择
- 体力恢复加速(激励视频广告)— 体力耗尽时"看广告立即恢复",转化率高
- 关卡切换(插屏广告)— 每 3 关切换时展示一次,频率控制避免骚扰
练习 3:WebSocket 重连
题目: 实现一个 WebSocket 断线重连逻辑,要求:指数退避、最大重连次数限制、随机抖动。
参考答案: 参见本章 7.8 节的 createReconnectStrategy 函数实现。
📚 相关阅读
- 开放数据域完整实现 — 排行榜功能的完整实现
- 10 运营与变现 — 广告、支付 API 的商业化应用
- 08 性能优化与包体控制 — API 调用中的性能注意事项