HarmonyOS开发:游戏性能优化——帧率稳定与内存控制

举报
Jack20 发表于 2026/06/28 21:01:44 2026/06/28
【摘要】 HarmonyOS开发:游戏性能优化——帧率稳定与内存控制📌 核心要点:游戏性能优化不是"出了问题再修",而是从架构阶段就要考虑的事情,渲染优化减少GPU负担,对象池控制内存分配,帧率稳定性靠固定步长和负载均衡,性能分析工具帮你找到瓶颈在哪。 背景与动机你的游戏跑60帧,但每隔几秒就掉到30帧一下——这种"卡一下"的体验比全程30帧还难受。为什么?因为人眼对帧率变化比绝对帧率更敏感。稳定...

HarmonyOS开发:游戏性能优化——帧率稳定与内存控制

📌 核心要点:游戏性能优化不是"出了问题再修",而是从架构阶段就要考虑的事情,渲染优化减少GPU负担,对象池控制内存分配,帧率稳定性靠固定步长和负载均衡,性能分析工具帮你找到瓶颈在哪。

背景与动机

你的游戏跑60帧,但每隔几秒就掉到30帧一下——这种"卡一下"的体验比全程30帧还难受。

为什么?因为人眼对帧率变化比绝对帧率更敏感。稳定30帧感觉"还行",60帧偶尔掉到30帧感觉"卡死了"。

游戏性能优化要解决的核心问题不是"怎么跑到60帧",而是"怎么稳定在60帧"。这俩问题难度差一个数量级。

内存控制也是个大问题。游戏对象不停创建销毁,GC(垃圾回收)频繁触发,每次GC都会暂停所有线程几毫秒——这就是帧率抖动的元凶之一。

核心原理

性能瓶颈分析

游戏性能瓶颈通常出在这几个地方:

graph TB
    A[游戏性能瓶颈] --> B[CPU瓶颈]
    A --> C[GPU瓶颈]
    A --> D[内存瓶颈]
    A --> E[IO瓶颈]
    
    B --> B1[物理计算过重]
    B --> B2[碰撞检测O]
    B --> B3[脚本逻辑复杂]
    B --> B4[GC频繁触发]
    
    C --> C1[绘制调用过多]
    C --> C2[纹理过大]
    C --> C3[着色器复杂]
    C --> C4[Overdraw严重]
    
    D --> D1[对象频繁创建销毁]
    D --> D2[内存泄漏]
    D --> D3[大纹理未压缩]
    D --> D4[资源未及时释放]
    
    E --> E1[资源加载阻塞]
    E --> E2[文件读取同步]
    E --> E3[网络请求等待]
    E --> E4[数据库查询]
    
    classDef mainStyle fill:#C0392B,stroke:#922B21,color:#fff,font-weight:bold
    classDef cpuStyle fill:#E74C3C,stroke:#C0392B,color:#fff
    classDef gpuStyle fill:#9B59B6,stroke:#8E44AD,color:#fff
    classDef memStyle fill:#E67E22,stroke:#D35400,color:#fff
    classDef ioStyle fill:#3498DB,stroke:#2980B9,color:#fff
    
    class A mainStyle
    class B,B1,B2,B3,B4 cpuStyle
    class C,C1,C2,C3,C4 gpuStyle
    class D,D1,D2,D3,D4 memStyle
    class E,E1,E2,E3,E4 ioStyle

怎么判断瓶颈在哪?看帧时间分布:

  • CPU时间占比高 → CPU瓶颈,优化逻辑
  • GPU时间占比高 → GPU瓶颈,优化渲染
  • 帧时间偶尔飙升 → GC或IO瓶颈,优化内存

渲染优化策略

优化策略 效果 实现难度 适用场景
批量绘制 ★★★★★ ★★★☆☆ 大量相同精灵
视口裁剪 ★★★★☆ ★★☆☆☆ 大地图/卷轴
纹理图集 ★★★★☆ ★★★☆☆ 多种小图片
脏矩形渲染 ★★★☆☆ ★★★★☆ 局部更新
LOD ★★★★☆ ★★★★☆ 3D远距离
合批渲染 ★★★★★ ★★★★★ 3D场景

内存池与对象复用

游戏里最常见的内存问题:每帧创建大量临时对象(子弹、粒子、伤害数字),用完就丢,GC疯狂回收。

