Skip to content

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.getUserProfilewx.getUserInfo 自基础库 2.27.1 起已完全废弃,返回的均为匿名信息(默认灰色头像 + "微信用户"昵称),不再能获取真实用户资料。

当前(基础库 ≥ 2.21.2)唯一合规的用户信息获取方案是让用户主动填写

  1. 头像:使用 <button open-type="chooseAvatar"> 让用户选择头像,获取临时 URL 后立即上传到自己的服务器(临时链接约 2 小时内失效)。
  2. 昵称:使用 <input type="nickname"> 让用户点击键盘上方的微信昵称条填入。
  3. 自定义 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 },
    })
  }
}
前端对比:Web API vs 微信 API
前端 Web微信小游戏区别
fetch() / axioswx.request()微信 API 自带 session 管理
localStoragewx.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)
  },
})
虚拟支付注意事项
  1. 需先在微信公众平台申请「小游戏虚拟支付」能力,且仅企业主体可申请。
  2. offerId 在米大师后台配置,对应具体的商品/礼包。
  3. iOS 虚拟支付:iOS 端小游戏虚拟支付长期受限;截至 2026-07,具体方案以微信官方最新公告为准。在正式上线前,建议按平台做功能开关或提示,并关注微信官方接入文档。
  4. 沙箱环境(env: 1)用于开发联调,上线前务必切换为正式环境。
  5. 支付结果以服务端回调为准,前端 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 使用原则
  1. 异步优先 — 所有 API 调用使用 Promise/async-await 包装
  2. 错误处理 — 每个 API 调用必须处理 fail 回调,做好降级方案
  3. 权限最小化 — 仅在必要时请求用户授权
  4. 缓存策略 — 用户信息、配置数据本地缓存,减少 API 调用
  5. 版本兼容 — 优先使用 wx.canIUse() 做能力检测;如需读取版本号,使用 wx.getAppBaseInfo().SDKVersion,避免字符串直接比较
  6. 分包意识 — 非核心功能可以通过分包延迟加载

将回调式 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金币" }
//   }
// }

订阅消息最佳实践

订阅消息使用指南
  1. 合理的订阅时机 — 在用户获得奖励、完成关卡、参与活动等积极情绪时刻弹出订阅请求,转化率远高于进入游戏时直接弹出。
  2. 明确告知价值 — 订阅弹窗前用自定义 UI 说明"订阅后可获得 XX 奖励"或"不错过限时活动",降低拒绝率。
  3. 注意频次限制 — 每个模板消息每月有发送次数上限,避免在低价值场景滥用。
  4. 模板内容合规 — 模板消息不能包含广告、营销内容,必须是服务通知性质。违规可能被封禁模板消息能力。
  5. 处理拒绝场景 — 用户拒绝后不要反复弹出,应提供手动开启入口(wx.openSetting)。
  6. 一次订阅多次发送 — 用户订阅一次后,服务端可在有效期内多次发送该模板消息。

应对模板消息限制的策略

小游戏没有 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)
  },
})

蓝牙(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 的完整链路中可能出错的环节。

参考答案:

  1. wx.login 获取 code 失败(网络问题)
  2. wx.request 发送 code 超时或被防火墙拦截
  3. 服务端用 code 换取 session_key 时 appid/secret 错误
  4. 服务端生成 token 时加密算法异常
  5. 前端 wx.setStorageSync 写入 token 失败(存储满)
  6. 后续请求未携带 token 或 token 已过期
练习 2:广告变现设计

题目: 为一款消除类游戏设计 3 个合适的广告插入点,说明每个点的广告类型和触发逻辑。

参考答案:

  1. 通关后的双倍奖励(激励视频广告)— 通关后弹出"看广告获得双倍金币",用户主动选择
  2. 体力恢复加速(激励视频广告)— 体力耗尽时"看广告立即恢复",转化率高
  3. 关卡切换(插屏广告)— 每 3 关切换时展示一次,频率控制避免骚扰
练习 3:WebSocket 重连

题目: 实现一个 WebSocket 断线重连逻辑,要求:指数退避、最大重连次数限制、随机抖动。

参考答案: 参见本章 7.8 节的 createReconnectStrategy 函数实现。


📚 相关阅读

用心学习,持续实践