微信小游戏开发:引擎选型、性能与变现

从零构建微信小游戏的完整技术路径:对比 Cocos Creator、LayaAir、Egret 与原生 WebGL 四类引擎的取舍,剖析渲染批次、纹理内存与首包体积的性能红线,梳理激励视频、插屏广告与虚拟支付三条变现链路的接入要点,并给出真机调优与审核合规的落地清单。

微信小游戏(WeChat Mini Game)运行在小程序基础库之上,却与普通小程序共享的是两套截然不同的运行环境。普通小程序由逻辑层(AppService)与渲染层(WebView)双线程构成,通过 setData 做跨线程通信;而小游戏只保留逻辑层,渲染层被替换为一块独占的 Canvas,开发者需要直接调用 Canvas 2D 或 WebGL 接口把画面「画」出来,没有 WXML、没有样式表、没有节点树。

这意味着小程序生态里成熟的那套「数据驱动视图」的心智模型在小游戏里完全失效。帧率、内存、DrawCall 全部要手动管理,首包体积更是决定了用户点开游戏后要等多久才能看到第一帧。本文按引擎选型、渲染性能、变现链路三条主线,把一套小游戏从立项到上线需要做的技术决策梳理清楚。如果你还在纠结要不要用引擎,也可以先看 Unity 和 Unreal 哪个更适合入门 了解原生引擎的思路差异,再回到小游戏这个受限环境做取舍。

一、小游戏与小程序的环境差异

先把边界划清楚,后面所有性能决策都建立在这些约束上。

维度普通小程序微信小游戏
视图层WXML + WXSS + WebView单个 Canvas(2D / WebGL)
线程模型逻辑层 + 渲染层双线程逻辑层 + 渲染(Canvas 在同一线程组)
通信setData 跨线程序列化直接调用绘制 API,无序列化开销
主包上限2MB(分包合计 20MB)4MB(分包合计 20MB)
入口app.json 页面路由game.json + game.js
可用 API全量 wx.*子集,无 wx.request 的 enableHttp2 等部分能力

主包从 2MB 放宽到 4MB 是小游戏相对宽松的地方,但引擎运行时本身就吃掉 1.5~3MB。以 Cocos Creator 3.x 为例,仅引擎核心 + 必要的 2D 模块,构建后 cocos-js 目录通常就有 1.8MB 左右,留给业务代码和首屏资源的空间非常紧张。

game.json 里的关键配置项:

{
  "deviceOrientation": "portrait",
  "showStatusBar": false,
  "networkTimeout": {
    "request": 10000,
    "downloadFile": 10000
  },
  "subpackages": [
    { "name": "level2", "root": "subpackages/level2/" }
  ]
}

subpackages 是小游戏的命脉:主包只放引擎、启动场景和第一关,后续关卡、皮肤、音效全部塞进分包,配合 wx.loadSubpackage 在关卡切换的过场动画里预下载,用户几乎感知不到加载。

二、引擎选型

小游戏引擎的选择本质是「开发效率」与「运行时开销」的博弈。主流方案可以分成四类。

2.1 重型一体化引擎:Cocos Creator / LayaAir / Egret

Cocos Creator 是目前小游戏生态占有率最高的方案,编辑器、场景系统、物理引擎、UI 系统、动画状态机一应俱全,还能一键发布到抖音、快手、支付宝等平台。代价是运行时体积和启动耗时。

# Cocos Creator 3.8 构建小游戏
# 编辑器内:项目 -> 构建发布 -> 微信小游戏 -> 构建
# 命令行构建(CI 场景)
/Applications/Cocos/Creator/3.8.0/CocosCreator.app/Contents/MacOS/CocosCreator \
  --project /path/to/project \
  --build "platform=wechatgame;debug=false;md5Cache=true"

LayaAir 的定位接近 Cocos,但在 2D 渲染上做了更多批处理优化,适合卡牌、休闲类。Egret 生态在 2019 年后活跃度下降,新项目不建议采用,但存量项目维护仍会遇到。

选这类引擎的判断标准很简单:如果你的游戏有复杂 UI 层级、需要动画状态机、需要跨平台发布,就直接上 Cocos Creator,别自己造轮子。

2.2 轻量渲染库:原生 WebGL + Three.js / PixiJS

