HarmonyOS 6.1 Canvas粒子效果系统从零实现

本文深入讲解如何在HarmonyOS 6.1中使用Canvas实现粒子效果系统,包括粒子系统原理、数据结构设计、物理模拟、渲染优化等核心技术。通过本文,你将掌握Canvas 2D绘图和粒子动画的实现方法。

📋 目录

  • 粒子系统简介
  • 粒子数据结构设计
  • 粒子发射器实现
  • 物理模拟与更新
  • Canvas渲染技术
  • 性能优化策略
  • 实战案例
  • 总结

一、粒子系统简介

1.1 什么是粒子系统?

粒子系统是一种计算机图形学技术,通过大量微小粒子的运动和渲染,模拟火焰、爆炸、烟雾、雨雪等自然现象。

1.2 应用场景

在游戏开发中,粒子效果常用于:

  • 💥 爆炸效果:碰撞、击杀
  • 特效反馈:得分、升级
  • 🌟 环境氛围:雨雪、星空
  • 🔥 魔法技能:火球、闪电
  • 💨 烟雾拖尾:飞行、移动

1.3 技术优势

  • 视觉冲击力强:增强游戏反馈
  • 性能开销可控:可调整粒子数量
  • 灵活性高:通过参数实现各种效果
  • 实现相对简单:基础数学和Canvas API

二、粒子数据结构设计

2.1 单个粒子的属性

一个粒子需要包含以下基本属性:

typescript 复制代码
export interface Particle {
  // 位置信息
  x: number           // X坐标
  y: number           // Y坐标
  
  // 速度信息
  vx: number          // X方向速度
  vy: number          // Y方向速度
  
  // 生命周期
  life: number        // 当前剩余生命(帧数)
  maxLife: number     // 最大生命(用于计算透明度)
  
  // 视觉属性
  color: string       // 颜色(支持hex、rgb、rgba)
  size: number        // 大小(像素)
}

设计要点:

  • 位置和速度:物理模拟的基础
  • 生命周期:控制粒子存在时间,自动回收
  • 视觉属性:颜色和大小决定粒子外观

2.2 扩展属性(可选)

根据需求,可以添加更多属性:

typescript 复制代码
export interface AdvancedParticle extends Particle {
  // 加速度(用于更复杂的运动)
  ax: number
  ay: number
  
  // 旋转
  rotation: number      // 当前角度
  rotationSpeed: number // 旋转速度
  
  // 缩放动画
  scale: number         // 当前缩放
  scaleSpeed: number    // 缩放速度
  
  // 透明度
  alpha: number         // 当前透明度
  alphaDecay: number    // 透明度衰减速率
}

三、粒子系统核心实现

3.1 基础架构

typescript 复制代码
export class ParticleSystem {
  private particles: Particle[] = []
  
  // 创建粒子效果
  createExplosion(x: number, y: number, color: string, count: number): void {}
  
  // 更新所有粒子状态
  update(): void {}
  
  // 渲染所有粒子
  render(context: CanvasRenderingContext2D): void 
  
  // 获取粒子数量
  getParticleCount(): number {
    return this.particles.length
  }
}

3.2 粒子发射器:爆炸效果

原理:从中心点向四周均匀发射粒子,形成放射状效果。

typescript 复制代码
createExplosion(x: number, y: number, color: string, count: number = 10): void {
  for (let i = 0; i < count; i++) {
    // 计算均匀分布的角度
    const angle = (Math.PI * 2 * i) / count
    
    // 随机速度(2-5像素/帧)
    const speed = 2 + Math.random() * 3
    
    this.particles.push({
      x: x,
      y: y,
      // 根据角度计算速度分量
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      life: 60,        // 存活60帧(约1秒)
      maxLife: 60,
      color: color,
      size: 3 + Math.random() * 3  // 随机大小3-6像素
    })
  }
}

关键公式:

  • 角度均分:angle = 2π × i / count
  • 速度分解:
    • vx = cos(angle) × speed
    • vy = sin(angle) × speed

效果图解:

复制代码
        ↑
    ↗   |   ↖
  →     ●     ←
    ↘   |   ↙
        ↓

3.3 更多发射器模式

1. 喷泉效果(向上发射)