解决方案:对象池。预先创建一批对象,用的时候从池里取,不用了还回池里,永远不new、永远不delete。

帧率稳定性保障

帧率不稳定的原因通常有:

  1. GC暂停:解决方法→对象池,减少GC触发
  2. IO阻塞:解决方法→异步加载,预加载
  3. 负载不均:解决方法→分帧处理,大任务拆小
  4. 系统调度:解决方法→降低CPU占用,给系统留余量

代码实战

基础用法:对象池实现

对象池是游戏性能优化的基石,先搞定它。

// ObjectPool.ets - 通用对象池

// 对象池接口
interface IPoolable {
  reset(): void    // 重置对象状态
  isUsing: boolean // 是否正在使用
}

// 通用对象池
class ObjectPool<T extends IPoolable> {
  private pool: T[] = []         // 可用对象
  private active: T[] = []       // 正在使用的对象
  private factory: () => T       // 对象工厂
  private maxSize: number        // 池最大容量
  private expandSize: number     // 每次扩容数量

  constructor(factory: () => T, initialSize: number = 20, maxSize: number = 200) {
    this.factory = factory
    this.maxSize = maxSize
    this.expandSize = Math.min(10, initialSize)

    // 预创建对象
    for (let i = 0; i < initialSize; i++) {
      const obj = this.factory()
      obj.isUsing = false
      this.pool.push(obj)
    }
  }

  // 从池中获取对象
  get(): T | null {
    // 池中有可用对象
    if (this.pool.length > 0) {
      const obj = this.pool.pop()!
      obj.isUsing = true
      obj.reset()
      this.active.push(obj)
      return obj
    }

    // 池空了,尝试扩容
    if (this.active.length + this.pool.length < this.maxSize) {
      for (let i = 0; i < this.expandSize; i++) {
        const obj = this.factory()
        obj.isUsing = false
        this.pool.push(obj)
      }
      const obj = this.pool.pop()!
      obj.isUsing = true
      obj.reset()
      this.active.push(obj)
      return obj
    }

    // 池满了,返回null
    console.warn('对象池已满')
    return null
  }

  // 归还对象到池中
  release(obj: T): void {
    const idx = this.active.indexOf(obj)
    if (idx === -1) return

    this.active.splice(idx, 1)
    obj.isUsing = false

    // 池未满则归还,满了就丢弃
    if (this.pool.length < this.maxSize) {
      this.pool.push(obj)
    }
  }

  // 归还所有活跃对象
  releaseAll(): void {
    for (const obj of this.active) {
      obj.isUsing = false
      if (this.pool.length < this.maxSize) {
        this.pool.push(obj)
      }
    }
    this.active = []
  }

  // 获取所有活跃对象
  getActive(): T[] {
    return this.active
  }

  // 获取池状态
  getStatus(): { available: number; active: number; total: number } {
    return {
      available: this.pool.length,
      active: this.active.length,
      total: this.pool.length + this.active.length
    }
  }
}

// ========== 具体应用:子弹对象池 ==========

// 可复用子弹
class PoolableBullet implements IPoolable {
  isUsing: boolean = false
  x: number = 0
  y: number = 0
  vx: number = 0
  vy: number = 0
  damage: number = 1
  isPlayerBullet: boolean = true
  alive: boolean = true

  reset(): void {
    this.x = 0
    this.y = 0
    this.vx = 0
    this.vy = 0
    this.damage = 1
    this.isPlayerBullet = true
    this.alive = true
  }

  // 初始化子弹参数
  init(x: number, y: number, vx: number, vy: number, isPlayer: boolean, damage: number = 1): void {
    this.x = x
    this.y = y
    this.vx = vx
    this.vy = vy
    this.isPlayerBullet = isPlayer
    this.damage = damage
  }

  update(dt: number): void {
    this.x += this.vx * dt
    this.y += this.vy * dt
  }
}

// 可复用粒子
class PoolableParticle implements IPoolable {
  isUsing: boolean = false
  x: number = 0
  y: number = 0
  vx: number = 0
  vy: number = 0
  life: number = 0
  maxLife: number = 1
  size: number = 4
  color: string = '#ffffff'
  alpha: number = 1.0

  reset(): void {
    this.x = 0; this.y = 0
    this.vx = 0; this.vy = 0
    this.life = 0; this.maxLife = 1
    this.size = 4; this.color = '#ffffff'
    this.alpha = 1.0
  }

