Particle -- 粒子
Flame提供了基本的,同时也很强大的,可扩展的粒子系统。这个系统的核心概念是Particle类,在行为上和ParticleComponent很相似。
Particle在BaseGame的最基本使用如下:
import 'package:flame/components/particle_component.dart';
game.add(
ParticleComponent(
particle: CircleParticle()
);
);
当Particle和自定义的Game实现一起使用时,请确保在每次游戏循环帧时,Particle的update和render生命周期钩子被调用。
实现想要的粒子效果的主要方式:
- 现有行为的组合
- 使用行为链(只是一个语法糖)
- 使用
ComputedParticle
通过从上到下的效果,合成的工作方式与Flutter小部件的工作方式类似。通过定义自底向上的行为,链式允许更流畅的表达相同的组合树。计算粒子又将行为的实现完全委派给您的代码。在需要的地方,任何方式都可以和现存的行为组合使用。
下面你可以看到显示使用上面定义的三种方式从(0,0)加速到随机方向的一个圆圈的效果示例。
Random rnd = Random();
Function randomOffset = () => Offset(
rnd.nextDouble() * 200 - 100,
rnd.nextDouble() * 200 - 100,
);
game.add(
ParticleComponent(
particle: Particle.generate(
count: 10,
generator: (i) => AcceleratedParticle(
acceleration: randomOffset(),
child: CircleParticle(
paint: Paint()..color = Colors.red
)
)
)
)
);
game.add(
Particle
.generate(
count: 10,
generator: (i) => CircleParticle(paint: Paint()..color = Colors.red)
.accelerating(randomOffset())
)
.asComponent()
);
game.add(
Particle
.gnerate(
count: 10,
generator: (i) {
var position = Offset.zero;
var 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);
}
);
}
)
)
你可以在这里找到更多以各种组合使用不同的内置粒子的示例。
Lifecycle 生命周期
所有Particle的普遍行为是它们都接受lifespan参数。一旦其内部粒子达到其使用寿命时,该值用于使ParticleComponent自我销毁。Particle内部的时间使用Flame的Timer。可以将其配置为double,通过将其传递到相应的Particle构造器来表示秒(精确到微妙)。
Particle(lifespan: .2);
Particle(lifespan: 4);
通过使用setLifespan方法,也可以重置Particle寿命,接收double类型的秒。
final particle = Particle(lifespan: 2);
particle.setLifespan(2);
在存活时间内,Particle追踪它存活的时间,并暴露progess进度,这是一个浮点型单位,值从0跨度到1.它的值与Flutter的AnimationController的value值类似。
final duration = const Duration(seconds: 2);
final particle = Particle(lifespan: duration.inMicroseconds / Duration.microsecondsPerSecond);
game.add(ParticleComponent(particle: particle));
Timer.periodic(duration * .1, () => print(particle.progress));
如果支持任何一个内建行为,生命周期会被传递给定Particle的所有后代。
内置粒子
Flame附带了一些内置的粒子行为:
TranslatedParticle,通过给定的Offset平移childMovingParticle,在两个预定义的Offset移动它的child,支持CurveAcceleratedParticle,允许基本的基于物理的效果,比如重力或者速度衰减CircleParticle,渲染所有形状和大小的圆形SpriteParticle,在粒子效果中渲染Flame精灵ImageParticle,在粒子效果中渲染dart:ui的ImageComponentParticle,在粒子效果中渲染Flame的ComponentFlareParticle,在粒子效果中渲染Flare动画
一起使用这些行为的更多粒子可以在这里找到。所有的可用实现都可以在Flame源码的particles文件夹中找到。
Translated Particle
只需平移粒子到渲染画布的指定便宜即可。不改变或修改位置。如果需要改变位置,考虑使用MovingParticle或者AcceleratedParticle。通过平移画布也可以达到相同的效果。
game.add(
ParticleComponent(
particle: TranslatedParticle(
offset: game.size.center(Offset.zero),
child: Particle(),
)
)
);
Moving Particle 移动粒子
在粒子的生命周期中,从from移动到to。通过CurvedParticle支持Curve。
game.add(
ParticleComponent(
particle: MovingParticle(
from: game.size.topLeft(Offset.zero),
to: game.size.bottomRight(Offset.zero),
child: Particle(),
)
)
);
Accelerated Particle 加速粒子
一个允许你指定它的初始位置,速度和加速度的基本物理粒子,update周期完成剩余工作。三个指定的都是Offset,你可以认为是向量。对于基于物理的爆发尤其有效,但不局限于此。Offset的单位是每秒逻辑像素。所以Offset(0, 100)的速度会在游戏时间的每秒移动子粒子100个设备像素。
final rnd = Random();
game.add(
ParticleComponent(
particle AcceleratedParticle(
position: game.size.center(Offset.zero),
speed: Offset(rnd.nextDouble() *200 - 100, -rnd.nextDouble() * 100),
acceleration: Offset(0, 100),
child:Particle(),
)
)
);
Circle Particle 原型粒子
传递给画布的0值偏移的画笔上渲染圆形的粒子。为了达到想要的位置,组合使用TranslatedParticle,MovingParticle或者AcceleratedParticle。
game.add(
ParticleComponent(
particle: CircleParticle(
radius: game.size.width / 2,
paint: Paint()..color = Colors.red.withOpacity(.5),
)
)
);
Sprite Particle 精灵粒子
允许你在粒子效果中插入Flame的精灵。使用SpriteSheet效果的图形时很有用
game.add(
ParticleComponent(
particle: SpriteParticle(
sprite: Sprite('sprite.png'),
size: Position(64, 64),
)
)
);
Image Particle 图片粒子
在粒子树种渲染给定的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'],
)
)
);
Animation Particle 动画粒子
嵌入Flame动画的粒子。默认情况下,对齐“动画” stepTime,以便在“粒子”生命周期中完全播放。可以使用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),
)
)
);
Component Particle 组件粒子
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) {
}
}
Flare Particle
FlareAnimation的容器,向它的子代传递update和render钩子。
const flareSize = 32.0;
final flareAnimation = await FlareAnimation.load('assets/sparkle.flr');
flareAnimation.updateAnimation('Shine');
flareAnimation.width = flareSize;
flareAnimation.height = flareSize;
game.add(
ParticleComponent(
particle: FlareParticle(flare: flareAnimation),
)
);
Computed Particle 计算的粒子
Particle可以在如下情况帮助你:
- 默认的行为不够
- 复杂效果优化
- 简化自定义
当创建的时候,提供的ParticleRenderDelegate代理所有的渲染,在每一帧被调用,并且执行必要的计算,渲染到画布上。
创建后,将所有渲染委托给提供的ParticleRenderDeletegate,在每一帧上调用该类,以执行必要的计算并将某些内容渲染到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,
)
)
)
)
)
Nesting behavior 内嵌行为
Flame的粒子实现遵循与Flutter 小组件相同的极端合成模式。封装每个粒子的小部分行为,然后嵌入到一起来达到想要的视觉效果来实现的。
允许粒子内嵌彼此的两个实体是:SingleChildParticle mixin 和ComposedParticle类。
SingleChildParticle可以帮助你创建自定义行为的粒子。
比如,在每帧中随机放置子组件的位置:
var rnd = Random();
class GlitchParticle extends Particle with SingleChildParticle {
@override
Particle child;
GlitchParticle({
@required this.child,
double lifespan,
}) : super(lifespan: lifespan);
@override
render(Canvas canvas) {
canvas.save();
canvas.translate(rnd.nextDouble(0 * 100, rnd.nextDouble() *100);
super.render();
canvas.restore();
}
}
ComposedParticle 可以单独使用,也可以使用在已存的粒子树种。