chore: format

fix: doc/example/particles/readme, attempt to embed webm preview
fix: doc/example/particles better sample for chaining
refactor: Particle, dropped duration support
This commit is contained in:
Ivan Cherepanov
2019-11-28 23:02:28 +03:00
parent ff425afa26
commit 0f98a8542d
22 changed files with 135 additions and 87 deletions

View File

@ -1,4 +1,6 @@
# render_flip # Particles
A Flame game showcasing how to use render flipping on a `PositionComponent` A Flame application showcasing multiple different examples of using
to control the rendered direction. Flame particle system as well as some sample effects which could be used in your projects.
![Particles Example](/doc/examples/particles/assets/particles-example.webm)

Binary file not shown.

View File

@ -109,13 +109,11 @@ class MyGame extends BaseGame {
add( add(
// Bind all the particles to a [Component] update // Bind all the particles to a [Component] update
// lifecycle from the [BaseGame]. // lifecycle from the [BaseGame].
ParticleComponent( TranslatedParticle(
particle: TranslatedParticle( lifespan: 1,
duration: sceneDuration, offset: cellCenter,
offset: cellCenter, child: particle,
child: particle, ).asComponent(),
),
),
); );
} while (particles.isNotEmpty); } while (particles.isNotEmpty);
} }
@ -494,19 +492,34 @@ class MyGame extends BaseGame {
); );
} }
/// [Particle] base class exposes a number
/// of convenience wrappers to make positioning.
///
/// Just remember that the less chaining and nesting - the
/// better for performance!
Particle chainingBehaviors() { Particle chainingBehaviors() {
return Particle.generate( final paint = Paint()..color = randomMaterialColor();
count: 10, final rect = ComputedParticle(
generator: (i) => CircleParticle( renderer: (canvas, _) => canvas.drawRect(
paint: Paint()..color = randomMaterialColor(), Rect.fromCenter(center: Offset.zero, width: 10, height: 10),
) paint,
.translated( ),
-halfCellSize,
)
.accelerated(
acceleration: randomCellOffset(),
),
); );
return ComposedParticle(children: <Particle>[
rect
.rotating(to: pi / 2)
.moving(to: -cellSize)
.scaled(2)
.accelerated(acceleration: halfCellSize * 5)
.translated(halfCellSize),
rect
.rotating(to: -pi)
.moving(to: cellSize.scale(1, -1))
.scaled(2)
.translated(halfCellSize.scale(-1, 1))
.accelerated(acceleration: halfCellSize.scale(-5, 5))
]);
} }
@override @override

View File

@ -68,7 +68,7 @@ game.add(
generator: (i) => CircleParticle(paint: Paint()..color = Colors.red) generator: (i) => CircleParticle(paint: Paint()..color = Colors.red)
.accelerating(randomOffset()) .accelerating(randomOffset())
) )
.component() .asComponent()
); );
// Computed Particle // Computed Particle
@ -93,7 +93,7 @@ game.add(
); );
} }
) )
.component() .asComponent()
) )
``` ```

View File

