Merge branch 'main' into feat/spaceship-entrance-ramp

pull/126/head
RuiAlonso 4 years ago
commit 679fc541c4

@ -23,6 +23,11 @@ abstract class ComponentController<T extends Component> extends Component {
);
await super.addToParent(parent);
}
@override
Future<void> add(Component component) {
throw Exception('ComponentController cannot add other components.');
}
}
/// Mixin that attaches a single [ComponentController] to a [Component].

@ -83,8 +83,9 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
final direction = body.linearVelocity.normalized();
final effect = FireEffect(
burstPower: _boostTimer,
direction: direction,
position: body.position,
direction: -direction,
position: Vector2(body.position.x, -body.position.y),
priority: priority - 1,
);
unawaited(gameRef.add(effect));

@ -1,5 +1,6 @@
import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame/extensions.dart';
import 'package:flame/particles.dart';
import 'package:flame_forge2d/flame_forge2d.dart' hide Particle;
@ -19,33 +20,24 @@ const _particleRadius = 0.25;
/// A [BodyComponent] which creates a fire trail effect using the given
/// parameters
/// {@endtemplate}
class FireEffect extends BodyComponent {
class FireEffect extends ParticleSystemComponent {
/// {@macro fire_effect}
FireEffect({
required this.burstPower,
required this.position,
required this.direction,
});
Vector2? position,
int? priority,
}) : super(
position: position,
priority: priority,
);
/// A [double] value that will define how "strong" the burst of particles
/// will be
/// will be.
final double burstPower;
/// The position of the burst
final Vector2 position;
/// Which direction the burst will aim
/// 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 {
@ -71,15 +63,15 @@ class FireEffect extends BodyComponent {
);
}),
];
final rng = math.Random();
final random = math.Random();
final spreadTween = Tween<double>(begin: -0.2, end: 0.2);
_particle = Particle.generate(
count: (rng.nextDouble() * (burstPower * 10)).toInt(),
particle = Particle.generate(
count: math.max((random.nextDouble() * (burstPower * 10)).toInt(), 1),
generator: (_) {
final spread = Vector2(
spreadTween.transform(rng.nextDouble()),
spreadTween.transform(rng.nextDouble()),
spreadTween.transform(random.nextDouble()),
spreadTween.transform(random.nextDouble()),
);
final finalDirection = Vector2(direction.x, -direction.y) + spread;
final speed = finalDirection * (burstPower * 20);
@ -88,26 +80,9 @@ class FireEffect extends BodyComponent {
lifespan: 5 / burstPower,
position: Vector2.zero(),
speed: speed,
child: children[rng.nextInt(children.length)],
child: children[random.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);
}
}

@ -34,7 +34,6 @@ class _EffectEmitter extends Component {
add(
FireEffect(
burstPower: (_timer / _timerLimit) * _force,
position: Vector2.zero(),
direction: _direction,
),
);

@ -1,12 +1,8 @@
import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball_components/pinball_components.dart';
class MockCanvas extends Mock implements Canvas {}
class MockFilter extends Mock implements Filter {}
class MockFixture extends Mock implements Fixture {}

@ -1,11 +1,8 @@
// 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';
@ -14,43 +11,16 @@ 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(
flameTester.test(
'loads correctly',
(game) async {
final fireEffect = FireEffect(
burstPower: 1,
position: Vector2.zero(),
direction: Vector2.all(2),
direction: Vector2.zero(),
);
await game.ensureAdd(effect);
await game.ready();
final canvas = MockCanvas();
effect.render(canvas);
await game.ensureAdd(fireEffect);
verify(() => canvas.drawCircle(any(), any(), any())).called(
greaterThan(0),
);
});
});
expect(game.contains(fireEffect), isTrue);
},
);
}

