| name | weixin-game |
| description | 微信小游戏开发技术限制与最佳实践指南,帮助 Claude Code 在开发微信小游戏时遵循正确的 API 和技术规范。 |
| version | 1.0.0 |
| author | community |
| tags | ["weixin","minigame","wechat","game-development","canvas"] |
微信小游戏开发技术限制与最佳实践
指导 Claude Code 在开发微信小游戏时遵循技术限制和实现规范。
触发条件
TRIGGER when:
- 用户提到"微信小游戏"、"小游戏开发"、"微信游戏"
- 代码中使用
wx. API(如 wx.createCanvas、wx.onTouchStart 等)
- 项目包含
game.json 或 project.config.json(微信小游戏配置文件)
- 用户询问关于 Canvas 游戏在微信环境的适配
DO NOT trigger when:
- 开发普通 Web 应用或 H5 页面
- 使用其他小程序框架(如 uni-app、taro 开发普通小程序)
- 纯浏览器环境游戏开发
核心技术限制
1. 运行环境差异
微信小游戏运行在 JavaScriptCore/V8 引擎中,不是浏览器环境:
| 特性 | 浏览器 | 微信小游戏 |
|---|
| 全局对象 | window | globalThis |
| DOM | document.* | 不支持 |
| Canvas 创建 | document.createElement('canvas') | wx.createCanvas() |
| 图片加载 | new Image() | wx.createImage() |
| 存储 | localStorage | wx.setStorageSync() |
| 音频 | new Audio() | wx.createInnerAudioContext() |
| 网络 | fetch() / XMLHttpRequest | wx.request() |
2. 包大小限制
主包限制: 4MB
分包总限制: 20MB
单分包限制: 2MB(建议不超过 1.5MB)
应对策略:
- 使用分包加载(
subpackages 配置)
- 资源远程加载(CDN)
- 图片压缩、音频压缩
- 代码混淆和压缩
3. Canvas API 限制
ctx.roundRect()
ctx.toDataURL()
ctx.getImageData()
function drawRoundRect(ctx, x, y, w, h, r) {
ctx.beginPath()
ctx.moveTo(x + r, y)
ctx.lineTo(x + w - r, y)
ctx.quadraticCurveTo(x + w, y, x + w, y + r)
ctx.lineTo(x + w, y + h - r)
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h)
ctx.lineTo(x + r, y + h)
ctx.quadraticCurveTo(x, y + h, x, y + h - r)
ctx.lineTo(x, y + r)
ctx.quadraticCurveTo(x, y, x + r, y)
ctx.closePath()
}
4. 颜色格式限制
ctx.fillStyle = '#00d4ff40'
ctx.fillStyle = 'hsl(200, 100%, 50%)'
ctx.fillStyle = 'rgba(0, 212, 255, 0.25)'
ctx.fillStyle = '#00d4ff'
5. 网络请求限制
fetch('https://api.example.com/data')
wx.request({
url: 'https://api.example.com/data',
method: 'GET',
success(res) {
console.log(res.data)
},
fail(err) {
console.error(err)
}
})
wx.connectSocket({
url: 'wss://socket.example.com'
})
API 适配指南
全局变量
window.gameState = {}
window.myGlobal = value
globalThis.gameState = {}
let gameState = {}
export const GameGlobal = {
state: {},
config: {}
}
Canvas 管理
const mainCanvas = wx.createCanvas()
const ctx = mainCanvas.getContext('2d')
const offCanvas = wx.createOffscreenCanvas({
type: '2d',
width: 512,
height: 512
})
const { windowWidth, windowHeight } = wx.getSystemInfoSync()
mainCanvas.width = windowWidth
mainCanvas.height = windowHeight
图片加载
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = wx.createImage()
img.onload = () => resolve(img)
img.onerror = (err) => reject(err)
img.src = src
})
}
async function loadAssets() {
const assets = {
player: await loadImage('images/player.png'),
enemy: await loadImage('images/enemy.png'),
bg: await loadImage('images/background.png')
}
return assets
}
触摸事件
class TouchManager {
constructor() {
this.touches = new Map()
this.callbacks = {
start: [],
move: [],
end: []
}
this.init()
}
init() {
wx.onTouchStart(e => this.handleTouch('start', e))
wx.onTouchMove(e => this.handleTouch('move', e))
wx.onTouchEnd(e => this.handleTouch('end', e))
wx.onTouchCancel(e => this.handleTouch('end', e))
}
handleTouch(type, e) {
e.touches.forEach(touch => {
this.callbacks[type].forEach(cb => cb(touch))
})
}
on(type, callback) {
this.callbacks[type].push(callback)
}
}
const touchManager = new TouchManager()
touchManager.on('start', (touch) => {
console.log('Touch at:', touch.clientX, touch.clientY)
})
音频播放
class AudioManager {
constructor() {
this.sounds = new Map()
this.music = null
this.muted = false
}
loadSound(name, src) {
const audio = wx.createInnerAudioContext()
audio.src = src
this.sounds.set(name, audio)
return audio
}
playSound(name, volume = 1.0) {
if (this.muted) return
const audio = this.sounds.get(name)
if (audio) {
audio.volume = volume
audio.stop()
audio.play()
}
}
playMusic(src, loop = true) {
if (this.music) {
this.music.stop()
this.music.destroy()
}
this.music = wx.createInnerAudioContext()
this.music.src = src
this.music.loop = loop
this.music.play()
}
destroy() {
this.sounds.forEach(audio => audio.destroy())
this.sounds.clear()
if (this.music) {
this.music.destroy()
this.music = null
}
}
}
本地存储
wx.setStorageSync('playerData', { level: 5, score: 1000 })
const data = wx.getStorageSync('playerData') || {}
wx.setStorage({
key: 'largeData',
data: largeObject,
success() { console.log('Saved') },
fail(err) { console.error(err) }
})
wx.removeStorageSync('key')
wx.clearStorageSync()
布局注意事项
1. 右上角菜单按钮区域
微信小游戏右上角有固定的操作菜单按钮("..."按钮),点击后显示菜单选项。必须避开此区域放置关键UI元素。
┌─────────────────────────────────┐
│ [≡] [•••] │ ← 右上角菜单按钮区域(约44px高度)
│ │
│ 安全区 (safeArea) │ ← 主要内容区域
│ │
│ │
│ │
│ _______________________________│ ← 底部安全区域(iPhone X系列手势条)
└─────────────────────────────────┘
避开区域尺寸:
- 右上角菜单按钮:高度约 44px,宽度约 87px(iOS)/ 48px(Android)
- 状态栏:
systemInfo.statusBarHeight
- 底部手势条:iPhone X系列约 34px
const systemInfo = wx.getSystemInfoSync()
const { safeArea, statusBarHeight } = systemInfo
console.log('安全区域:', {
top: safeArea.top,
bottom: safeArea.bottom,
left: safeArea.left,
right: safeArea.right,
width: safeArea.width,
height: safeArea.height
})
const menuButtonArea = {
top: statusBarHeight,
height: 44,
right: systemInfo.windowWidth,
width: 87
}
2. 安全区域适配方案
class SafeAreaManager {
constructor() {
const systemInfo = wx.getSystemInfoSync()
this.safeArea = systemInfo.safeArea
this.screenWidth = systemInfo.windowWidth
this.screenHeight = systemInfo.windowHeight
this.statusBarHeight = systemInfo.statusBarHeight
this.dangerAreas = {
top: {
y: 0,
height: Math.max(this.safeArea.top, this.statusBarHeight + 44)
},
topRight: {
x: this.screenWidth - 100,
y: 0,
width: 100,
height: this.statusBarHeight + 44
},
bottom: {
y: this.safeArea.bottom,
height: this.screenHeight - this.safeArea.bottom
}
}
}
isInSafeArea(x, y, width = 0, height = 0) {
const danger = this.dangerAreas
if (y < danger.top.height) return false
if (x + width > danger.topRight.x && y < danger.topRight.height) return false
if (y + height > danger.bottom.y) return false
return true
}
getSafeBounds() {
return {
top: this.dangerAreas.top.height,
bottom: this.safeArea.bottom,
left: this.safeArea.left,
right: this.safeArea.right
}
}
adjustToSafeArea(x, y, width, height) {
const bounds = this.getSafeBounds()
if (x < bounds.left) x = bounds.left
if (x + width > bounds.right) x = bounds.right - width
if (y < bounds.top) y = bounds.top
if (y + height > bounds.bottom) y = bounds.bottom - height
return { x, y }
}
}
const safeArea = new SafeAreaManager()
const button = { x: 10, y: 10, width: 100, height: 44 }
if (!safeArea.isInSafeArea(button.x, button.y, button.width, button.height)) {
const adjusted = safeArea.adjustToSafeArea(button.x, button.y, button.width, button.height)
button.x = adjusted.x
button.y = adjusted.y
}
3. 常见布局问题
const button = { x: screenWidth - 120, y: 20 }
const safeY = safeArea.top + 10
const button = { x: screenWidth - 120, y: safeY }
const bottomButton = { y: screenHeight - 50 }
const safeBottomY = safeArea.bottom - 50 - 10
const bottomButton = { y: safeBottomY }
4. 多设备适配
const deviceInfo = wx.getDeviceInfo()
const isIPhoneX = deviceInfo.model.includes('iPhone X') ||
deviceInfo.model.includes('iPhone 1')
if (isIPhoneX) {
this.bottomPadding = 34
}
屏幕适配方案
class ScreenAdapter {
constructor(designWidth = 375, designHeight = 667) {
this.designWidth = designWidth
this.designHeight = designHeight
const info = wx.getSystemInfoSync()
this.screenWidth = info.windowWidth
this.screenHeight = info.windowHeight
this.pixelRatio = info.pixelRatio
this.safeArea = info.safeArea
this.scale = Math.min(
this.screenWidth / designWidth,
this.screenHeight / designHeight
)
this.offsetX = (this.screenWidth - designWidth * this.scale) / 2
this.offsetY = (this.screenHeight - designHeight * this.scale) / 2
}
screenToGame(x, y) {
return {
x: (x - this.offsetX) / this.scale,
y: (y - this.offsetY) / this.scale
}
}
gameToScreen(x, y) {
return {
x: x * this.scale + this.offsetX,
y: y * this.scale + this.offsetY
}
}
getSafeArea() {
return {
top: this.safeArea.top,
bottom: this.safeArea.bottom,
left: this.safeArea.left,
right: this.safeArea.right
}
}
}
性能优化建议
1. 渲染优化
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.clearRect(obj.x - 1, obj.y - 1, obj.width + 2, obj.height + 2)
const cacheCanvas = wx.createOffscreenCanvas({ width: 200, height: 200 })
const cacheCtx = cacheCanvas.getContext('2d')
cacheCtx.drawImage(backgroundImg, 0, 0)
ctx.drawImage(cacheCanvas, 0, 0)
2. 对象池
class ObjectPool {
constructor(createFn, initialSize = 10) {
this.createFn = createFn
this.pool = []
this.active = []
for (let i = 0; i < initialSize; i++) {
this.pool.push(createFn())
}
}
get() {
const obj = this.pool.pop() || this.createFn()
this.active.push(obj)
return obj
}
release(obj) {
const index = this.active.indexOf(obj)
if (index > -1) {
this.active.splice(index, 1)
this.pool.push(obj)
}
}
releaseAll() {
while (this.active.length) {
this.pool.push(this.active.pop())
}
}
}
const bulletPool = new ObjectPool(() => ({ x: 0, y: 0, active: false }), 20)
3. 资源管理
class AssetLoader {
constructor() {
this.images = new Map()
this.audios = new Map()
}
async loadImages(list) {
const promises = list.map(item =>
loadImage(item.src).then(img => {
this.images.set(item.name, img)
return { name: item.name, img }
})
)
return Promise.all(promises)
}
getImage(name) {
return this.images.get(name)
}
}
const loader = new AssetLoader()
await loader.loadImages([
{ name: 'player', src: 'images/player.png' },
{ name: 'enemy', src: 'images/enemy.png' }
])
高级功能与限制
1. 开放数据域(排行榜)
微信小游戏的好友排行榜必须在开放数据域中实现,主域无法直接访问好友数据。
项目结构:
├── game.js # 主域入口
├── open-data-context/ # 开放数据域
│ └── index.js # 排行榜逻辑
└── game.json # 配置
game.json 配置:
{
"openDataContext": "open-data-context"
}
主域与开放数据域通信:
const openDataContext = wx.getOpenDataContext()
openDataContext.postMessage({
type: 'updateScore',
score: 1000,
level: 5
})
const sharedCanvas = openDataContext.canvas
sharedCanvas.width = 300
sharedCanvas.height = 400
const mainCtx = mainCanvas.getContext('2d')
mainCtx.drawImage(sharedCanvas, 50, 100)
开放数据域 index.js:
wx.onMessage(data => {
if (data.type === 'updateScore') {
wx.setUserCloudStorage({
KVDataList: [{
key: 'score',
value: JSON.stringify({ score: data.score, level: data.level })
}],
success() {
console.log('分数上传成功')
getFriendRanking()
}
})
}
if (data.type === 'getRanking') {
getFriendRanking()
}
})
function getFriendRanking() {
wx.getFriendCloudStorage({
keyList: ['score'],
success(res) {
const sorted = res.data.sort((a, b) => {
const scoreA = JSON.parse(a.KVDataList[0].value).score
const scoreB = JSON.parse(b.KVDataList[0].value).score
return scoreB - scoreA
})
drawRanking(sorted)
}
})
}
function drawRanking(data) {
const canvas = wx.createCanvas()
const ctx = canvas.getContext('2d')
ctx.fillStyle = '#fff'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = '#333'
ctx.font = '14px Arial'
data.forEach((player, index) => {
const score = JSON.parse(player.KVDataList[0].value).score
ctx.fillText(`${index + 1}. ${player.nickname}: ${score}`, 10, 30 + index * 25)
})
}
重要限制:
wx.getFriendCloudStorage() 只能在开放数据域调用
- 开放数据域无法使用大部分
wx. API(如网络请求受限)
- 开放数据域有独立的 Canvas,通过
sharedCanvas 与主域共享
2. 多线程 Worker
用于将耗时计算放到后台线程,避免阻塞主线程。
const worker = wx.createWorker('workers/physics.js')
worker.postMessage({
type: 'calculate',
bodies: physicsData
})
worker.onMessage(res => {
if (res.type === 'result') {
updatePhysics(res.data)
}
})
worker.onMessage(data => {
if (data.type === 'calculate') {
const result = heavyPhysicsCalculation(data.bodies)
worker.postMessage({
type: 'result',
data: result
})
}
})
3. 内存管理
微信小游戏对内存敏感,需要主动管理:
wx.onMemoryWarning((res) => {
console.log('内存警告,级别:', res.level)
textureCache.clear()
audioCache.clear()
gc()
})
wx.triggerGC()
const performance = wx.getPerformance()
const memory = performance.getEntriesByType('memory')
console.log('内存使用:', memory)
内存优化建议:
const audio = wx.createInnerAudioContext()
audio.src = 'sound.mp3'
audio.play()
audio.onEnded(() => {
audio.destroy()
})
class ImagePool {
constructor(maxSize = 20) {
this.pool = new Map()
this.maxSize = maxSize
}
get(src) {
if (this.pool.has(src)) {
return this.pool.get(src)
}
const img = wx.createImage()
img.src = src
if (this.pool.size >= this.maxSize) {
const firstKey = this.pool.keys().next().value
this.pool.delete(firstKey)
}
this.pool.set(src, img)
return img
}
}
class GameManager {
constructor() {
this.listeners = []
}
addListener(callback) {
this.listeners.push(callback)
}
destroy() {
this.listeners = []
}
}
4. 分享功能
wx.onShareAppMessage(() => {
return {
title: '来挑战我的高分!',
imageUrl: 'images/share.png',
query: 'level=5&score=1000'
}
})
wx.shareAppMessage({
title: '我通关了第5关!',
imageUrl: 'images/share-level5.png',
query: 'from=share&level=5',
success() {
console.log('分享成功')
},
fail(err) {
console.error('分享失败', err)
}
})
wx.shareAppMessage({
title: '一起来玩!',
imageUrl: 'images/share.png',
toGroup: true
})
5. 虚拟支付
wx.requestMidasPayment({
mode: 'game',
env: 0,
offerId: '123456',
currencyType: 'CNY',
buyQuantity: 10,
success(res) {
console.log('支付成功', res)
},
fail(err) {
console.error('支付失败', err)
}
})
6. 版本兼容
if (wx.canIUse('getDeviceInfo')) {
const deviceInfo = wx.getDeviceInfo()
}
function compareVersion(v1, v2) {
v1 = v1.split('.')
v2 = v2.split('.')
const len = Math.max(v1.length, v2.length)
while (v1.length < len) v1.push('0')
while (v2.length < len) v2.push('0')
for (let i = 0; i < len; i++) {
const num1 = parseInt(v1[i])
const num2 = parseInt(v2[i])
if (num1 > num2) return 1
if (num1 < num2) return -1
}
return 0
}
const systemInfo = wx.getSystemInfoSync()
const baseVersion = systemInfo.SDKVersion
if (compareVersion(baseVersion, '2.10.0') >= 0) {
} else {
}
常见错误与解决
错误 1: window is not defined
window.myVar = value
globalThis.myVar = value
错误 2: document is not defined
const el = document.getElementById('xxx')
document.createElement('canvas')
const canvas = wx.createCanvas()
错误 3: 图片加载失败
wx.createImage()
img.onerror = (e) => {
console.error('Image load failed:', e)
}
错误 4: 音频无法播放
let audioInitialized = false
wx.onTouchStart(() => {
if (!audioInitialized) {
audioInitialized = true
}
})
项目结构建议
my-minigame/
├── game.json # 游戏配置
├── game.js # 入口文件
├── project.config.json # 项目配置
├── js/
│ ├── Game.js # 游戏主类
│ ├── scenes/ # 场景
│ │ ├── MenuScene.js
│ │ └── GameScene.js
│ ├── entities/ # 实体类
│ │ ├── Player.js
│ │ └── Enemy.js
│ ├── managers/ # 管理器
│ │ ├── AudioManager.js
│ │ ├── InputManager.js
│ │ └── SceneManager.js
│ └── utils/ # 工具类
│ ├── ObjectPool.js
│ └── ScreenAdapter.js
├── images/ # 图片资源
├── audio/ # 音频资源
└── subpackages/ # 分包
├── level2/
└── level3/
game.json 配置示例
{
"deviceOrientation": "portrait",
"showStatusBar": false,
"networkTimeout": {
"request": 10000,
"connectSocket": 10000,
"uploadFile": 10000,
"downloadFile": 10000
},
"subpackages": [
{
"name": "level2",
"root": "subpackages/level2"
},
{
"name": "level3",
"root": "subpackages/level3"
}
],
"plugins": {}
}
检查清单
开发微信小游戏时,Claude Code 应确保:
基础 API 适配
布局与安全区域
性能与资源
高级功能
测试与调试