如果游戏逻辑简单(消除类、点击类、单场景跑酷),引入完整引擎是巨大的浪费。直接在小游戏里用 WebGL 写渲染,或者引入 PixiJS 这类 2D 渲染库,主包能压到 600KB 以内。

小游戏的 wx.createCanvas() 返回的 canvas 可以直接取 WebGL 上下文:

const canvas = wx.createCanvas()
const gl = canvas.getContext('webgl')

// 视口按设备像素比设置,否则高分屏会糊
const info = wx.getSystemInfoSync()
canvas.width = info.screenWidth * info.pixelRatio
canvas.height = info.screenHeight * info.pixelRatio
gl.viewport(0, 0, canvas.width, canvas.height)

用原生方案要自己处理纹理图集、Sprite 批处理、触摸事件分发、音频播放,工作量不小。适合团队里有图形学背景的工程师,或者游戏本身渲染极简单的情况。

2.3 引擎选型对照表

引擎运行时体积上手成本3D 支持跨平台适用品类
Cocos Creator 3.x~1.8MB中强全平台中重度 2D/3D
LayaAir 3.x~1.2MB中中全平台2D 卡牌/休闲
PixiJS + 自研逻辑~400KB高无需适配轻度 2D
原生 WebGL~0极高自研需适配极简/技术验证

2.4 混合方案:引擎 + 自研逻辑层

实际项目里最常见的是折中方案:用引擎负责渲染、资源加载与 UI,业务逻辑(数值、战斗结算、存档)全部抽成不依赖引擎的纯 JavaScript 模块。这样做的好处是逻辑层可以脱离引擎单独跑单元测试,未来换引擎或加平台时不用重写核心玩法。

// battle-core.js —— 纯逻辑,不 import 任何引擎 API
export function calcDamage(attacker, defender, rng) {
  const base = attacker.atk * (1 - defender.def * 0.6)
  const crit = rng() < attacker.critRate ? 1.5 : 1.0
  return Math.max(1, Math.floor(base * crit))
}

// battle-scene.ts —— 引擎侧只负责把结果渲染出来
import { calcDamage } from './battle-core'

判断是否该抽离的信号:当一段代码需要 import { Node } from 'cc' 才能测试时,它大概率不该待在场景脚本里。

2.5 从 3D 项目借来的经验

即使做的是 2D 小游戏,大型游戏世界的 AOI 系统设计 里关于「只渲染视野内对象」的思路依然适用——小游戏里对应的是视锥剔除与对象池,把屏幕外的 Sprite 从渲染列表里摘掉,能直接省下可观的 DrawCall。

三、渲染性能

小游戏的性能瓶颈几乎全在渲染线程。和 /miniprogram-performance-optimization/ 里讨论的 setData 优化不同,小游戏没有 setData,瓶颈变成 DrawCall 数量、纹理内存和 GC 停顿。

3.1 DrawCall 与合批

每一次 gl.drawElements 都是一次 DrawCall,小游戏的 DrawCall 建议控制在 100 以内,超过 200 在中低端安卓机上会明显掉帧。合批(Batching)是核心手段:

  • 同一张纹理图集(Atlas)内的 Sprite 可以合批成一次 DrawCall
  • 打乱图集顺序会打断合批,所以 UI 层级要按图集归组
  • 位图字体(Bitmap Font)替代系统字体渲染,避免每个字符一次 DrawCall
// Cocos Creator 中查看合批情况:开启调试模式
// 构建时勾选 debug=true,运行时按 F1 打开性能面板
// 关注 "Draw call" 与 "Instancing" 两项

3.2 纹理内存

一张 2048x2048 的 RGBA8888 纹理占 16MB 内存。小游戏在 iOS 上的内存上限大约 1GB(视机型),安卓更紧张。压缩纹理(Compressed Texture)能把它降到 1/4:

格式压缩比iOSAndroid小游戏支持
PNG/JPG无(运行时解码)是是是
ETC1/ETC26:1否是部分
ASTC4~8:1是(A8+)部分是
PVRTC4~8:1是否是

Cocos Creator 支持按平台自动分包纹理,构建时勾选 ASTC 与 ETC2 两套,运行时按 wx.getSystemInfoSync().platform 加载对应格式。

