HarmonyOS开发:2D游戏开发——Canvas游戏循环与精灵动画

举报
Jack20 发表于 2026/06/28 20:58:31 2026/06/28
【摘要】 HarmonyOS开发:2D游戏开发——Canvas游戏循环与精灵动画📌 核心要点:2D游戏的核心就是"画、算、画"——每帧算位置、画画面,精灵动画是让2D游戏活起来的关键,帧动画+碰撞检测是2D游戏开发的三大件。 背景与动机你有没有玩过那种2D小游戏——飞机大战、跑酷、消消乐?看起来简单对吧?但你真上手写一个,就会发现一堆问题:画面怎么刷新?角色怎么动?动画怎么播?两个东西撞上了怎么判...

HarmonyOS开发:2D游戏开发——Canvas游戏循环与精灵动画

📌 核心要点:2D游戏的核心就是"画、算、画"——每帧算位置、画画面,精灵动画是让2D游戏活起来的关键,帧动画+碰撞检测是2D游戏开发的三大件。

背景与动机

你有没有玩过那种2D小游戏——飞机大战、跑酷、消消乐?看起来简单对吧?但你真上手写一个,就会发现一堆问题:

画面怎么刷新?角色怎么动?动画怎么播?两个东西撞上了怎么判断?

这些问题,每一个都能卡你半天。

2D游戏开发的本质就是三件事:(逻辑更新)、(渲染绘制)、(碰撞检测)。游戏循环管"算和画"的节奏,精灵动画管"画"的细节,碰撞检测管"判"的精度。这三样搞定了,2D游戏的基本功就过关了。

鸿蒙上的Canvas 2D API和Web上的基本一致,但性能表现有差异。你要是不注意一些细节,60帧的目标根本达不到。

核心原理

游戏主循环的三个阶段

游戏主循环不是简单的"画一帧"就完事了。每一帧要经历三个阶段:

graph LR
    A[处理输入] --> B[更新逻辑]
    B --> C[渲染画面]
    C -->|下一帧| A
    
    subgraph 更新逻辑
        B1[更新精灵位置] --> B2[播放帧动画] --> B3[执行碰撞检测] --> B4[处理游戏事件]
    end
    
    B --> B1
    
    classDef phaseStyle fill:#E74C3C,stroke:#C0392B,color:#fff,font-weight:bold
    classDef subStyle fill:#3498DB,stroke:#2980B9,color:#fff
    classDef detailStyle fill:#2ECC71,stroke:#27AE60,color:#fff
    
    class A,B,C phaseStyle
    class B1,B2,B3,B4 detailStyle

为什么是这个顺序?因为输入是最新的,逻辑要基于最新输入来算,渲染要基于最新逻辑来画。顺序反了,画面就会"慢一拍"。

精灵与帧动画

精灵(Sprite)是2D游戏里最基本的视觉单位。一个角色、一颗子弹、一个金币,都是一个精灵。

帧动画的原理很简单:把一个动作拆成一帧一帧的图片,快速切换,人就"动"起来了。就像翻页动画——每页画一个姿势,快速翻,看起来就在动。

精灵表(Sprite Sheet)示意:
┌────┬────┬────┬────┐
│ 帧0 │ 帧1 │ 帧2 │ 帧3 │  ← 行走动画
├────┼────┼────┼────┤
│ 帧4 │ 帧5 │ 帧6 │ 帧7 │  ← 攻击动画
└────┴────┴────┴────┘
每帧 64x64 像素

在鸿蒙Canvas上,我们用drawImage的裁剪参数来从精灵表中取出单帧绘制。

碰撞检测算法

2D碰撞检测从简单到复杂,有这么几层:

算法 精度 性能 适用场景
AABB矩形碰撞 极快 粗略判断
圆形碰撞 弹幕、球类
OBB旋转矩形 中高 中等 旋转物体
像素级碰撞 精确判断
SAT分离轴 中等 凸多边形

实际开发中,90%的场景用AABB就够了。先做AABB粗筛,筛出可能碰撞的对,再做精确检测——这是碰撞检测的黄金法则。

代码实战

基础用法:游戏主循环与精灵系统

先搞定最基础的——让东西动起来。

// Sprite2D.ets - 2D精灵与游戏循环
class Vec2 {
  x: number = 0
  y: number = 0

  constructor(x: number = 0, y: number = 0) {
    this.x = x
    this.y = y
  }

