主题切换
Phaser 4 微信小游戏实战(Phaser 3.90+ 迁移参考)
预计阅读 35 分钟
版本信息
最后更新:2026-07-09 · Phaser 4.2.0(2026-06-19,代号 Giedi) · Phaser 3.90+ 项目可参照迁移指南升级,但存在破坏性变更 · 适用基础库:≥ 3.9.0(最低兼容 ≥ 2.30.0) · 微信开发者工具:稳定版 1.06+
Phaser 是 Web 2D 游戏开发领域最成熟的开源框架之一,拥有活跃的社区、丰富的插件生态和极佳的 TypeScript 支持。2026 年 4 月 Phaser 4 已正式发布,最新稳定版为 4.2.0(2026-06-19,代号 Giedi),带来了全新的 WebGL 渲染架构、Mesh2D、Stencil、Cone Lights 等高级特性。本章以 Phaser 4 为主角讲解微信小游戏适配(Phaser 3.90+ 项目可参照官方迁移指南升级,但渲染管线、FX、Geometry 等存在破坏性变更),并以一款极简的 2D 纵向射击游戏 为例,覆盖从环境搭建到上线的完整链路。
1. Phaser 4 适合微信小游戏吗?
客观评估
Phaser 原生面向浏览器环境,依赖 DOM、Window、Document 等对象。微信小游戏没有 DOM,因此不能直接运行官方 Phaser 构建包。你需要使用社区适配版本或自行裁剪渲染层。
如果你追求“开箱即用”的微信小游戏体验,Cocos Creator / LayaAir 会更合适;如果你已有大量 Phaser 3 项目积累,或希望一套代码同时发布 Web 和小游戏,Phaser 4 的适配方案值得考虑。
1.1 核心难点
| 浏览器环境 | 微信小游戏环境 | 适配方案 |
|---|---|---|
document.createElement('canvas') | wx.createCanvas() | 注入自定义 Canvas 工厂 |
window.addEventListener | wx.onTouchStart 等 | 适配输入事件 |
XMLHttpRequest / fetch | wx.request | 替换资源加载器 |
localStorage | wx.setStorageSync | 替换存储实现 |
Image | wx.createImage() | 替换图片加载 |
| 音频 API | wx.createInnerAudioContext() | 替换音频系统 |
1.2 可选适配方案
- 社区适配包:搜索
phaser-minigame或phaser-wechat等社区项目,它们通常提供了基础的 DOM polyfill 和 Canvas 注入。 - 自定义裁剪:只使用 Phaser 的 Game Loop、Scene、Input、Physics 逻辑层,自己用微信 Canvas API 实现渲染层。
- Web 优先 + 小游戏分包:先发布 Web 版本验证玩法,再为小游戏单独做一个精简适配版。
本章以方案 1(社区适配包思路)为主,演示最小可运行项目。
2. 环境准备
2.1 项目初始化
bash
# 创建项目
mkdir phaser-shooter-wechat
cd phaser-shooter-wechat
npm init -y
# 安装 Phaser 4 和 TypeScript
pnpm add phaser
pnpm add -D typescript vite @types/node2.2 目录结构
phaser-shooter-wechat/
├── src/
│ ├── main.ts # 游戏入口
│ ├── scenes/
│ │ ├── BootScene.ts # 资源预加载
│ │ ├── GameScene.ts # 主游戏场景
│ │ └── GameOverScene.ts # 结束场景
│ ├── objects/
│ │ ├── Player.ts # 玩家飞机
│ │ ├── Bullet.ts # 子弹
│ │ └── Enemy.ts # 敌人
│ └── utils/
│ └── wx-adapter.ts # 微信环境适配
├── raw-assets/ # 原始美术资源
├── dist/ # 构建输出
├── game.json # 小游戏配置
├── project.config.json # 开发者工具配置
└── index.html # Web 调试入口2.3 Vite 配置(同时支持 Web 和小游戏)
typescript
// vite.config.ts
import { defineConfig } from 'vite'
import path from 'path'
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
build: {
outDir: 'dist',
lib: {
entry: path.resolve(__dirname, 'src/main.ts'),
formats: ['iife'],
name: 'PhaserShooter',
fileName: () => 'game.js',
},
rollupOptions: {
// Phaser 较大,建议 external 后通过分包加载
external: ['phaser'],
output: {
globals: {
phaser: 'Phaser',
},
},
},
},
})3. 微信环境适配层
在 src/utils/wx-adapter.ts 中注入浏览器缺失的全局对象:
typescript
// src/utils/wx-adapter.ts
export function initWxAdapter() {
if (typeof wx === 'undefined') return
// 注入 Canvas
const canvas = wx.createCanvas()
;(globalThis as any).canvas = canvas
;(globalThis as any).document = {
createElement: (tag: string) => {
if (tag === 'canvas') return wx.createCanvas()
return {}
},
body: {},
}
// 注入 Image
// 注意:微信小游戏的 wx.createImage 不遵循浏览器 CORS,
// 因此 crossOrigin 属性仅作兼容占位,实际跨域由小游戏运行环境处理
;(globalThis as any).Image = class WxImage {
private _src = ''
private _crossOrigin: string | null = null
onload: (() => void) | null = null
onerror: (() => void) | null = null
width = 0
height = 0
get src() {
return this._src
}
set src(value: string) {
this._src = value
const img = wx.createImage()
img.src = value
img.onload = () => {
this.width = img.width ?? 0
this.height = img.height ?? 0
this.onload?.()
}
img.onerror = () => this.onerror?.()
}
get crossOrigin() {
return this._crossOrigin ?? ''
}
set crossOrigin(value: string) {
this._crossOrigin = value
}
}
// 注入 requestAnimationFrame
// 微信小游戏没有原生 RAF,使用递归 setTimeout 模拟,并尽量与目标帧率对齐
let rafId = 0
const callbacks = new Map<number, FrameRequestCallback>()
let running = false
const targetFps = 60
const frameInterval = 1000 / targetFps
const tick = () => {
if (!running) return
const now = Date.now()
// 复制当前帧的回调,避免回调中注册新回调导致本帧执行
const currentCallbacks = new Map(callbacks)
callbacks.clear()
currentCallbacks.forEach((fn, id) => fn(now))
// 计算下一帧延迟,尽量贴近 targetFps
const elapsed = Date.now() - now
const delay = Math.max(0, frameInterval - elapsed)
setTimeout(tick, delay)
}
;(globalThis as any).requestAnimationFrame = (cb: FrameRequestCallback) => {
const id = ++rafId
callbacks.set(id, cb)
if (!running) {
running = true
tick()
}
return id
}
;(globalThis as any).cancelAnimationFrame = (id: number) => {
callbacks.delete(id)
if (callbacks.size === 0) {
running = false
}
}
}
/** 在场景/游戏销毁时清理适配层注入的全局对象 */
export function destroyWxAdapter() {
;(globalThis as any).canvas = undefined
;(globalThis as any).document = undefined
;(globalThis as any).Image = undefined
;(globalThis as any).requestAnimationFrame = undefined
;(globalThis as any).cancelAnimationFrame = undefined
}适配层的局限
上述适配层只能让 Phaser 3 的轻量场景跑起来。复杂场景( Spine 骨骼、复杂粒子、WebGL 高级特性)可能需要更深入的 polyfill 或裁剪。建议先用最简场景验证可行性,再逐步扩展。
4. 最小可运行项目
4.1 游戏入口
typescript
// src/main.ts
import { initWxAdapter } from './utils/wx-adapter'
import { GameScene } from './scenes/GameScene'
initWxAdapter()
const canvas = (globalThis as any).canvas
const { windowWidth, windowHeight, pixelRatio } = wx.getSystemInfoSync()
const config: Phaser.Types.Core.GameConfig = {
type: Phaser.CANVAS,
canvas,
width: windowWidth,
height: windowHeight,
pixelArt: true,
physics: {
default: 'arcade',
arcade: {
gravity: { x: 0, y: 0 },
debug: false,
},
},
scene: [GameScene],
}
new Phaser.Game(config)4.2 游戏场景
typescript
// src/scenes/GameScene.ts
import { Scene } from 'phaser'
export class GameScene extends Scene {
private player!: Phaser.GameObjects.Rectangle
private bullets!: Phaser.GameObjects.Group
private enemies!: Phaser.GameObjects.Group
private lastFired = 0
private enemySpawnTimer = 0
private score = 0
private scoreText!: Phaser.GameObjects.Text
// 存储微信触摸事件回调引用,用于场景销毁时正确移除
private _onTouchStart: ((e: any) => void) | null = null
private _onTouchMove: ((e: any) => void) | null = null
constructor() {
super({ key: 'GameScene' })
}
create() {
// 玩家飞机(用矩形代替美术资源)
this.player = this.add.rectangle(
this.scale.width / 2,
this.scale.height - 100,
40,
40,
0x00e5ff
)
this.physics.add.existing(this.player)
// 子弹和敌人组
this.bullets = this.add.group()
this.enemies = this.add.group()
// 分数
this.scoreText = this.add.text(20, 20, 'Score: 0', {
fontSize: '24px',
color: '#ffffff',
})
// 触摸控制 — 存储回调引用以便场景销毁时移除
this._onTouchStart = (e: any) => {
const touch = e.touches[0]
this.player.x = touch.clientX
}
this._onTouchMove = (e: any) => {
const touch = e.touches[0]
this.player.x = touch.clientX
}
wx.onTouchStart(this._onTouchStart)
wx.onTouchMove(this._onTouchMove)
}
// ⚠️ 场景关闭时必须移除事件监听器,否则会导致重复注册和内存泄漏
shutdown() {
if (this._onTouchStart) wx.offTouchStart(this._onTouchStart)
if (this._onTouchMove) wx.offTouchMove(this._onTouchMove)
}
update(time: number, delta: number) {
// 自动发射子弹
if (time > this.lastFired + 200) {
this.fireBullet()
this.lastFired = time
}
// 生成敌人
this.enemySpawnTimer += delta
if (this.enemySpawnTimer > 1000) {
this.spawnEnemy()
this.enemySpawnTimer = 0
}
// 子弹上移
this.bullets.children.iterate((bullet: any) => {
bullet.y -= 500 * (delta / 1000)
if (bullet.y < -20) bullet.destroy()
return true
})
// 敌人下移
this.enemies.children.iterate((enemy: any) => {
enemy.y += 150 * (delta / 1000)
if (enemy.y > this.scale.height + 20) enemy.destroy()
return true
})
// 碰撞检测
this.physics.overlap(this.bullets, this.enemies, (bullet, enemy) => {
bullet.destroy()
enemy.destroy()
this.score += 10
this.scoreText.setText(`Score: ${this.score}`)
})
this.physics.overlap(this.player, this.enemies, () => {
this.scene.restart()
})
}
private fireBullet() {
const bullet = this.add.rectangle(this.player.x, this.player.y - 30, 6, 16, 0xff6ec7)
this.physics.add.existing(bullet)
this.bullets.add(bullet)
}
private spawnEnemy() {
const x = Phaser.Math.Between(30, this.scale.width - 30)
const enemy = this.add.rectangle(x, -20, 36, 36, 0xff4444)
this.physics.add.existing(enemy)
this.enemies.add(enemy)
}
}5. 微信 API 集成
5.1 登录与用户信息
typescript
// src/utils/wx-api.ts
export function wxLogin(): Promise<WechatMinigame.LoginSuccessCallbackResult> {
return new Promise((resolve, reject) => {
wx.login({ success: resolve, fail: reject })
})
}
export function uploadScore(score: number): void {
wx.setUserCloudStorage({
KVDataList: [{ key: 'shooterScore', value: String(score) }],
success: () => console.log('分数上传成功'),
fail: (err) => console.error('分数上传失败', err),
})
}5.2 激励视频广告
typescript
// src/ads/RewardedAd.ts
export class RewardedAdManager {
private videoAd: WechatMinigame.RewardedVideoAd | null = null
init(adUnitId: string) {
this.videoAd = wx.createRewardedVideoAd({ adUnitId })
this.videoAd.load()
}
async show(): Promise<boolean> {
if (!this.videoAd) return false
try {
await this.videoAd.show()
return new Promise((resolve) => {
this.videoAd?.onClose((res) => {
resolve(res.isEnded)
})
})
} catch {
// 首次展示失败,尝试重新加载
await this.videoAd.load()
try {
await this.videoAd.show()
} catch {
return false // 重试后仍失败
}
// 重试成功后,仍需监听关闭回调判断用户是否完整观看
return new Promise((resolve) => {
this.videoAd?.onClose((res) => {
resolve(res.isEnded)
})
})
}
}
}5.3 分享功能
typescript
export function shareGame(score: number) {
wx.shareAppMessage({
title: `我在 Phaser 射击游戏中获得了 ${score} 分,来挑战我吧!`,
imageUrl: 'https://your-cdn.com/share.png',
})
}6. 分包与性能
6.1 主包控制
Phaser 3 引擎本身体积较大(压缩后约 800KB+),建议:
- 将 Phaser 放入分包:主包只保留启动逻辑,Phaser 引擎作为分包加载。
- 按需加载场景:非首屏场景放入分包。
json
{
"subpackages": [
{
"name": "phaser-engine",
"root": "subpackages/phaser/"
}
]
}6.2 加载分包
typescript
async function loadPhaserEngine(): Promise<void> {
return new Promise((resolve, reject) => {
wx.loadSubpackage({
name: 'phaser-engine',
success: resolve,
fail: reject,
})
})
}
// 在启动画面显示后加载
loadPhaserEngine().then(() => {
// 初始化 Phaser 游戏
})6.3 性能建议
| 优化项 | 说明 |
|---|---|
| 使用 Arcade Physics | Phaser 的 Arcade 物理性能最好,适合 2D 射击游戏 |
| 对象池复用 | 子弹、敌人使用对象池,避免频繁创建销毁 |
| 限制粒子数量 | 微信小游戏粒子渲染开销较大 |
| 避免复杂 Shader | WebGL 特性在部分机型上支持不稳定 |
| 真机测试 | 开发者工具帧率不能代表真机表现 |
7. 构建与发布
7.1 构建命令
bash
# 构建 IIFE 格式的 game.js
pnpm build
# 复制小游戏配置文件到 dist
cp game.json dist/
cp project.config.json dist/7.2 小游戏配置
json
{
"deviceOrientation": "portrait",
"showStatusBar": false,
"networkTimeout": {
"request": 10000,
"downloadFile": 10000
}
}7.3 发布流程
- 用微信开发者工具打开
dist/目录 - 点击「预览」测试基础功能
- 真机调试验证性能
- 上传代码并提交审核
8. 与 Cocos / LayaAir 的对比
| 维度 | Phaser 4 | Cocos Creator | LayaAir |
|---|---|---|---|
| 学习曲线 | 低(纯代码) | 中(编辑器 + 代码) | 中 |
| 微信适配 | 需社区适配/自行裁剪 | 官方成熟适配 | 官方成熟适配 |
| 2D 渲染性能 | 优秀 | 优秀 | 优秀 |
| 3D 支持 | 无 | 内置 3D | 内置 3D |
| 编辑器 | 无官方可视化编辑器 | Cocos Editor | LayaAir IDE |
| 插件生态 | 丰富(Web 生态) | 中等 | 较少 |
| 最适合 | Web 优先 + 小游戏复用 | 微信小游戏原生 2D/3D | 2D 重度游戏 |
本章小结
通过本章,你了解了:
- Phaser 4 的微信小游戏适配原理和难点
- 如何搭建最小可运行的 Phaser 4 微信小游戏项目
- 微信 API(登录、广告、分享)与 Phaser 4 的集成方式
- 分包加载和性能优化策略
Phaser 4 实战建议
- 先做 Web 版本验证玩法,再投入时间做小游戏适配。
- 保持项目简单,避免使用 Phaser 的高级 WebGL 特性。
- 真机测试优先,开发者工具只能验证逻辑,不能验证性能。
- 考虑引擎锁定风险,如果你的目标平台只有微信小游戏,Cocos Creator 通常是更稳妥的选择。
WebGL vs Canvas 渲染器选择
Phaser 3 支持两种渲染模式:Phaser.CANVAS(Canvas 2D)和 Phaser.WEBGL(WebGL)。在微信小游戏环境中,选择正确的渲染器至关重要。
对比分析
| 维度 | Canvas 2D | WebGL |
|---|---|---|
| 兼容性 | ✅ 所有设备 | ⚠️ 部分低端安卓不支持 WebGL |
| 批量渲染 | ❌ 逐个 Draw Call | ✅ GPU 批量处理 |
| 同屏精灵数 | 100-300 个 | 1,000-5,000+ 个 |
| Shader 特效 | ❌ 不支持 | ✅ 自定义着色器 |
| 内存占用 | 低(CPU 内存) | 中(显存 + CPU 内存) |
| 微信小游戏环境 | 默认可用 | wx.getSystemInfoSync() 查询 |
如何选择
typescript
// main.ts — 启动时检测并选择渲染模式
function detectRenderMode(): Phaser.Types.Core.RenderType {
// 1. 检测微信小游戏环境的 WebGL 能力
try {
const sysInfo = wx.getSystemInfoSync()
// 部分设备信息中包含 GPU 信息,可辅助决策
const isLowEndDevice = sysInfo.benchmarkLevel !== undefined && sysInfo.benchmarkLevel < 20
// 2. 低端设备强制 Canvas
if (isLowEndDevice) {
console.log('低端设备,使用 Canvas 渲染')
return Phaser.CANVAS
}
// 3. 创建测试 Canvas 检测 WebGL 支持
const testCanvas = wx.createCanvas()
const gl = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl')
if (gl) {
console.log('WebGL 可用,使用 WebGL 渲染')
return Phaser.WEBGL
}
} catch (err) {
console.warn('WebGL 检测失败,降级为 Canvas', err)
}
console.log('使用 Canvas 渲染')
return Phaser.CANVAS
}
// Phaser 配置
const config: Phaser.Types.Core.GameConfig = {
type: detectRenderMode(),
width: 750,
height: 1334,
canvas: wx.createCanvas(),
// Canvas 渲染专用优化
render: {
antialias: false, // 关闭抗锯齿提升性能
pixelArt: true, // 像素风格避免模糊
roundPixels: true, // 像素对齐避免抖动
},
// 物理引擎选择
physics: {
default: 'arcade',
arcade: {
gravity: { x: 0, y: 300 },
// debug: process.env.NODE_ENV !== 'production',
},
},
}渲染器选择建议
- 休闲 2D 游戏 →
Phaser.CANVAS(兼容性最好,性能足够) - 精灵数量 > 500 →
Phaser.WEBGL(批量渲染优势明显) - 需要 Shader 效果 →
Phaser.WEBGL(唯一选择) - 不确定 → 先默认 Canvas,有性能问题再评估 WebGL
纹理加载系统适配
Phaser 3 的 Loader 底层使用浏览器的 XMLHttpRequest 或 fetch 来加载资源。在微信小游戏中,需要用 wx.request 替代。
自定义 File Loader
typescript
// adapter/WxImageLoader.ts
/**
* 微信小游戏图片加载器 — 替代 Phaser 默认的 ImageFile
* Phaser 3.90+ 支持通过 LoaderPlugin 注册自定义文件类型
* (Phaser 4 也支持类似机制,API 路径可能略有不同)
*/
class WxImageFile extends Phaser.Loader.File {
constructor(loader: Phaser.Loader.LoaderPlugin, key: string, url: string) {
super(loader, {
type: 'image',
key,
url,
})
}
/** 重写加载逻辑,使用 wx.request 代替 XMLHttpRequest */
load(): void {
const key = this.key
const url = this.url
// 对于本地分包内的资源,使用 wx.getFileSystemManager
if (url.startsWith('subpackages/') || url.startsWith('assets/')) {
this.loadLocal(key, url)
} else {
// CDN 远程资源使用 wx.request
this.loadRemote(key, url)
}
}
private loadLocal(key: string, url: string): void {
try {
// 微信小游戏不支持 Blob/URL.createObjectURL,使用 wx.createImage() 加载本地文件
const img = wx.createImage()
img.onload = () => {
this.loader.textureManager.addImage(key, img as unknown as HTMLImageElement)
this.onProcessComplete()
}
img.onerror = () => {
console.error(`本地纹理加载失败: ${url}`)
this.onProcessError()
}
img.src = url // 微信小游戏中 wx.createImage() 可直接加载本地文件路径
} catch (err) {
console.error(`本地纹理加载失败: ${url}`, err)
this.onProcessError()
}
}
private loadRemote(key: string, url: string): void {
wx.request({
url,
responseType: 'arraybuffer',
success: (res) => {
if (res.statusCode === 200) {
const uint8Array = new Uint8Array(res.data as ArrayBuffer)
const blob = new Blob([uint8Array])
const img = new Image()
img.onload = () => {
this.loader.textureManager.addImage(key, img)
this.onProcessComplete()
}
img.onerror = () => this.onProcessError()
img.src = URL.createObjectURL(blob)
} else {
this.onProcessError()
}
},
fail: () => {
this.onProcessError()
},
})
}
private onProcessComplete(): void {
this.pendingDestroy()
this.loader.nextFile(this, true)
}
private onProcessError(): void {
this.pendingDestroy()
this.loader.nextFile(this, false)
}
}注册自定义加载器
typescript
// main.ts — 在创建 Game 之前注册自定义文件类型
function registerCustomLoader(): void {
// 注意:完整替换 Phaser 的默认图片加载器需要深入 LoaderPlugin
// 以下为简化示例——实际使用时建议使用社区适配包
// 替代方案:使用 Phaser 的 Loader 事件钩子
const game = new Phaser.Game(config)
// 监听加载失败,尝试用 wx.request 重试
game.load.on('loaderror', (file: Phaser.Loader.File) => {
console.warn(`Phaser 默认加载失败: ${file.key}, 尝试 wx.request 重试`)
// 使用 WxImageFile 重试加载
const wxFile = new WxImageFile(game.load, file.key, file.url as string)
wxFile.load()
})
}适配器适用性
以上适配代码提供了思路框架,但完整适配 Phaser 3 的纹理系统到微信小游戏环境是一个复杂的工程。建议:
- 优先使用社区适配包(如
weapp-adapter-phaser、eastecho/WeChatGame-phaser),它们已经处理了大量细节。 - 保持项目简单:如果精灵数量不多(< 50 个),手动用 Canvas 2D 绘制可能比折腾适配器更高效。
- 考虑替代方案:如果 Phaser 适配成本过高,可以考虑 Cocos Creator(原生微信支持)或 Godot(Web 导出 + 简易桥接)。
Phaser 3 → Phaser 4 迁移注意点
Phaser 4 在 API 层面与 Phaser 3 保持高度兼容,但渲染架构和新增特性需要关注:
| 维度 | Phaser 3 | Phaser 4 |
|---|---|---|
| 渲染后端 | WebGL 1/2 + Canvas 2D 回退 | 全新 WebGL 2/3 渲染器,仍保留 Canvas 回退(Phaser.CANVAS 已弃用但可用) |
| 粒子系统 | CPU 粒子 ParticleEmitter | 新增 GPU 粒子,支持更多粒子数 |
| 新特性 | — | Mesh2D、Stencil 蒙版、Cone Lights、AlphaStrategy 透明模式 |
| 包体 | 相对较大 | 模块化更好,可按需裁剪 |
| 微信小游戏 | 社区适配包以 Phaser 3 为主 | Phaser 4 适配层仍在演进,建议先验证核心 Scene/Loader/Input |
迁移建议:
- 先用 Phaser 4 跑通一个最小场景(Preloader → GameScene → GameOverScene)。
- 重点验证资源加载器在微信小游戏中的行为;Phaser 4 的 Loader 事件与 Phaser 3 基本一致,但文件类型注册有调整。
- 如果使用 Spine 或 DragonBones 插件,确认插件已提供 Phaser 4 版本。
- 小游戏环境优先关闭 Phaser 4 的高级光照/后效,以降低 GPU 压力。
📝 课后练习
练习 1:对象池优化
题目: 本章示例中子弹和敌人都是每帧创建/销毁的。请用对象池改写 fireBullet 和 spawnEnemy。
参考答案要点:
- 预创建 50 个子弹对象和 20 个敌人对象
fireBullet从池中取出一个 inactive 对象,重置位置并激活- 对象飞出屏幕或碰撞后不销毁,改为 inactive 状态放回池中
练习 2:适配层完善
题目: 除了 Canvas 和 Image,Phaser 还可能用到哪些浏览器 API?列出至少 3 个,并说明在微信小游戏中的替代方案。
参考答案:
localStorage→wx.setStorageSyncAudio→wx.createInnerAudioContextfetch/XMLHttpRequest→wx.requestnavigator.userAgent→wx.getSystemInfoSync
📚 相关阅读
- 05 引擎对比与选型 — 为什么选择 Phaser 或其他引擎
- 06 引擎深度实战 — Cocos Creator workflow 详解
- 08 性能优化与包体控制 — 小游戏性能优化通用原则
- 资产管线 — Phaser 项目的资源管理
- 安全与反作弊 — 分数上传与服务端校验