typescript 复制代码
createFountain(x: number, y: number): void {
  const angle = -Math.PI / 2 + (Math.random() - 0.5) * 0.5  // 向上±15度
  const speed = 5 + Math.random() * 3
  
  this.particles.push({
    x: x,
    y: y,
    vx: Math.cos(angle) * speed,
    vy: Math.sin(angle) * speed,
    life: 80,
    maxLife: 80,
    color: '#4FC3F7',
    size: 4
  })
}

2. 随机散射

typescript 复制代码
createScatter(x: number, y: number, count: number): void {
  for (let i = 0; i < count; i++) {
    const angle = Math.random() * Math.PI * 2  // 随机角度
    const speed = Math.random() * 5
    
    this.particles.push({
      x: x,
      y: y,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      life: 40 + Math.random() * 40,
      maxLife: 80,
      color: `hsl(${Math.random() * 360}, 100%, 50%)`,  // 随机颜色
      size: 2 + Math.random() * 4
    })
  }
}

四、物理模拟与更新

4.1 基础物理更新

typescript 复制代码
update(): void {
  this.particles = this.particles.filter(p => {
    // 1. 更新位置(根据速度)
    p.x += p.vx
    p.y += p.vy
    
    // 2. 应用重力
    p.vy += 0.2  // 重力加速度
    
    // 3. 减少生命值
    p.life--
    
    // 4. 保留还存活的粒子
    return p.life > 0
  })
}

物理模拟要点:

  • 位置更新新位置 = 当前位置 + 速度
  • 重力模拟:Y方向速度持续增加
  • 自动回收:生命值为0时自动移除

4.2 高级物理效果

1. 空气阻力

typescript 复制代码
update(): void {
  this.particles.forEach(p => {
    // 应用空气阻力(速度衰减)
    p.vx *= 0.98
    p.vy *= 0.98
    
    p.x += p.vx
    p.y += p.vy
  })
}

2. 边界反弹

typescript 复制代码
update(width: number, height: number): void {
  this.particles.forEach(p => {
    p.x += p.vx
    p.y += p.vy
    
    // 左右边界反弹
    if (p.x <= 0 || p.x >= width) {
      p.vx *= -0.8  // 反向并损失能量
    }
    
    // 上下边界反弹
    if (p.y <= 0 || p.y >= height) {
      p.vy *= -0.8
    }
  })
}

3. 风力效果

typescript 复制代码
update(windX: number = 0.1): void {
  this.particles.forEach(p => {
    p.vx += windX  // 添加水平风力
    p.x += p.vx
    p.y += p.vy
  })
}

五、Canvas渲染技术

5.1 基础渲染

typescript 复制代码
render(context: CanvasRenderingContext2D): void {
  this.particles.forEach(p => {
    // 1. 计算透明度(根据剩余生命)
    const alpha = p.life / p.maxLife
    context.globalAlpha = alpha
    
    // 2. 设置颜色
    context.fillStyle = p.color
    
    // 3. 绘制矩形粒子
    context.fillRect(p.x, p.y, p.size, p.size)
  })
  
  // 4. 恢复透明度
  context.globalAlpha = 1
}

5.2 多种粒子形状

1. 圆形粒子

typescript 复制代码
renderCircle(context: CanvasRenderingContext2D, p: Particle): void {
  context.beginPath()
  context.arc(p.x, p.y, p.size / 2, 0, Math.PI * 2)
  context.fillStyle = p.color
  context.fill()
}

2. 发光效果

typescript 复制代码
renderGlow(context: CanvasRenderingContext2D, p: Particle): void {
  const gradient = context.createRadialGradient(
    p.x, p.y, 0,
    p.x, p.y, p.size
  )
  gradient.addColorStop(0, p.color)
  gradient.addColorStop(1, 'transparent')
  
  context.fillStyle = gradient
  context.fillRect(p.x - p.size, p.y - p.size, p.size * 2, p.size * 2)
}

3. 拖尾效果

typescript 复制代码
renderTrail(context: CanvasRenderingContext2D, p: Particle): void {
  context.beginPath()
  context.moveTo(p.x, p.y)
  context.lineTo(p.x - p.vx * 2, p.y - p.vy * 2)
  context.strokeStyle = p.color
  context.lineWidth = p.size
  context.stroke()
}

5.3 集成到HarmonyOS

1. 添加Canvas组件

typescript 复制代码
@Component
struct ParticleLayer {
  private particleContext: CanvasRenderingContext2D = new CanvasRenderingContext2D()
  private particleSystem: ParticleSystem = new ParticleSystem()
  