  init(x: number, y: number, vx: number, vy: number, life: number, color: string, size: number = 4): void {
    this.x = x; this.y = y
    this.vx = vx; this.vy = vy
    this.life = life; this.maxLife = life
    this.color = color; this.size = size
  }

  update(dt: number): boolean {
    this.x += this.vx * dt
    this.y += this.vy * dt
    this.vy += 200 * dt // 重力
    this.life -= dt
    this.alpha = Math.max(0, this.life / this.maxLife)
    return this.life > 0
  }
}

进阶用法:渲染优化与帧率监控

对象池解决了内存问题,渲染优化解决GPU问题,帧率监控帮你发现性能瓶颈。

// RenderOptimizer.ets - 渲染优化与帧率监控

// 帧率监控器
class FrameMonitor {
  private frameTimes: number[] = []
  private maxSamples: number = 60
  private lastTime: number = 0

  // 记录一帧
  record(): void {
    const now = Date.now()
    if (this.lastTime > 0) {
      const frameTime = now - this.lastTime
      this.frameTimes.push(frameTime)
      if (this.frameTimes.length > this.maxSamples) {
        this.frameTimes.shift()
      }
    }
    this.lastTime = now
  }

  // 获取平均FPS
  getAvgFps(): number {
    if (this.frameTimes.length === 0) return 0
    const avgTime = this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length
    return avgTime > 0 ? 1000 / avgTime : 0
  }

  // 获取最低FPS
  getMinFps(): number {
    if (this.frameTimes.length === 0) return 0
    const maxTime = Math.max(...this.frameTimes)
    return maxTime > 0 ? 1000 / maxTime : 0
  }

  // 获取帧时间统计
  getStats(): { avg: number; min: number; max: number; p95: number } {
    if (this.frameTimes.length === 0) return { avg: 0, min: 0, max: 0, p95: 0 }

    const sorted = [...this.frameTimes].sort((a, b) => a - b)
    const avg = sorted.reduce((a, b) => a + b, 0) / sorted.length
    const p95Idx = Math.floor(sorted.length * 0.95)

    return {
      avg: avg,
      min: sorted[0],
      max: sorted[sorted.length - 1],
      p95: sorted[p95Idx]
    }
  }

  // 绘制帧率信息(调试用)
  drawDebugInfo(ctx: CanvasRenderingContext2D, x: number, y: number): void {
    const stats = this.getStats()
    const fps = this.getAvgFps()

    ctx.save()
    ctx.fillStyle = 'rgba(0,0,0,0.6)'
    ctx.fillRect(x - 5, y - 15, 200, 75)

    ctx.fillStyle = fps >= 55 ? '#00ff00' : fps >= 30 ? '#ffff00' : '#ff0000'
    ctx.font = '14px monospace'
    ctx.fillText(`FPS: ${fps.toFixed(1)}`, x, y)
    ctx.fillStyle = '#cccccc'
    ctx.fillText(`帧时间: avg=${stats.avg.toFixed(1)}ms p95=${stats.p95.toFixed(1)}ms`, x, y + 18)
    ctx.fillText(`帧时间: min=${stats.min.toFixed(1)}ms max=${stats.max.toFixed(1)}ms`, x, y + 36)
    ctx.fillText(`样本数: ${this.frameTimes.length}`, x, y + 54)
    ctx.restore()
  }
}

// 视口裁剪器 - 只渲染屏幕内的对象
class ViewportCuller {
  private viewLeft: number = 0
  private viewTop: number = 0
  private viewRight: number = 360
  private viewBottom: number = 720
  private margin: number = 50 // 额外边距,防止边缘闪烁

  // 更新视口范围
  updateViewport(left: number, top: number, right: number, bottom: number): void {
    this.viewLeft = left - this.margin
    this.viewTop = top - this.margin
    this.viewRight = right + this.margin
    this.viewBottom = bottom + this.margin
  }

  // 检查对象是否在视口内
  isVisible(x: number, y: number, width: number = 0, height: number = 0): boolean {
    return x + width >= this.viewLeft &&
           x <= this.viewRight &&
           y + height >= this.viewTop &&
           y <= this.viewBottom
  }