@ -28,7 +28,7 @@ mixin SingleChildParticle on Particle {
@override @override
void setLifespan(double lifespan) { void setLifespan(double lifespan) {
assert(child != null); assert(child != null);
super.setLifespan(lifespan); super.setLifespan(lifespan);
child.setLifespan(lifespan); child.setLifespan(lifespan);
} }
@ -43,7 +43,7 @@ mixin SingleChildParticle on Particle {
@override @override
void update(double t) { void update(double t) {
assert(child != null); assert(child != null);
super.update(t); super.update(t);
child.update(t); child.update(t);
} }

View File

@ -7,7 +7,7 @@ import 'component.dart';
/// Base container for [Particle] instances to be attach /// Base container for [Particle] instances to be attach
/// to a [Component] tree. Could be added either to [BaseGame] /// to a [Component] tree. Could be added either to [BaseGame]
/// or [ComposedComponent] as needed. /// or [ComposedComponent] as needed.
/// Proxies [Component] lifecycle hooks to nested [Particle]. /// Proxies [Component] lifecycle hooks to nested [Particle].
class ParticleComponent extends Component { class ParticleComponent extends Component {
Particle particle; Particle particle;
@ -17,12 +17,15 @@ class ParticleComponent extends Component {
}); });
/// This [Component] will be automatically destroyed /// This [Component] will be automatically destroyed
/// as soon as /// as soon as
@override @override
bool destroy() => particle.destroy(); bool destroy() => particle.destroy();
/// Returns progress of the child [Particle]
/// so could be used by external code for something
double get progress => particle.progress; double get progress => particle.progress;
/// Passes rendering chain down to the inset /// Passes rendering chain down to the inset
/// [Particle] within this [Component]. /// [Particle] within this [Component].
@override @override
void render(Canvas canvas) { void render(Canvas canvas) {

View File

@ -1,8 +1,11 @@
import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:flame/components/particle_component.dart'; import 'package:flame/components/particle_component.dart';
import 'package:flame/particles/accelerated_particle.dart'; import 'package:flame/particles/accelerated_particle.dart';
import 'package:flame/particles/moving_particle.dart'; import 'package:flame/particles/moving_particle.dart';
import 'package:flame/particles/rotating_particle.dart';
import 'package:flame/particles/scaled_particle.dart';
import 'package:flame/particles/translated_particle.dart'; import 'package:flame/particles/translated_particle.dart';
import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -28,11 +31,9 @@ abstract class Particle {
int count = 10, int count = 10,
@required ParticleGenerator generator, @required ParticleGenerator generator,
double lifespan, double lifespan,
Duration duration,
}) { }) {
return ComposedParticle( return ComposedParticle(
lifespan: lifespan, lifespan: lifespan,
duration: duration,
children: List<Particle>.generate(count, generator), children: List<Particle>.generate(count, generator),
); );
} }
@ -54,18 +55,8 @@ abstract class Particle {
/// Particle lifespan in [Timer] format, /// Particle lifespan in [Timer] format,
/// double in seconds with microsecond precision /// double in seconds with microsecond precision
double lifespan, double lifespan,
/// Another way to set lifespan, using Flutter
/// [Duration] class
Duration duration,
}) { }) {
/// Either [double] lifespan or [Duration] duration, setLifespan(lifespan ?? .5);
/// defaulting to 500 milliseconds of life (or .5, in [Timer] double)
lifespan = lifespan ??
(duration ?? const Duration(milliseconds: 500)).inMicroseconds /
Duration.microsecondsPerSecond;
setLifespan(lifespan);
} }
/// This method will return true as /// This method will return true as
@ -112,7 +103,7 @@ abstract class Particle {
} }
/// Wtaps this particle with [TranslatedParticle] /// Wtaps this particle with [TranslatedParticle]
/// statically repositioning it for the time /// statically repositioning it for the time
/// of the lifespan. /// of the lifespan.
Particle translated(Offset offset) { Particle translated(Offset offset) {
return TranslatedParticle( return TranslatedParticle(
@ -123,7 +114,7 @@ abstract class Particle {
} }
/// Wraps this particle with [MovingParticle] /// Wraps this particle with [MovingParticle]
/// allowing it to move from one [Offset] /// allowing it to move from one [Offset]
/// on the canvas to another one. /// on the canvas to another one.
Particle moving({ Particle moving({
Offset from = Offset.zero, Offset from = Offset.zero,
@ -156,11 +147,36 @@ abstract class Particle {
); );
} }
/// Wraps this particle with [ParticleComponent] /// Rotates this particle to a fixed angle
/// to be used within the [BaseGame] component system. /// in radians with [RotatingParticle]
Component component() { Particle rotated([double angle = 0]) {
return ParticleComponent( return RotatingParticle(
particle: this child: this, lifespan: _lifespan, from: angle, to: angle);
}
/// Rotates this particle from given angle
/// to another one in radians with [RotatingParticle]
Particle rotating({
double from = 0,
double to = pi,
}) {
return RotatingParticle(
child: this,
lifespan: _lifespan,
from: from,
to: to,
); );
} }
/// Wraps this particle with [ScaledParticle]
/// allowing to change size of it and/or its children
Particle scaled(double scale) {
return ScaledParticle(scale: scale, child: this, lifespan: _lifespan);
}
/// Wraps this particle with [ParticleComponent]
/// to be used within the [BaseGame] component system.
Component asComponent() {
return ParticleComponent(particle: this);
}
} }

View File

@ -1,3 +1,4 @@
import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -25,10 +26,8 @@ class AcceleratedParticle extends CurvedParticle with SingleChildParticle {
this.speed = Offset.zero, this.speed = Offset.zero,
this.position = Offset.zero, this.position = Offset.zero,
double lifespan, double lifespan,
Duration duration,
}) : super( }) : super(
lifespan: lifespan, lifespan: lifespan,
duration: duration,
); );
@override @override
@ -42,7 +41,7 @@ class AcceleratedParticle extends CurvedParticle with SingleChildParticle {
@override @override
void update(double t) { void update(double t) {
speed += acceleration * t; speed += acceleration * t;
position += speed * t; position += speed * t - (acceleration * pow(t, 2)) / 2;
super.update(t); super.update(t);
} }

View File

@ -17,18 +17,16 @@ class AnimationParticle extends Particle {
this.size, this.size,
this.overridePaint, this.overridePaint,
double lifespan, double lifespan,
Duration duration,
this.alignAnimationTime = true, this.alignAnimationTime = true,
}) : super( }) : super(
lifespan: lifespan, lifespan: lifespan,
duration: duration,
); );
@override @override
void setLifespan(double lifespan) { void setLifespan(double lifespan) {
super.setLifespan(lifespan); super.setLifespan(lifespan);
if (alignAnimationTime) { if (alignAnimationTime && lifespan != null) {
animation.stepTime = lifespan / animation.frames.length; animation.stepTime = lifespan / animation.frames.length;
animation.reset(); animation.reset();
} }
@ -36,10 +34,12 @@ class AnimationParticle extends Particle {
@override @override
void render(Canvas canvas) { void render(Canvas canvas) {
animation.getSprite().renderCentered(canvas, Position.empty(), animation.getSprite().renderCentered(
overridePaint: overridePaint, canvas,
size: size Position.empty(),
); overridePaint: overridePaint,
size: size,
);
} }
@override @override
@ -47,4 +47,4 @@ class AnimationParticle extends Particle {
super.update(dt); super.update(dt);
animation.update(dt); animation.update(dt);
} }
} }

