mirror of https://github.com/flutter/pinball.git
parent
e6dca1ed7f
commit
de963cbc86
@ -1,3 +1,4 @@
|
||||
export 'ball.dart';
|
||||
export 'fire_effect.dart';
|
||||
export 'initial_position.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';
|
||||
|
||||
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 = '';
|
||||
}
|
||||
}
|
||||
export 'games.dart';
|
||||
export 'methods.dart';
|
||||
|
@ -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';
|
||||
|
@ -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