  // 过滤可见对象
  filterVisible<T>(objects: T[], getPos: (obj: T) => { x: number; y: number; w?: number; h?: number }): T[] {
    return objects.filter(obj => {
      const pos = getPos(obj)
      return this.isVisible(pos.x, pos.y, pos.w ?? 0, pos.h ?? 0)
    })
  }
}

// 批量绘制优化器 - 合并相同类型的绘制调用
class BatchRenderer {
  // 按颜色分批绘制矩形
  drawRectsBatch(
    ctx: CanvasRenderingContext2D,
    rects: Array<{ x: number; y: number; w: number; h: number; color: string }>
  ): void {
    // 按颜色分组
    const groups: Map<string, Array<{ x: number; y: number; w: number; h: number }>> = new Map()

    for (const rect of rects) {
      if (!groups.has(rect.color)) {
        groups.set(rect.color, [])
      }
      groups.get(rect.color)!.push({ x: rect.x, y: rect.y, w: rect.w, h: rect.h })
    }

    // 每种颜色只设置一次fillStyle
    for (const [color, items] of groups) {
      ctx.fillStyle = color
      for (const item of items) {
        ctx.fillRect(item.x, item.y, item.w, item.h)
      }
    }
  }

  // 按纹理分批绘制精灵(减少drawImage调用)
  drawSpritesBatch(
    ctx: CanvasRenderingContext2D,
    sprites: Array<{
      img: ImageBitmap
      srcX: number; srcY: number; srcW: number; srcH: number
      dstX: number; dstY: number; dstW: number; dstH: number
    }>
  ): void {
    // 按图片分组
    const groups: Map<ImageBitmap, typeof sprites> = new Map()

    for (const sp of sprites) {
      if (!groups.has(sp.img)) {
        groups.set(sp.img, [])
      }
      groups.get(sp.img)!.push(sp)
    }

    // 同一张图片的精灵连续绘制
    for (const [_img, items] of groups) {
      for (const item of items) {
        ctx.drawImage(item.img, item.srcX, item.srcY, item.srcW, item.srcH, item.dstX, item.dstY, item.dstW, item.dstH)
      }
    }
  }
}

// 分帧任务处理器 - 把大任务拆到多帧执行
class FrameTaskProcessor {
  private tasks: Array<{ execute: () => boolean; priority: number }> = []
  private budgetPerFrame: number = 4 // 每帧最多执行4ms的任务

  // 添加任务
  addTask(execute: () => boolean, priority: number = 0): void {
    this.tasks.push({ execute, priority })
    // 按优先级排序
    this.tasks.sort((a, b) => b.priority - a.priority)
  }

  // 每帧处理
  processFrame(): void {
    const startTime = Date.now()

    while (this.tasks.length > 0) {
      const task = this.tasks[0]
      const done = task.execute()

      if (done) {
        this.tasks.shift()
      }

      // 检查是否超出本帧预算
      if (Date.now() - startTime >= this.budgetPerFrame) {
        break
      }
    }
  }

  // 清空所有任务
  clear(): void {
    this.tasks = []
  }

  // 获取待处理任务数
  getPendingCount(): number {
    return this.tasks.length
  }
}

完整示例:优化后的游戏循环

把对象池、渲染优化、帧率监控集成到游戏循环里:

// OptimizedGameLoop.ets - 优化后的游戏循环

@Entry
@Component
struct OptimizedGamePage {
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
  private canvasW: number = 360
  private canvasH: number = 720

  // 性能组件
  private frameMonitor: FrameMonitor = new FrameMonitor()
  private viewportCuller: ViewportCuller = new ViewportCuller()
  private batchRenderer: BatchRenderer = new BatchRenderer()
  private taskProcessor: FrameTaskProcessor = new FrameTaskProcessor()

  // 对象池
  private bulletPool: ObjectPool<PoolableBullet> = new ObjectPool(
    () => new PoolableBullet(), 50, 200
  )
  private particlePool: ObjectPool<PoolableParticle> = new ObjectPool(
    () => new PoolableParticle(), 100, 500
  )

  // 游戏状态
  private playerX: number = 180
  private playerY: number = 600
  private isTouching: boolean = false
  private shootTimer: number = 0
  private running: boolean = false
  private lastTime: number = 0
  private timer: number = -1
  private showDebug: boolean = true

  aboutToAppear(): void {
    this.running = true
    this.viewportCuller.updateViewport(0, 0, this.canvasW, this.canvasH)
  }