View File

@ -15,10 +15,8 @@ class CircleParticle extends Particle {
@required this.paint, @required this.paint,
this.radius = 10.0, this.radius = 10.0,
double lifespan, double lifespan,
Duration duration,
}) : super( }) : super(
lifespan: lifespan, lifespan: lifespan,
duration: duration,
); );
@override @override

View File

@ -16,10 +16,8 @@ class ComponentParticle extends Particle {
this.size, this.size,
this.overridePaint, this.overridePaint,
double lifespan, double lifespan,
Duration duration,
}) : super( }) : super(
lifespan: lifespan, lifespan: lifespan,
duration: duration,
); );
@override @override
@ -32,4 +30,4 @@ class ComponentParticle extends Particle {
super.update(dt); super.update(dt);
component.update(dt); component.update(dt);
} }
} }

View File

@ -11,10 +11,8 @@ class ComposedParticle extends Particle {
ComposedParticle({ ComposedParticle({
@required this.children, @required this.children,
double lifespan, double lifespan,
Duration duration,
}) : super( }) : super(
lifespan: lifespan, lifespan: lifespan,
duration: duration,
); );
@override @override

View File

@ -22,7 +22,6 @@ class ComputedParticle extends Particle {
Duration duration, Duration duration,
}) : super( }) : super(
lifespan: lifespan, lifespan: lifespan,
duration: duration,
); );
@override @override

View File

