Install any skill in seconds. Free to start, no credit card required.
Get Started Free →微信小游戏开发技术限制与最佳实践指南,帮助 Claude Code 在开发微信小游戏时遵循正确的 API 和技术规范。
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | 338% | 0% |
| case-20 | ✗→✓ | ▲ Improved | 178% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 466% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 587% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 196% | 0% |
<!-- source: weixin-minigame-dev — https://raw.githubusercontent.com/wukaikailive/weixin-game-skill/main/skill.md -->
指导 Claude Code 在开发微信小游戏时遵循技术限制和实现规范。
TRIGGER when:
wx. API(如 wx.createCanvas、wx.onTouchStart 等)game.json 或 project.config.json(微信小游戏配置文件)DO NOT trigger when:
微信小游戏运行在 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() |
主包限制: 4MB
分包总限制: 20MB
单分包限制: 2MB(建议不超过 1.5MB)应对策略:
subpackages 配置)javascript// ❌ 不支持的 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() }
javascript// ❌ 部分格式不支持或不稳定 ctx.fillStyle = '#00d4ff40' // 带透明度的简写 hex ctx.fillStyle = 'hsl(200, 100%, 50%)' // HSL 格式 // ✅ 推荐使用 rgba ctx.fillStyle = 'rgba(0, 212, 255, 0.25)' ctx.fillStyle = '#00d4ff' // 标准 hex
javascript// 必须配置合法域名(小程序后台配置) // 仅支持 HTTPS // 请求超时默认 60 秒 // ❌ 不支持 fetch('https://api.example.com/data') // ✅ 使用 wx.request wx.request({ url: 'https://api.example.com/data', method: 'GET', success(res) { console.log(res.data) }, fail(err) { console.error(err) } }) // WebSocket 限制 wx.connectSocket({ url: 'wss://socket.example.com' // 必须是 wss })
javascript// ❌ 浏览器写法 window.gameState = {} window.myGlobal = value // ✅ 微信小游戏写法 // 方案1: 使用 globalThis globalThis.gameState = {} // 方案2: 文件顶层定义 let gameState = {} // 模块级变量 // 方案3: 创建全局管理器 // GameGlobal.js export const GameGlobal = { state: {}, config: {} }
javascript// 主 Canvas(屏幕 Canvas) const mainCanvas = wx.createCanvas() const ctx = mainCanvas.getContext('2d') // 离屏 Canvas(用于缓存、预渲染) const offCanvas = wx.createOffscreenCanvas({ type: '2d', width: 512, height: 512 }) // 获取实际显示尺寸 const { windowWidth, windowHeight } = wx.getSystemInfoSync() mainCanvas.width = windowWidth mainCanvas.height = windowHeight
javascript// ✅ 微信小游戏图片加载 function loadImage(src) { return new Promise((resolve, reject) => { const img = wx.createImage() img.onload = () => resolve(img) img.onerror = (err) => reject(err) img.src = src // 相对路径或配置的 CDN 域名 }) } // 加载多张图片 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 }
javascript// 微信使用全局触摸事件,不是 Canvas 上的事件 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) })
javascript// 音频管理器 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 } } }
javascript// ✅ 微信存储 API // 同步存储 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() // 清空所有
微信小游戏右上角有固定的操作菜单按钮("..."按钮),点击后显示菜单选项。必须避开此区域放置关键UI元素。
┌─────────────────────────────────┐
│ [≡] [•••] │ ← 右上角菜单按钮区域(约44px高度)
│ │
│ 安全区 (safeArea) │ ← 主要内容区域
│ │
│ │
│ │
│ _______________________________│ ← 底部安全区域(iPhone X系列手势条)
└─────────────────────────────────┘避开区域尺寸:
systemInfo.statusBarHeightjavascript// 获取安全区域信息 const systemInfo = wx.getSystemInfoSync() const { safeArea, statusBarHeight } = systemInfo // 安全区域边界 console.log('安全区域:', { top: safeArea.top, // 安全区域顶部 y 坐标 bottom: safeArea.bottom, // 安全区域底部 y 坐标 left: safeArea.left, // 安全区域左侧 x 坐标 right: safeArea.right, // 安全区域右侧 x 坐标 width: safeArea.width, // 安全区域宽度 height: safeArea.height // 安全区域高度 }) // 右上角按钮区域(保守估计) const menuButtonArea = { top: statusBarHeight, height: 44, right: systemInfo.windowWidth, width: 87 // iOS 较宽,Android 较窄 }
javascriptclass 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 }, // 底部:手势条区域(iPhone X系列) 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() // 调整 x 坐标 if (x < bounds.left) x = bounds.left if (x + width > bounds.right) x = bounds.right - width // 调整 y 坐标 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 }
javascript// ❌ 错误:按钮放在右上角菜单按钮下方 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 }
javascript// 获取设备信息 const deviceInfo = wx.getDeviceInfo() const isIPhoneX = deviceInfo.model.includes('iPhone X') || deviceInfo.model.includes('iPhone 1') // iPhone 11, 12, 13, 14, 15 系列 // iPhone X 系列特殊处理 if (isIPhoneX) { // 底部额外留白 this.bottomPadding = 34 }
javascriptclass 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 } } }
javascript// ❌ 每帧都清除整个画布 ctx.clearRect(0, 0, canvas.width, canvas.height) // ✅ 只清除变化区域(对于静态背景) ctx.clearRect(obj.x - 1, obj.y - 1, obj.width + 2, obj.height + 2) // ✅ 使用离屏 Canvas 缓存静态元素 const cacheCanvas = wx.createOffscreenCanvas({ width: 200, height: 200 }) const cacheCtx = cacheCanvas.getContext('2d') // 绘制一次 cacheCtx.drawImage(backgroundImg, 0, 0) // 主循环中直接绘制缓存 ctx.drawImage(cacheCanvas, 0, 0)
javascriptclass 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)
javascript// 资源预加载 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' } ])
微信小游戏的好友排行榜必须在开放数据域中实现,主域无法直接访问好友数据。
项目结构:
├── game.js # 主域入口
├── open-data-context/ # 开放数据域
│ └── index.js # 排行榜逻辑
└── game.json # 配置game.json 配置:
json{ "openDataContext": "open-data-context" }
主域与开放数据域通信:
javascript// 主域 game.js const openDataContext = wx.getOpenDataContext() // 发送消息到开放数据域 openDataContext.postMessage({ type: 'updateScore', score: 1000, level: 5 }) // 创建共享 Canvas(用于显示排行榜) const sharedCanvas = openDataContext.canvas sharedCanvas.width = 300 sharedCanvas.height = 400 // 在主 Canvas 上绘制共享 Canvas const mainCtx = mainCanvas.getContext('2d') mainCtx.drawImage(sharedCanvas, 50, 100)
开放数据域 index.js:
javascript// 接收主域消息 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) { // res.data 是好友数据数组 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 }) // 绘制排行榜到 Canvas 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(如网络请求受限)sharedCanvas 与主域共享用于将耗时计算放到后台线程,避免阻塞主线程。
javascript// 主线程 const worker = wx.createWorker('workers/physics.js') // 发送数据 worker.postMessage({ type: 'calculate', bodies: physicsData }) // 接收结果 worker.onMessage(res => { if (res.type === 'result') { // 更新物理状态 updatePhysics(res.data) } }) // workers/physics.js worker.onMessage(data => { if (data.type === 'calculate') { // 执行耗时计算 const result = heavyPhysicsCalculation(data.bodies) // 返回结果 worker.postMessage({ type: 'result', data: result }) } })
微信小游戏对内存敏感,需要主动管理:
javascript// 监听内存警告 wx.onMemoryWarning((res) => { console.log('内存警告,级别:', res.level) // level: 0-5,越大越严重 // 清理缓存 textureCache.clear() audioCache.clear() // 释放未使用的资源 gc() }) // 主动触发垃圾回收(调试用) wx.triggerGC() // 获取内存使用情况 const performance = wx.getPerformance() const memory = performance.getEntriesByType('memory') console.log('内存使用:', memory)
内存优化建议:
javascript// 1. 及时销毁音频 const audio = wx.createInnerAudioContext() audio.src = 'sound.mp3' audio.play() audio.onEnded(() => { audio.destroy() // 播放完毕后销毁 }) // 2. 图片对象池 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 } } // 3. 避免内存泄漏 class GameManager { constructor() { this.listeners = [] } addListener(callback) { this.listeners.push(callback) } destroy() { // 清除所有监听器 this.listeners = [] } }
javascript// 被动分享(用户点击右上角菜单) 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 })
javascript// 发起支付(需在小程序后台开通) wx.requestMidasPayment({ mode: 'game', env: 0, // 0: 正式环境, 1: 沙箱环境 offerId: '123456', // 商品ID currencyType: 'CNY', buyQuantity: 10, success(res) { console.log('支付成功', res) // 发放道具 }, fail(err) { console.error('支付失败', err) } })
javascript// 检查 API 是否可用 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) { // 使用新 API } else { // 降级处理 }
window is not definedjavascript// ❌ 使用 window window.myVar = value // ✅ 使用 globalThis 或模块变量 globalThis.myVar = value
document is not definedjavascript// ❌ 使用 document const el = document.getElementById('xxx') document.createElement('canvas') // ✅ 使用微信 API const canvas = wx.createCanvas()
javascript// 检查路径是否正确 // 检查是否配置了 download 域名(网络图片) // 检查图片格式(支持 png, jpg, gif) wx.createImage() img.onerror = (e) => { console.error('Image load failed:', e) }
javascript// 用户交互后才能播放音频 // 首次播放需要在 touchstart/touchend 回调中触发 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/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 应确保:
window、document、DOM APIwx.createCanvas() 创建 Canvaswx.createImage() 加载图片wx.createInnerAudioContext() 播放音频wx.onTouchStart/Move/End 处理触摸wx.setStorageSync/getStorageSync 存储wx.request() 进行网络请求roundRect() 等不支持的 APIrgba() 或标准 #RRGGBB 格式wx.getSystemInfoSync().safeArea 获取安全区域wx.onMemoryWarning 处理内存警告wx.canIUse() 检查 API 兼容性Other measured skills in the registry, with their headline benchmark lift.