首先捋一下实现仿现实烟花特效的核心思路,主要通过模拟烟花的完整生命周期(发射、上升、爆炸、粒子消散),结合物理运动规律(重力、阻力)和视觉效果增强,实现贴近真实的烟花体验。以下是动态效果图:

下面开始具体的实现过程:
一、数据模型设计
kotlin
// 粒子数据类,存储粒子的物理属性
data class Particle(
var x: Float,
var y: Float,
var velocityX: Float,
var velocityY: Float,
var radius: Float,
var color: Color,
var life: Float,
var maxLife: Float,
var rotation: Float,
var rotationSpeed: Float
)
// 花炮数据类
data class Firecracker(
val id: Int,
var x: Float,
var y: Float,
val targetY: Float, // 目标高度,到达后自动爆炸
var isExploding: Boolean = false,
var isFinished: Boolean = false,
val color: Color,
var trail: List<Offset> = emptyList() // 花炮尾迹
)
通过数据类封装烟花和粒子的核心属性,为物理模拟和绘制提供数据基础:
Particle
(粒子) :存储爆炸后粒子的物理状态,包括位置(x
/y
)、速度(velocityX
/velocityY
)、大小(radius
)、颜色(color
)、生命周期(life
/maxLife
)、旋转状态(rotation
/rotationSpeed
)。Firecracker
(烟花弹) :存储未爆炸时烟花的状态,包括位置(x
/y
)、目标爆炸高度(targetY
)、爆炸状态(isExploding
)、是否结束(isFinished
)、颜色(color
)和上升尾迹(trail
)。
二、烟花生命周期管理
通过多个协程(LaunchedEffect
)分别处理烟花从生成到消散的完整流程:
1. 烟花弹生成
scss
// 定时生成新的花炮
LaunchedEffect(Unit) {
while (isActive) {
val times = Random.nextInt(4, 10)
repeat(times) {
// 随机水平位置生成新花炮
val x = Random.nextFloat() * (width - 200.dp.value) + 100.dp.value
// 随机目标高度
val targetY = Random.nextFloat() * (height / 3) + height / 4
fireworks = fireworks + Firecracker(
id = nextFirecrackerId++,
x = x,
y = height - 200.dp.value, // 从底部开始
targetY = targetY,
color = when (Random.nextInt(6)) {
0 -> Color(0xFFFF5252) // 红
1 -> Color(0xFFFFEB3B) // 黄
2 -> Color(0xFF4CAF50) // 绿
3 -> Color(0xFF2196F3) // 蓝
4 -> Color(0xFF9C27B0) // 紫
else -> Color(0xFFFF9800) // 橙
}
)
delay(300)
}
// 过滤掉已完成的花炮
fireworks = fireworks.filter { !it.isFinished }
delay(3000) // 每几秒生成多个新花炮
}
}
- 定时(每3秒)在随机水平位置(
x
)生成多个新烟花弹,初始位置从底部开始,目标爆炸高度(targetY
)随机,颜色随机选取红、黄、绿等6种颜色。 - 自动过滤已完成生命周期的烟花弹,避免内存累积。
2. 烟花弹上升动画
ini
// 更新花炮位置(上升动画)
LaunchedEffect(fireworks) {
while (isActive) {
fireworks = fireworks.map { firecracker ->
if (firecracker.isExploding || firecracker.isFinished) {
return@map firecracker
}
// 花炮上升,速度逐渐减慢(模拟重力影响)
val progress = 1 - (firecracker.y / height * 1.1f)
val speed = 8 * (1 - progress * 0.8f)
var newY = firecracker.y - speed
// 更新尾迹
val newTrail = (listOf(Offset(firecracker.x, firecracker.y)) + firecracker.trail)
.take(30) // 只保留最近的30个点
// 如果到达目标高度,自动爆炸
if (newY <= firecracker.targetY) {
newY = firecracker.targetY
// 创建爆炸效果
val explosion = createExplosion(
centerX = firecracker.x,
centerY = newY,
particleCount = particleCount,
minSize = minParticleSize,
maxSize = maxParticleSize,
baseColor = firecracker.color
)
explosions = explosions + listOf(explosion)
return@map firecracker.copy(
y = newY,
isExploding = true,
trail = newTrail
)
}
firecracker.copy(y = newY, trail = newTrail)
}
delay(16) // 约60fps
}
}
- 实时更新烟花弹位置:上升速度随高度增加而减慢(模拟重力影响),计算公式为
speed = 8f * (1 - progress * 0.8f)
(progress
为上升进度)。 - 同步更新尾迹:保留最近30个位置点,形成逐渐消失的轨迹效果。
- 触发爆炸条件:当烟花弹到达目标高度(
y <= targetY
)时,生成爆炸粒子并标记为爆炸状态(isExploding
)
3. 爆炸粒子物理模拟
arduino
// 更新爆炸粒子状态
LaunchedEffect(explosions) {
while (isActive) {
// 更新所有粒子的物理状态
explosions = explosions.map { particles ->
particles.mapNotNull { particle ->
// 更新粒子生命
particle.life -= 0.015f
if (particle.life <= 0) {
return@mapNotNull null
}
// 应用重力 (模拟向下的加速度)
particle.velocityY += 0.15f
// 应用空气阻力 (速度逐渐减慢)
particle.velocityX *= 0.98f
particle.velocityY *= 0.98f
// 更新位置
particle.x += particle.velocityX
particle.y += particle.velocityY
// 更新旋转
particle.rotation += particle.rotationSpeed
particle
}
}.filter { it.isNotEmpty() } // 过滤掉已经没有粒子的爆炸
// 标记已爆炸完成的花炮
fireworks = fireworks.map { firecracker ->
if (firecracker.isExploding &&
explosions.none { explosion ->
explosion.any { it.x == firecracker.x && it.y == firecracker.y }
}) {
firecracker.copy(isFinished = true, isExploding = false)
} else {
firecracker
}
}
delay(16) // 约60fps
}
}
- 粒子生命周期管理:每个粒子的
life
随时间减少(-0.015f/帧
),生命周期结束后自动消失。 - 物理运动模拟:
- 重力:粒子竖直方向速度(
velocityY
)持续增加(+0.15f/帧
),模拟向下的加速度。 - 空气阻力:水平和竖直速度均乘以0.98(
*0.98f/帧
),模拟速度衰减。 - 位置更新:根据速度实时更新粒子坐标(
x += velocityX
,y += velocityY
)。
- 重力:粒子竖直方向速度(
- 旋转效果:粒子随机旋转速度(
rotationSpeed
),实时更新旋转角度(rotation
),增强视觉动态感。
三、交互设计
ini
// 处理点击事件
fun handleFirecrackerTap(x: Float, y: Float) {
fireworks.forEachIndexed { index, firecracker ->
if (!firecracker.isExploding && !firecracker.isFinished) {
// 计算点击位置与花炮的距离
val distance = sqrt(
(x - firecracker.x).pow(2) + (y - firecracker.y).pow(2)
)
// 如果点击在花炮附近
if (distance < firecrackerSize * 8) {
// 创建爆炸效果
val explosion = createExplosion(
centerX = firecracker.x,
centerY = firecracker.y,
particleCount = particleCount,
minSize = minParticleSize,
maxSize = maxParticleSize,
baseColor = firecracker.color
)
explosions = explosions + listOf(explosion)
// 更新花炮状态
fireworks = fireworks.mapIndexed { i, f ->
if (i == index) f.copy(isExploding = true) else f
}
}
}
}
}
Box(
modifier = modifier
.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures { tapOffset ->
handleFirecrackerTap(tapOffset.x, tapOffset.y)
}
}
)
支持用户点击提前引爆烟花:
- 通过
detectTapGesture
监听点击位置,计算点击点与烟花弹的距离。 - 当距离小于烟花弹大小的8倍时,触发爆炸,生成粒子效果并标记烟花弹为爆炸状态。
四、爆炸粒子生成逻辑
scss
// 创建一次爆炸的所有粒子,基于花炮颜色生成相近色
private fun createExplosion(
centerX: Float,
centerY: Float,
particleCount: Int,
minSize: Float,
maxSize: Float,
baseColor: Color? = null
): List<Particle> {
return List(particleCount) {
// 随机角度和速度
val angle = Random.nextFloat() * 2 * Math.PI.toFloat()
val speed = Random.nextFloat() * 6f + 2f
// 计算速度分量
val velocityX = cos(angle).toFloat() * speed
val velocityY = sin(angle).toFloat() * speed
// 随机大小
val radius = Random.nextFloat() * (maxSize - minSize) + minSize
// 颜色 - 基于基础颜色生成相近色
val color = baseColor?.let {
var hsv = floatArrayOf(0f, 0f, 0f)
android.graphics.Color.colorToHSV(it.toArgb(), hsv)
val hue = (hsv[0] + (Random.nextFloat() * 30f - 15f)).mod(360f)
Color.hsv(hue, hsv[1], hsv[2])
} ?: when (Random.nextInt(6)) {
0 -> Color(0xFFFF5252) // 红
1 -> Color(0xFFFFEB3B) // 黄
2 -> Color(0xFF4CAF50) // 绿
3 -> Color(0xFF2196F3) // 蓝
4 -> Color(0xFF9C27B0) // 紫
else -> Color(0xFFFF9800) // 橙
}
// 随机生命周期和旋转
val maxLife = Random.nextFloat() * 0.5f + 1.0f
Particle(
x = centerX,
y = centerY,
velocityX = velocityX,
velocityY = velocityY,
radius = radius,
color = color,
life = maxLife,
maxLife = maxLife,
rotation = Random.nextFloat() * 360f,
rotationSpeed = Random.nextFloat() * 10f - 5f
)
}
}
createExplosion
函数负责生成爆炸时的粒子群:
- 随机角度(
0~2π
)和速度(2~8f
),确保粒子向四周扩散。 - 随机大小(
minParticleSize~maxParticleSize
),模拟不同颗粒的爆炸效果。 - 颜色基于烟花弹主色生成相近色(色调±15°范围内波动),保持视觉一致性;若无主色则随机选取预设颜色。
总结
该实现通过拆分烟花生命周期 (生成→上升→爆炸→消散),结合物理规律模拟 (重力、阻力、速度衰减)和分层视觉绘制 (背景、尾迹、主体、粒子),再加上用户交互触发,最终实现了贴近真实且具有交互性的烟花特效。
完整代码 请点击Fireworks查看,因技能水平有限,如有问题欢迎指正。