@ -10,10 +10,8 @@ class CurvedParticle extends Particle {
CurvedParticle({ CurvedParticle({
this.curve = Curves.linear, this.curve = Curves.linear,
double lifespan, double lifespan,
Duration duration,
}) : super( }) : super(
lifespan: lifespan, lifespan: lifespan,
duration: duration,
); );
@override @override

View File

@ -11,10 +11,8 @@ class FlareParticle extends Particle {
FlareParticle({ FlareParticle({
@required this.flare, @required this.flare,
double lifespan, double lifespan,
Duration duration,
}) : super( }) : super(
lifespan: lifespan, lifespan: lifespan,
duration: duration,
); );
@override @override
@ -30,4 +28,4 @@ class FlareParticle extends Particle {
super.update(dt); super.update(dt);
flare.update(dt); flare.update(dt);
} }
} }

View File

@ -18,8 +18,7 @@ class ImageParticle extends Particle {
@required this.image, @required this.image,
Size size, Size size,
double lifespan, double lifespan,
Duration duration, }) : super(lifespan: lifespan) {
}) : super(lifespan: lifespan, duration: duration) {
final srcWidth = image.width.toDouble(); final srcWidth = image.width.toDouble();
final srcHeight = image.height.toDouble(); final srcHeight = image.height.toDouble();
final destWidth = size?.width ?? srcWidth; final destWidth = size?.width ?? srcWidth;

View File

@ -21,11 +21,9 @@ class MovingParticle extends CurvedParticle with SingleChildParticle {
@required this.to, @required this.to,
this.from = Offset.zero, this.from = Offset.zero,
double lifespan, double lifespan,
Duration duration,
Curve curve = Curves.linear, Curve curve = Curves.linear,
}) : super( }) : super(
lifespan: lifespan, lifespan: lifespan,
duration: duration,
curve: curve, curve: curve,
); );

View File

@ -28,10 +28,8 @@ class PaintParticle extends CurvedParticle with SingleChildParticle {
// Reasonably large rect for most particles // Reasonably large rect for most particles
this.bounds = const Rect.fromLTRB(-50, -50, 50, 50), this.bounds = const Rect.fromLTRB(-50, -50, 50, 50),
double lifespan, double lifespan,
Duration duration,
}) : super( }) : super(
lifespan: lifespan, lifespan: lifespan,
duration: duration,
); );
@override @override

View File

@ -21,8 +21,9 @@ class RotatingParticle extends CurvedParticle with SingleChildParticle {
this.from = 0, this.from = 0,
this.to = 2 * pi, this.to = 2 * pi,
double lifespan, double lifespan,
Duration duration, }) : super(
}) : super(lifespan: lifespan, duration: duration); lifespan: lifespan,
);
double get angle => lerpDouble(from, to, progress); double get angle => lerpDouble(from, to, progress);

View File

@ -0,0 +1,32 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
import '../components/mixins/single_child_particle.dart';
import '../particle.dart';
import 'curved_particle.dart';
/// A particle which rotates its child over the lifespan
/// between two given bounds in radians
class ScaledParticle extends CurvedParticle with SingleChildParticle {
@override
Particle child;
final double scale;
ScaledParticle({
@required this.child,
this.scale = 1.0,
double lifespan,
}) : super(
lifespan: lifespan,
);
@override
void render(Canvas canvas) {
canvas.save();
canvas.scale(scale);
super.render(canvas);
canvas.restore();
}
}

View File

@ -16,17 +16,17 @@ class SpriteParticle extends Particle {
this.size, this.size,
this.overridePaint, this.overridePaint,
double lifespan, double lifespan,
Duration duration,
}) : super( }) : super(
lifespan: lifespan, lifespan: lifespan,
duration: duration,
); );
@override @override
void render(Canvas canvas) { void render(Canvas canvas) {
sprite.renderCentered(canvas, Position.empty(), sprite.renderCentered(
canvas,
Position.empty(),
overridePaint: overridePaint, overridePaint: overridePaint,
size: size size: size,
); );
} }
} }

View File

@ -16,10 +16,8 @@ class TranslatedParticle extends Particle with SingleChildParticle {
@required this.child, @required this.child,
@required this.offset, @required this.offset,
double lifespan, double lifespan,
Duration duration,
}) : super( }) : super(
lifespan: lifespan, lifespan: lifespan,
duration: duration,
); );
@override @override