mirror of https://github.com/flutter/pinball.git
parent
e6dca1ed7f
commit
de963cbc86
@ -1,3 +1,4 @@
|
|||||||
export 'ball.dart';
|
export 'ball.dart';
|
||||||
|
export 'fire_effect.dart';
|
||||||
export 'initial_position.dart';
|
export 'initial_position.dart';
|
||||||
export 'layer.dart';
|
export 'layer.dart';
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flame/extensions.dart';
|
||||||
|
import 'package:flame/particles.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart' hide Particle;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
const _particleRadius = 0.25;
|
||||||
|
|
||||||
|
// TODO(erickzanardo): This component could just be a ParticleComponet,
|
||||||
|
/// unfortunately there is a Particle Component is not a PositionComponent,
|
||||||
|
/// which makes it hard to be used since we have camera transformations and on
|
||||||
|
// top of that, PositionComponent has a bug inside forge 2d games
|
||||||
|
///
|
||||||
|
/// https://github.com/flame-engine/flame/issues/1484
|
||||||
|
/// https://github.com/flame-engine/flame/issues/1484
|
||||||
|
|
||||||
|
/// {@template fire_effect}
|
||||||
|
/// A [BodyComponent] which creates a fire trail effect using the given
|
||||||
|
/// parameters
|
||||||
|
/// {@endtemplate}
|
||||||
|
class FireEffect extends BodyComponent {
|
||||||
|
/// {@macro fire_effect}
|
||||||
|
FireEffect({
|
||||||
|
required this.burstPower,
|
||||||
|
required this.position,
|
||||||
|
required this.direction,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// A [double] value that will define how "strong" the burst of particles
|
||||||
|
/// will be
|
||||||
|
final double burstPower;
|
||||||
|
|
||||||
|
/// The position of the burst
|
||||||
|
final Vector2 position;
|
||||||
|
|
||||||
|
/// Which direction the burst will aim
|
||||||
|
final Vector2 direction;
|
||||||
|
late Particle _particle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final bodyDef = BodyDef()..position = position;
|
||||||
|
|
||||||
|
final fixtureDef = FixtureDef(CircleShape()..radius = 0)..isSensor = true;
|
||||||
|
|
||||||
|
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
final children = [
|
||||||
|
...List.generate(4, (index) {
|
||||||
|
return CircleParticle(
|
||||||
|
radius: _particleRadius,
|
||||||
|
paint: Paint()..color = Colors.yellow.darken((index + 1) / 4),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
...List.generate(4, (index) {
|
||||||
|
return CircleParticle(
|
||||||
|
radius: _particleRadius,
|
||||||
|
paint: Paint()..color = Colors.red.darken((index + 1) / 4),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
...List.generate(4, (index) {
|
||||||
|
return CircleParticle(
|
||||||
|
radius: _particleRadius,
|
||||||
|
paint: Paint()..color = Colors.orange.darken((index + 1) / 4),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
final rng = math.Random();
|
||||||
|
final spreadTween = Tween<double>(begin: -0.2, end: 0.2);
|
||||||
|
|
||||||
|
_particle = Particle.generate(
|
||||||
|
count: (rng.nextDouble() * (burstPower * 10)).toInt(),
|
||||||
|
generator: (_) {
|
||||||
|
final spread = Vector2(
|
||||||
|
spreadTween.transform(rng.nextDouble()),
|
||||||
|
spreadTween.transform(rng.nextDouble()),
|
||||||
|
);
|
||||||
|
final finalDirection = Vector2(direction.x, -direction.y) + spread;
|
||||||
|
final speed = finalDirection * (burstPower * 20);
|
||||||
|
|
||||||
|
return AcceleratedParticle(
|
||||||
|
lifespan: 5 / burstPower,
|
||||||
|
position: Vector2.zero(),
|
||||||
|
speed: speed,
|
||||||
|
child: children[rng.nextInt(children.length)],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
_particle.update(dt);
|
||||||
|
|
||||||
|
if (_particle.shouldRemove) {
|
||||||
|
removeFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
super.render(canvas);
|
||||||
|
|
||||||
|
_particle.render(canvas);
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,2 @@
|
|||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
export 'games.dart';
|
||||||
|
export 'methods.dart';
|
||||||
String buildSourceLink(String path) {
|
|
||||||
return 'https://github.com/VGVentures/pinball/tree/main/packages/pinball_components/sandbox/lib/stories/$path';
|
|
||||||
}
|
|
||||||
|
|
||||||
class BasicGame extends Forge2DGame {
|
|
||||||
BasicGame() {
|
|
||||||
images.prefix = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/input.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class BasicGame extends Forge2DGame {
|
||||||
|
BasicGame() {
|
||||||
|
images.prefix = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class LineGame extends BasicGame with PanDetector {
|
||||||
|
Vector2? _lineEnd;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
camera.followVector2(Vector2.zero());
|
||||||
|
unawaited(add(_PreviewLine()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onPanStart(DragStartInfo info) {
|
||||||
|
_lineEnd = info.eventPosition.game;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onPanUpdate(DragUpdateInfo info) {
|
||||||
|
_lineEnd = info.eventPosition.game;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onPanEnd(DragEndInfo info) {
|
||||||
|
if (_lineEnd != null) {
|
||||||
|
final line = _lineEnd! - Vector2.zero();
|
||||||
|
onLine(line);
|
||||||
|
_lineEnd = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onLine(Vector2 line);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PreviewLine extends PositionComponent with HasGameRef<LineGame> {
|
||||||
|
static final _previewLinePaint = Paint()
|
||||||
|
..color = Colors.pink
|
||||||
|
..strokeWidth = 0.2
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
|
||||||
|
Vector2? lineEnd;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
|
||||||
|
lineEnd = gameRef._lineEnd?.clone()?..multiply(Vector2(1, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
super.render(canvas);
|
||||||
|
|
||||||
|
if (lineEnd != null) {
|
||||||
|
canvas.drawLine(
|
||||||
|
Vector2.zero().toOffset(),
|
||||||
|
lineEnd!.toOffset(),
|
||||||
|
_previewLinePaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
String buildSourceLink(String path) {
|
||||||
|
return 'https://github.com/VGVentures/pinball/tree/main/packages/pinball_components/sandbox/lib/stories/$path';
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
|
||||||
|
class BallBoosterExample extends LineGame {
|
||||||
|
static const info = '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onLine(Vector2 line) {
|
||||||
|
final ball = Ball(baseColor: Colors.transparent);
|
||||||
|
add(ball);
|
||||||
|
|
||||||
|
ball.mounted.then((value) => ball.boost(line * -1 * 20));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:dashbook/dashbook.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
import 'package:sandbox/stories/effects/fire_effect.dart';
|
||||||
|
|
||||||
|
void addEffectsStories(Dashbook dashbook) {
|
||||||
|
dashbook.storiesOf('Effects').add(
|
||||||
|
'Fire Effect',
|
||||||
|
(context) => GameWidget(game: FireEffectExample()),
|
||||||
|
codeLink: buildSourceLink('effects/fire_effect.dart'),
|
||||||
|
info: FireEffectExample.info,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
|
||||||
|
class FireEffectExample extends LineGame {
|
||||||
|
static const info = 'Demonstrate the fire trail effect '
|
||||||
|
'drag a line to define the trail direction';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onLine(Vector2 line) {
|
||||||
|
add(_EffectEmitter(line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EffectEmitter extends Component {
|
||||||
|
_EffectEmitter(this.line) {
|
||||||
|
_direction = line.normalized();
|
||||||
|
_force = line.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const _timerLimit = 2.0;
|
||||||
|
var _timer = _timerLimit;
|
||||||
|
|
||||||
|
final Vector2 line;
|
||||||
|
|
||||||
|
late Vector2 _direction;
|
||||||
|
late double _force;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
|
||||||
|
if (_timer > 0) {
|
||||||
|
add(
|
||||||
|
FireEffect(
|
||||||
|
burstPower: (_timer / _timerLimit) * _force,
|
||||||
|
position: Vector2.zero(),
|
||||||
|
direction: _direction,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_timer -= dt;
|
||||||
|
} else {
|
||||||
|
removeFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
|
export 'mocks.dart';
|
||||||
export 'test_game.dart';
|
export 'test_game.dart';
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
|
class MockCanvas extends Mock implements Canvas {}
|
@ -0,0 +1,55 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(TestGame.new);
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
registerFallbackValue(Offset.zero);
|
||||||
|
registerFallbackValue(Paint());
|
||||||
|
});
|
||||||
|
|
||||||
|
group('FireEffect', () {
|
||||||
|
flameTester.test('is removed once its particles are done', (game) async {
|
||||||
|
await game.ensureAdd(
|
||||||
|
FireEffect(
|
||||||
|
burstPower: 1,
|
||||||
|
position: Vector2.zero(),
|
||||||
|
direction: Vector2.all(2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await game.ready();
|
||||||
|
expect(game.children.whereType<FireEffect>().length, equals(1));
|
||||||
|
game.update(5);
|
||||||
|
|
||||||
|
await game.ready();
|
||||||
|
expect(game.children.whereType<FireEffect>().length, equals(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('render circles on the canvas', (game) async {
|
||||||
|
final effect = FireEffect(
|
||||||
|
burstPower: 1,
|
||||||
|
position: Vector2.zero(),
|
||||||
|
direction: Vector2.all(2),
|
||||||
|
);
|
||||||
|
await game.ensureAdd(effect);
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
final canvas = MockCanvas();
|
||||||
|
effect.render(canvas);
|
||||||
|
|
||||||
|
verify(() => canvas.drawCircle(any(), any(), any()))
|
||||||
|
.called(greaterThan(0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in new issue