mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-20 15:08:36 +08:00
Renamed doc to docs and minor language fixes
This commit is contained in:
636
docs/examples/particles/lib/main.dart
Normal file
636
docs/examples/particles/lib/main.dart
Normal file
@@ -0,0 +1,636 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/animation.dart';
|
||||
import 'package:flame/components/component.dart';
|
||||
import 'package:flame/flare_animation.dart';
|
||||
import 'package:flame/particles/circle_particle.dart';
|
||||
import 'package:flame/particles/composed_particle.dart';
|
||||
import 'package:flame/particles/curved_particle.dart';
|
||||
import 'package:flame/particles/moving_particle.dart';
|
||||
import 'package:flame/particles/sprite_particle.dart';
|
||||
import 'package:flame/particles/translated_particle.dart';
|
||||
import 'package:flame/particles/computed_particle.dart';
|
||||
import 'package:flame/particles/image_particle.dart';
|
||||
import 'package:flame/particles/rotating_particle.dart';
|
||||
import 'package:flame/particles/accelerated_particle.dart';
|
||||
import 'package:flame/particles/paint_particle.dart';
|
||||
import 'package:flame/particles/animation_particle.dart';
|
||||
import 'package:flame/particles/component_particle.dart';
|
||||
import 'package:flame/particles/flare_particle.dart';
|
||||
import 'package:flame/flame.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/time.dart' as flame_time;
|
||||
import 'package:flame/particle.dart';
|
||||
import 'package:flame/position.dart';
|
||||
import 'package:flame/sprite.dart';
|
||||
import 'package:flame/spritesheet.dart';
|
||||
import 'package:flame/text_config.dart';
|
||||
import 'package:flutter/material.dart' hide Animation, Image;
|
||||
|
||||
void main() async => runApp((await loadGame()).widget);
|
||||
|
||||
class MyGame extends BaseGame {
|
||||
/// Defines dimensions of the sample
|
||||
/// grid to be displayed on the screen,
|
||||
/// 5x5 in this particular case
|
||||
static const gridSize = 5;
|
||||
static const steps = 5;
|
||||
|
||||
/// Miscellaneous values used
|
||||
/// by examples below
|
||||
final Random rnd = Random();
|
||||
final StepTween steppedTween = StepTween(begin: 0, end: 5);
|
||||
final trafficLight = TrafficLightComponent();
|
||||
final TextConfig fpsTextConfig = const TextConfig(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
);
|
||||
|
||||
/// Defines the lifespan of all the particles in these examples
|
||||
final sceneDuration = const Duration(seconds: 1);
|
||||
|
||||
Offset cellSize;
|
||||
Offset halfCellSize;
|
||||
FlareAnimation flareAnimation;
|
||||
|
||||
MyGame({
|
||||
Size screenSize,
|
||||
this.flareAnimation,
|
||||
}) {
|
||||
size = screenSize;
|
||||
cellSize = Offset(size.width / gridSize, size.height / gridSize);
|
||||
halfCellSize = cellSize * .5;
|
||||
|
||||
// Spawn new particles every second
|
||||
Timer.periodic(sceneDuration, (_) => spawnParticles());
|
||||
}
|
||||
|
||||
/// Showcases various different uses of [Particle]
|
||||
/// and its derivatives
|
||||
void spawnParticles() {
|
||||
// Contains sample particles, in order by complexity
|
||||
// and amount of used features. Jump to source for more explanation on each
|
||||
final particles = <Particle>[
|
||||
circle(),
|
||||
smallWhiteCircle(),
|
||||
movingParticle(),
|
||||
randomMovingParticle(),
|
||||
alignedMovingParticles(),
|
||||
easedMovingParticle(),
|
||||
intervalMovingParticle(),
|
||||
computedParticle(),
|
||||
chainingBehaviors(),
|
||||
steppedComputedParticle(),
|
||||
reuseParticles(),
|
||||
imageParticle(),
|
||||
reuseImageParticle(),
|
||||
rotatingImage(),
|
||||
acceleratedParticles(),
|
||||
paintParticle(),
|
||||
spriteParticle(),
|
||||
animationParticle(),
|
||||
fireworkParticle(),
|
||||
componentParticle(),
|
||||
flareParticle(),
|
||||
];
|
||||
|
||||
// Place all the [Particle] instances
|
||||
// defined above in a grid on the screen
|
||||
// as per defined grid parameters
|
||||
do {
|
||||
final particle = particles.removeLast();
|
||||
final col = particles.length % gridSize;
|
||||
final row = particles.length ~/ gridSize;
|
||||
final cellCenter =
|
||||
cellSize.scale(col.toDouble(), row.toDouble()) + (cellSize * .5);
|
||||
|
||||
add(
|
||||
// Bind all the particles to a [Component] update
|
||||
// lifecycle from the [BaseGame].
|
||||
TranslatedParticle(
|
||||
lifespan: 1,
|
||||
offset: cellCenter,
|
||||
child: particle,
|
||||
).asComponent(),
|
||||
);
|
||||
} while (particles.isNotEmpty);
|
||||
}
|
||||
|
||||
/// Simple static circle, doesn't move or
|
||||
/// change any of its attributes
|
||||
Particle circle() {
|
||||
return CircleParticle(
|
||||
paint: Paint()..color = Colors.white10,
|
||||
);
|
||||
}
|
||||
|
||||
/// This one will is a bit smaller,
|
||||
/// and a bit less transparent
|
||||
Particle smallWhiteCircle() {
|
||||
return CircleParticle(
|
||||
radius: 5.0,
|
||||
paint: Paint()..color = Colors.white,
|
||||
);
|
||||
}
|
||||
|
||||
/// Particle which is moving from
|
||||
/// one predefined position to another one
|
||||
Particle movingParticle() {
|
||||
return MovingParticle(
|
||||
// This parameter is optional, will
|
||||
// default to [Offset.zero]
|
||||
from: const Offset(-20, -20),
|
||||
to: const Offset(20, 20),
|
||||
child: CircleParticle(paint: Paint()..color = Colors.amber),
|
||||
);
|
||||
}
|
||||
|
||||
/// [Particle] which is moving to a random direction
|
||||
/// within each cell each time created
|
||||
Particle randomMovingParticle() {
|
||||
return MovingParticle(
|
||||
to: randomCellOffset(),
|
||||
child: CircleParticle(
|
||||
radius: 5 + rnd.nextDouble() * 5,
|
||||
paint: Paint()..color = Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Generates 5 particles, each moving
|
||||
/// symmetrically within grid cell
|
||||
Particle alignedMovingParticles() {
|
||||
return Particle.generate(
|
||||
count: 5,
|
||||
generator: (i) {
|
||||
final currentColumn = (cellSize.dx / 5) * i - halfCellSize.dx;
|
||||
return MovingParticle(
|
||||
from: Offset(currentColumn, -halfCellSize.dy),
|
||||
to: Offset(currentColumn, halfCellSize.dy),
|
||||
child: CircleParticle(
|
||||
radius: 2.0,
|
||||
paint: Paint()..color = Colors.blue,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Burst of 5 particles each moving
|
||||
/// to a random direction within the cell
|
||||
Particle randomMovingParticles() {
|
||||
return Particle.generate(
|
||||
count: 5,
|
||||
generator: (i) => MovingParticle(
|
||||
to: randomCellOffset() * .5,
|
||||
child: CircleParticle(
|
||||
radius: 5 + rnd.nextDouble() * 5,
|
||||
paint: Paint()..color = Colors.deepOrange,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Same example as above, but
|
||||
/// with easing, utilising [CurvedParticle] extension
|
||||
Particle easedMovingParticle() {
|
||||
return Particle.generate(
|
||||
count: 5,
|
||||
generator: (i) => MovingParticle(
|
||||
curve: Curves.easeOutQuad,
|
||||
to: randomCellOffset() * .5,
|
||||
child: CircleParticle(
|
||||
radius: 5 + rnd.nextDouble() * 5,
|
||||
paint: Paint()..color = Colors.deepPurple,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Same example as above, but using awesome [Inverval]
|
||||
/// curve, which "schedules" transition to happen between
|
||||
/// certain values of progress. In this example, circles will
|
||||
/// move from their initial to their final position
|
||||
/// when progress is changing from 0.2 to 0.6 respectively.
|
||||
Particle intervalMovingParticle() {
|
||||
return Particle.generate(
|
||||
count: 5,
|
||||
generator: (i) => MovingParticle(
|
||||
curve: const Interval(.2, .6, curve: Curves.easeInOutCubic),
|
||||
to: randomCellOffset() * .5,
|
||||
child: CircleParticle(
|
||||
radius: 5 + rnd.nextDouble() * 5,
|
||||
paint: Paint()..color = Colors.greenAccent,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// A [ComputedParticle] completely delegates all the rendering
|
||||
/// to an external function, hence It's very flexible, as you can implement
|
||||
/// any currently missing behavior with it.
|
||||
/// Also, it allows to optimize complex behaviors by avoiding nesting too
|
||||
/// many [Particle] together and having all the computations in place.
|
||||
Particle computedParticle() {
|
||||
return ComputedParticle(
|
||||
renderer: (canvas, particle) => canvas.drawCircle(
|
||||
Offset.zero,
|
||||
particle.progress * halfCellSize.dx,
|
||||
Paint()
|
||||
..color = Color.lerp(
|
||||
Colors.red,
|
||||
Colors.blue,
|
||||
particle.progress,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Using [ComputedParticle] to use custom tweening
|
||||
/// In reality, you would like to keep as much of renderer state
|
||||
/// defined outside and reused between each call
|
||||
Particle steppedComputedParticle() {
|
||||
return ComputedParticle(
|
||||
lifespan: 2,
|
||||
renderer: (canvas, particle) {
|
||||
const steps = 5;
|
||||
final steppedProgress =
|
||||
steppedTween.transform(particle.progress) / steps;
|
||||
|
||||
canvas.drawCircle(
|
||||
Offset.zero,
|
||||
(1 - steppedProgress) * halfCellSize.dx,
|
||||
Paint()
|
||||
..color = Color.lerp(
|
||||
Colors.red,
|
||||
Colors.blue,
|
||||
steppedProgress,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Particle which is used in example below
|
||||
Particle reusablePatricle;
|
||||
|
||||
/// A burst of white circles which actually using a single circle
|
||||
/// as a form of optimization. Look for reusing parts of particle effects
|
||||
/// whenever possible, as there are limits which are relatively easy to reach.
|
||||
Particle reuseParticles() {
|
||||
reusablePatricle ??= circle();
|
||||
|
||||
return Particle.generate(
|
||||
count: 10,
|
||||
generator: (i) => MovingParticle(
|
||||
curve: Interval(rnd.nextDouble() * .1, rnd.nextDouble() * .8 + .1),
|
||||
to: randomCellOffset() * .5,
|
||||
child: reusablePatricle,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Simple static image particle which doesn't do much.
|
||||
/// Images are great examples of where assets should
|
||||
/// be reused across particles. See example below for more details.
|
||||
Particle imageParticle() {
|
||||
return ImageParticle(
|
||||
size: const Size.square(24),
|
||||
image: Flame.images.loadedFiles['zap.png'],
|
||||
);
|
||||
}
|
||||
|
||||
/// Particle which is used in example below
|
||||
Particle reusableImageParticle;
|
||||
|
||||
/// A single [imageParticle] is drawn 9 times
|
||||
/// in a grid within grid cell. Looks as 9 particles
|
||||
/// to user, saves us 8 particle objects.
|
||||
Particle reuseImageParticle() {
|
||||
const count = 9;
|
||||
const perLine = 3;
|
||||
const imageSize = 24.0;
|
||||
final colWidth = cellSize.dx / perLine;
|
||||
final rowHeight = cellSize.dy / perLine;
|
||||
|
||||
reusableImageParticle ??= imageParticle();
|
||||
|
||||
return Particle.generate(
|
||||
count: count,
|
||||
generator: (i) => TranslatedParticle(
|
||||
offset: Offset(
|
||||
(i % perLine) * colWidth - halfCellSize.dx + imageSize,
|
||||
(i ~/ perLine) * rowHeight - halfCellSize.dy + imageSize,
|
||||
),
|
||||
child: reusableImageParticle),
|
||||
);
|
||||
}
|
||||
|
||||
/// [RotatingParticle] is a simple container which rotates
|
||||
/// a child particle passed to it.
|
||||
/// As you can see, we're reusing [imageParticle] from example above.
|
||||
/// Such a composability is one of the main implementation features.
|
||||
Particle rotatingImage({double initialAngle = 0}) {
|
||||
return RotatingParticle(from: initialAngle, child: imageParticle());
|
||||
}
|
||||
|
||||
/// [AcceleratedParticle] is a very basic acceleration physics container,
|
||||
/// which could help implementing such behaviors as gravity, or adding
|
||||
/// some non-linearity to something like [MovingParticle]
|
||||
Particle acceleratedParticles() {
|
||||
return Particle.generate(
|
||||
count: 10,
|
||||
generator: (i) => AcceleratedParticle(
|
||||
speed:
|
||||
Offset(rnd.nextDouble() * 600 - 300, -rnd.nextDouble() * 600) * .2,
|
||||
acceleration: const Offset(0, 200),
|
||||
child: rotatingImage(initialAngle: rnd.nextDouble() * pi),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// [PaintParticle] allows to perform basic composite operations
|
||||
/// by specifying custom [Paint].
|
||||
/// Be aware that it's very easy to get *really* bad performance
|
||||
/// misusing composites.
|
||||
Particle paintParticle() {
|
||||
final List<Color> colors = [
|
||||
const Color(0xffff0000),
|
||||
const Color(0xff00ff00),
|
||||
const Color(0xff0000ff),
|
||||
];
|
||||
final List<Offset> positions = [
|
||||
const Offset(-10, 10),
|
||||
const Offset(10, 10),
|
||||
const Offset(0, -14),
|
||||
];
|
||||
|
||||
return Particle.generate(
|
||||
count: 3,
|
||||
generator: (i) => PaintParticle(
|
||||
paint: Paint()..blendMode = BlendMode.difference,
|
||||
child: MovingParticle(
|
||||
curve: SineCurve(),
|
||||
from: positions[i],
|
||||
to: i == 0 ? positions.last : positions[i - 1],
|
||||
child: CircleParticle(
|
||||
radius: 20.0,
|
||||
paint: Paint()..color = colors[i],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// [SpriteParticle] allows easily embed
|
||||
/// Flame's [Sprite] into the effect.
|
||||
Particle spriteParticle() {
|
||||
return SpriteParticle(
|
||||
sprite: Sprite('zap.png'),
|
||||
size: Position.fromOffset(cellSize * .5),
|
||||
);
|
||||
}
|
||||
|
||||
/// An [AnimationParticle] takes a Flame [Animation]
|
||||
/// and plays it during the particle lifespan.
|
||||
Particle animationParticle() {
|
||||
return AnimationParticle(
|
||||
animation: getBoomAnimation(),
|
||||
size: Position(128, 128),
|
||||
);
|
||||
}
|
||||
|
||||
/// [ComponentParticle] proxies particle lifecycle hooks
|
||||
/// to its child [Component]. In example below, [Component] is
|
||||
/// reused between particle effects and has internal behavior
|
||||
/// which is independent from the parent [Particle].
|
||||
Particle componentParticle() {
|
||||
return MovingParticle(
|
||||
from: -halfCellSize * .2,
|
||||
to: halfCellSize * .2,
|
||||
curve: SineCurve(),
|
||||
child: ComponentParticle(component: trafficLight),
|
||||
);
|
||||
}
|
||||
|
||||
/// Not very realistic firework, yet it highlights
|
||||
/// use of [ComputedParticle] within other particles,
|
||||
/// mixing predefined and fully custom behavior.
|
||||
Particle fireworkParticle() {
|
||||
// A pallete to paint over the "sky"
|
||||
final List<Paint> paints = [
|
||||
Colors.amber,
|
||||
Colors.amberAccent,
|
||||
Colors.red,
|
||||
Colors.redAccent,
|
||||
Colors.yellow,
|
||||
Colors.yellowAccent,
|
||||
// Adds a nice "lense" tint
|
||||
// to overall effect
|
||||
Colors.blue,
|
||||
].map<Paint>((color) => Paint()..color = color).toList();
|
||||
|
||||
return Particle.generate(
|
||||
count: 10,
|
||||
generator: (i) {
|
||||
final initialSpeed = randomCellOffset();
|
||||
final deceleration = initialSpeed * -1;
|
||||
const gravity = const Offset(0, 40);
|
||||
|
||||
return AcceleratedParticle(
|
||||
speed: initialSpeed,
|
||||
acceleration: deceleration + gravity,
|
||||
child: ComputedParticle(renderer: (canvas, particle) {
|
||||
final paint = randomElement(paints);
|
||||
// Override the color to dynamically update opacity
|
||||
paint.color = paint.color.withOpacity(1 - particle.progress);
|
||||
|
||||
canvas.drawCircle(
|
||||
Offset.zero,
|
||||
// Closer to the end of lifespan particles
|
||||
// will turn into larger glaring circles
|
||||
rnd.nextDouble() * particle.progress > .6
|
||||
? rnd.nextDouble() * (50 * particle.progress)
|
||||
: 2 + (3 * particle.progress),
|
||||
paint,
|
||||
);
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// [FlareParticle] renders fiven [FlareAnimation] inside
|
||||
/// as you can see, animation could be reused across
|
||||
/// different particles.
|
||||
Particle flareParticle() {
|
||||
final flare = ComposedParticle(children: <Particle>[
|
||||
// Circle Particle for background
|
||||
CircleParticle(
|
||||
paint: Paint()..color = Colors.white12,
|
||||
radius: flareAnimation.width / 2),
|
||||
FlareParticle(flare: flareAnimation),
|
||||
]);
|
||||
|
||||
final List<Offset> corners = [
|
||||
-halfCellSize,
|
||||
halfCellSize,
|
||||
];
|
||||
|
||||
return RotatingParticle(
|
||||
to: pi,
|
||||
child: Particle.generate(
|
||||
count: 2,
|
||||
generator: (i) => MovingParticle(
|
||||
to: corners[i] * .4,
|
||||
curve: SineCurve(),
|
||||
child: flare,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// [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() {
|
||||
final paint = Paint()..color = randomMaterialColor();
|
||||
final rect = ComputedParticle(
|
||||
renderer: (canvas, _) => canvas.drawRect(
|
||||
Rect.fromCenter(center: Offset.zero, width: 10, height: 10),
|
||||
paint,
|
||||
),
|
||||
);
|
||||
|
||||
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
|
||||
bool debugMode() => true;
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
super.render(canvas);
|
||||
|
||||
if (debugMode()) {
|
||||
fpsTextConfig.render(canvas, '${fps(120).toStringAsFixed(2)}fps',
|
||||
Position(0, size.height - 24));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns random [Offset] within a virtual
|
||||
/// grid cell
|
||||
Offset randomCellOffset() {
|
||||
return cellSize.scale(rnd.nextDouble(), rnd.nextDouble()) - halfCellSize;
|
||||
}
|
||||
|
||||
/// Returns random [Color] from primary swatches
|
||||
/// of material palette
|
||||
Color randomMaterialColor() {
|
||||
return Colors.primaries[rnd.nextInt(Colors.primaries.length)];
|
||||
}
|
||||
|
||||
/// Returns a random element from a given list
|
||||
T randomElement<T>(List<T> list) {
|
||||
return list[rnd.nextInt(list.length)];
|
||||
}
|
||||
|
||||
/// Sample "explosion" animation for [AnimationParticle] example
|
||||
Animation getBoomAnimation() {
|
||||
const columns = 8;
|
||||
const rows = 8;
|
||||
const frames = columns * rows;
|
||||
const imagePath = 'boom3.png';
|
||||
final spriteImage = Flame.images.loadedFiles[imagePath];
|
||||
final spritesheet = SpriteSheet(
|
||||
rows: rows,
|
||||
columns: columns,
|
||||
imageName: imagePath,
|
||||
textureWidth: spriteImage.width ~/ columns,
|
||||
textureHeight: spriteImage.height ~/ rows,
|
||||
);
|
||||
final sprites = List<Sprite>.generate(
|
||||
frames,
|
||||
(i) => spritesheet.getSprite(i ~/ rows, i % columns),
|
||||
);
|
||||
|
||||
return Animation.spriteList(sprites);
|
||||
}
|
||||
}
|
||||
|
||||
Future<BaseGame> loadGame() async {
|
||||
Size gameSize;
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await Future.wait([
|
||||
Flame.util.initialDimensions().then((size) => gameSize = size),
|
||||
Flame.images.loadAll(const [
|
||||
'zap.png',
|
||||
|
||||
/// Credits to Stumpy Strust from
|
||||
/// https://opengameart.org/content/explosion-sheet
|
||||
'boom3.png',
|
||||
]),
|
||||
]);
|
||||
const flareSize = 32.0;
|
||||
final flareAnimation = await FlareAnimation.load('assets/diamond.flr');
|
||||
flareAnimation.updateAnimation('Spin');
|
||||
flareAnimation.width = flareSize;
|
||||
flareAnimation.height = flareSize;
|
||||
|
||||
return MyGame(screenSize: gameSize, flareAnimation: flareAnimation);
|
||||
}
|
||||
|
||||
/// A curve which maps sinus output (-1..1,0..pi)
|
||||
/// to an oscillating (0..1..0,0..1), essentially "ease-in-out and back"
|
||||
class SineCurve extends Curve {
|
||||
@override
|
||||
double transformInternal(double t) {
|
||||
return (sin(pi * (t * 2 - 1 / 2)) + 1) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sample for [ComponentParticle], changes its colors
|
||||
/// each 2s of registered lifetime.
|
||||
class TrafficLightComponent extends Component {
|
||||
final Rect rect = Rect.fromCenter(center: Offset.zero, height: 32, width: 32);
|
||||
final flame_time.Timer colorChangeTimer = flame_time.Timer(2, repeat: true);
|
||||
final colors = <Color>[
|
||||
Colors.green,
|
||||
Colors.orange,
|
||||
Colors.red,
|
||||
];
|
||||
|
||||
TrafficLightComponent() {
|
||||
colorChangeTimer.start();
|
||||
}
|
||||
|
||||
@override
|
||||
void render(Canvas c) {
|
||||
c.drawRect(rect, Paint()..color = currentColor);
|
||||
}
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
colorChangeTimer.update(dt);
|
||||
}
|
||||
|
||||
Color get currentColor {
|
||||
return colors[(colorChangeTimer.progress * colors.length).toInt()];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user