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); 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]. /// 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 direction = body.linearVelocity.normalized();
final effect = FireEffect( final effect = FireEffect(
burstPower: _boostTimer, burstPower: _boostTimer,
direction: direction, direction: -direction,
position: body.position, position: Vector2(body.position.x, -body.position.y),
priority: priority - 1,
); );
unawaited(gameRef.add(effect)); unawaited(gameRef.add(effect));

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

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

@ -1,11 +1,8 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'dart:ui';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart'; import '../../helpers/helpers.dart';
@ -14,43 +11,16 @@ void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new); final flameTester = FlameTester(TestGame.new);
setUpAll(() { flameTester.test(
registerFallbackValue(Offset.zero); 'loads correctly',
registerFallbackValue(Paint()); (game) async {
}); final fireEffect = FireEffect(
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, burstPower: 1,
position: Vector2.zero(), direction: Vector2.zero(),
direction: Vector2.all(2),
); );
await game.ensureAdd(effect); await game.ensureAdd(fireEffect);
await game.ready();
final canvas = MockCanvas();
effect.render(canvas);
verify(() => canvas.drawCircle(any(), any(), any())).called( expect(game.contains(fireEffect), isTrue);
greaterThan(0), },
); );
});
});
} }

@ -31,6 +31,7 @@ void main() {
); );
}, },
); );
flameTester.test( flameTester.test(
'throws AssertionError when not attached to controlled component', 'throws AssertionError when not attached to controlled component',
(game) async { (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', () { group('Controls', () {

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

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

@ -86,12 +86,12 @@ void main() {
group('controller', () { group('controller', () {
group('listenWhen', () { group('listenWhen', () {
final gameBloc = MockGameBloc(); final gameBloc = MockGameBloc();
final tester = flameBlocTester( final flameBlocTester = FlameBlocTester<TestGame, GameBloc>(
game: TestGame.new, gameBuilder: TestGame.new,
gameBloc: () => gameBloc, blocBuilder: () => gameBloc,
); );
tester.testGameWidget( flameBlocTester.testGameWidget(
'listens when a Bonus.dashNest is added', 'listens when a Bonus.dashNest is added',
verify: (game, tester) async { verify: (game, tester) async {
final flutterForest = FlutterForest(); final flutterForest = FlutterForest();
@ -145,12 +145,12 @@ void main() {
); );
}); });
final tester = flameBlocTester<PinballGame>( final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
game: PinballGameTest.create, gameBuilder: PinballGameTest.create,
gameBloc: () => gameBloc, blocBuilder: () => gameBloc,
); );
tester.testGameWidget( flameBlocTester.testGameWidget(
'add DashNestActivated event', 'add DashNestActivated event',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.ready(); await game.ready();
@ -171,7 +171,7 @@ void main() {
}, },
); );
tester.testGameWidget( flameBlocTester.testGameWidget(
'add Scored event', 'add Scored event',
setUp: (game, tester) async { setUp: (game, tester) async {
final flutterForest = FlutterForest(); 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:flame_test/flame_test.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart';
FlameTester<T> flameBlocTester<T extends Forge2DGame>({ class FlameBlocTester<T extends FlameGame, B extends Bloc<dynamic, dynamic>>
required T Function() game, extends FlameTester<T> {
required GameBloc Function() gameBloc, FlameBlocTester({
}) { required GameCreateFunction<T> gameBuilder,
return FlameTester<T>( required B Function() blocBuilder,
game, }) : super(
pumpWidget: (gameWidget, tester) async { gameBuilder,
await tester.pumpWidget( pumpWidget: (gameWidget, tester) async {
BlocProvider.value( await tester.pumpWidget(
value: gameBloc(), BlocProvider.value(
child: gameWidget, value: blocBuilder(),
), child: gameWidget,
); ),
}, );
); },
);
} }

Loading…
Cancel
Save