3.3 帧率与自适应

小游戏默认 60fps,但可以通过 wx.setPreferredFramesPerSecond 降到 30fps 来省电:

// 静态场景降到 30fps,战斗场景拉回 60fps
wx.setPreferredFramesPerSecond(30)

// 监听掉帧,动态降级
let lowFpsCount = 0
setInterval(() => {
  // Cocos 中通过 director.getDeltaTime() 反推
}, 1000)

对战中一旦连续掉帧,应该主动降低特效粒子数量、关闭后处理,而不是硬扛。

3.4 内存与 GC

JavaScript 的垃圾回收会在堆增长时触发,一次 Full GC 可能停顿几十毫秒,直接表现为卡顿。核心原则是「对象池化」:子弹、敌人、飘字、粒子全部预分配,用完回收而不是 new 出来再丢弃。

class ObjectPool {
  constructor(factory, size) {
    this.factory = factory
    this.pool = []
    for (let i = 0; i < size; i++) this.pool.push(factory())
  }
  acquire() {
    return this.pool.pop() || this.factory()
  }
  release(obj) {
    obj.reset()
    this.pool.push(obj)
  }
}

配合 wx.onMemoryWarning 做兜底:收到内存警告时立刻释放非必要纹理、清空缓存关卡资源。

wx.onMemoryWarning(res => {
  // level 5 表示内存压力较低,10 以上需要立即释放
  if (res.level >= 10) {
    resourceManager.releaseUnused()
    cc.assetManager.releaseUnusedAssets()
  }
})

3.5 音频与网络

音频在小游戏里有两套接口:wx.createInnerAudioContext() 适合短音效,wx.createWebAudioContext() 适合需要混音、变速的场景。音效对象必须池化复用,否则频繁创建销毁会触发音频子系统抖动。网络层则要警惕小游戏不保证 TCP 长连接稳定,弱网下应做请求重试与离线兜底。

四、变现链路

小游戏的收入主要来自广告与内购两条线,/miniprogram-monetization/ 里讨论的通用模型在小游戏里同样成立,但激励视频的权重远高于普通小程序。

4.1 激励视频广告

激励视频(Rewarded Video)是休闲小游戏最主流的变现方式,用户看完 15~30 秒视频换取复活、道具或双倍奖励。核心接口:

const videoAd = wx.createRewardedVideoAd({ adUnitId: 'adunit-xxxx' })

videoAd.onError(err => {
  // 广告拉取失败,必须给用户降级方案,不能卡死流程
  console.error('rewarded video error', err)
})

videoAd.onClose(res => {
  // res.isEnded === true 表示完整观看
  if (res && res.isEnded) {
    grantReward()
  } else {
    wx.showToast({ title: '需完整观看才能领取', icon: 'none' })
  }
})

// 预加载,避免用户点击时才开始拉取
videoAd.load().then(() => videoAd.show())

关键细节:onClose 的 isEnded 字段在低版本基础库可能为 undefined,此时应视为「已观看」以避免误伤用户;广告拉取失败率在弱网下可达 10% 以上,必须准备无广告的降级路径。

4.2 插屏与 Banner

插屏广告(Interstitial)适合关卡结算、复活失败等自然停顿点,但频率过高会被平台判罚。Banner 常驻在屏幕边缘,收益低但稳定。

const banner = wx.createBannerAd({
  adUnitId: 'adunit-yyy',
  style: { left: 0, top: 0, width: 320 }
})
banner.onResize(size => {
  // 安卓上 banner 高度可能变化,需要重新定位避免遮挡
  banner.style.top = wx.getSystemInfoSync().screenHeight - size.height
})

4.3 虚拟支付

小游戏的虚拟支付(道具、金币、皮肤)走的是「米大师」(Midas)体系,而非普通小程序的微信支付。注意 iOS 端的限制:iOS 小游戏不允许直接跳转外部支付,虚拟支付能力仅对安卓开放,iOS 用户只能通过广告变现。这是产品设计阶段就必须考虑的收入结构差异。

// 安卓端虚拟支付(需先开通并配置 offerId)
wx.requestMidasPayment({
  mode: 'game',
  env: 0,
  offerId: 'your-offer-id',
  currencyType: 'CNY',
  platform: 'android',
  buyQuantity: 100,
  zoneId: '1',
  success: () => syncBalanceToServer()
})