  // 向量加法
  add(other: Vec2): Vec2 {
    return new Vec2(this.x + other.x, this.y + other.y)
  }

  // 向量长度
  magnitude(): number {
    return Math.sqrt(this.x * this.x + this.y * this.y)
  }

  // 归一化
  normalize(): Vec2 {
    const len = this.magnitude()
    if (len === 0) return new Vec2(0, 0)
    return new Vec2(this.x / len, this.y / len)
  }
}

// 2D精灵类
class Sprite2D {
  // 位置与大小
  position: Vec2 = new Vec2()
  size: Vec2 = new Vec2(50, 50)
  velocity: Vec2 = new Vec2() // 速度
  acceleration: Vec2 = new Vec2() // 加速度

  // 旋转与缩放
  rotation: number = 0
  scale: number = 1.0

  // 可见性
  visible: boolean = true
  alpha: number = 1.0

  // 标签,用于碰撞分组
  tag: string = ''

  // 是否存活
  alive: boolean = true

  // 更新精灵状态
  update(dt: number): void {
    if (!this.alive) return

    // 速度 += 加速度 * 时间
    this.velocity = this.velocity.add(
      new Vec2(this.acceleration.x * dt, this.acceleration.y * dt)
    )
    // 位置 += 速度 * 时间
    this.position = this.position.add(
      new Vec2(this.velocity.x * dt, this.velocity.y * dt)
    )
  }

  // 获取AABB包围盒
  getBounds(): Rect {
    const halfW = this.size.x * this.scale / 2
    const halfH = this.size.y * this.scale / 2
    return {
      left: this.position.x - halfW,
      top: this.position.y - halfH,
      right: this.position.x + halfW,
      bottom: this.position.y + halfH
    }
  }
}

interface Rect {
  left: number
  top: number
  right: number
  bottom: number
}

// 游戏主循环
class GameLoop2D {
  private sprites: Sprite2D[] = []
  private lastTime: number = 0
  private running: boolean = false
  private timer: number = -1
  private onUpdate?: (dt: number) => void
  private onRender?: (sprites: Sprite2D[]) => void

  // 添加精灵
  addSprite(sprite: Sprite2D): void {
    this.sprites.push(sprite)
  }

  // 移除精灵
  removeSprite(sprite: Sprite2D): void {
    const idx = this.sprites.indexOf(sprite)
    if (idx > -1) this.sprites.splice(idx, 1)
  }

  // 设置回调
  setCallbacks(onUpdate: (dt: number) => void, onRender: (sprites: Sprite2D[]) => void): void {
    this.onUpdate = onUpdate
    this.onRender = onRender
  }

  // 启动
  start(): void {
    this.running = true
    this.lastTime = Date.now()
    this.tick()
  }

  // 停止
  stop(): void {
    this.running = false
    if (this.timer !== -1) clearTimeout(this.timer)
  }

  // 每帧执行
  private tick(): void {
    if (!this.running) return

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

    // 更新所有精灵
    for (const sp of this.sprites) {
      sp.update(dt)
    }

    // 清理已死亡的精灵
    this.sprites = this.sprites.filter(sp => sp.alive)

    // 执行外部回调
    if (this.onUpdate) this.onUpdate(dt)
    if (this.onRender) this.onRender(this.sprites)

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

  getSprites(): Sprite2D[] {
    return this.sprites
  }
}

进阶用法:帧动画系统

精灵不动是死的,动起来才是活的。帧动画系统就是让精灵"活"的关键。

// FrameAnimation.ets - 帧动画系统

// 单帧数据
interface FrameData {
  srcX: number       // 精灵表中X偏移
  srcY: number       // 精灵表中Y偏移
  width: number      // 帧宽度
  height: number     // 帧高度
  duration: number   // 帧持续时间(毫秒)
}

// 动画片段
class AnimationClip {
  name: string = ''
  frames: FrameData[] = []
  loop: boolean = true
  speed: number = 1.0 // 播放速度倍率

