Skip to content

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 / TTIFPS / 帧时间 / 首帧渲染
优化目标页面加载快、交互响应快每帧稳定 16.6ms(60fps)
内存考量避免内存泄漏(MB 级)严格控制在 200MB 以内
资源策略懒加载、Code Splitting分包加载、纹理压缩
性能工具Lighthouse / Chrome DevToolsPerfDog / 微信 Performance 面板

关键差异: Web 性能优化关注的是"某个瞬间"(页面加载完成),游戏性能关注的是"每一帧的持续稳定"。在 Web 端偶尔掉帧无感知,在游戏中掉帧 100ms 就是"卡了一下"。

8.2 渲染优化

Draw Call 合并

  • 合批渲染 — 使用同一纹理的精灵自动合批
  • 静态合批 — 不动的背景元素合并为一个 draw call
  • 动态合批 — 相同材质的小对象自动合批

减少 Overdraw

typescript
// ❌ 避免大面积透明区域
// ❌ 避免多层半透明叠加
// ✅ 裁剪不可见区域
// ✅ 使用不透明背景遮挡

帧率目标设置

typescript
// 将目标帧率设置为 60fps(默认即为 60,通常无需手动设置)
wx.setPreferredFramesPerSecond(60)

在发热严重或低端机上,可动态降低到 30fps 以平衡流畅度与耗电。

8.3 内存优化

纹理压缩

格式压缩率平台支持
ETC26:1Android
ASTC8:1iOS (A8+)
PVRTC8:1iOS (旧设备)

对象池模式

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?.()
}

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)
  },
})
分包最佳实践
  1. 按关卡/场景分包 — 把非首屏资源放到分包,进入对应玩法时再加载。
  2. 预加载时机 — 在首页 loading 时静默预加载下一关分包,减少等待。
  3. 独立分包 — 对可直接进入的玩法配置 independent: true,不依赖主包逻辑。
  4. WASM 分包 — 若使用 Unity/LayaAir 的 WASM,单独配置 wasm 分包以控制包体。

各引擎分包配置方式

不同引擎中配置分包的方式有差异,但最终都会生成相同的 game.json 配置结构:

在 Cocos Creator 构建面板中直接配置分包:

  1. 构建发布微信小游戏分包配置
  2. assets/ 目录下为每个分包创建独立文件夹
  3. 勾选「配置为分包」,Cocos 构建时会自动生成 game.jsonsubpackages 字段
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 性能
优化原则
  1. 先测量,再优化 — 不要凭感觉优化
  2. 找到瓶颈 — 90% 的问题来自 10% 的代码
  3. 优化算法优于优化代码 — O(n²) → O(n) 比局部优化收益大得多
  4. 真机测试 — 开发者工具的性能不代表真机性能
  5. 低端机验证 — 至少要在千元安卓机上测试通过

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 传入减少每帧重复计算
避免 discarddiscard 会禁用 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 优化黄金法则
  1. 精度是移动端性能的第一要素 — 能用 lowp 不用 mediump,能用 mediump 不用 highp
  2. 纹理压缩 > Shader 优化 — 先压缩纹理(ETC2 6:1、ASTC 8:1),再优化 Shader 逻辑
  3. 合批优先于 Shader 优化 — 减少 Draw Call 的收益远大于 Shader 内部优化
  4. 真机测试不可替代 — 桌面 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.requestdata 参数默认行为是 JSON.stringify(data),因此直接传入 ArrayBufferUint8Array 会导致数据损坏。推荐做法是:

  1. base64 方案(兼容性最好):将二进制数据编码为 base64 字符串放入 JSON 传输
  2. ArrayBuffer 方案(需验证版本支持):设置 responseType: 'arraybuffer' + content-type: application/octet-stream
  3. 自定义序列化:使用 dataType: '其他' 并自行处理序列化(行为可能因平台版本而异)
数据格式典型大小适合场景
JSON100% (基准)配置数据、低频请求
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)


📝 课后练习

练习 1:性能预算制定

题目: 为一款 2D 弹幕射击游戏(同屏最多 200 个子弹 + 20 个敌人)制定性能预算表(FPS、内存、Draw Call、首包大小),并说明每项预算的依据。

参考答案:

指标目标值依据
FPS≥ 55射击游戏对流畅度要求高,低于 50 影响体验
内存≤ 250MB200 个子弹对象池 + 敌人 + 背景,预留安全余量
Draw Call≤ 80子弹用相同纹理可合批,主体约 30-40 DC
首包大小≤ 3MB射击类主打快速进入,用户等待耐心低
练习 2:对象池实现

题目: 为游戏的子弹系统实现一个完整的对象池,包括预创建、获取、回收、自动扩容。

参考答案: 参见 游戏设计模式 中"对象池模式"的完整实现,特别关注预热逻辑和周期性收缩策略。

练习 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 下自动预加载下一组分包。


📚 相关阅读

用心学习,持续实践