mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 19:12:31 +08:00
Unify examples structure (#1118)
* Animations, CameraAndViewport, CollisionDetection and Components unified * Added descriptions to effects * Rename input games * Unify input stories * Add info to parallax section * Added descriptions to the rendering examples * Add descriptions to the sprites directory * Fix utils and rendering section * Add descriptions to the widgets section * Delete directory that rebase brought back * Unify game names * Added some styleguide docs for examples * Fix analyze issues * All files should have _example as suffix * Made the FollowComponentExample a bit easier to understand * Change priority of ember
This commit is contained in:
@ -1,46 +0,0 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/game.dart';
|
||||
|
||||
class FlipSpriteGame extends FlameGame {
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
final image = await images.load('animations/chopper.png');
|
||||
|
||||
final regular = buildAnimationComponent(image);
|
||||
regular.y = 100;
|
||||
add(regular);
|
||||
|
||||
final flipX = buildAnimationComponent(image);
|
||||
flipX.y = 300;
|
||||
flipX.flipHorizontally();
|
||||
add(flipX);
|
||||
|
||||
final flipY = buildAnimationComponent(image);
|
||||
flipY.y = 500;
|
||||
flipY.flipVertically();
|
||||
add(flipY);
|
||||
}
|
||||
|
||||
SpriteAnimationComponent buildAnimationComponent(Image image) {
|
||||
final ac = SpriteAnimationComponent(
|
||||
animation: buildAnimation(image),
|
||||
size: Vector2.all(100),
|
||||
);
|
||||
ac.x = size.x / 2 - ac.x / 2;
|
||||
return ac;
|
||||
}
|
||||
|
||||
SpriteAnimation buildAnimation(Image image) {
|
||||
return SpriteAnimation.fromFrameData(
|
||||
image,
|
||||
SpriteAnimationData.sequenced(
|
||||
amount: 4,
|
||||
textureSize: Vector2.all(48),
|
||||
stepTime: 0.15,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
32
examples/lib/stories/rendering/flip_sprite_example.dart
Normal file
32
examples/lib/stories/rendering/flip_sprite_example.dart
Normal file
@ -0,0 +1,32 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/game.dart';
|
||||
|
||||
import '../../commons/ember.dart';
|
||||
|
||||
class FlipSpriteExample extends FlameGame {
|
||||
static const String description = '''
|
||||
In this example we show how you can flip components horizontally and
|
||||
vertically.
|
||||
''';
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
final regular = Ember(position: Vector2(size.x / 2 - 100, 200));
|
||||
add(regular);
|
||||
|
||||
final flipX = Ember(position: Vector2(size.x / 2 - 100, 400));
|
||||
flipX.flipHorizontally();
|
||||
add(flipX);
|
||||
|
||||
final flipY = Ember(position: Vector2(size.x / 2 + 100, 200));
|
||||
flipY.flipVertically();
|
||||
add(flipY);
|
||||
|
||||
final flipWithRotation = Ember(position: Vector2(size.x / 2 + 100, 400))
|
||||
..angle = 2;
|
||||
flipWithRotation.flipVertically();
|
||||
add(flipWithRotation);
|
||||
}
|
||||
}
|
||||
101
examples/lib/stories/rendering/isometric_tile_map_example.dart
Normal file
101
examples/lib/stories/rendering/isometric_tile_map_example.dart
Normal file
@ -0,0 +1,101 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/extensions.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/input.dart';
|
||||
import 'package:flame/sprite.dart';
|
||||
import 'package:flutter/material.dart' hide Image;
|
||||
|
||||
class IsometricTileMapExample extends FlameGame with MouseMovementDetector {
|
||||
static const String description = '''
|
||||
Shows an example of how to use the `IsometricTileMapComponent`.\n\n
|
||||
Move the mouse over the board to see a selector appearing on the tiles.
|
||||
''';
|
||||
|
||||
final topLeft = Vector2.all(500);
|
||||
|
||||
static const scale = 2.0;
|
||||
static const srcTileSize = 32.0;
|
||||
static const destTileSize = scale * srcTileSize;
|
||||
|
||||
static const halfSize = true;
|
||||
static const tileHeight = scale * (halfSize ? 8.0 : 16.0);
|
||||
static const suffix = halfSize ? '-short' : '';
|
||||
|
||||
final originColor = Paint()..color = const Color(0xFFFF00FF);
|
||||
final originColor2 = Paint()..color = const Color(0xFFAA55FF);
|
||||
|
||||
late IsometricTileMapComponent base;
|
||||
late Selector selector;
|
||||
|
||||
IsometricTileMapExample();
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
final tilesetImage = await images.load('tile_maps/tiles$suffix.png');
|
||||
final tileset = SpriteSheet(
|
||||
image: tilesetImage,
|
||||
srcSize: Vector2.all(srcTileSize),
|
||||
);
|
||||
final matrix = [
|
||||
[3, 1, 1, 1, 0, 0],
|
||||
[-1, 1, 2, 1, 0, 0],
|
||||
[-1, 0, 1, 1, 0, 0],
|
||||
[-1, 1, 1, 1, 0, 0],
|
||||
[1, 1, 1, 1, 0, 2],
|
||||
[1, 3, 3, 3, 0, 2],
|
||||
];
|
||||
add(
|
||||
base = IsometricTileMapComponent(
|
||||
tileset,
|
||||
matrix,
|
||||
destTileSize: Vector2.all(destTileSize),
|
||||
tileHeight: tileHeight,
|
||||
position: topLeft,
|
||||
),
|
||||
);
|
||||
|
||||
final selectorImage = await images.load('tile_maps/selector$suffix.png');
|
||||
add(selector = Selector(destTileSize, selectorImage));
|
||||
}
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
super.render(canvas);
|
||||
canvas.renderPoint(topLeft, size: 5, paint: originColor);
|
||||
canvas.renderPoint(
|
||||
topLeft.clone()..y -= tileHeight,
|
||||
size: 5,
|
||||
paint: originColor2,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onMouseMove(PointerHoverInfo info) {
|
||||
final screenPosition = info.eventPosition.game;
|
||||
final block = base.getBlock(screenPosition);
|
||||
selector.show = base.containsBlock(block);
|
||||
selector.position.setFrom(topLeft + base.getBlockRenderPosition(block));
|
||||
}
|
||||
}
|
||||
|
||||
class Selector extends SpriteComponent {
|
||||
bool show = true;
|
||||
|
||||
Selector(double s, Image image)
|
||||
: super(
|
||||
sprite: Sprite(image, srcSize: Vector2.all(32.0)),
|
||||
size: Vector2.all(s),
|
||||
);
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
if (!show) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.render(canvas);
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,36 @@ import 'package:flame/components.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/layers.dart';
|
||||
|
||||
class LayerExample extends FlameGame {
|
||||
static const String description = '''
|
||||
In this example we show how layers can be used to produce a shadow effect.
|
||||
''';
|
||||
|
||||
late Layer gameLayer;
|
||||
late Layer backgroundLayer;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
final playerSprite = Sprite(await images.load('layers/player.png'));
|
||||
final enemySprite = Sprite(await images.load('layers/enemy.png'));
|
||||
final backgroundSprite = Sprite(await images.load('layers/background.png'));
|
||||
|
||||
gameLayer = GameLayer(playerSprite, enemySprite);
|
||||
backgroundLayer = BackgroundLayer(backgroundSprite);
|
||||
}
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
super.render(canvas);
|
||||
gameLayer.render(canvas);
|
||||
backgroundLayer.render(canvas);
|
||||
}
|
||||
|
||||
@override
|
||||
Color backgroundColor() => const Color(0xFF38607C);
|
||||
}
|
||||
|
||||
class GameLayer extends DynamicLayer {
|
||||
final Sprite playerSprite;
|
||||
final Sprite enemySprite;
|
||||
@ -43,29 +73,3 @@ class BackgroundLayer extends PreRenderedLayer {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LayerGame extends FlameGame {
|
||||
late Layer gameLayer;
|
||||
late Layer backgroundLayer;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
final playerSprite = Sprite(await images.load('layers/player.png'));
|
||||
final enemySprite = Sprite(await images.load('layers/enemy.png'));
|
||||
final backgroundSprite = Sprite(await images.load('layers/background.png'));
|
||||
|
||||
gameLayer = GameLayer(playerSprite, enemySprite);
|
||||
backgroundLayer = BackgroundLayer(backgroundSprite);
|
||||
}
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
super.render(canvas);
|
||||
gameLayer.render(canvas);
|
||||
backgroundLayer.render(canvas);
|
||||
}
|
||||
|
||||
@override
|
||||
Color backgroundColor() => const Color(0xFF38607C);
|
||||
}
|
||||
39
examples/lib/stories/rendering/nine_tile_box_example.dart
Normal file
39
examples/lib/stories/rendering/nine_tile_box_example.dart
Normal file
@ -0,0 +1,39 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/input.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NineTileBoxExample extends FlameGame with TapDetector, DoubleTapDetector {
|
||||
static const String description = '''
|
||||
If you want to create a background for something that can stretch you can
|
||||
use the `NineTileBox` which is showcased here.\n\n
|
||||
Tap to make the box bigger and double tap to make it smaller.
|
||||
''';
|
||||
|
||||
late NineTileBox nineTileBox;
|
||||
final Vector2 boxSize = Vector2.all(300);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
final sprite = Sprite(await images.load('nine-box.png'));
|
||||
nineTileBox = NineTileBox(sprite, tileSize: 8, destTileSize: 24);
|
||||
}
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
super.render(canvas);
|
||||
final position = (size - boxSize) / 2;
|
||||
nineTileBox.draw(canvas, position, boxSize);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTap() {
|
||||
boxSize.scale(1.2);
|
||||
}
|
||||
|
||||
@override
|
||||
void onDoubleTap() {
|
||||
boxSize.scale(0.8);
|
||||
}
|
||||
}
|
||||
580
examples/lib/stories/rendering/particles_example.dart
Normal file
580
examples/lib/stories/rendering/particles_example.dart
Normal file
@ -0,0 +1,580 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/components.dart' hide Timer;
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/particles.dart';
|
||||
import 'package:flame/sprite.dart';
|
||||
import 'package:flame/timer.dart' as flame_timer;
|
||||
import 'package:flutter/material.dart' hide Image;
|
||||
|
||||
class ParticlesExample extends FlameGame with FPSCounter {
|
||||
static const String description = '''
|
||||
In this example we show how to render a lot of different particles.
|
||||
''';
|
||||
|
||||
/// Defines dimensions of the sample
|
||||
/// grid to be displayed on the screen,
|
||||
/// 5x5 in this particular case
|
||||
static const gridSize = 5.0;
|
||||
static const steps = 5;
|
||||
|
||||
/// Miscellaneous values used
|
||||
/// by examples below
|
||||
final Random rnd = Random();
|
||||
Timer? spawnTimer;
|
||||
final StepTween steppedTween = StepTween(begin: 0, end: 5);
|
||||
final trafficLight = TrafficLightComponent();
|
||||
final TextPaint fpsTextPaint = TextPaint(
|
||||
style: const TextStyle(color: Colors.white),
|
||||
);
|
||||
|
||||
/// Defines the lifespan of all the particles in these examples
|
||||
final sceneDuration = const Duration(seconds: 1);
|
||||
|
||||
Vector2 get cellSize => size / gridSize;
|
||||
Vector2 get halfCellSize => cellSize / 2;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
await images.load('zap.png');
|
||||
await images.load('boom.png');
|
||||
}
|
||||
|
||||
@override
|
||||
void onMount() {
|
||||
spawnParticles();
|
||||
// Spawn new particles every second
|
||||
spawnTimer = Timer.periodic(sceneDuration, (_) {
|
||||
spawnParticles();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onRemove() {
|
||||
super.onRemove();
|
||||
spawnTimer?.cancel();
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
];
|
||||
|
||||
// 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).toDouble();
|
||||
final cellCenter = (cellSize..multiply(Vector2(col, row))) + halfCellSize;
|
||||
|
||||
add(
|
||||
// Bind all the particles to a [Component] update
|
||||
// lifecycle from the [FlameGame].
|
||||
ParticleComponent(
|
||||
TranslatedParticle(
|
||||
lifespan: 1,
|
||||
offset: cellCenter,
|
||||
child: particle,
|
||||
),
|
||||
),
|
||||
);
|
||||
} 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 [Vector2.zero]
|
||||
from: Vector2(-20, -20),
|
||||
to: Vector2(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: randomCellVector2(),
|
||||
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.x / 5) * i - halfCellSize.x;
|
||||
return MovingParticle(
|
||||
from: Vector2(currentColumn, -halfCellSize.y),
|
||||
to: Vector2(currentColumn, halfCellSize.y),
|
||||
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: randomCellVector2()..scale(.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: randomCellVector2()..scale(.5),
|
||||
child: CircleParticle(
|
||||
radius: 5 + rnd.nextDouble() * 5,
|
||||
paint: Paint()..color = Colors.deepPurple,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Same example as above, but using awesome [Interval]
|
||||
/// 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: randomCellVector2()..scale(.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.x,
|
||||
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.x,
|
||||
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(
|
||||
generator: (i) => MovingParticle(
|
||||
curve: Interval(rnd.nextDouble() * .1, rnd.nextDouble() * .8 + .1),
|
||||
to: randomCellVector2()..scale(.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: Vector2.all(24),
|
||||
image: images.fromCache('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.x / perLine;
|
||||
final rowHeight = cellSize.y / perLine;
|
||||
|
||||
reusableImageParticle ??= imageParticle();
|
||||
|
||||
return Particle.generate(
|
||||
count: count,
|
||||
generator: (i) => TranslatedParticle(
|
||||
offset: Vector2(
|
||||
(i % perLine) * colWidth - halfCellSize.x + imageSize,
|
||||
(i ~/ perLine) * rowHeight - halfCellSize.y + 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(
|
||||
generator: (i) => AcceleratedParticle(
|
||||
speed:
|
||||
Vector2(rnd.nextDouble() * 600 - 300, -rnd.nextDouble() * 600) * .2,
|
||||
acceleration: Vector2(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 colors = [
|
||||
const Color(0xffff0000),
|
||||
const Color(0xff00ff00),
|
||||
const Color(0xff0000ff),
|
||||
];
|
||||
final positions = [
|
||||
Vector2(-10, 10),
|
||||
Vector2(10, 10),
|
||||
Vector2(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(images.fromCache('zap.png')),
|
||||
size: cellSize * .5,
|
||||
);
|
||||
}
|
||||
|
||||
/// An [SpriteAnimationParticle] takes a Flame [SpriteAnimation]
|
||||
/// and plays it during the particle lifespan.
|
||||
Particle animationParticle() {
|
||||
return SpriteAnimationParticle(
|
||||
animation: getBoomAnimation(),
|
||||
size: Vector2(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 paints = [
|
||||
Colors.amber,
|
||||
Colors.amberAccent,
|
||||
Colors.red,
|
||||
Colors.redAccent,
|
||||
Colors.yellow,
|
||||
Colors.yellowAccent,
|
||||
// Adds a nice "lense" tint
|
||||
// to overall effect
|
||||
Colors.blue,
|
||||
].map((color) => Paint()..color = color).toList();
|
||||
|
||||
return Particle.generate(
|
||||
generator: (i) {
|
||||
final initialSpeed = randomCellVector2();
|
||||
final deceleration = initialSpeed * -1;
|
||||
final gravity = Vector2(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,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// [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: [
|
||||
rect
|
||||
.rotating(to: pi / 2)
|
||||
.moving(to: -cellSize)
|
||||
.scaled(2)
|
||||
.accelerated(acceleration: halfCellSize * 5)
|
||||
.translated(halfCellSize),
|
||||
rect
|
||||
.rotating(to: -pi)
|
||||
.moving(to: Vector2(1, -1)..multiply(cellSize))
|
||||
.scaled(2)
|
||||
.translated(Vector2(1, -1)..multiply(halfCellSize))
|
||||
.accelerated(acceleration: Vector2(-5, 5)..multiply(halfCellSize)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool debugMode = true;
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
super.render(canvas);
|
||||
|
||||
if (debugMode) {
|
||||
fpsTextPaint.render(
|
||||
canvas,
|
||||
'${fps(120).toStringAsFixed(2)}fps',
|
||||
Vector2(0, size.y - 24),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns random [Vector2] within a virtual grid cell
|
||||
Vector2 randomCellVector2() {
|
||||
return (Vector2.random() - Vector2.random())..multiply(cellSize);
|
||||
}
|
||||
|
||||
/// 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 [SpriteAnimationParticle] example
|
||||
SpriteAnimation getBoomAnimation() {
|
||||
const columns = 8;
|
||||
const rows = 8;
|
||||
const frames = columns * rows;
|
||||
final spriteImage = images.fromCache('boom.png');
|
||||
final spritesheet = SpriteSheet.fromColumnsAndRows(
|
||||
image: spriteImage,
|
||||
columns: columns,
|
||||
rows: rows,
|
||||
);
|
||||
final sprites = List<Sprite>.generate(frames, spritesheet.getSpriteById);
|
||||
return SpriteAnimation.spriteList(sprites, stepTime: 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
Future<FlameGame> loadGame() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
return ParticlesExample();
|
||||
}
|
||||
|
||||
/// 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_timer.Timer colorChangeTimer = flame_timer.Timer(2, repeat: true);
|
||||
final colors = <Color>[
|
||||
Colors.green,
|
||||
Colors.orange,
|
||||
Colors.red,
|
||||
];
|
||||
|
||||
@override
|
||||
void onMount() {
|
||||
colorChangeTimer.start();
|
||||
}
|
||||
|
||||
@override
|
||||
void render(Canvas c) {
|
||||
c.drawRect(rect, Paint()..color = currentColor);
|
||||
}
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
super.update(dt);
|
||||
colorChangeTimer.update(dt);
|
||||
}
|
||||
|
||||
Color get currentColor {
|
||||
return colors[(colorChangeTimer.progress * colors.length).toInt()];
|
||||
}
|
||||
}
|
||||
@ -2,25 +2,49 @@ import 'package:dashbook/dashbook.dart';
|
||||
import 'package:flame/game.dart';
|
||||
|
||||
import '../../commons/commons.dart';
|
||||
import 'flip.dart';
|
||||
import 'layers.dart';
|
||||
import 'text.dart';
|
||||
import 'flip_sprite_example.dart';
|
||||
import 'isometric_tile_map_example.dart';
|
||||
import 'layers_example.dart';
|
||||
import 'nine_tile_box_example.dart';
|
||||
import 'particles_example.dart';
|
||||
import 'text_example.dart';
|
||||
|
||||
void addRenderingStories(Dashbook dashbook) {
|
||||
dashbook.storiesOf('Rendering')
|
||||
..add(
|
||||
'Text',
|
||||
(_) => GameWidget(game: TextGame()),
|
||||
codeLink: baseLink('rendering/text.dart'),
|
||||
(_) => GameWidget(game: TextExample()),
|
||||
codeLink: baseLink('rendering/text_example.dart'),
|
||||
info: TextExample.description,
|
||||
)
|
||||
..add(
|
||||
'Isometric Tile Map',
|
||||
(_) => GameWidget(game: IsometricTileMapExample()),
|
||||
codeLink: baseLink('tile_maps/isometric_tile_map_example.dart'),
|
||||
info: IsometricTileMapExample.description,
|
||||
)
|
||||
..add(
|
||||
'Nine Tile Box',
|
||||
(_) => GameWidget(game: NineTileBoxExample()),
|
||||
codeLink: baseLink('utils/nine_tile_box_example.dart'),
|
||||
info: NineTileBoxExample.description,
|
||||
)
|
||||
..add(
|
||||
'Flip Sprite',
|
||||
(_) => GameWidget(game: FlipSpriteGame()),
|
||||
codeLink: baseLink('rendering/flip.dart'),
|
||||
(_) => GameWidget(game: FlipSpriteExample()),
|
||||
codeLink: baseLink('rendering/flip_sprite_example.dart'),
|
||||
info: FlipSpriteExample.description,
|
||||
)
|
||||
..add(
|
||||
'Layers',
|
||||
(_) => GameWidget(game: LayerGame()),
|
||||
codeLink: baseLink('rendering/layers.dart'),
|
||||
(_) => GameWidget(game: LayerExample()),
|
||||
codeLink: baseLink('rendering/layers_example.dart'),
|
||||
info: LayerExample.description,
|
||||
)
|
||||
..add(
|
||||
'Particles',
|
||||
(_) => GameWidget(game: ParticlesExample()),
|
||||
codeLink: baseLink('utils/particles_example.dart'),
|
||||
info: ParticlesExample.description,
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,6 +5,53 @@ import 'package:flame/game.dart';
|
||||
import 'package:flame/palette.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextExample extends FlameGame {
|
||||
static const String description = '''
|
||||
In this example we show different ways of rendering text.
|
||||
''';
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
add(
|
||||
TextComponent(text: 'Hello, Flame', textRenderer: _regular)
|
||||
..anchor = Anchor.topCenter
|
||||
..x = size.x / 2
|
||||
..y = 32.0,
|
||||
);
|
||||
|
||||
add(
|
||||
TextComponent(text: 'Text with shade', textRenderer: _shaded)
|
||||
..anchor = Anchor.topRight
|
||||
..position = size - Vector2.all(100),
|
||||
);
|
||||
|
||||
add(
|
||||
TextComponent(text: 'center', textRenderer: _tiny)
|
||||
..anchor = Anchor.center
|
||||
..position.setFrom(size / 2),
|
||||
);
|
||||
|
||||
add(
|
||||
TextComponent(text: 'bottomRight', textRenderer: _tiny)
|
||||
..anchor = Anchor.bottomRight
|
||||
..position.setFrom(size),
|
||||
);
|
||||
|
||||
add(
|
||||
MyTextBox(
|
||||
'"This is our world now. The world of the electron and the switch; '
|
||||
'the beauty of the baud. We exist without nationality, skin color, '
|
||||
'or religious bias. You wage wars, murder, cheat, lie to us and try '
|
||||
"to make us believe it's for our own good, yet we're the "
|
||||
'criminals. Yes, I am a criminal. My crime is that of curiosity."',
|
||||
)
|
||||
..anchor = Anchor.bottomLeft
|
||||
..y = size.y,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final _regularTextStyle =
|
||||
TextStyle(fontSize: 18, color: BasicPalette.white.color);
|
||||
final _regular = TextPaint(style: _regularTextStyle);
|
||||
@ -46,46 +93,3 @@ class MyTextBox extends TextBoxComponent {
|
||||
c.drawRect(rect, Paint()..color = Colors.white10);
|
||||
}
|
||||
}
|
||||
|
||||
class TextGame extends FlameGame {
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
add(
|
||||
TextComponent(text: 'Hello, Flame', textRenderer: _regular)
|
||||
..anchor = Anchor.topCenter
|
||||
..x = size.x / 2
|
||||
..y = 32.0,
|
||||
);
|
||||
|
||||
add(
|
||||
TextComponent(text: 'Text with shade', textRenderer: _shaded)
|
||||
..anchor = Anchor.topRight
|
||||
..position = size - Vector2.all(100),
|
||||
);
|
||||
|
||||
add(
|
||||
TextComponent(text: 'center', textRenderer: _tiny)
|
||||
..anchor = Anchor.center
|
||||
..position.setFrom(size / 2),
|
||||
);
|
||||
|
||||
add(
|
||||
TextComponent(text: 'bottomRight', textRenderer: _tiny)
|
||||
..anchor = Anchor.bottomRight
|
||||
..position.setFrom(size),
|
||||
);
|
||||
|
||||
add(
|
||||
MyTextBox(
|
||||
'"This is our world now. The world of the electron and the switch; '
|
||||
'the beauty of the baud. We exist without nationality, skin color, '
|
||||
'or religious bias. You wage wars, murder, cheat, lie to us and try '
|
||||
"to make us believe it's for our own good, yet we're the "
|
||||
'criminals. Yes, I am a criminal. My crime is that of curiosity."',
|
||||
)
|
||||
..anchor = Anchor.bottomLeft
|
||||
..y = size.y,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user