  // 从精灵表自动生成帧数据
  static fromSpriteSheet(
    name: string,
    sheetWidth: number,
    sheetHeight: number,
    frameWidth: number,
    frameHeight: number,
    frameCount: number,
    frameDuration: number = 100,
    loop: boolean = true
  ): AnimationClip {
    const clip = new AnimationClip()
    clip.name = name
    clip.loop = loop
    const cols = Math.floor(sheetWidth / frameWidth)

    for (let i = 0; i < frameCount; i++) {
      const col = i % cols
      const row = Math.floor(i / cols)
      clip.frames.push({
        srcX: col * frameWidth,
        srcY: row * frameHeight,
        width: frameWidth,
        height: frameHeight,
        duration: frameDuration
      })
    }
    return clip
  }
}

// 动画播放器
class AnimationPlayer {
  private clips: Map<string, AnimationClip> = new Map()
  private currentClip: AnimationClip | null = null
  private currentFrameIndex: number = 0
  private elapsed: number = 0 // 已经过的时间(毫秒)
  private playing: boolean = false
  private finished: boolean = false
  private onFinishCallback?: () => void

  // 添加动画片段
  addClip(clip: AnimationClip): void {
    this.clips.set(clip.name, clip)
  }

  // 播放指定动画
  play(name: string, reset: boolean = true): void {
    const clip = this.clips.get(name)
    if (!clip) {
      console.warn(`动画片段不存在: ${name}`)
      return
    }

    // 如果正在播放同一个动画,不重置
    if (this.currentClip === clip && !reset) return

    this.currentClip = clip
    this.currentFrameIndex = 0
    this.elapsed = 0
    this.playing = true
    this.finished = false
  }

  // 停止
  stop(): void {
    this.playing = false
  }

  // 设置播放完成回调
  onFinish(cb: () => void): void {
    this.onFinishCallback = cb
  }

  // 获取当前帧数据
  getCurrentFrame(): FrameData | null {
    if (!this.currentClip || this.currentClip.frames.length === 0) return null
    return this.currentClip.frames[this.currentFrameIndex]
  }

  // 更新动画
  update(dt: number): void {
    if (!this.playing || !this.currentClip) return

    this.elapsed += dt * 1000 * this.currentClip.speed
    const frame = this.currentClip.frames[this.currentFrameIndex]

    if (this.elapsed >= frame.duration) {
      this.elapsed -= frame.duration
      this.currentFrameIndex++

      // 检查是否播完
      if (this.currentFrameIndex >= this.currentClip.frames.length) {
        if (this.currentClip.loop) {
          this.currentFrameIndex = 0
        } else {
          this.currentFrameIndex = this.currentClip.frames.length - 1
          this.playing = false
          this.finished = true
          if (this.onFinishCallback) this.onFinishCallback()
        }
      }
    }
  }

  isPlaying(): boolean {
    return this.playing
  }

  isFinished(): boolean {
    return this.finished
  }
}

// 带动画的精灵
class AnimatedSprite {
  position: { x: number; y: number } = { x: 0, y: 0 }
  scale: number = 1.0
  rotation: number = 0
  alpha: number = 1.0
  visible: boolean = true
  flipX: boolean = false // 水平翻转

  private animator: AnimationPlayer = new AnimationPlayer()
  private image?: ImageBitmap // 精灵表图片

  // 添加动画
  addClip(clip: AnimationClip): void {
    this.animator.addClip(clip)
  }

  // 播放动画
  play(name: string): void {
    this.animator.play(name)
  }

  // 更新
  update(dt: number): void {
    this.animator.update(dt)
  }

  // 绘制到Canvas
  draw(ctx: CanvasRenderingContext2D): void {
    if (!this.visible) return

    const frame = this.animator.getCurrentFrame()
    if (!frame || !this.image) return

    ctx.save()
    ctx.translate(this.position.x, this.position.y)
    ctx.rotate(this.rotation)
    ctx.scale(this.flipX ? -this.scale : this.scale, this.scale)
    ctx.globalAlpha = this.alpha

    // 从精灵表中裁剪当前帧并绘制
    ctx.drawImage(
      this.image,
      frame.srcX, frame.srcY, frame.width, frame.height,  // 源区域
      -frame.width / 2, -frame.height / 2, frame.width, frame.height  // 目标区域
    )

    ctx.restore()
  }
}

完整示例:飞机大战核心逻辑

把游戏循环、精灵动画、碰撞检测串起来,做一个能玩的飞机大战:

// PlaneWar.ets - 飞机大战核心
class Bullet extends Sprite2D {
  damage: number = 1
  isPlayerBullet: boolean = true

