mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-03 04:18:25 +08:00
feat: ⚡particles ⚡
This commit is contained in:
70
doc/examples/particles/.gitignore
vendored
Normal file
70
doc/examples/particles/.gitignore
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# Visual Studio Code related
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Android related
|
||||
**/android/**/gradle-wrapper.jar
|
||||
**/android/.gradle
|
||||
**/android/captures/
|
||||
**/android/gradlew
|
||||
**/android/gradlew.bat
|
||||
**/android/local.properties
|
||||
**/android/**/GeneratedPluginRegistrant.java
|
||||
|
||||
# iOS/XCode related
|
||||
**/ios/**/*.mode1v3
|
||||
**/ios/**/*.mode2v3
|
||||
**/ios/**/*.moved-aside
|
||||
**/ios/**/*.pbxuser
|
||||
**/ios/**/*.perspectivev3
|
||||
**/ios/**/*sync/
|
||||
**/ios/**/.sconsign.dblite
|
||||
**/ios/**/.tags*
|
||||
**/ios/**/.vagrant/
|
||||
**/ios/**/DerivedData/
|
||||
**/ios/**/Icon?
|
||||
**/ios/**/Pods/
|
||||
**/ios/**/.symlinks/
|
||||
**/ios/**/profile
|
||||
**/ios/**/xcuserdata
|
||||
**/ios/.generated/
|
||||
**/ios/Flutter/App.framework
|
||||
**/ios/Flutter/Flutter.framework
|
||||
**/ios/Flutter/Generated.xcconfig
|
||||
**/ios/Flutter/app.flx
|
||||
**/ios/Flutter/app.zip
|
||||
**/ios/Flutter/flutter_assets/
|
||||
**/ios/ServiceDefinitions.json
|
||||
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!**/ios/**/default.mode1v3
|
||||
!**/ios/**/default.mode2v3
|
||||
!**/ios/**/default.pbxuser
|
||||
!**/ios/**/default.perspectivev3
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
10
doc/examples/particles/.metadata
Normal file
10
doc/examples/particles/.metadata
Normal file
@ -0,0 +1,10 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: 4c64b715d9a677922ef02f2643211a6282926eb5
|
||||
channel: dev
|
||||
|
||||
project_type: app
|
||||
4
doc/examples/particles/README.md
Normal file
4
doc/examples/particles/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# render_flip
|
||||
|
||||
A Flame game showcasing how to use render flipping on a `PositionComponent`
|
||||
to control the rendered direction.
|
||||
BIN
doc/examples/particles/assets/images/zap.png
Normal file
BIN
doc/examples/particles/assets/images/zap.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 KiB |
361
doc/examples/particles/lib/main.dart
Normal file
361
doc/examples/particles/lib/main.dart
Normal file
@ -0,0 +1,361 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flame/components/particle_component.dart';
|
||||
import 'package:flame/components/particles/circle_particle.dart';
|
||||
import 'package:flame/components/particles/moving_particle.dart';
|
||||
import 'package:flame/components/particles/translated_particle.dart';
|
||||
import 'package:flame/components/particles/computed_particle.dart';
|
||||
import 'package:flame/components/particles/image_particle.dart';
|
||||
import 'package:flame/components/particles/rotating_particle.dart';
|
||||
import 'package:flame/components/particles/accelerated_particle.dart';
|
||||
import 'package:flame/components/particles/paint_particle.dart';
|
||||
import 'package:flame/flame.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() async {
|
||||
Size gameSize;
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await Future.wait([
|
||||
Flame.util.initialDimensions().then((size) => gameSize = size),
|
||||
Flame.images.loadAll(const ['zap.png']),
|
||||
]);
|
||||
|
||||
final game = MyGame(gameSize);
|
||||
runApp(game.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;
|
||||
|
||||
Offset cellSize;
|
||||
Offset halfCellSize;
|
||||
|
||||
Random rnd = Random();
|
||||
StepTween steppedTween = StepTween(begin: 0, end: 5);
|
||||
|
||||
MyGame(Size screenSize) {
|
||||
size = screenSize;
|
||||
cellSize = Offset(size.width / gridSize, size.height / gridSize);
|
||||
halfCellSize = cellSize * .5;
|
||||
|
||||
// Spawn new particles every second
|
||||
Timer.periodic(const Duration(seconds: 1), (_) => 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(),
|
||||
steppedComputedParticle(),
|
||||
reuseParticles(),
|
||||
imageParticle(),
|
||||
reuseImageParticle(),
|
||||
rotatingImage(),
|
||||
acceleratedParticles(),
|
||||
paintParticle(),
|
||||
];
|
||||
|
||||
// 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(
|
||||
TranslatedParticle(
|
||||
lifespan: 1.0,
|
||||
offset: cellCenter,
|
||||
child: particle,
|
||||
),
|
||||
);
|
||||
} while (particles.isNotEmpty);
|
||||
}
|
||||
|
||||
/// 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)];
|
||||
}
|
||||
|
||||
/// 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: 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(
|
||||
lifespan: 2,
|
||||
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) * .4,
|
||||
acceleration: const Offset(0, 600),
|
||||
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 bad performance misusing composites.
|
||||
Particle paintParticle() {
|
||||
return Particle.generate(
|
||||
count: 10,
|
||||
generator: (i) => AcceleratedParticle(
|
||||
speed:
|
||||
Offset(rnd.nextDouble() * 600 - 300, -rnd.nextDouble() * 600) * .4,
|
||||
acceleration: const Offset(0, 600),
|
||||
child: PaintParticle(
|
||||
paint: Paint()..blendMode = BlendMode.difference,
|
||||
child: CircleParticle(
|
||||
radius: 12.0,
|
||||
paint: Paint()..color = randomMaterialColor()
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
21
doc/examples/particles/pubspec.yaml
Normal file
21
doc/examples/particles/pubspec.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
name: particles
|
||||
description: Flame sample game showcasing particle effects
|
||||
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: ">=2.1.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flame:
|
||||
path: ../../../
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
assets:
|
||||
- assets/images/zap.png
|
||||
30
doc/examples/particles/test/widget_test.dart
Normal file
30
doc/examples/particles/test/widget_test.dart
Normal file
@ -0,0 +1,30 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility that Flutter provides. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:particles/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
10
doc/examples/particles/web/index.html
Normal file
10
doc/examples/particles/web/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>particles</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="main.dart.js" type="application/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
44
lib/components/mixins/single_child_particle.dart
Normal file
44
lib/components/mixins/single_child_particle.dart
Normal file
@ -0,0 +1,44 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import '../particle_component.dart';
|
||||
|
||||
/// Implements basic behavior for nesting [Particle] instances
|
||||
/// into each other.
|
||||
///
|
||||
/// ```dart
|
||||
/// class BehaviorParticle extends Particle with SingleChildParticle {
|
||||
/// Particle child;
|
||||
///
|
||||
/// BehaviorParticle({
|
||||
/// @required this.child
|
||||
/// });
|
||||
///
|
||||
/// @override
|
||||
/// update(double dt) {
|
||||
/// // Will ensure that child [Particle] is properly updated
|
||||
/// super.update(dt);
|
||||
///
|
||||
/// // ... Custom behavior
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
mixin SingleChildParticle on Particle {
|
||||
Particle child;
|
||||
|
||||
@override
|
||||
void setLifespan(double lifespan) {
|
||||
super.setLifespan(lifespan);
|
||||
child.setLifespan(lifespan);
|
||||
}
|
||||
|
||||
@override
|
||||
void render(Canvas c) {
|
||||
child.render(c);
|
||||
}
|
||||
|
||||
@override
|
||||
void update(double t) {
|
||||
super.update(t);
|
||||
child.update(t);
|
||||
}
|
||||
}
|
||||
80
lib/components/particle_component.dart
Normal file
80
lib/components/particle_component.dart
Normal file
@ -0,0 +1,80 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/components/particles/composed_particle.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../time.dart';
|
||||
import 'component.dart';
|
||||
|
||||
/// A function which returns [Particle] when called
|
||||
typedef ParticleGenerator = Particle Function(int);
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
@override
|
||||
bool destroy() => _shouldBeDestroyed;
|
||||
double get progress => _timer.progress;
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
// Do nothing by default
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
46
lib/components/particles/accelerated_particle.dart
Normal file
46
lib/components/particles/accelerated_particle.dart
Normal file
@ -0,0 +1,46 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
29
lib/components/particles/circle_particle.dart
Normal file
29
lib/components/particles/circle_particle.dart
Normal file
@ -0,0 +1,29 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
43
lib/components/particles/composed_particle.dart
Normal file
43
lib/components/particles/composed_particle.dart
Normal file
@ -0,0 +1,43 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../particle_component.dart';
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
lib/components/particles/computed_particle.dart
Normal file
32
lib/components/particles/computed_particle.dart
Normal file
@ -0,0 +1,32 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
16
lib/components/particles/curved_particle.dart
Normal file
16
lib/components/particles/curved_particle.dart
Normal file
@ -0,0 +1,16 @@
|
||||
import 'package:flutter/animation.dart';
|
||||
|
||||
import '../particle_component.dart';
|
||||
|
||||
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);
|
||||
}
|
||||
33
lib/components/particles/image_particle.dart
Normal file
33
lib/components/particles/image_particle.dart
Normal file
@ -0,0 +1,33 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../particle_component.dart';
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
40
lib/components/particles/moving_particle.dart
Normal file
40
lib/components/particles/moving_particle.dart
Normal file
@ -0,0 +1,40 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
44
lib/components/particles/paint_particle.dart
Normal file
44
lib/components/particles/paint_particle.dart
Normal file
@ -0,0 +1,44 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
36
lib/components/particles/rotating_particle.dart
Normal file
36
lib/components/particles/rotating_particle.dart
Normal file
@ -0,0 +1,36 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
33
lib/components/particles/translated_particle.dart
Normal file
33
lib/components/particles/translated_particle.dart
Normal file
@ -0,0 +1,33 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user