  build() {
    Canvas(this.particleContext)
      .width('100%')
      .height('100%')
      .backgroundColor(Color.Transparent)
      .onReady(() => {
        // 启动渲染循环
        setInterval(() => {
          // 清空画布
          this.particleContext.clearRect(0, 0, 360, 760)
          
          // 更新粒子
          this.particleSystem.update()
          
          // 渲染粒子
          this.particleSystem.render(this.particleContext)
        }, 16)  // 约60fps
      })
  }
}

2. 层叠布局

typescript 复制代码
Stack() {
  // 游戏背景层
  Image($r('app.media.background'))
  
  // 游戏元素层
  GameElements()
  
  // 粒子效果层(最上层)
  ParticleLayer()
}

六、性能优化策略

6.1 粒子数量控制

typescript 复制代码
export class ParticleSystem {
  private particles: Particle[] = []
  private maxParticles: number = 500  // 最大粒子数
  
  createExplosion(x: number, y: number, color: string, count: number): void {
    // 检查粒子数量限制
    if (this.particles.length + count > this.maxParticles) {
      return  // 达到上限,不再创建
    }
    
    // 创建粒子...
  }
}

6.2 对象池优化

问题:频繁创建和销毁粒子对象会导致GC压力。

解决方案:使用对象池复用粒子对象。

typescript 复制代码
export class ParticlePool {
  private pool: Particle[] = []
  private active: Particle[] = []
  
  // 获取粒子(复用或新建)
  acquire(): Particle {
    if (this.pool.length > 0) {
      const particle = this.pool.pop()!
      this.active.push(particle)
      return particle
    }
    
    const newParticle: Particle = {
      x: 0, y: 0, vx: 0, vy: 0,
      life: 0, maxLife: 0,
      color: '', size: 0
    }
    this.active.push(newParticle)
    return newParticle
  }
  
  // 回收粒子
  release(particle: Particle): void {
    const index = this.active.indexOf(particle)
    if (index !== -1) {
      this.active.splice(index, 1)
      this.pool.push(particle)
    }
  }
  
  // 更新并自动回收
  update(): void {
    for (let i = this.active.length - 1; i >= 0; i--) {
      const p = this.active[i]
      p.x += p.vx
      p.y += p.vy
      p.vy += 0.2
      p.life--
      
      if (p.life <= 0) {
        this.release(p)
      }
    }
  }
}

6.3 渲染优化

1. 离屏渲染

typescript 复制代码
private offscreenCanvas: OffscreenCanvas
private offscreenContext: OffscreenCanvasRenderingContext2D

render(context: CanvasRenderingContext2D): void {
  // 在离屏Canvas上绘制
  this.offscreenContext.clearRect(0, 0, width, height)
  this.particles.forEach(p => {
    this.offscreenContext.fillStyle = p.color
    this.offscreenContext.fillRect(p.x, p.y, p.size, p.size)
  })
  
  // 一次性绘制到主Canvas
  context.drawImage(this.offscreenCanvas, 0, 0)
}

2. 批量绘制

typescript 复制代码
render(context: CanvasRenderingContext2D): void {
  // 按颜色分组,减少fillStyle切换
  const groups = new Map<string, Particle[]>()
  
  this.particles.forEach(p => {
    if (!groups.has(p.color)) {
      groups.set(p.color, [])
    }
    groups.get(p.color)!.push(p)
  })
  
  // 批量绘制同颜色粒子
  groups.forEach((particles, color) => {
    context.fillStyle = color
    particles.forEach(p => {
      context.fillRect(p.x, p.y, p.size, p.size)
    })
  })
}

七、实战案例

7.1 FlappyBird中的应用

场景1:死亡爆炸效果

typescript 复制代码
// 游戏结束时触发
if (this.birdIsDead()) {
  // 红色爆炸,15个粒子
  this.particleSystem.createExplosion(
    this.birdX + 20,  // 小鸟中心X
    this.birdY + 20,  // 小鸟中心Y
    '#FF0000',        // 红色
    15                // 粒子数量
  )
  
  this.soundManager.play(SoundType.DIE)
  this.dialogController.open()
}

场景2:得分特效

typescript 复制代码
// 每次得分时触发
if (this.score++) {
  // 金色粒子,8个粒子
  this.particleSystem.createExplosion(
    this.birdX + 20,
    this.birdY,
    '#FFD700',  // 金色
    8
  )
  
  this.soundManager.play(SoundType.SCORE)
}

7.2 完整集成示例