  constructor(x: number, y: number, speed: number, isPlayer: boolean) {
    super()
    this.position = new Vec2(x, y)
    this.size = new Vec2(6, 16)
    this.velocity = new Vec2(0, isPlayer ? -speed : speed)
    this.isPlayerBullet = isPlayer
    this.tag = isPlayer ? 'playerBullet' : 'enemyBullet'
  }
}

class Enemy extends Sprite2D {
  hp: number = 1
  score: number = 100
  movePattern: number = 0 // 0=直线 1=正弦 3=追踪
  private startX: number = 0
  private time: number = 0

  constructor(x: number, y: number, hp: number, speed: number, pattern: number) {
    super()
    this.position = new Vec2(x, y)
    this.size = new Vec2(36, 36)
    this.velocity = new Vec2(0, speed)
    this.hp = hp
    this.movePattern = pattern
    this.startX = x
    this.tag = 'enemy'
  }

  override update(dt: number): void {
    super.update(dt)
    this.time += dt

    // 正弦移动模式
    if (this.movePattern === 1) {
      this.position.x = this.startX + Math.sin(this.time * 3) * 60
    }
  }

  takeDamage(dmg: number): boolean {
    this.hp -= dmg
    if (this.hp <= 0) {
      this.alive = false
      return true
    }
    return false
  }
}

class Player extends Sprite2D {
  hp: number = 3
  maxHp: number = 3
  shootInterval: number = 0.2 // 射击间隔(秒)
  private shootTimer: number = 0
  private invincible: boolean = false
  private invincibleTimer: number = 0

  constructor(x: number, y: number) {
    super()
    this.position = new Vec2(x, y)
    this.size = new Vec2(40, 48)
    this.tag = 'player'
  }

  // 受伤
  takeDamage(): void {
    if (this.invincible) return
    this.hp--
    this.invincible = true
    this.invincibleTimer = 1.5 // 无敌1.5秒
  }

  // 更新
  override update(dt: number): void {
    super.update(dt)

    // 无敌时间倒计时
    if (this.invincible) {
      this.invincibleTimer -= dt
      // 闪烁效果
      this.alpha = Math.sin(Date.now() * 0.02) > 0 ? 1.0 : 0.3
      if (this.invincibleTimer <= 0) {
        this.invincible = false
        this.alpha = 1.0
      }
    }

    // 射击计时
    this.shootTimer -= dt
  }

  // 是否可以射击
  canShoot(): boolean {
    if (this.shootTimer <= 0) {
      this.shootTimer = this.shootInterval
      return true
    }
    return false
  }
}

// AABB碰撞检测
function checkAABB(a: Sprite2D, b: Sprite2D): boolean {
  const ra = a.getBounds()
  const rb = b.getBounds()
  return ra.left < rb.right && ra.right > rb.left &&
         ra.top < rb.bottom && ra.bottom > rb.top
}

// 游戏主控制器
@Entry
@Component
struct PlaneWarGame {
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
  private canvasW: number = 360
  private canvasH: number = 720

  private player: Player = new Player(180, 650)
  private bullets: Bullet[] = []
  private enemies: Enemy[] = []
  private score: number = 0
  private spawnTimer: number = 0
  private running: boolean = false
  private lastTime: number = 0
  private timer: number = -1
  private touchX: number = 180
  private touchY: number = 650
  private isTouching: boolean = false

  aboutToAppear(): void {
    this.running = true
  }

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

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

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

    this.update(dt)
    this.render()

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

