| 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( => (touch))
})
}
() {
.[type].(callback)
}
}
touchManager = ()
touchManager.(, {
.(, touch., touch.)
})
音频播放
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()
..()
}
. = wx.()
.. = src
.. = loop
..()
}
() {
..( audio.())
..()
(.) {
..()
. =
}
}
}
本地存储
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: {
: ..,
: . - ..
}
}
}
() {
danger = .
(y < danger..)
(x + width > danger.. && y < danger..)
(y + height > danger..)
}
() {
{
: ...,
: ..,
: ..,
: ..
}
}
() {
bounds = .()
(x < bounds.) x = bounds.
(x + width > bounds.) x = bounds. - width
(y < bounds.) y = bounds.
(y + height > bounds.) y = bounds. - height
{ x, y }
}
}
safeArea = ()
button = { : , : , : , : }
(!safeArea.(button., button., button., button.)) {
adjusted = safeArea.(button., button., button., button.)
button. = adjusted.
button. = adjusted.
}
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
}
() {
{
: (x - .) / .,
: (y - .) / .
}
}
() {
{
: x * . + .,
: y * . + .
}
}
() {
{
: ..,
: ..,
: ..,
: ..
}
}
}
性能优化建议
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.) {
..(..())
}
}
}
bulletPool = ( ({ : , : , : }), )
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: , : }
])
高级功能与限制
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).
scoreB = .(b.[].).
scoreB - scoreA
})
(sorted)
}
})
}
() {
canvas = wx.()
ctx = canvas.()
ctx. =
ctx.(, , canvas., canvas.)
ctx. =
ctx. =
data.( {
score = .(player.[].).
ctx.(, , + index * )
})
}
重要限制:
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)
}
..(src, img)
img
}
}
{
() {
. = []
}
() {
..(callback)
}
() {
. = []
}
}
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) {
} {
}
常见错误与解决
错误 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 适配
布局与安全区域
性能与资源
高级功能
测试与调试