  aboutToDisappear(): void {
    this.running = false
    if (this.timer !== -1) clearTimeout(this.timer)
    this.bulletPool.releaseAll()
    this.particlePool.releaseAll()
  }

  build() {
    Column() {
      Canvas(this.ctx)
        .width('100%')
        .height('100%')
        .onReady(() => {
          this.lastTime = Date.now()
          this.gameLoop()
        })
        .onTouch((event: TouchEvent) => {
          if (event.type === TouchType.Down) {
            this.isTouching = true
          } else if (event.type === TouchType.Up) {
            this.isTouching = false
          }
        })
    }
    .width('100%')
    .height('100%')
  }

  private gameLoop(): void {
    if (!this.running) return

    const now = Date.now()
    const dt = Math.min((now - this.lastTime) / 1000, 0.05)
    this.lastTime = now

    this.frameMonitor.record()
    this.update(dt)
    this.render()

    this.timer = setTimeout(() => this.gameLoop(), 16)
  }

  private update(dt: number): void {
    // 射击
    this.shootTimer -= dt
    if (this.isTouching && this.shootTimer <= 0) {
      this.shootTimer = 0.12
      this.shoot()
    }

    // 更新子弹
    for (const bullet of this.bulletPool.getActive()) {
      bullet.update(dt)
      // 超出屏幕则回收
      if (bullet.y < -20 || bullet.y > this.canvasH + 20 ||
          bullet.x < -20 || bullet.x > this.canvasW + 20) {
        bullet.alive = false
        this.bulletPool.release(bullet)
      }
    }

    // 更新粒子
    for (const particle of this.particlePool.getActive()) {
      if (!particle.update(dt)) {
        this.particlePool.release(particle)
      }
    }

    // 分帧处理任务
    this.taskProcessor.processFrame()
  }

  // 射击
  private shoot(): void {
    const bullet = this.bulletPool.get()
    if (bullet) {
      bullet.init(this.playerX, this.playerY - 20, 0, -500, true)
    }

    // 生成枪口火焰粒子
    this.spawnParticles(this.playerX, this.playerY - 20, 3, '#ffaa00')
  }

  // 生成粒子
  private spawnParticles(x: number, y: number, count: number, color: string): void {
    for (let i = 0; i < count; i++) {
      const p = this.particlePool.get()
      if (p) {
        const angle = Math.random() * Math.PI * 2
        const speed = 50 + Math.random() * 100
        p.init(
          x, y,
          Math.cos(angle) * speed,
          Math.sin(angle) * speed - 50,
          0.3 + Math.random() * 0.3,
          color,
          2 + Math.random() * 4
        )
      }
    }
  }

  private render(): void {
    const ctx = this.ctx
    ctx.clearRect(0, 0, this.canvasW, this.canvasH)

    // 背景
    ctx.fillStyle = '#0a0a1a'
    ctx.fillRect(0, 0, this.canvasW, this.canvasH)

    // 批量绘制子弹(按颜色分组)
    const bulletRects: Array<{ x: number; y: number; w: number; h: number; color: string }> = []
    for (const bullet of this.bulletPool.getActive()) {
      if (!this.viewportCuller.isVisible(bullet.x, bullet.y, 6, 16)) continue
      bulletRects.push({
        x: bullet.x - 3, y: bullet.y - 8,
        w: 6, h: 16,
        color: bullet.isPlayerBullet ? '#00ffff' : '#ff4444'
      })
    }
    this.batchRenderer.drawRectsBatch(ctx, bulletRects)

    // 批量绘制粒子
    const particleRects: Array<{ x: number; y: number; w: number; h: number; color: string }> = []
    for (const p of this.particlePool.getActive()) {
      if (!this.viewportCuller.isVisible(p.x, p.y, p.size, p.size)) continue
      ctx.globalAlpha = p.alpha
      particleRects.push({
        x: p.x - p.size / 2, y: p.y - p.size / 2,
        w: p.size, h: p.size,
        color: p.color
      })
    }
    // 粒子需要单独处理alpha,这里简化为批量绘制
    ctx.globalAlpha = 1.0
    this.batchRenderer.drawRectsBatch(ctx, particleRects)

    // 绘制玩家
    ctx.fillStyle = '#00ff88'
    ctx.beginPath()
    ctx.moveTo(this.playerX, this.playerY - 24)
    ctx.lineTo(this.playerX - 20, this.playerY + 24)
    ctx.lineTo(this.playerX + 20, this.playerY + 24)
    ctx.closePath()
    ctx.fill()

    // 调试信息
    if (this.showDebug) {
      this.frameMonitor.drawDebugInfo(ctx, 10, 20)

      // 对象池状态
      const bulletStatus = this.bulletPool.getStatus()
      const particleStatus = this.particlePool.getStatus()
      ctx.fillStyle = 'rgba(0,0,0,0.6)'
      ctx.fillRect(5, 95, 200, 45)
      ctx.fillStyle = '#cccccc'
      ctx.font = '12px monospace'
      ctx.fillText(`子弹池: ${bulletStatus.active}/${bulletStatus.total} 粒子池: ${particleStatus.active}/${particleStatus.total}`, 10, 112)
      ctx.fillText(`分帧任务: ${this.taskProcessor.getPendingCount()}`, 10, 130)
    }
  }
}