  private update(dt: number): void {
    // 玩家跟随触控
    if (this.isTouching) {
      const dx = this.touchX - this.player.position.x
      const dy = this.touchY - this.player.position.y
      this.player.velocity = new Vec2(dx * 8, dy * 8)
    } else {
      this.player.velocity = new Vec2(0, 0)
    }
    this.player.update(dt)

    // 限制玩家在屏幕内
    this.player.position.x = Math.max(20, Math.min(this.canvasW - 20, this.player.position.x))
    this.player.position.y = Math.max(20, Math.min(this.canvasH - 20, this.player.position.y))

    // 自动射击
    if (this.player.canShoot() && this.isTouching) {
      this.bullets.push(new Bullet(this.player.position.x, this.player.position.y - 30, 500, true))
    }

    // 生成敌人
    this.spawnTimer -= dt
    if (this.spawnTimer <= 0) {
      const x = 30 + Math.random() * (this.canvasW - 60)
      const pattern = Math.random() > 0.5 ? 1 : 0
      this.enemies.push(new Enemy(x, -30, 1, 80 + Math.random() * 60, pattern))
      this.spawnTimer = 0.8 + Math.random() * 0.5
    }

    // 更新子弹
    for (const b of this.bullets) {
      b.update(dt)
      // 超出屏幕则标记死亡
      if (b.position.y < -20 || b.position.y > this.canvasH + 20) {
        b.alive = false
      }
    }

    // 更新敌人
    for (const e of this.enemies) {
      e.update(dt)
      if (e.position.y > this.canvasH + 50) {
        e.alive = false
      }
    }

    // 碰撞检测:玩家子弹 vs 敌人
    for (const bullet of this.bullets) {
      if (!bullet.alive || !bullet.isPlayerBullet) continue
      for (const enemy of this.enemies) {
        if (!enemy.alive) continue
        if (checkAABB(bullet, enemy)) {
          bullet.alive = false
          if (enemy.takeDamage(bullet.damage)) {
            this.score += enemy.score
          }
          break
        }
      }
    }

    // 碰撞检测:敌人子弹 vs 玩家
    for (const bullet of this.bullets) {
      if (!bullet.alive || bullet.isPlayerBullet) continue
      if (checkAABB(bullet, this.player)) {
        bullet.alive = false
        this.player.takeDamage()
      }
    }

    // 碰撞检测:敌人 vs 玩家
    for (const enemy of this.enemies) {
      if (!enemy.alive) continue
      if (checkAABB(enemy, this.player)) {
        enemy.alive = false
        this.player.takeDamage()
      }
    }

    // 清理死亡对象
    this.bullets = this.bullets.filter(b => b.alive)
    this.enemies = this.enemies.filter(e => e.alive)

    // 游戏结束
    if (this.player.hp <= 0) {
      this.running = false
    }
  }

  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)

    // 绘制星星背景
    ctx.fillStyle = '#ffffff'
    for (let i = 0; i < 50; i++) {
      const sx = (i * 73 + Date.now() * 0.01) % this.canvasW
      const sy = (i * 137 + Date.now() * 0.02) % this.canvasH
      ctx.fillRect(sx, sy, 1, 1)
    }

    // 绘制玩家
    ctx.save()
    ctx.translate(this.player.position.x, this.player.position.y)
    ctx.globalAlpha = this.player.alpha
    // 简单三角形表示飞机
    ctx.fillStyle = '#00ff88'
    ctx.beginPath()
    ctx.moveTo(0, -24)
    ctx.lineTo(-20, 24)
    ctx.lineTo(20, 24)
    ctx.closePath()
    ctx.fill()
    ctx.restore()

    // 绘制子弹
    ctx.fillStyle = '#ffff00'
    for (const b of this.bullets) {
      if (b.isPlayerBullet) {
        ctx.fillStyle = '#00ffff'
      } else {
        ctx.fillStyle = '#ff4444'
      }
      ctx.fillRect(b.position.x - 3, b.position.y - 8, 6, 16)
    }

    // 绘制敌人
    for (const e of this.enemies) {
      ctx.save()
      ctx.translate(e.position.x, e.position.y)
      ctx.fillStyle = '#ff4466'
      ctx.beginPath()
      ctx.moveTo(0, 18)
      ctx.lineTo(-18, -18)
      ctx.lineTo(18, -18)
      ctx.closePath()
      ctx.fill()
      ctx.restore()
    }

    // 绘制UI
    ctx.fillStyle = '#ffffff'
    ctx.font = '18px sans-serif'
    ctx.fillText(`分数: ${this.score}`, 10, 30)
    ctx.fillText(`生命: ${'♥'.repeat(Math.max(0, this.player.hp))}`, 10, 55)

    // 游戏结束
    if (this.player.hp <= 0) {
      ctx.fillStyle = 'rgba(0,0,0,0.7)'
      ctx.fillRect(0, 0, this.canvasW, this.canvasH)
      ctx.fillStyle = '#ff4444'
      ctx.font = '36px sans-serif'
      ctx.textAlign = 'center'
      ctx.fillText('游戏结束', this.canvasW / 2, this.canvasH / 2 - 20)
      ctx.fillStyle = '#ffffff'
      ctx.font = '20px sans-serif'
      ctx.fillText(`最终分数: ${this.score}`, this.canvasW / 2, this.canvasH / 2 + 20)
      ctx.textAlign = 'start'
    }
  }

  build() {
    Column() {
      Canvas(this.ctx)
        .width('100%')
        .height('100%')
        .onReady(() => {
          this.lastTime = Date.now()
          this.gameTick()
        })
        .onTouch((event: TouchEvent) => {
          const touch = event.touches[0]
          this.touchX = touch.x
          this.touchY = touch.y
          if (event.type === TouchType.Down) {
            this.isTouching = true
          } else if (event.type === TouchType.Up) {
            this.isTouching = false
          }
        })
    }
    .width('100%')
    .height('100%')
  }
}