@ -31,6 +31,7 @@ void main() {
);
},
);
flameTester.test(
'throws AssertionError when not attached to controlled component',
(game) async {
@ -44,6 +45,35 @@ void main() {
);
},
);
flameTester.test(
'throws Exception when adding a component',
(game) async {
final component = ControlledComponent();
final controller = TestComponentController(component);
await expectLater(
() async => controller.add(Component()),
throwsException,
);
},
);
flameTester.test(
'throws Exception when adding multiple components',
(game) async {
final component = ControlledComponent();
final controller = TestComponentController(component);
await expectLater(
() async => controller.addAll([
Component(),
Component(),
]),
throwsException,
);
},
);
});
group('Controls', () {

@ -196,10 +196,10 @@ void main() {
group('bonus letter activation', () {
late GameBloc gameBloc;
final tester = flameBlocTester<PinballGame>(
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
// TODO(alestiago): Use TestGame once BonusLetter has controller.
game: PinballGameTest.create,
gameBloc: () => gameBloc,
gameBuilder: PinballGameTest.create,
blocBuilder: () => gameBloc,
);
setUp(() {
@ -211,7 +211,7 @@ void main() {
);
});
tester.testGameWidget(
flameBlocTester.testGameWidget(
'adds BonusLetterActivated to GameBloc when not activated',
setUp: (game, tester) async {
await game.ready();
@ -225,7 +225,7 @@ void main() {
},
);
tester.testGameWidget(
flameBlocTester.testGameWidget(
"doesn't add BonusLetterActivated to GameBloc when already activated",
setUp: (game, tester) async {
const state = GameState(
@ -253,7 +253,7 @@ void main() {
},
);
tester.testGameWidget(
flameBlocTester.testGameWidget(
'adds a ColorEffect',
setUp: (game, tester) async {
const state = GameState(
@ -284,7 +284,7 @@ void main() {
},
);
tester.testGameWidget(
flameBlocTester.testGameWidget(
'only listens when there is a change on the letter status',
setUp: (game, tester) async {
await game.ready();

@ -66,12 +66,12 @@ void main() {
);
});
final tester = flameBlocTester<PinballGame>(
game: PinballGameTest.create,
gameBloc: () => gameBloc,
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: PinballGameTest.create,
blocBuilder: () => gameBloc,
);
tester.testGameWidget(
flameBlocTester.testGameWidget(
'lost adds BallLost to GameBloc',
setUp: (game, tester) async {
final controller = LaunchedBallController(ball);
@ -86,7 +86,7 @@ void main() {
);
group('listenWhen', () {
tester.testGameWidget(
flameBlocTester.testGameWidget(
'listens when a ball has been lost',
setUp: (game, tester) async {
final controller = LaunchedBallController(ball);
@ -107,7 +107,7 @@ void main() {
},
);
tester.testGameWidget(
flameBlocTester.testGameWidget(
'does not listen when a ball has not been lost',
setUp: (game, tester) async {
final controller = LaunchedBallController(ball);
@ -130,7 +130,7 @@ void main() {
});
group('onNewState', () {
tester.testGameWidget(
flameBlocTester.testGameWidget(
'removes ball',
setUp: (game, tester) async {
final controller = LaunchedBallController(ball);
@ -147,7 +147,7 @@ void main() {
},
);
tester.testGameWidget(
flameBlocTester.testGameWidget(
'spawns a new ball when the ball is not the last one',
setUp: (game, tester) async {
final controller = LaunchedBallController(ball);
@ -168,7 +168,7 @@ void main() {
},
);
tester.testGameWidget(
flameBlocTester.testGameWidget(
'does not spawn a new ball is the last one',
setUp: (game, tester) async {
final controller = LaunchedBallController(ball);

@ -86,12 +86,12 @@ void main() {
group('controller', () {
group('listenWhen', () {
final gameBloc = MockGameBloc();
final tester = flameBlocTester(
game: TestGame.new,
gameBloc: () => gameBloc,
final flameBlocTester = FlameBlocTester<TestGame, GameBloc>(
gameBuilder: TestGame.new,
blocBuilder: () => gameBloc,
);
tester.testGameWidget(
flameBlocTester.testGameWidget(
'listens when a Bonus.dashNest is added',
verify: (game, tester) async {
final flutterForest = FlutterForest();
@ -145,12 +145,12 @@ void main() {
);
});
final tester = flameBlocTester<PinballGame>(
game: PinballGameTest.create,
gameBloc: () => gameBloc,
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: PinballGameTest.create,
blocBuilder: () => gameBloc,
);
tester.testGameWidget(
flameBlocTester.testGameWidget(
'add DashNestActivated event',
setUp: (game, tester) async {
await game.ready();
@ -171,7 +171,7 @@ void main() {
},
);
tester.testGameWidget(
flameBlocTester.testGameWidget(
'add Scored event',
setUp: (game, tester) async {
final flutterForest = FlutterForest();

@ -1,21 +1,21 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame/src/game/flame_game.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart';
FlameTester<T> flameBlocTester<T extends Forge2DGame>({
required T Function() game,
required GameBloc Function() gameBloc,
}) {
return FlameTester<T>(
game,
pumpWidget: (gameWidget, tester) async {
await tester.pumpWidget(
BlocProvider.value(
value: gameBloc(),
child: gameWidget,
),
);
},
);
class FlameBlocTester<T extends FlameGame, B extends Bloc<dynamic, dynamic>>
extends FlameTester<T> {
FlameBlocTester({
required GameCreateFunction<T> gameBuilder,
required B Function() blocBuilder,
}) : super(
gameBuilder,
pumpWidget: (gameWidget, tester) async {
await tester.pumpWidget(
BlocProvider.value(
value: blocBuilder(),
child: gameWidget,
),
);
},
);
}

Loading…
Cancel
Save