4.4 变现与体验的平衡

广告密度直接伤害留存。经验值:单次会话激励视频不超过 5 次,插屏间隔不小于 90 秒,且绝不在用户操作过程中弹出打断。服务端要对奖励发放做校验与幂等,防止客户端被篡改后刷奖励。

4.5 变现模型测算

上线前应把变现模型算清楚,而不是先堆广告再看数据。一个简化的测算框架:

指标典型值说明
DAU10,000日活
人均激励视频次数2.5受广告位设计影响
激励视频 eCPM60 元受地域、时段影响
插屏 eCPM25 元通常低于激励视频
广告 ARPU≈ 0.19 元2.5 × 60 / 1000 + 插屏折算
安卓付费率1.5%iOS 无虚拟支付
付费 ARPPU18 元首充 + 复购

按此模型,1 万 DAU 的日广告收入约 1,900 元,安卓付费收入约 2,700 元,其中安卓用户的付费贡献远高于广告。所以产品早期应优先做安卓端的付费点设计,iOS 端则把广告位密度做到上限。任何变现决策都要回到「是否伤害次日留存」这条红线,留存掉了,再高的 eCPM 也是负收益。

五、调试与发布

5.1 真机性能分析

开发者工具的性能面板只能作参考,真实数据必须真机抓取。iOS 上用 Safari 的 Web Inspector 连真机调试,安卓用 Chrome 的 chrome://inspect。重点关注:

  • 帧率曲线是否有周期性尖刺(通常是 GC)
  • 内存曲线是否单调上升(泄漏)
  • DrawCall 与三角形数量

5.2 首屏启动优化

小游戏的首屏加载链路是:下载主包 → 解压 → 执行 game.js → 初始化引擎 → 加载首场景 → 渲染第一帧。每一步都要压:

  1. 主包体积压到 4MB 以下,且尽量小
  2. 引擎初始化时只加载必要模块(Cocos 的「功能裁剪」)
  3. 首场景资源随主包走,避免二次网络请求
  4. 用 wx.getLaunchOptionsSync() 拿到的 scene 做首屏差异化,分享进来的用户直达对应关卡

5.3 分包与版本更新

小游戏的更新策略与小程序一致:主包更新后需重新下载,分包可按需拉取。wx.getUpdateManager() 用来处理版本热更:

const updateManager = wx.getUpdateManager()
updateManager.onUpdateReady(() => {
  wx.showModal({
    title: '更新提示',
    content: '新版本已就绪,重启后生效',
    success: res => {
      if (res.confirm) updateManager.applyUpdate()
    }
  })
})

资源层面,把小游戏的所有远程资源(关卡、皮肤、音频)挂 CDN,配合版本号做缓存失效。资源清单(manifest)随主包发布,客户端启动时比对版本,只拉变化的分片,能把一次更新的下载量从几十兆压到几百 KB。

5.4 审核与合规

小游戏审核比小程序更严,尤其对「诱导分享」「赌博玩法」「未实名」的判定。必须接入实名认证与防沉迷(wx.getUserInfo 已废弃,改用实名 + 时长限制接口),未成年人在线时长受强制限制。虚拟抽奖类玩法需要概率公示,否则会被驳回。

小结

微信小游戏的技术栈可以概括为「受限环境下的图形工程」:环境约束(无 DOM、主包 4MB、Canvas 单视图)决定了引擎选型的边界,渲染性能(DrawCall、纹理、GC)决定了下限体验,变现链路(激励视频、虚拟支付、iOS 限制)决定了商业上限。三条线中任何一条没处理好,游戏都跑不起来。

落地建议:先按品类决定引擎(重度上 Cocos,轻度自研渲染),再用分包和纹理压缩守住包体与内存红线,最后把广告与支付抽象成独立服务层,方便按平台差异做开关。上线前务必真机跑一遍中低端安卓机,开发者工具里的流畅不代表用户手机上的流畅。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「miniprogram」更多文章

  1. 小程序第三方 SDK 集成与治理
  2. 小程序架构演进与遗留重构
  3. 小程序无障碍与适老化改造