踩坑与注意事项

坑1:对象池忘记归还

从池里取了对象,用完了不归还,池会越来越大,最终OOM。解决方案:在游戏循环的update里统一检查和回收,不要依赖手动归还。

坑2:Canvas的fillStyle频繁切换

每次调用ctx.fillStyle = '#xxx',Canvas内部要做一次状态切换。1000个不同颜色的矩形,就是1000次状态切换。按颜色分批绘制,切换次数降到颜色种类数。

坑3:视口裁剪的边距太小

对象刚好在视口边缘时,可能这一帧在、下一帧就出去了,导致闪烁。裁剪边距至少设为对象最大尺寸,确保对象完全离开视口后才被裁掉。

坑4:分帧任务的预算设置不当

每帧给分帧任务的预算太大会影响主循环帧率,太小会导致任务堆积。建议从2-4ms开始,根据帧率监控数据调整。

坑5:GC不可预测

即使你用了对象池,ArkTS的GC还是会在不可预测的时间点触发。你能做的是尽量减少临时对象的创建——比如循环里不要用new,用预分配的临时变量。

HarmonyOS 6适配说明

HarmonyOS 6在性能方面有几个重要更新:

  1. 性能分析工具增强:DevEco Studio新增了Game Profiler,可以实时监控游戏的CPU、GPU、内存、帧率,还能录制性能快照做对比分析。

  2. 增量GC:HarmonyOS 6的ArkTS引擎支持增量GC,不再需要暂停所有线程做全量回收。GC暂停时间从几十毫秒降低到1-2ms,帧率抖动大幅减少。

// HarmonyOS 6 增量GC配置
import { runtime } from '@ohos.arkruntime'

// 在应用启动时配置GC策略
runtime.setGCStrategy({
  type: 'incremental',  // 增量GC
  maxPause: 2,          // 最大暂停时间2ms
  threshold: 0.7        // 内存使用70%时触发
})
  1. GPU指令缓冲:Canvas 2D渲染支持GPU指令缓冲(Command Buffer),重复的绘制指令可以缓存重放,减少CPU到GPU的通信开销。

  2. 内存压力回调:新增了系统内存压力通知,当系统内存紧张时,你可以主动释放不必要的资源,避免被系统杀掉。

总结

游戏性能优化是持续的过程,不是一次性工作。核心思路就三条:减少CPU计算量(对象池、分帧处理)、减少GPU绘制量(批量绘制、视口裁剪)、减少内存分配(对象池、预分配)。

帧率监控是优化的前提——你不知道瓶颈在哪,优化就是瞎搞。先量后改,每次优化都要看数据。

对象池是游戏性能优化的第一优先级。没有对象池,其他优化都是白搭。GC暂停是帧率抖动的头号元凶,对象池直接消灭它。

评估维度 学习难度 使用频率 重要程度
对象池 ★★★☆☆ ★★★★★ ★★★★★
帧率监控 ★★☆☆☆ ★★★★★ ★★★★★
批量绘制 ★★★☆☆ ★★★★☆ ★★★★☆
视口裁剪 ★★☆☆☆ ★★★★☆ ★★★★☆
分帧处理 ★★★★☆ ★★★☆☆ ★★★★☆
GC优化 ★★★★☆ ★★★★☆ ★★★★★

下一篇讲游戏发布——应用市场上架与内购集成。做完了游戏,得让玩家能玩到、能付费。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。