跑起来,你就能用手指控制飞机,自动发射子弹,击落从上方飞来的敌人。这就是一个完整的2D游戏核心了。

踩坑与注意事项

坑1:Canvas的drawImage裁剪参数

drawImage有9个参数的版本,源区域和目标区域别搞反了。源区域在前(精灵表上的坐标),目标区域在后(Canvas上的坐标)。参数顺序错了,要么画面错位,要么直接白屏。

坑2:精灵表图片加载时机

在鸿蒙上,图片加载是异步的。你在aboutToAppear里创建ImageBitmap,到onReady的时候图片可能还没加载完。解决方案:用Image组件的onComplete回调来确认图片加载完毕后再启动游戏循环。

坑3:碰撞检测的性能

子弹多了,碰撞检测就是O(n²)的复杂度。100颗子弹 × 50个敌人 = 5000次碰撞检查,每帧都要跑。解决方法:用空间分区(网格法或四叉树),只检查同一区域内的对象。具体实现后面性能优化的文章会讲。

坑4:帧动画的帧率与游戏帧率不同步

帧动画的帧率是固定的(比如每帧100ms),游戏帧率是波动的(16ms左右)。你不能用游戏帧数来驱动动画帧切换,必须用时间累积。上面代码里的elapsed就是干这个的。

坑5:对象清理不及时

子弹飞出屏幕、敌人被击毁,这些"死"对象如果不及时清理,会越积越多,内存和CPU都扛不住。每帧都做一次filter清理是必要的,但如果对象特别多,可以考虑分帧清理——每帧只清理一部分。

HarmonyOS 6适配说明

HarmonyOS 6在2D游戏开发方面有几个值得关注的更新:

  1. Canvas 2D性能提升:底层渲染管线优化,批量绘制调用(batch draw)效率提升约30%。大量精灵场景下帧率更稳定。

  2. 新增CanvasRenderingContext2DV2:扩展了Canvas 2D的API,新增了drawPixelMap方法,可以直接绘制PixelMap对象,省去了ImageBitmap的转换开销。

// HarmonyOS 6 新API示例
import { image } from '@kit.ImageKit'

// 使用PixelMap直接绘制,性能更好
async function drawWithPixelMap(ctx: CanvasRenderingContext2DV2) {
  const pixelMap = await image.createPixelMap(data, {
    size: { width: 64, height: 64 }
  })
  ctx.drawPixelMap(pixelMap, 100, 100)
}
  1. 硬件加速Canvas:HarmonyOS 6默认为Canvas开启GPU加速,2D绘制不再走CPU软渲染。这对复杂2D游戏是重大利好,帧率提升明显。

  2. 游戏帧率自适应:新增DisplaySyncAPI,可以根据屏幕刷新率自动调整游戏循环频率,支持120Hz高刷屏。

总结

2D游戏开发,说到底就是三件事:让东西动起来(游戏循环+精灵系统)、让东西看起来像活的(帧动画)、让东西撞上了有反应(碰撞检测)。

游戏循环是心脏,每帧泵一次血。精灵系统是骨架,所有游戏对象都挂在上面。帧动画是皮肤,让角色看起来在动。碰撞检测是神经,让游戏有交互。

评估维度 学习难度 使用频率 重要程度
游戏主循环 ★★★☆☆ ★★★★★ ★★★★★
精灵系统 ★★★☆☆ ★★★★★ ★★★★★
帧动画 ★★★★☆ ★★★★☆ ★★★★☆
AABB碰撞检测 ★★☆☆☆ ★★★★★ ★★★★★
精灵表裁剪绘制 ★★★☆☆ ★★★★☆ ★★★★☆
Canvas 2D API ★★★☆☆ ★★★★★ ★★★★★

下一篇我们进3D领域,看看XComponent和OpenGL ES怎么在鸿蒙上搞3D渲染。维度升了一级,复杂度也升了一级,但原理还是那个原理——算、画、判。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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