typescript 复制代码
@Entry
@Component
struct GamePage {
  private particleContext: CanvasRenderingContext2D = new CanvasRenderingContext2D()
  private particleSystem: ParticleSystem = new ParticleSystem()
  
  @State birdX: number = 160
  @State birdY: number = 300
  
  build() {
    Stack() {
      // 游戏背景
      Image($r('app.media.background'))
      
      // 游戏元素
      Image($r('app.media.bird'))
        .width(40)
        .height(40)
        .position({ x: this.birdX, y: this.birdY })
      
      // 粒子效果层
      Canvas(this.particleContext)
        .width('100%')
        .height('100%')
        .backgroundColor(Color.Transparent)
        .onReady(() => {
          // 渲染循环
          setInterval(() => {
            this.particleContext.clearRect(0, 0, 360, 760)
            this.particleSystem.update()
            this.particleSystem.render(this.particleContext)
          }, 16)
        })
    }
    .gesture(
      TapGesture().onAction(() => {
        // 点击创建粒子效果
        this.particleSystem.createExplosion(
          this.birdX + 20,
          this.birdY + 20,
          '#00FF00',
          10
        )
      })
    )
  }
}

7.3 彩虹粒子效果

typescript 复制代码
createRainbowExplosion(x: number, y: number): void {
  const colors = ['#FF0000', '#FF7F00', '#FFFF00', '#00FF00', '#0000FF', '#4B0082', '#9400D3']
  
  colors.forEach((color, index) => {
    const angle = (Math.PI * 2 * index) / colors.length
    const speed = 3
    
    this.particles.push({
      x: x,
      y: y,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      life: 60,
      maxLife: 60,
      color: color,
      size: 5
    })
  })
}

7.4 连续粒子流

typescript 复制代码
createContinuousStream(x: number, y: number): void {
  // 每帧创建2-3个粒子
  setInterval(() => {
    for (let i = 0; i < 2 + Math.random() * 2; i++) {
      const angle = -Math.PI / 2 + (Math.random() - 0.5) * 0.3
      const speed = 3 + Math.random() * 2
      
      this.particles.push({
        x: x,
        y: y,
        vx: Math.cos(angle) * speed,
        vy: Math.sin(angle) * speed,
        life: 40,
        maxLife: 40,
        color: '#FFA500',
        size: 3
      })
    }
  }, 50)  // 每50ms创建一批
}

八、调试与测试

8.1 调试工具

typescript 复制代码
export class ParticleSystem {
  private debugMode: boolean = false
  
  enableDebug(): void {
    this.debugMode = true
  }
  
  render(context: CanvasRenderingContext2D): void {
    this.particles.forEach(p => {
      // 正常渲染
      context.fillStyle = p.color
      context.fillRect(p.x, p.y, p.size, p.size)
      
      // 调试模式:显示速度向量
      if (this.debugMode) {
        context.strokeStyle = '#00FF00'
        context.beginPath()
        context.moveTo(p.x, p.y)
        context.lineTo(p.x + p.vx * 5, p.y + p.vy * 5)
        context.stroke()
      }
    })
    
    // 显示粒子数量
    if (this.debugMode) {
      context.fillStyle = '#FFFFFF'
      context.font = '16px Arial'
      context.fillText(`Particles: ${this.particles.length}`, 10, 20)
    }
  }
}

8.2 性能监控

typescript 复制代码
export class ParticleSystem {
  private updateTime: number = 0
  private renderTime: number = 0
  
  update(): void {
    const startTime = Date.now()
    
    // 更新逻辑...
    
    this.updateTime = Date.now() - startTime
  }
  
  render(context: CanvasRenderingContext2D): void {
    const startTime = Date.now()
    
    // 渲染逻辑...
    
    this.renderTime = Date.now() - startTime
    
    // 显示性能数据
    context.fillStyle = '#FFFFFF'
    context.fillText(`Update: ${this.updateTime}ms`, 10, 40)
    context.fillText(`Render: ${this.renderTime}ms`, 10, 60)
  }
}

九、总结

9.1 核心要点回顾

要点 说明
数据结构 位置、速度、生命周期、视觉属性
物理模拟 速度更新、重力、空气阻力
渲染技术 Canvas API、透明度渐变、多种形状
性能优化 数量限制、对象池、批量渲染
实战应用 爆炸、得分、拖尾、连续流

