主题切换
08 性能优化与包体控制
预计阅读 25 分钟
版本信息
最后更新:2026-07-09 · 适用基础库:≥ 3.9.0(最低兼容 ≥ 2.30.0)· 微信开发者工具:稳定版 1.06+
微信小游戏有严格的性能限制。本章讲解如何将帧率稳定在 60fps、控制内存、优化包体体积。
8.1 性能预算
| 指标 | 目标值 | 红线值 |
|---|---|---|
| 帧率 (FPS) | ≥ 55 | ≥ 30 |
| 首帧渲染 | ≤ 3s | ≤ 5s |
| 内存占用 | ≤ 200MB | ≤ 400MB |
| Draw Call | ≤ 100 | ≤ 200 |
| 首包大小 | ≤ 2MB | ≤ 4MB |
前端对比:Lighthouse vs 游戏性能预算
| 维度 | Web 性能 | 游戏性能 |
|---|---|---|
| 核心指标 | FCP / LCP / TTI | FPS / 帧时间 / 首帧渲染 |
| 优化目标 | 页面加载快、交互响应快 | 每帧稳定 16.6ms(60fps) |
| 内存考量 | 避免内存泄漏(MB 级) | 严格控制在 200MB 以内 |
| 资源策略 | 懒加载、Code Splitting | 分包加载、纹理压缩 |
| 性能工具 | Lighthouse / Chrome DevTools | PerfDog / 微信 Performance 面板 |
关键差异: Web 性能优化关注的是"某个瞬间"(页面加载完成),游戏性能关注的是"每一帧的持续稳定"。在 Web 端偶尔掉帧无感知,在游戏中掉帧 100ms 就是"卡了一下"。
8.2 渲染优化
Draw Call 合并
- 合批渲染 — 使用同一纹理的精灵自动合批
- 静态合批 — 不动的背景元素合并为一个 draw call
- 动态合批 — 相同材质的小对象自动合批
减少 Overdraw
typescript
// ❌ 避免大面积透明区域
// ❌ 避免多层半透明叠加
// ✅ 裁剪不可见区域
// ✅ 使用不透明背景遮挡帧率目标设置
typescript
// 将目标帧率设置为 60fps(默认即为 60,通常无需手动设置)
wx.setPreferredFramesPerSecond(60)在发热严重或低端机上,可动态降低到 30fps 以平衡流畅度与耗电。
8.3 内存优化
纹理压缩
| 格式 | 压缩率 | 平台支持 |
|---|---|---|
| ETC2 | 6:1 | Android |
| ASTC | 8:1 | iOS (A8+) |
| PVRTC | 8:1 | iOS (旧设备) |
对象池模式
typescript
class ObjectPool<T> {
private pool: T[] = []
get(): T | undefined {
return this.pool.pop()
}
recycle(obj: T) {
this.pool.push(obj)
}
}
// 使用对象池避免 GC
const bulletPool = new ObjectPool<Bullet>()
const bullet = bulletPool.get() ?? new Bullet()
// ... 使用完毕
bulletPool.recycle(bullet)主动触发垃圾回收
微信小游戏提供 wx.triggerGC() 用于主动触发 JavaScript 垃圾回收。它适合在场景切换、关卡结束、资源卸载等明确的安全时机调用,以释放短期对象占用的内存。
typescript
// 关卡切换时主动触发 GC
function onLevelTransition() {
// 先释放本关卡资源
unloadLevelAssets()
// 再触发 GC,回收游离对象
wx.triggerGC?.()
}不要滥用 triggerGC
wx.triggerGC() 会暂停主线程进行垃圾回收,如果在游戏进行中频繁调用,反而会造成卡顿。应只在场景切换、结算面板、加载界面等玩家对帧率不敏感的时刻调用。
8.4 包体优化
分包策略
小游戏主包大小限制为 4MB,使用分包后总包大小可扩展至 20MB(开通虚拟支付后可提升至 30MB)。合理分包能显著降低首包体积,加快首次启动速度。
json
{
"subpackages": [
{
"name": "level1-10",
"root": "levels/group1/"
},
{
"name": "level11-20",
"root": "levels/group2/"
}
]
}分包加载代码示例:
typescript
// 按需加载分包
wx.loadSubpackage({
name: 'level1-10',
success: () => {
console.log('分包加载成功')
// 执行分包内脚本
},
fail: (err) => {
console.error('分包加载失败', err)
},
})分包最佳实践
- 按关卡/场景分包 — 把非首屏资源放到分包,进入对应玩法时再加载。
- 预加载时机 — 在首页 loading 时静默预加载下一关分包,减少等待。
- 独立分包 — 对可直接进入的玩法配置
independent: true,不依赖主包逻辑。 - WASM 分包 — 若使用 Unity/LayaAir 的 WASM,单独配置
wasm分包以控制包体。
各引擎分包配置方式
不同引擎中配置分包的方式有差异,但最终都会生成相同的 game.json 配置结构:
在 Cocos Creator 构建面板中直接配置分包:
- 构建发布 → 微信小游戏 → 分包配置
- 在
assets/目录下为每个分包创建独立文件夹 - 勾选「配置为分包」,Cocos 构建时会自动生成
game.json的subpackages字段
json
// Cocos Creator 构建后自动生成的 game.json 分包部分
{
"subpackages": [
{ "name": "level-1", "root": "assets/levels/group1/" },
{ "name": "level-2", "root": "assets/levels/group2/" }
]
}资源优化清单
- ✅ 图片使用 WebP 格式(比 PNG 小 30%+)
- ✅ 音频使用 mp3/aac 格式,码率 ≤ 128kbps
- ✅ 字体使用系统字体,避免内嵌大字体文件
- ✅ JSON 数据启用 Gzip 压缩
- ✅ 移除未使用的资源和代码(Tree Shaking)
- ✅ 大资源使用 CDN 动态加载
8.5 性能分析工具
| 工具 | 用途 |
|---|---|
| 微信开发者工具 Performance 面板 | FPS、内存、CPU 使用 |
| Cocos Creator 调试面板 | Draw Call、节点数、纹理内存 |
| PerfDog | 真机性能测试 |
| Chrome DevTools Performance | 主线程 JS 性能 |
优化原则
- 先测量,再优化 — 不要凭感觉优化
- 找到瓶颈 — 90% 的问题来自 10% 的代码
- 优化算法优于优化代码 — O(n²) → O(n) 比局部优化收益大得多
- 真机测试 — 开发者工具的性能不代表真机性能
- 低端机验证 — 至少要在千元安卓机上测试通过
8.6 WebGL Shader 优化
Shader(着色器)是运行在 GPU 上的小程序,决定了每个顶点和像素的最终呈现。合理优化 Shader 可以显著降低 GPU 负载。
GLSL 基础
微信小游戏使用 WebGL 1.0/2.0,Shader 使用 GLSL(OpenGL Shading Language)编写:
glsl
// 顶点着色器 — 处理每个顶点的位置变换
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = a_Position;
v_TexCoord = a_TexCoord;
}
// 片元着色器 — 处理每个像素的颜色
precision mediump float;
varying vec2 v_TexCoord;
uniform sampler2D u_Texture;
void main() {
gl_FragColor = texture2D(u_Texture, v_TexCoord);
}核心优化技巧
| 优化方向 | 具体做法 | 收益 |
|---|---|---|
| 精度选择 | 颜色计算使用 lowp/mediump,仅变换矩阵用 highp | 移动端 GPU 性能提升 20-50% |
| 减少分支 | 用 step() / mix() 替代 if-else | 避免 GPU 流水线停顿 |
| 纹理压缩 | 使用 ETC2/ASTC 压缩纹理替代 RGBA8888 | 纹理内存降低 6-8x |
| 减少采样 | 合并多次纹理采样为单次 | 降低带宽消耗 |
| 预计算 | 将不变的计算结果作为 uniform 传入 | 减少每帧重复计算 |
| 避免 discard | discard 会禁用 Early-Z 优化 | 改用 alpha blend |
多引擎 Shader 实战
Cocos Creator 3.x 使用 Effect 资源管理 Shader。以下是一个灰度 + 发光效果的 Effect 示例:
glsl
// gray-glow.effect
CCEffect %{
techniques:
- passes:
- vert: sprite-vs:vert
frag: sprite-fs:frag
properties:
glowIntensity: { value: 0.3 }
}%
CCProgram sprite-fs %{
precision mediump float;
in vec2 v_uv;
uniform sampler2D u_texture;
uniform Constant {
float glowIntensity;
};
vec4 frag() {
vec4 color = texture2D(u_texture, v_uv);
// 用 dot 替代分支判断实现灰度转换
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
// 用 mix 控制发光强度,避免分支
color.rgb = mix(color.rgb, vec3(gray), glowIntensity);
return color;
}
}%Cocos Creator Shader 优化要点:
- 尽量复用引擎内置 Effect,避免手写完整 Shader
- 使用材质变体(Material Variant)管理不同 Shader 参数组合
- 大量精灵使用相同 Effect 时开启合批(Batching)
Shader 优化黄金法则
- 精度是移动端性能的第一要素 — 能用
lowp不用mediump,能用mediump不用highp - 纹理压缩 > Shader 优化 — 先压缩纹理(ETC2 6:1、ASTC 8:1),再优化 Shader 逻辑
- 合批优先于 Shader 优化 — 减少 Draw Call 的收益远大于 Shader 内部优化
- 真机测试不可替代 — 桌面 GPU 性能远超手机,务必在真机上验证 Shader 性能
8.7 网络请求优化
小游戏中的网络请求直接影响加载体验和实时交互质量。合理的网络优化策略能显著提升用户体验。
请求合并与缓存
typescript
// 请求合并:将多次小请求合并为一次批量请求
class RequestBatcher {
private queue: Array<{
method: string
params: any
resolve: (value: any) => void
reject: (reason: any) => void
}> = []
private timer: number | null = null
private readonly maxWait = 100 // 最多等待 100ms
add(method: string, params: any): Promise<any> {
return new Promise((resolve, reject) => {
this.queue.push({ method, params, resolve, reject })
if (this.queue.length >= 10) {
this.flush()
} else if (!this.timer) {
this.timer = setTimeout(() => this.flush(), this.maxWait) as unknown as number
}
})
}
private flush() {
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
if (this.queue.length === 0) return
const batch = [...this.queue]
this.queue = []
wx.request({
url: 'https://your-server.com/api/batch',
method: 'POST',
data: { requests: batch.map((b) => ({ method: b.method, params: b.params })) },
success: (res) => {
batch.forEach((item, i) => item.resolve(res.data.results[i]))
},
fail: (err) => {
batch.forEach((item) => item.reject(err))
},
})
}
}
// 响应缓存:LRU 缓存策略
class LRUCache<T> {
private cache = new Map<string, { data: T; expireAt: number }>()
constructor(private maxSize: number = 50) {}
get(key: string): T | undefined {
const entry = this.cache.get(key)
if (!entry) return undefined
if (Date.now() > entry.expireAt) {
this.cache.delete(key)
return undefined
}
// 移到末尾(最近使用)
this.cache.delete(key)
this.cache.set(key, entry)
return entry.data
}
set(key: string, data: T, ttlMs: number = 60000) {
if (this.cache.has(key)) this.cache.delete(key)
else if (this.cache.size >= this.maxSize) {
// 删除最久未使用的(Map 迭代顺序 = 插入顺序)
this.cache.delete(this.cache.keys().next().value)
}
this.cache.set(key, { data, expireAt: Date.now() + ttlMs })
}
}
// 使用示例
const configCache = new LRUCache<any>(20)
async function getGameConfig(levelId: string) {
const cached = configCache.get(levelId)
if (cached) return cached
const data = await wxRequest({ url: `/api/config/${levelId}` })
configCache.set(levelId, data, 5 * 60 * 1000) // 5 分钟过期
return data
}WebSocket 长连接管理
微信小游戏中,WebSocket 用于实现实时对战、聊天、状态同步等功能:
typescript
// WebSocket 连接管理器(带心跳和重连)
class WSManager {
private socket: wx.SocketTask | null = null
private heartbeatTimer: number | null = null
private reconnectTimer: number | null = null
private reconnectAttempts = 0
private maxReconnectAttempts = 5
private readonly heartbeatInterval = 30000 // 30s 心跳间隔
private readonly url: string
constructor(url: string) {
this.url = url
}
connect() {
this.socket = wx.connectSocket({
url: this.url,
header: { 'content-type': 'application/json' },
protocols: ['game-protocol'], // 自定义子协议
success: () => console.log('WebSocket 连接成功'),
})
this.socket.onOpen(() => {
this.reconnectAttempts = 0 // 重置重连计数
this.startHeartbeat()
})
this.socket.onMessage((res) => {
const msg = JSON.parse(res.data)
// 心跳响应不触发业务逻辑
if (msg.type === 'pong') return
this.handleMessage(msg)
})
this.socket.onClose((res) => {
this.stopHeartbeat()
if (res.code !== 1000) {
// 非正常关闭,尝试重连
this.tryReconnect()
}
})
this.socket.onError((err) => {
console.error('WebSocket 错误', err)
this.tryReconnect()
})
}
// 心跳保活
private startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
this.send({ type: 'ping', timestamp: Date.now() })
}, this.heartbeatInterval) as unknown as number
}
private stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = null
}
}
// 指数退避重连
private tryReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('WebSocket 重连次数已达上限')
return
}
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000)
// 加入随机抖动,避免大量客户端同时重连
const jitter = delay * (0.5 + Math.random() * 0.5)
this.reconnectTimer = setTimeout(() => {
this.reconnectAttempts++
console.log(`WebSocket 第 ${this.reconnectAttempts} 次重连,延迟 ${jitter.toFixed(0)}ms`)
this.connect()
}, jitter) as unknown as number
}
send(data: object) {
this.socket?.send({ data: JSON.stringify(data) })
}
private handleMessage(msg: any) {
// 业务消息处理
}
close() {
this.stopHeartbeat()
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer)
}
this.socket?.close({ code: 1000, reason: '主动关闭' })
}
}弱网环境处理
typescript
// 网络质量检测与降级策略
class NetworkDetector {
private latencySamples: number[] = []
private readonly sampleSize = 5
// 检测当前网络延迟
async pingLatency(): Promise<number> {
const start = Date.now()
await wx.request({
url: 'https://your-server.com/ping',
method: 'HEAD', // 使用 HEAD 请求减少传输数据
})
return Date.now() - start
}
// 判断网络等级
async getNetworkLevel(): Promise<'good' | 'normal' | 'poor'> {
// 多次采样取中位数
for (let i = 0; i < this.sampleSize; i++) {
try {
const latency = await this.pingLatency()
this.latencySamples.push(latency)
} catch {
// 请求失败直接判定为弱网
return 'poor'
}
}
this.latencySamples.sort((a, b) => a - b)
const median = this.latencySamples[Math.floor(this.latencySamples.length / 2)]
if (median < 100) return 'good' // < 100ms
if (median < 300) return 'normal' // 100-300ms
return 'poor' // > 300ms
}
}
// 根据网络质量调整策略
async function adaptiveLoad(level: string, detector: NetworkDetector) {
const networkLevel = await detector.getNetworkLevel()
switch (networkLevel) {
case 'good':
// 正常加载高清资源
return loadLevelAssets(level, 'high')
case 'normal':
// 加载中等质量资源
return loadLevelAssets(level, 'medium')
case 'poor':
// 仅加载低质量资源,跳过非必要内容
// 向玩家显示网络提示
wx.showToast({ title: '网络较慢,已切换到省流模式', icon: 'none' })
return loadLevelAssets(level, 'low')
}
}数据压缩:Protobuf
javascript
// 使用 Protobuf 替代 JSON 传输数据
// game.proto 定义示例
// message ScoreData {
// required int32 score = 1;
// required int32 level = 2;
// optional string playerName = 3;
// }
// 发送端:编码为 ArrayBuffer,避免 JSON.stringify 破坏二进制数据
const protoMessage = protobuf.encode('ScoreData', {
score: 9999,
level: 5,
playerName: 'Player1',
}) // 结果约 10 bytes,而 JSON 约 50 bytes
// ⚠️ 关键:wx.request 的 data 字段会自动 JSON.stringify,会破坏二进制数据
// 正确做法:将 ArrayBuffer 转为 base64 字符串,或使用 wx.request 的 ArrayBuffer 模式
// 方案一:base64 编码传输(兼容性最好)
const base64Data = arrayBufferToBase64(protoMessage)
wx.request({
url: 'https://your-server.com/api/score',
method: 'POST',
data: { payload: base64Data }, // JSON 中传输 base64 字符串
success(res) {
// 接收端从 base64 解码回二进制
const decoded = protobuf.decode('ScoreData', base64ToArrayBuffer(res.data.payload))
console.log(decoded.score) // 9999
},
})
// 方案二:直接使用 ArrayBuffer(部分微信版本支持,需验证)
// wx.request({
// url: 'https://your-server.com/api/score',
// method: 'POST',
// data: protoMessage.buffer, // ArrayBuffer
// header: { 'content-type': 'application/octet-stream' },
// responseType: 'arraybuffer'
// })Protobuf 与 JSON 序列化
wx.request 的 data 参数默认行为是 JSON.stringify(data),因此直接传入 ArrayBuffer 或 Uint8Array 会导致数据损坏。推荐做法是:
- base64 方案(兼容性最好):将二进制数据编码为 base64 字符串放入 JSON 传输
- ArrayBuffer 方案(需验证版本支持):设置
responseType: 'arraybuffer'+content-type: application/octet-stream - 自定义序列化:使用
dataType: '其他'并自行处理序列化(行为可能因平台版本而异)
| 数据格式 | 典型大小 | 适合场景 |
|---|---|---|
| JSON | 100% (基准) | 配置数据、低频请求 |
| MessagePack | ~60% | 平衡可读性与体积 |
| Protobuf | ~30% | 高频同步、实时对战 |
| FlatBuffers | ~40% | 零解析开销场景 |
8.8 电量与发热控制
手机游戏的耗电和发热是影响用户留存的关键因素。一个让手机变成"暖手宝"的游戏,再好玩也会被卸载。
帧率动态调整策略
typescript
// 根据场景复杂度动态调整帧率
class FrameRateController {
private currentFPS: number = 60
private targetFPS: number = 60
private samples: number[] = []
private readonly CHECK_INTERVAL = 5000 // 每 5 秒检查一次
constructor() {
setInterval(() => this.adjustFPS(), this.CHECK_INTERVAL)
}
// 由场景管理器调用,传入当前场景类型
setScene(sceneType: 'menu' | 'gameplay' | 'cutscene' | 'loading') {
switch (sceneType) {
case 'menu':
this.targetFPS = 30 // 菜单不需要 60fps
break
case 'gameplay':
this.targetFPS = 60 // 核心玩法保持流畅
break
case 'cutscene':
this.targetFPS = 30 // 过场动画 30fps 足够
break
case 'loading':
this.targetFPS = 15 // 加载画面可以更低
break
}
}
private adjustFPS() {
this.currentFPS = this.targetFPS
wx.setPreferredFramesPerSecond(this.currentFPS)
}
}后台省电模式
typescript
// 监听前后台切换,自动管理渲染
wx.onHide(() => {
// 进入后台:停止渲染循环
stopGameLoop()
// 暂停所有音效
pauseAllAudio()
// 断开非必要的网络连接
wsManager.close()
// 设置最低帧率
wx.setPreferredFramesPerSecond(1)
})
wx.onShow(() => {
// 回到前台:恢复渲染
wx.setPreferredFramesPerSecond(60)
startGameLoop()
resumeAllAudio()
wsManager.connect()
})渲染负载监控
typescript
// 游戏内渲染负载监控
class RenderLoadMonitor {
private frameTimeSamples: number[] = []
private lastFrameTime = 0
private loadLevel: 'low' | 'medium' | 'high' = 'low'
private onLoadChange: ((level: string) => void) | null = null
// 每帧调用
recordFrame() {
const now = performance.now()
if (this.lastFrameTime > 0) {
const frameTime = now - this.lastFrameTime
this.frameTimeSamples.push(frameTime)
}
this.lastFrameTime = now
// 每秒统计一次
if (this.frameTimeSamples.length >= 60) {
this.evaluate()
this.frameTimeSamples = []
}
}
private evaluate() {
const avgTime = this.frameTimeSamples.reduce((a, b) => a + b, 0) / this.frameTimeSamples.length
const newLevel = avgTime < 18 ? 'low' : avgTime < 28 ? 'medium' : 'high'
if (newLevel !== this.loadLevel) {
this.loadLevel = newLevel
this.onLoadChange?.(newLevel)
console.log(`渲染负载变更: ${newLevel} (平均帧时间: ${avgTime.toFixed(1)}ms)`)
}
}
setOnLoadChange(callback: (level: string) => void) {
this.onLoadChange = callback
}
getLoadLevel() {
return this.loadLevel
}
}
// 使用:根据负载动态降质
const monitor = new RenderLoadMonitor()
monitor.setOnLoadChange((level) => {
switch (level) {
case 'high':
// GPU 负载高:降低粒子数量、减少特效、关闭后处理
particleSystem.setMaxCount(20)
effects.disableBloom()
effects.disableShadow()
break
case 'medium':
// 中等负载:适当降低特效质量
particleSystem.setMaxCount(50)
effects.setBloomQuality('low')
break
case 'low':
// 负载低:全特效运行
particleSystem.setMaxCount(100)
effects.setBloomQuality('high')
effects.enableShadow()
break
}
})低端机适配方案
typescript
// 设备分级检测
interface DeviceTier {
level: 'high' | 'medium' | 'low'
cpu: string
memory: number
benchmarkScore: number
}
async function detectDeviceTier(): Promise<'high' | 'medium' | 'low'> {
// 使用新版 API 替代已废弃的 getSystemInfoSync
const deviceInfo = wx.getDeviceInfo()
const windowInfo = wx.getWindowInfo()
const { platform, model, benchmarkLevel } = deviceInfo
// benchmarkLevel: -1(未知), 1~100(分数越高性能越好)
// 注意:benchmarkLevel 在 getDeviceInfo 中自基础库 3.4.5 起已废弃,
// 如需获取 benchmark 信息,可使用 wx.getDeviceBenchmarkInfo()
if (benchmarkLevel <= 0 || benchmarkLevel === undefined) {
// 根据系统和型号推测
if (platform === 'ios') {
// iOS 设备通常性能较好
return model.includes('iPhone 8') || model.includes('iPhone 7') ? 'medium' : 'high'
}
// Android 谨慎评估 — memorySize 在 getDeviceInfo() 中
const memory = deviceInfo.memorySize || 0
if (memory <= 2048) return 'low'
if (memory <= 4096) return 'medium'
return 'high'
}
if (benchmarkLevel <= 20) return 'low'
if (benchmarkLevel <= 50) return 'medium'
return 'high'
}
// 根据设备等级加载不同配置
const tier = await detectDeviceTier()
const config = {
high: { particleCount: 100, textureQuality: 'high', shadowEnabled: true, fps: 60 },
medium: { particleCount: 50, textureQuality: 'medium', shadowEnabled: false, fps: 45 },
low: { particleCount: 0, textureQuality: 'low', shadowEnabled: false, fps: 30 },
}[tier]
// 应用设备配置
applyDeviceConfig(config)发热警告
当设备温度过高时,iOS/Android 系统可能会强制降频或杀死进程。建议:
- 监控
wx.onMemoryWarning— 收到警告时立即释放非必要资源 - 30fps 是"安全线" — 绝大多数休闲游戏 30fps 体验已足够
- 连续游戏 30 分钟无发热 — 内测阶段的硬性验收标准
📝 课后练习
练习 1:性能预算制定
题目: 为一款 2D 弹幕射击游戏(同屏最多 200 个子弹 + 20 个敌人)制定性能预算表(FPS、内存、Draw Call、首包大小),并说明每项预算的依据。
参考答案:
| 指标 | 目标值 | 依据 |
|---|---|---|
| FPS | ≥ 55 | 射击游戏对流畅度要求高,低于 50 影响体验 |
| 内存 | ≤ 250MB | 200 个子弹对象池 + 敌人 + 背景,预留安全余量 |
| Draw Call | ≤ 80 | 子弹用相同纹理可合批,主体约 30-40 DC |
| 首包大小 | ≤ 3MB | 射击类主打快速进入,用户等待耐心低 |
练习 3:分包策略
题目: 你的游戏有 20 个关卡。前 5 关是新手教程(必须首包加载),第 6-20 关分 3 个分包。设计 game.json 的分包配置。
参考答案:
json
{
"subpackages": [
{ "name": "levels-6-10", "root": "levels/group1/" },
{ "name": "levels-11-15", "root": "levels/group2/" },
{ "name": "levels-16-20", "root": "levels/group3/" }
],
"preloadRule": {
"levels/group1/": { "network": "wifi", "packages": ["levels-11-15"] }
}
}预加载规则
预加载规则:在 Wi-Fi 下自动预加载下一组分包。
📚 相关阅读
- 性能分析工具实操 — 从工具层面深入性能诊断
- iOS 高性能模式 — 在 iOS 设备上获取更高性能
- CI/CD 构建与部署 — 将性能优化纳入自动化流程