diff --git a/lib/game/components/bottom_group.dart b/lib/game/components/bottom_group.dart index bc5f473c..b4a888f4 100644 --- a/lib/game/components/bottom_group.dart +++ b/lib/game/components/bottom_group.dart @@ -38,7 +38,7 @@ class _BottomGroupSide extends Component { @override Future onLoad() async { final direction = _side.direction; - final centerXAdjustment = _side.isLeft ? 0 : -6.5; + final centerXAdjustment = _side.isLeft ? 0 : -6.66; final flipper = ControlledFlipper( side: _side, @@ -46,16 +46,16 @@ class _BottomGroupSide extends Component { final baseboard = Baseboard(side: _side) ..initialPosition = Vector2( (25.58 * direction) + centerXAdjustment, - 28.69, + 28.71, ); final kicker = Kicker( side: _side, children: [ - ScoringBehavior(points: 5000), + ScoringBehavior(points: 5000)..applyTo(['bouncy_edge']), ], )..initialPosition = Vector2( - (22.4 * direction) + centerXAdjustment, - 25, + (22.64 * direction) + centerXAdjustment, + 25.1, ); await addAll([flipper, baseboard, kicker]); diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index 744593fb..e48ab7ff 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -24,8 +24,10 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.flipper.right.keyName), images.load(components.Assets.images.baseboard.left.keyName), images.load(components.Assets.images.baseboard.right.keyName), - images.load(components.Assets.images.kicker.left.keyName), - images.load(components.Assets.images.kicker.right.keyName), + images.load(components.Assets.images.kicker.left.lit.keyName), + images.load(components.Assets.images.kicker.left.dimmed.keyName), + images.load(components.Assets.images.kicker.right.lit.keyName), + images.load(components.Assets.images.kicker.right.dimmed.keyName), images.load(components.Assets.images.slingshot.upper.keyName), images.load(components.Assets.images.slingshot.lower.keyName), images.load(components.Assets.images.launchRamp.ramp.keyName), diff --git a/packages/pinball_components/assets/images/kicker/left.png b/packages/pinball_components/assets/images/kicker/left.png deleted file mode 100644 index 42bd5030..00000000 Binary files a/packages/pinball_components/assets/images/kicker/left.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/kicker/left/dimmed.png b/packages/pinball_components/assets/images/kicker/left/dimmed.png new file mode 100644 index 00000000..70196876 Binary files /dev/null and b/packages/pinball_components/assets/images/kicker/left/dimmed.png differ diff --git a/packages/pinball_components/assets/images/kicker/left/lit.png b/packages/pinball_components/assets/images/kicker/left/lit.png new file mode 100644 index 00000000..d2f57661 Binary files /dev/null and b/packages/pinball_components/assets/images/kicker/left/lit.png differ diff --git a/packages/pinball_components/assets/images/kicker/right.png b/packages/pinball_components/assets/images/kicker/right.png deleted file mode 100644 index 0a746f3c..00000000 Binary files a/packages/pinball_components/assets/images/kicker/right.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/kicker/right/dimmed.png b/packages/pinball_components/assets/images/kicker/right/dimmed.png new file mode 100644 index 00000000..3e4a3b4f Binary files /dev/null and b/packages/pinball_components/assets/images/kicker/right/dimmed.png differ diff --git a/packages/pinball_components/assets/images/kicker/right/lit.png b/packages/pinball_components/assets/images/kicker/right/lit.png new file mode 100644 index 00000000..cfe992b4 Binary files /dev/null and b/packages/pinball_components/assets/images/kicker/right/lit.png differ diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index 94022534..9e6e6df1 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -169,13 +169,8 @@ class $AssetsImagesGoogleWordGen { class $AssetsImagesKickerGen { const $AssetsImagesKickerGen(); - /// File path: assets/images/kicker/left.png - AssetGenImage get left => - const AssetGenImage('assets/images/kicker/left.png'); - - /// File path: assets/images/kicker/right.png - AssetGenImage get right => - const AssetGenImage('assets/images/kicker/right.png'); + $AssetsImagesKickerLeftGen get left => const $AssetsImagesKickerLeftGen(); + $AssetsImagesKickerRightGen get right => const $AssetsImagesKickerRightGen(); } class $AssetsImagesLaunchRampGen { @@ -344,6 +339,30 @@ class $AssetsImagesDinoAnimatronicGen { const AssetGenImage('assets/images/dino/animatronic/mouth.png'); } +class $AssetsImagesKickerLeftGen { + const $AssetsImagesKickerLeftGen(); + + /// File path: assets/images/kicker/left/dimmed.png + AssetGenImage get dimmed => + const AssetGenImage('assets/images/kicker/left/dimmed.png'); + + /// File path: assets/images/kicker/left/lit.png + AssetGenImage get lit => + const AssetGenImage('assets/images/kicker/left/lit.png'); +} + +class $AssetsImagesKickerRightGen { + const $AssetsImagesKickerRightGen(); + + /// File path: assets/images/kicker/right/dimmed.png + AssetGenImage get dimmed => + const AssetGenImage('assets/images/kicker/right/dimmed.png'); + + /// File path: assets/images/kicker/right/lit.png + AssetGenImage get lit => + const AssetGenImage('assets/images/kicker/right/lit.png'); +} + class $AssetsImagesMultiplierX2Gen { const $AssetsImagesMultiplierX2Gen(); diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index fc037fbd..a0beda53 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -17,7 +17,7 @@ export 'flipper.dart'; export 'google_letter/google_letter.dart'; export 'initial_position.dart'; export 'joint_anchor.dart'; -export 'kicker.dart'; +export 'kicker/kicker.dart'; export 'launch_ramp.dart'; export 'layer.dart'; export 'layer_sensor.dart'; diff --git a/packages/pinball_components/lib/src/components/kicker/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/kicker/behaviors/behaviors.dart new file mode 100644 index 00000000..e1098a34 --- /dev/null +++ b/packages/pinball_components/lib/src/components/kicker/behaviors/behaviors.dart @@ -0,0 +1,2 @@ +export 'kicker_ball_contact_behavior.dart'; +export 'kicker_blinking_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/kicker/behaviors/kicker_ball_contact_behavior.dart b/packages/pinball_components/lib/src/components/kicker/behaviors/kicker_ball_contact_behavior.dart new file mode 100644 index 00000000..d5d2eb6c --- /dev/null +++ b/packages/pinball_components/lib/src/components/kicker/behaviors/kicker_ball_contact_behavior.dart @@ -0,0 +1,14 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class KickerBallContactBehavior extends ContactBehavior { + @override + void beginContact(Object other, Contact contact) { + super.beginContact(other, contact); + if (other is! Ball) return; + parent.bloc.onBallContacted(); + } +} diff --git a/packages/pinball_components/lib/src/components/kicker/behaviors/kicker_blinking_behavior.dart b/packages/pinball_components/lib/src/components/kicker/behaviors/kicker_blinking_behavior.dart new file mode 100644 index 00000000..569d461f --- /dev/null +++ b/packages/pinball_components/lib/src/components/kicker/behaviors/kicker_blinking_behavior.dart @@ -0,0 +1,37 @@ +import 'package:flame/components.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// {@template kicker_blinking_behavior} +/// Makes a [Kicker] blink back to [KickerState.lit] when [KickerState.dimmed]. +/// {@endtemplate} +class KickerBlinkingBehavior extends TimerComponent with ParentIsA { + /// {@macro kicker_blinking_behavior} + KickerBlinkingBehavior() : super(period: 0.05); + + void _onNewState(KickerState state) { + switch (state) { + case KickerState.lit: + break; + case KickerState.dimmed: + timer + ..reset() + ..start(); + break; + } + } + + @override + Future onLoad() async { + await super.onLoad(); + timer.stop(); + parent.bloc.stream.listen(_onNewState); + } + + @override + void onTick() { + super.onTick(); + timer.stop(); + parent.bloc.onBlinked(); + } +} diff --git a/packages/pinball_components/lib/src/components/kicker/cubit/kicker_cubit.dart b/packages/pinball_components/lib/src/components/kicker/cubit/kicker_cubit.dart new file mode 100644 index 00000000..488f4683 --- /dev/null +++ b/packages/pinball_components/lib/src/components/kicker/cubit/kicker_cubit.dart @@ -0,0 +1,17 @@ +// ignore_for_file: public_member_api_docs + +import 'package:bloc/bloc.dart'; + +part 'kicker_state.dart'; + +class KickerCubit extends Cubit { + KickerCubit() : super(KickerState.lit); + + void onBallContacted() { + emit(KickerState.dimmed); + } + + void onBlinked() { + emit(KickerState.lit); + } +} diff --git a/packages/pinball_components/lib/src/components/kicker/cubit/kicker_state.dart b/packages/pinball_components/lib/src/components/kicker/cubit/kicker_state.dart new file mode 100644 index 00000000..08d52709 --- /dev/null +++ b/packages/pinball_components/lib/src/components/kicker/cubit/kicker_state.dart @@ -0,0 +1,8 @@ +// ignore_for_file: public_member_api_docs + +part of 'kicker_cubit.dart'; + +enum KickerState { + lit, + dimmed, +} diff --git a/packages/pinball_components/lib/src/components/kicker.dart b/packages/pinball_components/lib/src/components/kicker/kicker.dart similarity index 55% rename from packages/pinball_components/lib/src/components/kicker.dart rename to packages/pinball_components/lib/src/components/kicker/kicker.dart index 527ffde4..3301e2ba 100644 --- a/packages/pinball_components/lib/src/components/kicker.dart +++ b/packages/pinball_components/lib/src/components/kicker/kicker.dart @@ -2,9 +2,15 @@ import 'dart:math' as math; import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; import 'package:geometry/geometry.dart' as geometry show centroid; import 'package:pinball_components/gen/assets.gen.dart'; import 'package:pinball_components/pinball_components.dart' hide Assets; +import 'package:pinball_components/src/components/bumping_behavior.dart'; +import 'package:pinball_components/src/components/kicker/behaviors/behaviors.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +export 'cubit/kicker_cubit.dart'; /// {@template kicker} /// Triangular [BodyType.static] body that propels the [Ball] towards the @@ -17,42 +23,69 @@ class Kicker extends BodyComponent with InitialPosition { Kicker({ required BoardSide side, Iterable? children, + }) : this._( + side: side, + bloc: KickerCubit(), + children: children, + ); + + Kicker._({ + required BoardSide side, + required this.bloc, + Iterable? children, }) : _side = side, super( children: [ - _KickerSpriteComponent(side: side), + BumpingBehavior(strength: 15)..applyTo(['bouncy_edge']), + KickerBallContactBehavior()..applyTo(['bouncy_edge']), + KickerBlinkingBehavior(), + _KickerSpriteGroupComponent( + side: side, + state: bloc.state, + ), ...?children, ], renderBody: false, ); - /// The size of the [Kicker] body. - static final Vector2 size = Vector2(4.4, 15); + /// Creates a [Kicker] without any children. + /// + /// This can be used for testing [Kicker]'s behaviors in isolation. + // TODO(alestiago): Refactor injecting bloc once the following is merged: + // https://github.com/flame-engine/flame/pull/1538 + @visibleForTesting + Kicker.test({ + required this.bloc, + required BoardSide side, + }) : _side = side; + + // TODO(alestiago): Consider refactoring once the following is merged: + // https://github.com/flame-engine/flame/pull/1538 + // ignore: public_member_api_docs + final KickerCubit bloc; + + @override + void onRemove() { + bloc.close(); + super.onRemove(); + } /// Whether the [Kicker] is on the left or right side of the board. - /// - /// A [Kicker] with [BoardSide.left] propels the [Ball] to the right, - /// whereas a [Kicker] with [BoardSide.right] propels the [Ball] to the - /// left. final BoardSide _side; List _createFixtureDefs() { - final fixturesDefs = []; final direction = _side.direction; const quarterPi = math.pi / 4; + final size = Vector2(4.4, 15); final upperCircle = CircleShape()..radius = 1.6; upperCircle.position.setValues(0, upperCircle.radius / 2); - final upperCircleFixtureDef = FixtureDef(upperCircle); - fixturesDefs.add(upperCircleFixtureDef); final lowerCircle = CircleShape()..radius = 1.6; lowerCircle.position.setValues( size.x * -direction, size.y + 0.8, ); - final lowerCircleFixtureDef = FixtureDef(lowerCircle); - fixturesDefs.add(lowerCircleFixtureDef); final wallFacingEdge = EdgeShape() ..set( @@ -63,8 +96,6 @@ class Kicker extends BodyComponent with InitialPosition { ), Vector2(2.5 * direction, size.y - 2), ); - final wallFacingLineFixtureDef = FixtureDef(wallFacingEdge); - fixturesDefs.add(wallFacingLineFixtureDef); final bottomEdge = EdgeShape() ..set( @@ -75,8 +106,6 @@ class Kicker extends BodyComponent with InitialPosition { lowerCircle.radius * math.sin(quarterPi), ), ); - final bottomLineFixtureDef = FixtureDef(bottomEdge); - fixturesDefs.add(bottomLineFixtureDef); final bouncyEdge = EdgeShape() ..set( @@ -92,12 +121,13 @@ class Kicker extends BodyComponent with InitialPosition { ), ); - final bouncyFixtureDef = FixtureDef( - bouncyEdge, - // TODO(alestiago): Play with restitution value once game is bundled. - restitution: 10, - ); - fixturesDefs.add(bouncyFixtureDef); + final fixturesDefs = [ + FixtureDef(upperCircle), + FixtureDef(lowerCircle), + FixtureDef(wallFacingEdge), + FixtureDef(bottomEdge), + FixtureDef(bouncyEdge, userData: 'bouncy_edge'), + ]; // TODO(alestiago): Evaluate if there is value on centering the fixtures. final centroid = geometry.centroid( @@ -130,25 +160,46 @@ class Kicker extends BodyComponent with InitialPosition { } } -class _KickerSpriteComponent extends SpriteComponent with HasGameRef { - _KickerSpriteComponent({required BoardSide side}) : _side = side; +class _KickerSpriteGroupComponent extends SpriteGroupComponent + with HasGameRef, ParentIsA { + _KickerSpriteGroupComponent({ + required BoardSide side, + required KickerState state, + }) : _side = side, + super( + anchor: Anchor.center, + position: Vector2(0.7 * -side.direction, -2.2), + current: state, + ); final BoardSide _side; @override Future onLoad() async { await super.onLoad(); - - // TODO(alestiago): Used cached asset. - final sprite = await gameRef.loadSprite( - (_side.isLeft) - ? Assets.images.kicker.left.keyName - : Assets.images.kicker.right.keyName, - ); - this.sprite = sprite; - size = sprite.originalSize / 10; - anchor = Anchor.center; - position = Vector2(0.7 * -_side.direction, -2.2); + // TODO(alestiago): Consider refactoring once the following is merged: + // https://github.com/flame-engine/flame/pull/1538 + // ignore: public_member_api_docs + parent.bloc.stream.listen((state) => current = state); + + final sprites = { + KickerState.lit: Sprite( + gameRef.images.fromCache( + (_side.isLeft) + ? Assets.images.kicker.left.lit.keyName + : Assets.images.kicker.right.lit.keyName, + ), + ), + KickerState.dimmed: Sprite( + gameRef.images.fromCache( + (_side.isLeft) + ? Assets.images.kicker.left.dimmed.keyName + : Assets.images.kicker.right.dimmed.keyName, + ), + ), + }; + this.sprites = sprites; + size = sprites[current]!.originalSize / 10; } } diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index 29b4e31a..8352afa3 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -64,7 +64,8 @@ flutter: - assets/images/android/bumper/a/ - assets/images/android/bumper/b/ - assets/images/android/bumper/cow/ - - assets/images/kicker/ + - assets/images/kicker/left/ + - assets/images/kicker/right/ - assets/images/plunger/ - assets/images/slingshot/ - assets/images/sparky/ diff --git a/packages/pinball_components/sandbox/lib/stories/bottom_group/kicker_game.dart b/packages/pinball_components/sandbox/lib/stories/bottom_group/kicker_game.dart index 9b7d96cc..590638e0 100644 --- a/packages/pinball_components/sandbox/lib/stories/bottom_group/kicker_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/bottom_group/kicker_game.dart @@ -3,6 +3,16 @@ import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; class KickerGame extends BallGame { + KickerGame() + : super( + imagesFileNames: [ + Assets.images.kicker.left.lit.keyName, + Assets.images.kicker.left.dimmed.keyName, + Assets.images.kicker.right.lit.keyName, + Assets.images.kicker.right.dimmed.keyName, + ], + ); + static const description = ''' Shows how Kickers are rendered. @@ -18,9 +28,9 @@ class KickerGame extends BallGame { await addAll( [ Kicker(side: BoardSide.left) - ..initialPosition = Vector2(center.x - (Kicker.size.x * 2), center.y), + ..initialPosition = Vector2(center.x - 8.8, center.y), Kicker(side: BoardSide.right) - ..initialPosition = Vector2(center.x + (Kicker.size.x * 2), center.y), + ..initialPosition = Vector2(center.x + 8.8, center.y), ], ); diff --git a/packages/pinball_components/sandbox/lib/stories/plunger/plunger_game.dart b/packages/pinball_components/sandbox/lib/stories/plunger/plunger_game.dart index 29eded8c..0f1ec2e4 100644 --- a/packages/pinball_components/sandbox/lib/stories/plunger/plunger_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/plunger/plunger_game.dart @@ -29,7 +29,7 @@ class PlungerGame extends BallGame with KeyboardEvents, Traceable { final center = screenToWorld(camera.viewport.canvasSize! / 2); await add( plunger = Plunger(compressionDistance: 29) - ..initialPosition = Vector2(center.x - (Kicker.size.x * 2), center.y), + ..initialPosition = Vector2(center.x - 8.8, center.y), ); await traceAllBodies(); } diff --git a/packages/pinball_components/test/src/components/golden/kickers.png b/packages/pinball_components/test/src/components/golden/kickers.png index 23176923..1b019de9 100644 Binary files a/packages/pinball_components/test/src/components/golden/kickers.png and b/packages/pinball_components/test/src/components/golden/kickers.png differ diff --git a/packages/pinball_components/test/src/components/kicker/behaviors/kicker_ball_contact_behavior_test.dart b/packages/pinball_components/test/src/components/kicker/behaviors/kicker_ball_contact_behavior_test.dart new file mode 100644 index 00000000..6c04cdcb --- /dev/null +++ b/packages/pinball_components/test/src/components/kicker/behaviors/kicker_ball_contact_behavior_test.dart @@ -0,0 +1,53 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.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 'package:pinball_components/src/components/kicker/behaviors/behaviors.dart'; + +import '../../../../helpers/helpers.dart'; + +class _MockKickerCubit extends Mock implements KickerCubit {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(TestGame.new); + + group( + 'KickerBallContactBehavior', + () { + test('can be instantiated', () { + expect( + KickerBallContactBehavior(), + isA(), + ); + }); + + flameTester.test( + 'beginContact emits onBallContacted when contacts with a ball', + (game) async { + final behavior = KickerBallContactBehavior(); + final bloc = _MockKickerCubit(); + whenListen( + bloc, + const Stream.empty(), + initialState: KickerState.lit, + ); + + final kicker = Kicker.test( + side: BoardSide.left, + bloc: bloc, + ); + await kicker.add(behavior); + await game.ensureAdd(kicker); + + behavior.beginContact(MockBall(), MockContact()); + + verify(kicker.bloc.onBallContacted).called(1); + }, + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/kicker/behaviors/kicker_blinking_behavior_test.dart b/packages/pinball_components/test/src/components/kicker/behaviors/kicker_blinking_behavior_test.dart new file mode 100644 index 00000000..3b6f0c20 --- /dev/null +++ b/packages/pinball_components/test/src/components/kicker/behaviors/kicker_blinking_behavior_test.dart @@ -0,0 +1,50 @@ +import 'dart:async'; + +import 'package:bloc_test/bloc_test.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 'package:pinball_components/src/components/kicker/behaviors/behaviors.dart'; + +import '../../../../helpers/helpers.dart'; + +class _MockKickerCubit extends Mock implements KickerCubit {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(TestGame.new); + + group( + 'KickerBlinkingBehavior', + () { + flameTester.testGameWidget( + 'calls onBlinked after 0.05 seconds when dimmed', + setUp: (game, tester) async { + final behavior = KickerBlinkingBehavior(); + final bloc = _MockKickerCubit(); + final streamController = StreamController(); + whenListen( + bloc, + streamController.stream, + initialState: KickerState.lit, + ); + + final kicker = Kicker.test( + side: BoardSide.left, + bloc: bloc, + ); + await kicker.add(behavior); + await game.ensureAdd(kicker); + + streamController.add(KickerState.dimmed); + await tester.pump(); + game.update(0.05); + + await streamController.close(); + verify(bloc.onBlinked).called(1); + }, + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/kicker/cubit/kicker_cubit_test.dart b/packages/pinball_components/test/src/components/kicker/cubit/kicker_cubit_test.dart new file mode 100644 index 00000000..ed1d4a46 --- /dev/null +++ b/packages/pinball_components/test/src/components/kicker/cubit/kicker_cubit_test.dart @@ -0,0 +1,24 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +void main() { + group( + 'KickerCubit', + () { + blocTest( + 'onBallContacted emits dimmed', + build: KickerCubit.new, + act: (bloc) => bloc.onBallContacted(), + expect: () => [KickerState.dimmed], + ); + + blocTest( + 'onBlinked emits lit', + build: KickerCubit.new, + act: (bloc) => bloc.onBlinked(), + expect: () => [KickerState.lit], + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/kicker_test.dart b/packages/pinball_components/test/src/components/kicker_test.dart index aebf9380..4d3fc14d 100644 --- a/packages/pinball_components/test/src/components/kicker_test.dart +++ b/packages/pinball_components/test/src/components/kicker_test.dart @@ -1,29 +1,44 @@ // ignore_for_file: cascade_invocations +import 'package:bloc_test/bloc_test.dart'; import 'package:flame/components.dart'; -import 'package:flame_forge2d/flame_forge2d.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 'package:pinball_components/src/components/bumping_behavior.dart'; +import 'package:pinball_components/src/components/kicker/behaviors/behaviors.dart'; import '../../helpers/helpers.dart'; +class _MockKickerCubit extends Mock implements KickerCubit {} + void main() { group('Kicker', () { - final flameTester = FlameTester(TestGame.new); + final assets = [ + Assets.images.kicker.left.lit.keyName, + Assets.images.kicker.left.dimmed.keyName, + Assets.images.kicker.right.lit.keyName, + Assets.images.kicker.right.dimmed.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); flameTester.testGameWidget( 'renders correctly', setUp: (game, tester) async { + await game.images.loadAll(assets); final leftKicker = Kicker( side: BoardSide.left, - )..initialPosition = Vector2(-20, 0); + ) + ..initialPosition = Vector2(-20, 0) + ..renderBody = false; final rightKicker = Kicker( side: BoardSide.right, )..initialPosition = Vector2(20, 0); await game.ensureAddAll([leftKicker, rightKicker]); game.camera.followVector2(Vector2.zero()); + await tester.pump(); }, verify: (game, tester) async { await expectLater( @@ -36,8 +51,9 @@ void main() { flameTester.test( 'loads correctly', (game) async { - final kicker = Kicker( + final kicker = Kicker.test( side: BoardSide.left, + bloc: KickerCubit(), ); await game.ensureAdd(kicker); @@ -45,58 +61,72 @@ void main() { }, ); - flameTester.test('adds new children', (game) async { - final component = Component(); - final kicker = Kicker( + // TODO(alestiago): Consider refactoring once the following is merged: + // https://github.com/flame-engine/flame/pull/1538 + // ignore: public_member_api_docs + flameTester.test('closes bloc when removed', (game) async { + final bloc = _MockKickerCubit(); + whenListen( + bloc, + const Stream.empty(), + initialState: KickerState.lit, + ); + when(bloc.close).thenAnswer((_) async {}); + final kicker = Kicker.test( side: BoardSide.left, - children: [component], + bloc: bloc, ); + await game.ensureAdd(kicker); - expect(kicker.children, contains(component)); + game.remove(kicker); + await game.ready(); + + verify(bloc.close).called(1); }); - flameTester.test( - 'body is static', - (game) async { + group('adds', () { + flameTester.test('new children', (game) async { + final component = Component(); final kicker = Kicker( side: BoardSide.left, + children: [component], ); await game.ensureAdd(kicker); + expect(kicker.children, contains(component)); + }); - expect(kicker.body.bodyType, equals(BodyType.static)); - }, - ); - - flameTester.test( - 'has restitution', - (game) async { + flameTester.test('a BumpingBehavior', (game) async { final kicker = Kicker( side: BoardSide.left, ); await game.ensureAdd(kicker); - - final totalRestitution = kicker.body.fixtures.fold( - 0, - (total, fixture) => total + fixture.restitution, + expect( + kicker.children.whereType().single, + isNotNull, ); - expect(totalRestitution, greaterThan(0)); - }, - ); + }); - flameTester.test( - 'has no friction', - (game) async { + flameTester.test('a KickerBallContactBehavior', (game) async { final kicker = Kicker( side: BoardSide.left, ); await game.ensureAdd(kicker); + expect( + kicker.children.whereType().single, + isNotNull, + ); + }); - final totalFriction = kicker.body.fixtures.fold( - 0, - (total, fixture) => total + fixture.friction, + flameTester.test('a KickerBlinkingBehavior', (game) async { + final kicker = Kicker( + side: BoardSide.left, ); - expect(totalFriction, equals(0)); - }, - ); + await game.ensureAdd(kicker); + expect( + kicker.children.whereType().single, + isNotNull, + ); + }); + }); }); } diff --git a/test/game/components/bottom_group_test.dart b/test/game/components/bottom_group_test.dart index 3254f155..1d9e58ab 100644 --- a/test/game/components/bottom_group_test.dart +++ b/test/game/components/bottom_group_test.dart @@ -10,6 +10,10 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); final assets = [ + Assets.images.kicker.left.lit.keyName, + Assets.images.kicker.left.dimmed.keyName, + Assets.images.kicker.right.lit.keyName, + Assets.images.kicker.right.dimmed.keyName, Assets.images.baseboard.left.keyName, Assets.images.baseboard.right.keyName, Assets.images.flipper.left.keyName, diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index 6af907d6..687280c0 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -51,8 +51,10 @@ void main() { Assets.images.googleWord.letter4.keyName, Assets.images.googleWord.letter5.keyName, Assets.images.googleWord.letter6.keyName, - Assets.images.kicker.left.keyName, - Assets.images.kicker.right.keyName, + Assets.images.kicker.left.lit.keyName, + Assets.images.kicker.left.dimmed.keyName, + Assets.images.kicker.right.lit.keyName, + Assets.images.kicker.right.dimmed.keyName, Assets.images.launchRamp.ramp.keyName, Assets.images.launchRamp.foregroundRailing.keyName, Assets.images.launchRamp.backgroundRailing.keyName,