粒子
Flame提供了一个基本而又强大且可扩展的粒子系统。 该系统的核心概念是 Particle
类,其行为与 ParticleComponent
非常相似。
Particle
在 BaseGame
中的最基本用法如下所示:
import 'package:flame/components/particle_component.dart';
// ...
game.add(
// 用[ParticleComponent]包装一个[Particle]
// 将[Component]生命周期挂钩映射到[Particle]
// 并嵌入一个销毁组件的触发器。
ParticleComponent(
particle: CircleParticle()
)
);
当使用具有自定义 Game
实现的 Particle
时,请确保在每个游戏循环帧期间调用 Particle
的 update
和 render
。
实现所需粒子效果的主要方法:
现有行为的组成
使用行为链(只是第一个的语法糖)
*使用 ComputedParticle
定义粒子组件的方式与Flutter小部件的类似。 通过链式方式定义从底部到顶部的行为可以更流畅地实现相同的合成树。 计算粒子行为的实现完全委派给您的代码。 任何需求实现方法都可以与现有行为结合使用。
如下示例,使用以上三种方式实现了从 (0,0)
加速随机一个方向的圆圈。
Random rnd = Random();
Function randomOffset = () => Offset(
rnd.nextDouble() * 200 - 100,
rnd.nextDouble() * 200 - 100,
);
// 组件
// 将粒子效果定义为一组嵌套
// 从上到下的行为,一个在另一个内部:
// ParticleComponent
// > ComposedParticle
// > AcceleratedParticle
// > CircleParticle
game.add(
ParticleComponent(
particle: Particle.generate(
count: 10,
generator: (i) => AcceleratedParticle(
acceleration: randomOffset(),
child: CircleParticle(
paint: Paint()..color = Colors.red
)
)
)
)
);
// 链式
// 与上述行为相同,但具有更流畅的API。
// 只有带有[SingleChildParticle] mixin 的 [Particles] 可以用作可链式操作。
game.add(
Particle
.generate(
count: 10,
generator: (i) => CircleParticle(paint: Paint()..color = Colors.red)
.accelerating(randomOffset())
)
.asComponent()
);
// 计算粒子
// 所有行为均已明确定义。 提供更大的灵活性
// 与内置行为相比。
game.add(
Particle
.generate(
count: 10,
generator: (i) {
final position = Offset.zero;
final speed = Offset.zero;
final acceleration = randomOffset();
final paint = Paint()..color = Colors.red;
return ComputedParticle(
renderer: (canvas, _) {
speed += acceleration;
position += speed;
canvas.drawCircle(position, 10, paint);
}
);
}
)
.asComponent()
)
您可以在 此处 中找到更多使用不同内建粒子的示例。
生命周期
所有 Particle
他们都有 lifespan
参数。 一旦内部 Particle
达到使用寿命, ParticleComponent
将销毁。
Particle
内置了Flame Timer
跟踪 。通过 Particle
构造函数传入 lifespan:double
单位秒(精确到微秒)。
如下:
Particle(lifespan: .2); // 存活 200ms
Particle(lifespan: 4); // 存活 4s
也可以使用 setLifespan
方法重置 Particle
寿命,该方法接受 double
秒。
final particle = Particle(lifespan: 2);
// ... 在某个时间点之后
particle.setLifespan(2) // 从现在起将再存活2秒
在其生命周期内, Particle
会跟踪其存活时间并通过 progress
(介于0.0到1.0之间)将其公开暴露到外部。
此值使用方式与Flutter中 AnimationController
的 value
类似。
final duration = const Duration(seconds: 2);
final particle = Particle(lifespan: duration.inMicroseconds / Durations.microsecondsPerSecond);
game.add(ParticleComponent(particle: particle));
// 将以0.1的步长打印0到1的值:0、0.1、0.2 ... 0.9、1.0
Timer.periodic(duration * .1, () => print(particle.progress));
使用链式方式可以将寿命传递到所有 Particle
的继承类中。
内置颗粒
Flame带有一些内置的 Particle
行为:
TranslatedParticle
,通过Offset
平移child
。MovingParticle
通过两个预定义的Offset
之间移动child
,支持Curve
AcceleratedParticle
(加速粒子)允许基于物理的基本效果,例如重力或速度衰减CircleParticle
,渲染各种形状和大小的圆圈SpriteParticle
,在Particle
内渲染 FlameSprite
ImageParticle
,在Particle
内渲染dart:ui
图片ParticleParticle
,在Particle
内渲染 FlameComponent
FlareParticle
,在Particle
内渲染Flare动画
使用以上粒子示例 此处 所有实现都可以在Flame源中的粒子文件夹中找到。
平移粒子
在 Canvas
内只需将基本的 Particle
设置指定 Offset
即可。
不更改或更改其位置时请考虑使用 MovingParticle
或 AcceleratedParticle
。
通过平移 Canvas
层可以达到相同的效果。
game.add(
ParticleComponent(
particle: TranslatedParticle(
// Will translate child Particle effect to
// the center of game canvas
offset: game.size.center(Offset.zero),
child: Particle(),
)
)
);
运动粒子
在其寿命期间,从 from
到 to
之间移动子 Particle
。
通过 CurvedParticle
支持 Curve
。
game.add(
ParticleComponent(
particle: MovingParticle(
// 会从一个角落移到另一个角落
// 游戏画布
from: game.size.topLeft(Offset.zero),
to: game.size.bottomRight(Offset.zero),
child: Particle(),
)
)
);
加速粒子
一个基本的物理粒子,它允许您指定其初始 position
(位置), speed
(速度) 和 acceleration
(加速度),并让 update
循环完成其余的工作。 所有这三个都指定为 Offset
,
您可以将其视为向量。 它对于基于物理的 "bursts" 特别有效,但不仅限于此。
Offset
值的单位为 px/s , 因此,Offset(0, 100)
的速度将使游戏中每隔一秒钟的子 Particle
移动设备的100个逻辑像素。
final rnd = Random();
game.add(
ParticleComponent(
particle: AcceleratedParticle(
// 将在游戏画布的中心发射
position: game.size.center(Offset.zero),
// 具有随机的初始偏移速度(-100..100,0 ..- 100)
speed: Offset(rnd.nextDouble() * 200 - 100, -rnd.nextDouble() * 100),
// 向下加速,模拟 “重力”
speed: Offset(0, 100),
child: Particle(),
)
)
);
循环粒子
设置 Paint
渲染圆的一个 Particle
,通过的 Canvas
的偏移量为零。 结合 TranslatedParticle
,MovingParticle
或 AcceleratedParticle
使用。
game.add(
ParticleComponent(
particle: CircleParticle(
radius: game.size.width / 2,
paint: Paint()..color = Colors.red.withOpacity(.5),
)
)
);
精灵粒子
允许您将 Flame 的 Sprite 嵌入到粒子效果中。 当从 SpriteSheet
中获取效果时使用。
game.add(
ParticleComponent(
particle: SpriteParticle(
sprite: Sprite('sprite.png'),
size: Position(64, 64),
)
)
);
图片粒子
在粒子树内渲染给定的 dart:ui
图像。
// 游戏初始化期间
await Flame.images.loadAll(const [
'image.png'
]);
// ...
// 游戏循环中的某个地方
game.add(
ParticleComponent(
particle: ImageParticle(
size: const Size.square(24),
image: Flame.images.loadedFiles['image.png'],
);
)
);
动画粒子
嵌入 Flame Animation
的 Particle
。 默认情况下,对齐 Animation
的 stepTime
,以便在 Particle
使用期限内完全播放。 可以使用 alignAnimationTime
参数覆盖此行为。
final spritesheet = SpriteSheet(
imageName: 'spritesheet.png',
textureWidth: 16,
textureHeight: 16,
columns: 10,
rows: 2
);
game.add(
ParticleComponent(
particle: AnimationParticle(
animation: spritesheet.createAnimation(0, stepTime: 0.1),
);
)
);
组件粒子
该 Particle
可让您将 Flame Component
嵌入到粒子效果中。 Component
可以有自己的 update
生命周期,可以在不同的效果树之间重复使用。 如果您要为某些 Component
的实例添加动态特性,请考虑将其直接添加到 game
中,而不要在中间添加 Particle
。
var longLivingRect = RectComponent();
game.add(
ParticleComponent(
particle: ComponentParticle(
component: longLivingRect
);
)
);
class RectComponent extends Component {
void render(Canvas c) {
c.drawRect(
Rect.fromCenter(center: Offset.zero, width: 100, height: 100),
Paint()..color = Colors.red
);
}
void update(double dt) {
/// 将由父级调用[Particle]
}
}
Flare粒子
要在 Flame 中使用 Flare,请使用 flame_flare
包。
Flame 提供一个 FlareParticle
类,该类是 FlareActorAnimation
的容器,并向其子对象传播 update
和 render
事件。
import 'package:flame_flare/flame_flare.dart';
// 游戏初始化期间
const flareSize = 32.0;
final flareAnimation = FlareActorAnimation('assets/sparkle.flr');
flareAnimation.width = flareSize;
flareAnimation.height = flareSize;
// 游戏中的某个地方
game.add(
ParticleComponent(
particle: FlareParticle(flare: flareAnimation);
)
);
计算粒子
可以在以下情况下为您提供帮助的 Particle
:
- 默认功能不能达到您的需求时
- 复杂效果优化
- 用户自定义
创建后,将所有渲染委托给 ParticleRenderDelegate
,在每个帧上调用
执行必要的计算并将内容渲染在 Canvas
上
game.add(
ParticleComponent(
// 逐渐渲染一个圆圈
// 在粒子寿命期间更改其颜色和大小
particle: ComputedParticle(
renderer: (canvas, particle) => canvas.drawCircle(
Offset.zero,
particle.progress * 10,
Paint()
..color = Color.lerp(
Colors.red,
Colors.blue,
particle.progress,
),
),
)
)
)
嵌套
Flame 对粒子的实现遵循与Flutter部件相同的嵌套模式。 通过将行为片段封装在每个粒子中,然后将这些行为嵌套在一起以实现所需的视觉效果。
允许 Particle
相互嵌套的两个实体是: SingleChildParticle
和 ComposedParticle
类。
SingleChildParticle
可能会帮助您创建具有自定义行为的 Particles
。
例如,在每帧中随机定位它的子元素:
var rnd = Random();
class GlitchParticle extends Particle with SingleChildParticle {
Particle child;
GlitchParticle({
this.child,
double lifespan,
}) : super(lifespan: lifespan);
render(Canvas canvas) {
canvas.save();
canvas.translate(rnd.nextDouble() * 100, rnd.nextDouble() * 100);
// Will also render the child
super.render();
canvas.restore();
}
}
ComposedParticle
既可以单独使用,也可以在现有的 Particle
树中使用。