feat: docs, adde particles docs

fix: doc/examples/particles, added web to .gitignore
feat: doc/examples/particles, added more examples,
refactor: Particle does not extend Component
refactor: Particle subclasses in separate folder
refactor: ParticleComponent is now simple container
fix: SingleChildParticle, asserts for child existing
feat: AnimationParticle for Flame Animation
feat: ComponentParticle for Flame Component
feat: SpriteParticle for Flame Sprite
This commit is contained in:
Ivan Cherepanov
2019-11-28 18:01:31 +03:00
parent ce0b99ecbd
commit ff425afa26
26 changed files with 1828 additions and 165 deletions

View File

@ -1,6 +1,6 @@
import 'dart:ui';
import '../particle_component.dart';
import '../../particle.dart';
/// Implements basic behavior for nesting [Particle] instances
/// into each other.
@ -27,17 +27,23 @@ mixin SingleChildParticle on Particle {
@override
void setLifespan(double lifespan) {
assert(child != null);
super.setLifespan(lifespan);
child.setLifespan(lifespan);
}
@override
void render(Canvas c) {
assert(child != null);
child.render(c);
}
@override
void update(double t) {
assert(child != null);
super.update(t);
child.update(t);
}

View File

@ -1,79 +1,37 @@
import 'dart:ui';
import 'package:flame/components/particles/composed_particle.dart';
import 'package:flutter/foundation.dart';
import '../time.dart';
import '../particle.dart';
import 'component.dart';
/// A function which returns [Particle] when called
typedef ParticleGenerator = Particle Function(int);
/// Base container for [Particle] instances to be attach
/// to a [Component] tree. Could be added either to [BaseGame]
/// or [ComposedComponent] as needed.
/// Proxies [Component] lifecycle hooks to nested [Particle].
class ParticleComponent extends Component {
Particle particle;
/// Base class implementing common behavior for all the particles.
///
/// Intention is to follow same "Extreme Composability" style
/// as across the whole Flutter framework, so each type of particle implements
/// some particular behavior which then could be nested and combined together
/// to create specifically required experience.
abstract class Particle extends Component {
/// Generates given amount of particles,
/// combining them into one [ComposedParticle]
/// Useful for procedural particle generation.
static Particle generate({
int count = 10,
@required ParticleGenerator generator,
double lifespan,
Duration duration,
}) {
return ComposedParticle(
lifespan: lifespan,
duration: duration,
children: List<Particle>.generate(count, generator),
);
}
Timer _timer;
bool _shouldBeDestroyed = false;
Particle({
/// Particle lifespan in [Timer] format,
/// double in seconds with microsecond precision
double lifespan,
/// Another way to set lifespan, using Flutter
/// [Duration] class
Duration duration,
}) {
/// Either [double] lifespan or [Duration] duration,
/// defaulting to 500 milliseconds of life (or .5, in [Timer] double)
lifespan = lifespan ??
(duration ?? const Duration(milliseconds: 500)).inMicroseconds /
Duration.microsecondsPerSecond;
setLifespan(lifespan);
}
ParticleComponent({
@required this.particle,
});
/// This [Component] will be automatically destroyed
/// as soon as
@override
bool destroy() => _shouldBeDestroyed;
double get progress => _timer.progress;
bool destroy() => particle.destroy();
double get progress => particle.progress;
/// Passes rendering chain down to the inset
/// [Particle] within this [Component].
@override
void render(Canvas canvas) {
// Do nothing by default
particle.render(canvas);
}
/// Passes update chain to child [Particle].
@override
void update(double dt) {
_timer.update(dt);
if (_timer.progress >= 1) {
_shouldBeDestroyed = true;
}
}
void setLifespan(double lifespan) {
_timer?.stop();
_timer = Timer(lifespan);
_timer.start();
particle.update(dt);
}
}

View File

@ -1,46 +0,0 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
import '../mixins/single_child_particle.dart';
import '../particle_component.dart';
import 'curved_particle.dart';
/// A particle serves as a container for basic
/// acceleration physics.
/// [Offset] unit is logical px per second.
/// speed = Offset(0, 100) is 100 logical pixels per second, down
/// acceleration = Offset(-40, 0) will accelerate to left at rate of 40 px/s
class AcceleratedParticle extends CurvedParticle with SingleChildParticle {
@override
Particle child;
final Offset acceleration;
Offset speed;
Offset position;
AcceleratedParticle({
@required this.child,
this.acceleration = Offset.zero,
this.speed = Offset.zero,
this.position = Offset.zero,
double lifespan,
Duration duration,
}) : super(lifespan: lifespan, duration: duration);
@override
void render(Canvas canvas) {
canvas.save();
canvas.translate(position.dx, position.dy);
super.render(canvas);
canvas.restore();
}
@override
void update(double t) {
speed += acceleration * t;
position += speed * t;
super.update(t);
}
}

View File

@ -1,29 +0,0 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
import '../../time.dart';
import '../particle_component.dart';
/// Plain circle with no other behaviors
/// Consider composing with other [Particle]
/// to achieve needed effects
class CircleParticle extends Particle {
final Paint paint;
final double radius;
CircleParticle({
@required this.paint,
this.radius = 10.0,
double lifespan,
Duration duration,
}) : super(
lifespan: lifespan,
duration: duration,
);
@override
void render(Canvas c) {
c.drawCircle(Offset.zero, radius, paint);
}
}

View File

@ -1,45 +0,0 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
import '../particle_component.dart';
/// A single [Particle] which manages multiple children
/// by proxying all lifecycle hooks.
class ComposedParticle extends Particle {
final List<Particle> children;
ComposedParticle({
@required this.children,
double lifespan,
Duration duration,
}) : super(
lifespan: lifespan,
duration: duration,
);
@override
void setLifespan(double lifespan) {
super.setLifespan(lifespan);
for (var child in children) {
child.setLifespan(lifespan);
}
}
@override
void render(Canvas c) {
for (var child in children) {
child.render(c);
}
}
@override
void update(double dt) {
super.update(dt);
for (var child in children) {
child.update(dt);
}
}
}

View File

@ -1,32 +0,0 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
import '../particle_component.dart';
/// A function which should render desired contents
/// onto a given canvas. External state needed for
/// rendering should be stored elsewhere, so that this delegate could use it
typedef ParticleRenderDelegate = void Function(Canvas c, Particle particle);
/// An abstract [Particle] container which delegates renderign outside
/// Allows to implement very interesting scenarios from scratch.
class ComputedParticle extends Particle {
// A delegate function which will be called
// to render particle on each frame
ParticleRenderDelegate renderer;
ComputedParticle({
@required this.renderer,
double lifespan,
Duration duration,
}) : super(
lifespan: lifespan,
duration: duration,
);
@override
void render(Canvas canvas) {
renderer(canvas, this);
}
}

View File

@ -1,18 +0,0 @@
import 'package:flutter/animation.dart';
import '../particle_component.dart';
/// A [Particle] which applies certain [Curve] for
/// easing or other purposes to its [progress] getter.
class CurvedParticle extends Particle {
final Curve curve;
CurvedParticle({
this.curve = Curves.linear,
double lifespan,
Duration duration,
}) : super(lifespan: lifespan, duration: duration);
@override
double get progress => curve.transform(super.progress);
}

View File

@ -1,37 +0,0 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
import '../particle_component.dart';
/// A [Particle] which renders given [Image] on a [Canvas]
/// image is centered. If any other behavior is needed, consider
/// using [ComputedParticle].
class ImageParticle extends Particle {
/// dart.ui [Image] to draw
Image image;
Rect src;
Rect dest;
ImageParticle({
@required this.image,
Size size,
double lifespan,
Duration duration,
}) : super(lifespan: lifespan, duration: duration) {
final srcWidth = image.width.toDouble();
final srcHeight = image.height.toDouble();
final destWidth = size?.width ?? srcWidth;
final destHeight = size?.height ?? srcHeight;
src = Rect.fromLTWH(0, 0, srcWidth, srcHeight);
dest =
Rect.fromLTWH(-destWidth / 2, -destHeight / 2, destWidth, destHeight);
}
@override
void render(Canvas canvas) {
canvas.drawImageRect(image, src, dest, Paint());
}
}

View File

@ -1,40 +0,0 @@
import 'dart:ui';
import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart';
import '../particles/curved_particle.dart';
import '../mixins/single_child_particle.dart';
import '../particle_component.dart';
/// Statically offset given child [Particle] by given [Offset]
/// If you're loking to move the child, consider [MovingParticle]
class MovingParticle extends CurvedParticle with SingleChildParticle {
@override
Particle child;
final Offset from;
final Offset to;
MovingParticle({
@required this.child,
@required this.to,
this.from = Offset.zero,
double lifespan,
Duration duration,
Curve curve = Curves.linear,
}) : super(
lifespan: lifespan,
duration: duration,
curve: curve,
);
@override
void render(Canvas c) {
c.save();
final Offset current = Offset.lerp(from, to, progress);
c.translate(current.dx, current.dy);
super.render(c);
c.restore();
}
}

View File

@ -1,43 +0,0 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
import '../mixins/single_child_particle.dart';
import '../particle_component.dart';
import 'curved_particle.dart';
/// A particle which renders its child with certain [Paint]
/// Could be used for applying composite effects.
/// Be aware that any composite operation is relatively expensive, as involves
/// copying portions of GPU memory. The less pixels copied - the faster it'll be.
class PaintParticle extends CurvedParticle with SingleChildParticle {
@override
Particle child;
final Paint paint;
/// Defines Canvas layer bounds
/// for applying this particle composite effect.
/// Any child content outside this bounds will be clipped.
final Rect bounds;
PaintParticle({
@required this.child,
@required this.paint,
// Reasonably large rect for most particles
this.bounds = const Rect.fromLTRB(-50, -50, 50, 50),
double lifespan,
Duration duration,
}) : super(
lifespan: lifespan,
duration: duration,
);
@override
void render(Canvas canvas) {
canvas.saveLayer(bounds, paint);
super.render(canvas);
canvas.restore();
}
}

View File

@ -1,36 +0,0 @@
import 'dart:math';
import 'dart:ui';
import 'package:flutter/foundation.dart';
import '../mixins/single_child_particle.dart';
import '../particle_component.dart';
import 'curved_particle.dart';
/// A particle which rotates its child over the lifespan
/// between two given bounds in radians
class RotatingParticle extends CurvedParticle with SingleChildParticle {
@override
Particle child;
final double from;
final double to;
RotatingParticle({
@required this.child,
this.from = 0,
this.to = 2 * pi,
double lifespan,
Duration duration,
}) : super(lifespan: lifespan, duration: duration);
double get angle => lerpDouble(from, to, progress);
@override
void render(Canvas canvas) {
canvas.save();
canvas.rotate(angle);
super.render(canvas);
canvas.restore();
}
}

View File

@ -1,33 +0,0 @@
import 'dart:ui';
import 'package:flame/components/mixins/single_child_particle.dart';
import 'package:flutter/foundation.dart';
import '../../time.dart';
import '../particle_component.dart';
/// Statically offset given child [Particle] by given [Offset]
/// If you're loking to move the child, consider [MovingParticle]
class TranslatedParticle extends Particle with SingleChildParticle {
@override
Particle child;
Offset offset;
TranslatedParticle({
@required this.child,
@required this.offset,
double lifespan,
Duration duration,
}) : super(
lifespan: lifespan,
duration: duration,
);
@override
void render(Canvas c) {
c.save();
c.translate(offset.dx, offset.dy);
super.render(c);
c.restore();
}
}