9.2 学习路径建议

  1. 第一步:实现基础粒子系统(位置、速度、渲染)
  2. 第二步:添加物理效果(重力、阻力、反弹)
  3. 第三步:丰富视觉效果(渐变、发光、拖尾)
  4. 第四步:性能优化(对象池、批量渲染)
  5. 第五步:创意发挥(各种特效组合)

9.3 常见问题

Q1:粒子太多导致卡顿?

  • 限制最大粒子数量(<500)
  • 使用对象池减少GC
  • 降低渲染复杂度(使用简单形状)

Q2:粒子效果不明显?

  • 增加粒子数量和大小
  • 使用更鲜艳的颜色
  • 添加发光效果
  • 搭配音效增强反馈

Q3:如何实现更真实的物理?

  • 添加空气阻力
  • 实现边界碰撞
  • 考虑粒子间相互作用(高级)

9.4 进阶方向

1. 高级粒子系统

  • 纹理粒子(使用图片)
  • 粒子发射器(持续发射)
  • 粒子场(力场影响)
  • 粒子碰撞

2. 性能极致优化

  • WebGL渲染(GPU加速)
  • 空间分区优化
  • LOD(距离细节层次)
  • 预计算轨迹

3. 特效库封装

  • 预设效果集合
  • 参数可视化调试
  • 时间轴动画
  • 效果组合器

十、完整代码示例

typescript 复制代码
// ParticleSystem.ets
export interface Particle {
  x: number
  y: number
  vx: number
  vy: number
  life: number
  maxLife: number
  color: string
  size: number
}

export class ParticleSystem {
  private particles: Particle[] = []

  createExplosion(x: number, y: number, color: string, count: number = 10): void {
    for (let i = 0; i < count; i++) {
      const angle = (Math.PI * 2 * i) / count
      this.particles.push({
        x: x,
        y: y,
        vx: Math.cos(angle) * (2 + Math.random() * 3),
        vy: Math.sin(angle) * (2 + Math.random() * 3),
        life: 60,
        maxLife: 60,
        color: color,
        size: 3 + Math.random() * 3
      })
    }
  }

  update(): void {
    this.particles = this.particles.filter(p => {
      p.x += p.vx
      p.y += p.vy
      p.vy += 0.2  // 重力
      p.life--
      return p.life > 0
    })
  }

  render(context: CanvasRenderingContext2D): void {
    this.particles.forEach(p => {
      const alpha = p.life / p.maxLife
      context.globalAlpha = alpha
      context.fillStyle = p.color
      context.fillRect(p.x, p.y, p.size, p.size)
    })
    context.globalAlpha = 1
  }

  getParticleCount(): number {
    return this.particles.length
  }
}

项目源码

本文示例代码项目源码

参考资料


如果本文对你有帮助,欢迎点赞、收藏、关注!

前两篇文章链接

  • [HarmonyOS 6.1 Lottie动画集成完全指南](#HarmonyOS 6.1 Lottie动画集成完全指南)
  • [HarmonyOS 6.1 游戏开发实战:FlappyBird完整实现](#HarmonyOS 6.1 游戏开发实战:FlappyBird完整实现)
相关推荐
祭曦念2 小时前
宠物成长日记_鸿蒙开发实战
华为·harmonyos·宠物
又至冬日2 小时前
鸿蒙(HarmoneyOS),封装一个通用关系型数据库操作类
数据库·oracle·harmonyos
G_dou_3 小时前
Flutter三方库适配OpenHarmony【palindrome_checker】回文检测器项目完整实战
flutter·harmonyos
风满城333 小时前
鸿蒙原生应用实战(五):个人中心与数据可视化 —— 统计图表与成就徽章
harmonyos
木咺吟3 小时前
鸿蒙原生应用开发实战(二):添加电影与表单交互 — 电影清单App
harmonyos
AI_零食3 小时前
HarmonyOS ArkTS 设计系统构建实战指南
学习·华为·harmonyos·鸿蒙·鸿蒙系统
风华圆舞3 小时前
鸿蒙工程里build-profile-module-oh-package分别负责什么
华为·harmonyos
佛系菜狗3 小时前
【鸿蒙NEXT】textInput银行卡号格式化(每四位显示加一个空格),用户在中间修改时光标总是移动到最右侧重置
华为·harmonyos
AI_零食3 小时前
HarmonyOS ArkTS 常用工具函数实现与应用详解
华为·开源·harmonyos·鸿蒙·鸿蒙系统