From 973375a9b32cde64fc044cafe45acf6fa752326d Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Mon, 9 May 2022 21:02:57 +0100 Subject: [PATCH 1/5] refactor: implemented `FlipperMovingBehavior` (#444) --- .../components/game_bloc_status_listener.dart | 6 +- lib/game/pinball_game.dart | 43 ++-- .../flipper/behaviors/behaviors.dart | 1 + .../flipper_key_controlling_behavior.dart | 12 +- .../behaviors/flipper_moving_behavior.dart | 40 ++++ .../flipper/cubit/flipper_cubit.dart | 11 + .../flipper/cubit/flipper_state.dart | 11 + .../lib/src/components/flipper/flipper.dart | 23 +- .../plunger/cubit/plunger_cubit.dart | 8 +- .../flipper_jointing_behavior_test.dart | 5 +- ...flipper_key_controlling_behavior_test.dart | 210 ++++++++++++------ .../flipper_moving_behavior_test.dart | 101 +++++++++ .../flipper/cubit/flipper_cubit_test.dart | 23 ++ .../src/components/flipper/flipper_test.dart | 26 --- .../game_bloc_status_listener_test.dart | 17 +- test/game/pinball_game_test.dart | 102 +++++---- 16 files changed, 443 insertions(+), 196 deletions(-) create mode 100644 packages/pinball_components/lib/src/components/flipper/behaviors/flipper_moving_behavior.dart create mode 100644 packages/pinball_components/lib/src/components/flipper/cubit/flipper_cubit.dart create mode 100644 packages/pinball_components/lib/src/components/flipper/cubit/flipper_state.dart create mode 100644 packages/pinball_components/test/src/components/flipper/behaviors/flipper_moving_behavior_test.dart create mode 100644 packages/pinball_components/test/src/components/flipper/cubit/flipper_cubit_test.dart diff --git a/lib/game/components/game_bloc_status_listener.dart b/lib/game/components/game_bloc_status_listener.dart index c463cd94..efd085a5 100644 --- a/lib/game/components/game_bloc_status_listener.dart +++ b/lib/game/components/game_bloc_status_listener.dart @@ -31,7 +31,6 @@ class GameBlocStatusListener extends Component .descendants() .whereType() .forEach(_addPlungerBehaviors); - gameRef.overlays.remove(PinballGame.playButtonOverlay); gameRef.overlays.remove(PinballGame.replayButtonOverlay); break; @@ -43,7 +42,6 @@ class GameBlocStatusListener extends Component .state .characterTheme, ); - gameRef .descendants() .whereType() @@ -97,8 +95,8 @@ class GameBlocStatusListener extends Component } void _addFlipperBehaviors(Flipper flipper) => flipper - ..add(FlipperKeyControllingBehavior()) - ..moveDown(); + .firstChild>()! + .add(FlipperKeyControllingBehavior()); void _removeFlipperBehaviors(Flipper flipper) => flipper .descendants() diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index c102eb0b..ad81425f 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -169,13 +169,18 @@ class PinballGame extends PinballForge2DGame .bloc .pulled(); } else { - final leftSide = info.eventPosition.widget.x < canvasSize.x / 2; + final tappedLeftSide = info.eventPosition.widget.x < canvasSize.x / 2; focusedBoardSide[pointerId] = - leftSide ? BoardSide.left : BoardSide.right; - final flippers = descendants().whereType().where((flipper) { - return flipper.side == focusedBoardSide[pointerId]; - }); - flippers.first.moveUp(); + tappedLeftSide ? BoardSide.left : BoardSide.right; + final flippers = descendants() + .whereType() + .where((flipper) => flipper.side == focusedBoardSide[pointerId]); + for (final flipper in flippers) { + flipper + .descendants() + .whereType>() + .forEach((provider) => provider.bloc.moveUp()); + } } } @@ -196,11 +201,15 @@ class PinballGame extends PinballForge2DGame void _moveFlippersDown(int pointerId) { if (focusedBoardSide[pointerId] != null) { - final flippers = descendants().whereType().where((flipper) { - return flipper.side == focusedBoardSide[pointerId]; - }); - flippers.first.moveDown(); - focusedBoardSide.remove(pointerId); + final flippers = descendants() + .whereType() + .where((flipper) => flipper.side == focusedBoardSide[pointerId]); + for (final flipper in flippers) { + flipper + .descendants() + .whereType>() + .forEach((provider) => provider.bloc.moveDown()); + } } } } @@ -230,9 +239,7 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector { @override Future onLoad() async { await super.onLoad(); - await add(PreviewLine()); - - await add(_DebugInformation()); + await addAll([PreviewLine(), _DebugInformation()]); } @override @@ -247,14 +254,10 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector { } @override - void onPanStart(DragStartInfo info) { - lineStart = info.eventPosition.game; - } + void onPanStart(DragStartInfo info) => lineStart = info.eventPosition.game; @override - void onPanUpdate(DragUpdateInfo info) { - lineEnd = info.eventPosition.game; - } + void onPanUpdate(DragUpdateInfo info) => lineEnd = info.eventPosition.game; @override void onPanEnd(DragEndInfo info) { diff --git a/packages/pinball_components/lib/src/components/flipper/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/flipper/behaviors/behaviors.dart index ef3630e7..d3743ae9 100644 --- a/packages/pinball_components/lib/src/components/flipper/behaviors/behaviors.dart +++ b/packages/pinball_components/lib/src/components/flipper/behaviors/behaviors.dart @@ -1,2 +1,3 @@ export 'flipper_jointing_behavior.dart'; export 'flipper_key_controlling_behavior.dart'; +export 'flipper_moving_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/flipper/behaviors/flipper_key_controlling_behavior.dart b/packages/pinball_components/lib/src/components/flipper/behaviors/flipper_key_controlling_behavior.dart index ca4fcece..b002420a 100644 --- a/packages/pinball_components/lib/src/components/flipper/behaviors/flipper_key_controlling_behavior.dart +++ b/packages/pinball_components/lib/src/components/flipper/behaviors/flipper_key_controlling_behavior.dart @@ -1,11 +1,11 @@ import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flutter/services.dart'; import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; /// Allows controlling the [Flipper]'s movement with keyboard input. class FlipperKeyControllingBehavior extends Component - with KeyboardHandler, ParentIsA { + with KeyboardHandler, FlameBlocReader { /// The [LogicalKeyboardKey]s that will control the [Flipper]. /// /// [onKeyEvent] method listens to when one of these keys is pressed. @@ -14,8 +14,8 @@ class FlipperKeyControllingBehavior extends Component @override Future onLoad() async { await super.onLoad(); - - switch (parent.side) { + final flipper = parent!.parent! as Flipper; + switch (flipper.side) { case BoardSide.left: _keys = [ LogicalKeyboardKey.arrowLeft, @@ -39,9 +39,9 @@ class FlipperKeyControllingBehavior extends Component if (!_keys.contains(event.logicalKey)) return true; if (event is RawKeyDownEvent) { - parent.moveUp(); + bloc.moveUp(); } else if (event is RawKeyUpEvent) { - parent.moveDown(); + bloc.moveDown(); } return false; diff --git a/packages/pinball_components/lib/src/components/flipper/behaviors/flipper_moving_behavior.dart b/packages/pinball_components/lib/src/components/flipper/behaviors/flipper_moving_behavior.dart new file mode 100644 index 00000000..13989192 --- /dev/null +++ b/packages/pinball_components/lib/src/components/flipper/behaviors/flipper_moving_behavior.dart @@ -0,0 +1,40 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:pinball_components/pinball_components.dart'; + +class FlipperMovingBehavior extends Component + with + FlameBlocListenable, + FlameBlocReader { + FlipperMovingBehavior({ + required double strength, + }) : assert(strength >= 0, "Strength can't be negative"), + _strength = strength; + + final double _strength; + + late final Flipper _flipper; + + void _moveUp() => _flipper.body.linearVelocity = Vector2(0, -_strength); + + void _moveDown() => _flipper.body.linearVelocity = Vector2(0, _strength); + + @override + void onNewState(FlipperState state) { + super.onNewState(state); + if (bloc.state.isMovingDown) _moveDown(); + } + + @override + void update(double dt) { + super.update(dt); + if (bloc.state.isMovingUp) _moveUp(); + } + + @override + Future onLoad() async { + await super.onLoad(); + _flipper = parent!.parent! as Flipper; + _moveDown(); + } +} diff --git a/packages/pinball_components/lib/src/components/flipper/cubit/flipper_cubit.dart b/packages/pinball_components/lib/src/components/flipper/cubit/flipper_cubit.dart new file mode 100644 index 00000000..21ddb2d4 --- /dev/null +++ b/packages/pinball_components/lib/src/components/flipper/cubit/flipper_cubit.dart @@ -0,0 +1,11 @@ +import 'package:bloc/bloc.dart'; + +part 'flipper_state.dart'; + +class FlipperCubit extends Cubit { + FlipperCubit() : super(FlipperState.movingDown); + + void moveUp() => emit(FlipperState.movingUp); + + void moveDown() => emit(FlipperState.movingDown); +} diff --git a/packages/pinball_components/lib/src/components/flipper/cubit/flipper_state.dart b/packages/pinball_components/lib/src/components/flipper/cubit/flipper_state.dart new file mode 100644 index 00000000..e14a495e --- /dev/null +++ b/packages/pinball_components/lib/src/components/flipper/cubit/flipper_state.dart @@ -0,0 +1,11 @@ +part of 'flipper_cubit.dart'; + +enum FlipperState { + movingDown, + movingUp, +} + +extension FlipperStateX on FlipperState { + bool get isMovingDown => this == FlipperState.movingDown; + bool get isMovingUp => this == FlipperState.movingUp; +} diff --git a/packages/pinball_components/lib/src/components/flipper/flipper.dart b/packages/pinball_components/lib/src/components/flipper/flipper.dart index 280c157f..265e7924 100644 --- a/packages/pinball_components/lib/src/components/flipper/flipper.dart +++ b/packages/pinball_components/lib/src/components/flipper/flipper.dart @@ -1,11 +1,13 @@ import 'dart:async'; import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/foundation.dart'; import 'package:pinball_components/pinball_components.dart'; export 'behaviors/behaviors.dart'; +export 'cubit/flipper_cubit.dart'; /// {@template flipper} /// A bat, typically found in pairs at the bottom of the board. @@ -21,6 +23,10 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition { children: [ _FlipperSpriteComponent(side: side), FlipperJointingBehavior(), + FlameBlocProvider( + create: FlipperCubit.new, + children: [FlipperMovingBehavior(strength: 90)], + ), ], ); @@ -33,29 +39,12 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition { /// The size of the [Flipper]. static final size = Vector2(13.5, 4.3); - /// The speed required to move the [Flipper] to its highest position. - /// - /// The higher the value, the faster the [Flipper] will move. - static const double _speed = 90; - /// Whether the [Flipper] is on the left or right side of the board. /// /// A [Flipper] with [BoardSide.left] has a counter-clockwise arc motion, /// whereas a [Flipper] with [BoardSide.right] has a clockwise arc motion. final BoardSide side; - /// Applies downward linear velocity to the [Flipper], moving it to its - /// resting position. - void moveDown() { - body.linearVelocity = Vector2(0, _speed); - } - - /// Applies upward linear velocity to the [Flipper], moving it to its highest - /// position. - void moveUp() { - body.linearVelocity = Vector2(0, -_speed); - } - List _createFixtureDefs() { final direction = side.direction; diff --git a/packages/pinball_components/lib/src/components/plunger/cubit/plunger_cubit.dart b/packages/pinball_components/lib/src/components/plunger/cubit/plunger_cubit.dart index 601257d2..ce845197 100644 --- a/packages/pinball_components/lib/src/components/plunger/cubit/plunger_cubit.dart +++ b/packages/pinball_components/lib/src/components/plunger/cubit/plunger_cubit.dart @@ -5,11 +5,7 @@ part 'plunger_state.dart'; class PlungerCubit extends Cubit { PlungerCubit() : super(PlungerState.releasing); - void pulled() { - emit(PlungerState.pulling); - } + void pulled() => emit(PlungerState.pulling); - void released() { - emit(PlungerState.releasing); - } + void released() => emit(PlungerState.releasing); } diff --git a/packages/pinball_components/test/src/components/flipper/behaviors/flipper_jointing_behavior_test.dart b/packages/pinball_components/test/src/components/flipper/behaviors/flipper_jointing_behavior_test.dart index 70c93439..3128d286 100644 --- a/packages/pinball_components/test/src/components/flipper/behaviors/flipper_jointing_behavior_test.dart +++ b/packages/pinball_components/test/src/components/flipper/behaviors/flipper_jointing_behavior_test.dart @@ -1,15 +1,14 @@ // ignore_for_file: cascade_invocations +import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pinball_components/src/components/components.dart'; -import '../../../../helpers/helpers.dart'; - void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('FlipperJointingBehavior', () { - final flameTester = FlameTester(TestGame.new); + final flameTester = FlameTester(Forge2DGame.new); test('can be instantiated', () { expect( diff --git a/packages/pinball_components/test/src/components/flipper/behaviors/flipper_key_controlling_behavior_test.dart b/packages/pinball_components/test/src/components/flipper/behaviors/flipper_key_controlling_behavior_test.dart index 6a6ac91c..307264f0 100644 --- a/packages/pinball_components/test/src/components/flipper/behaviors/flipper_key_controlling_behavior_test.dart +++ b/packages/pinball_components/test/src/components/flipper/behaviors/flipper_key_controlling_behavior_test.dart @@ -1,5 +1,7 @@ // ignore_for_file: cascade_invocations +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter/foundation.dart'; @@ -9,6 +11,25 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball_components/pinball_components.dart'; +class _TestGame extends Forge2DGame { + Future pump( + FlipperKeyControllingBehavior behavior, { + required BoardSide side, + FlipperCubit? flipperBloc, + }) async { + final flipper = Flipper.test(side: side); + await ensureAdd(flipper); + await flipper.ensureAdd( + FlameBlocProvider.value( + value: flipperBloc ?? FlipperCubit(), + children: [behavior], + ), + ); + } +} + +class _MockFlipperCubit extends Mock implements FlipperCubit {} + class _MockRawKeyDownEvent extends Mock implements RawKeyDownEvent { @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { @@ -26,26 +47,32 @@ class _MockRawKeyUpEvent extends Mock implements RawKeyUpEvent { void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('FlipperKeyControllingBehavior', () { - final flameTester = FlameTester(Forge2DGame.new); + final flameTester = FlameTester(_TestGame.new); group( 'onKeyEvent', () { - late Flipper rightFlipper; - late Flipper leftFlipper; + late FlipperCubit flipperBloc; setUp(() { - rightFlipper = Flipper.test(side: BoardSide.right); - leftFlipper = Flipper.test(side: BoardSide.left); + flipperBloc = _MockFlipperCubit(); + whenListen( + flipperBloc, + const Stream.empty(), + initialState: FlipperState.movingDown, + ); }); group('on right Flipper', () { flameTester.test( 'moves upwards when right arrow is pressed', (game) async { - await game.ensureAdd(rightFlipper); final behavior = FlipperKeyControllingBehavior(); - await rightFlipper.ensureAdd(behavior); + await game.pump( + behavior, + flipperBloc: flipperBloc, + side: BoardSide.right, + ); final event = _MockRawKeyDownEvent(); when(() => event.logicalKey).thenReturn( @@ -54,17 +81,20 @@ void main() { behavior.onKeyEvent(event, {}); - expect(rightFlipper.body.linearVelocity.y, isNegative); - expect(rightFlipper.body.linearVelocity.x, isZero); + await Future.delayed(Duration.zero); + verify(flipperBloc.moveUp).called(1); }, ); flameTester.test( 'moves downwards when right arrow is released', (game) async { - await game.ensureAdd(rightFlipper); final behavior = FlipperKeyControllingBehavior(); - await rightFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.right, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyUpEvent(); when(() => event.logicalKey).thenReturn( @@ -73,17 +103,20 @@ void main() { behavior.onKeyEvent(event, {}); - expect(rightFlipper.body.linearVelocity.y, isPositive); - expect(rightFlipper.body.linearVelocity.x, isZero); + await Future.delayed(Duration.zero); + verify(flipperBloc.moveDown).called(1); }, ); flameTester.test( 'moves upwards when D is pressed', (game) async { - await game.ensureAdd(rightFlipper); final behavior = FlipperKeyControllingBehavior(); - await rightFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.right, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyDownEvent(); when(() => event.logicalKey).thenReturn( @@ -92,17 +125,20 @@ void main() { behavior.onKeyEvent(event, {}); - expect(rightFlipper.body.linearVelocity.y, isNegative); - expect(rightFlipper.body.linearVelocity.x, isZero); + await Future.delayed(Duration.zero); + verify(flipperBloc.moveUp).called(1); }, ); flameTester.test( 'moves downwards when D is released', (game) async { - await game.ensureAdd(rightFlipper); final behavior = FlipperKeyControllingBehavior(); - await rightFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.right, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyUpEvent(); when(() => event.logicalKey).thenReturn( @@ -111,8 +147,8 @@ void main() { behavior.onKeyEvent(event, {}); - expect(rightFlipper.body.linearVelocity.y, isPositive); - expect(rightFlipper.body.linearVelocity.x, isZero); + await Future.delayed(Duration.zero); + verify(flipperBloc.moveDown).called(1); }, ); @@ -120,9 +156,12 @@ void main() { flameTester.test( 'left arrow is pressed', (game) async { - await game.ensureAdd(rightFlipper); final behavior = FlipperKeyControllingBehavior(); - await rightFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.right, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyDownEvent(); when(() => event.logicalKey).thenReturn( @@ -131,17 +170,20 @@ void main() { behavior.onKeyEvent(event, {}); - expect(rightFlipper.body.linearVelocity.y, isZero); - expect(rightFlipper.body.linearVelocity.x, isZero); + verifyNever(flipperBloc.moveDown); + verifyNever(flipperBloc.moveUp); }, ); flameTester.test( 'left arrow is released', (game) async { - await game.ensureAdd(rightFlipper); final behavior = FlipperKeyControllingBehavior(); - await rightFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.right, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyUpEvent(); when(() => event.logicalKey).thenReturn( @@ -150,17 +192,20 @@ void main() { behavior.onKeyEvent(event, {}); - expect(rightFlipper.body.linearVelocity.y, isZero); - expect(rightFlipper.body.linearVelocity.x, isZero); + verifyNever(flipperBloc.moveDown); + verifyNever(flipperBloc.moveUp); }, ); flameTester.test( 'A is pressed', (game) async { - await game.ensureAdd(rightFlipper); final behavior = FlipperKeyControllingBehavior(); - await rightFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.right, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyDownEvent(); when(() => event.logicalKey).thenReturn( @@ -169,17 +214,20 @@ void main() { behavior.onKeyEvent(event, {}); - expect(rightFlipper.body.linearVelocity.y, isZero); - expect(rightFlipper.body.linearVelocity.x, isZero); + verifyNever(flipperBloc.moveDown); + verifyNever(flipperBloc.moveUp); }, ); flameTester.test( 'A is released', (game) async { - await game.ensureAdd(rightFlipper); final behavior = FlipperKeyControllingBehavior(); - await rightFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.right, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyUpEvent(); when(() => event.logicalKey).thenReturn( @@ -188,8 +236,8 @@ void main() { behavior.onKeyEvent(event, {}); - expect(rightFlipper.body.linearVelocity.y, isZero); - expect(rightFlipper.body.linearVelocity.x, isZero); + verifyNever(flipperBloc.moveDown); + verifyNever(flipperBloc.moveUp); }, ); }); @@ -199,9 +247,12 @@ void main() { flameTester.test( 'moves upwards when left arrow is pressed', (game) async { - await game.ensureAdd(leftFlipper); final behavior = FlipperKeyControllingBehavior(); - await leftFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.left, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyDownEvent(); when(() => event.logicalKey).thenReturn( @@ -210,17 +261,20 @@ void main() { behavior.onKeyEvent(event, {}); - expect(leftFlipper.body.linearVelocity.y, isNegative); - expect(leftFlipper.body.linearVelocity.x, isZero); + await Future.delayed(Duration.zero); + verify(flipperBloc.moveUp).called(1); }, ); flameTester.test( 'moves downwards when left arrow is released', (game) async { - await game.ensureAdd(leftFlipper); final behavior = FlipperKeyControllingBehavior(); - await leftFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.left, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyUpEvent(); when(() => event.logicalKey).thenReturn( @@ -229,17 +283,20 @@ void main() { behavior.onKeyEvent(event, {}); - expect(leftFlipper.body.linearVelocity.y, isPositive); - expect(leftFlipper.body.linearVelocity.x, isZero); + await Future.delayed(Duration.zero); + verify(flipperBloc.moveDown).called(1); }, ); flameTester.test( 'moves upwards when A is pressed', (game) async { - await game.ensureAdd(leftFlipper); final behavior = FlipperKeyControllingBehavior(); - await leftFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.left, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyDownEvent(); when(() => event.logicalKey).thenReturn( @@ -248,17 +305,20 @@ void main() { behavior.onKeyEvent(event, {}); - expect(leftFlipper.body.linearVelocity.y, isNegative); - expect(leftFlipper.body.linearVelocity.x, isZero); + await Future.delayed(Duration.zero); + verify(flipperBloc.moveUp).called(1); }, ); flameTester.test( 'moves downwards when A is released', (game) async { - await game.ensureAdd(leftFlipper); final behavior = FlipperKeyControllingBehavior(); - await leftFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.left, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyUpEvent(); when(() => event.logicalKey).thenReturn( @@ -267,8 +327,8 @@ void main() { behavior.onKeyEvent(event, {}); - expect(leftFlipper.body.linearVelocity.y, isPositive); - expect(leftFlipper.body.linearVelocity.x, isZero); + await Future.delayed(Duration.zero); + verify(flipperBloc.moveDown).called(1); }, ); @@ -276,9 +336,12 @@ void main() { flameTester.test( 'right arrow is pressed', (game) async { - await game.ensureAdd(leftFlipper); final behavior = FlipperKeyControllingBehavior(); - await leftFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.left, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyDownEvent(); when(() => event.logicalKey).thenReturn( @@ -287,17 +350,20 @@ void main() { behavior.onKeyEvent(event, {}); - expect(leftFlipper.body.linearVelocity.y, isZero); - expect(leftFlipper.body.linearVelocity.x, isZero); + verifyNever(flipperBloc.moveDown); + verifyNever(flipperBloc.moveUp); }, ); flameTester.test( 'right arrow is released', (game) async { - await game.ensureAdd(leftFlipper); final behavior = FlipperKeyControllingBehavior(); - await leftFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.left, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyUpEvent(); when(() => event.logicalKey).thenReturn( @@ -306,17 +372,20 @@ void main() { behavior.onKeyEvent(event, {}); - expect(leftFlipper.body.linearVelocity.y, isZero); - expect(leftFlipper.body.linearVelocity.x, isZero); + verifyNever(flipperBloc.moveDown); + verifyNever(flipperBloc.moveUp); }, ); flameTester.test( 'D is pressed', (game) async { - await game.ensureAdd(leftFlipper); final behavior = FlipperKeyControllingBehavior(); - await leftFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.left, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyDownEvent(); when(() => event.logicalKey).thenReturn( @@ -325,17 +394,20 @@ void main() { behavior.onKeyEvent(event, {}); - expect(leftFlipper.body.linearVelocity.y, isZero); - expect(leftFlipper.body.linearVelocity.x, isZero); + verifyNever(flipperBloc.moveDown); + verifyNever(flipperBloc.moveUp); }, ); flameTester.test( 'D is released', (game) async { - await game.ensureAdd(leftFlipper); final behavior = FlipperKeyControllingBehavior(); - await leftFlipper.ensureAdd(behavior); + await game.pump( + behavior, + side: BoardSide.left, + flipperBloc: flipperBloc, + ); final event = _MockRawKeyUpEvent(); when(() => event.logicalKey).thenReturn( @@ -344,8 +416,8 @@ void main() { behavior.onKeyEvent(event, {}); - expect(leftFlipper.body.linearVelocity.y, isZero); - expect(leftFlipper.body.linearVelocity.x, isZero); + verifyNever(flipperBloc.moveDown); + verifyNever(flipperBloc.moveUp); }, ); }); diff --git a/packages/pinball_components/test/src/components/flipper/behaviors/flipper_moving_behavior_test.dart b/packages/pinball_components/test/src/components/flipper/behaviors/flipper_moving_behavior_test.dart new file mode 100644 index 00000000..be48d795 --- /dev/null +++ b/packages/pinball_components/test/src/components/flipper/behaviors/flipper_moving_behavior_test.dart @@ -0,0 +1,101 @@ +// ignore_for_file: avoid_dynamic_calls, cascade_invocations + +import 'dart:async'; + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame_bloc/flame_bloc.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'; + +class _TestGame extends Forge2DGame { + Future pump( + FlipperMovingBehavior behavior, { + FlipperCubit? flipperBloc, + }) async { + final flipper = Flipper.test(side: BoardSide.left); + await ensureAdd(flipper); + await flipper.ensureAdd( + FlameBlocProvider.value( + value: flipperBloc ?? FlipperCubit(), + children: [behavior], + ), + ); + } +} + +class _MockFlipperCubit extends Mock implements FlipperCubit {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(_TestGame.new); + + group('FlipperMovingBehavior', () { + test('can be instantiated', () { + expect( + FlipperMovingBehavior(strength: 0), + isA(), + ); + }); + + test('throws assertion error when strength is negative', () { + expect( + () => FlipperMovingBehavior(strength: -1), + throwsAssertionError, + ); + }); + + flameTester.test('can be loaded', (game) async { + final behavior = FlipperMovingBehavior(strength: 0); + await game.pump(behavior); + expect(game.descendants(), contains(behavior)); + }); + + flameTester.test( + 'applies vertical velocity to flipper when moving down', + (game) async { + final bloc = _MockFlipperCubit(); + final streamController = StreamController(); + whenListen( + bloc, + streamController.stream, + initialState: FlipperState.movingUp, + ); + + const strength = 10.0; + final behavior = FlipperMovingBehavior(strength: strength); + await game.pump(behavior, flipperBloc: bloc); + + streamController.add(FlipperState.movingDown); + await Future.delayed(Duration.zero); + + final flipper = behavior.ancestors().whereType().single; + expect(flipper.body.linearVelocity.x, 0); + expect(flipper.body.linearVelocity.y, strength); + }, + ); + + flameTester.test( + 'applies vertical velocity to flipper when moving up', + (game) async { + final bloc = _MockFlipperCubit(); + whenListen( + bloc, + Stream.value(FlipperState.movingUp), + initialState: FlipperState.movingUp, + ); + + const strength = 10.0; + final behavior = FlipperMovingBehavior(strength: strength); + await game.pump(behavior, flipperBloc: bloc); + game.update(0); + + final flipper = behavior.ancestors().whereType().single; + expect(flipper.body.linearVelocity.x, 0); + expect(flipper.body.linearVelocity.y, -strength); + }, + ); + }); +} diff --git a/packages/pinball_components/test/src/components/flipper/cubit/flipper_cubit_test.dart b/packages/pinball_components/test/src/components/flipper/cubit/flipper_cubit_test.dart new file mode 100644 index 00000000..6cd9c591 --- /dev/null +++ b/packages/pinball_components/test/src/components/flipper/cubit/flipper_cubit_test.dart @@ -0,0 +1,23 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +void main() { + group('FlipperCubit', () { + test('can be instantiated', () { + expect(FlipperCubit(), isA()); + }); + + blocTest( + 'moves', + build: FlipperCubit.new, + act: (cubit) => cubit + ..moveUp() + ..moveDown(), + expect: () => [ + FlipperState.movingUp, + FlipperState.movingDown, + ], + ); + }); +} diff --git a/packages/pinball_components/test/src/components/flipper/flipper_test.dart b/packages/pinball_components/test/src/components/flipper/flipper_test.dart index 4569f3ec..0aba00bc 100644 --- a/packages/pinball_components/test/src/components/flipper/flipper_test.dart +++ b/packages/pinball_components/test/src/components/flipper/flipper_test.dart @@ -128,31 +128,5 @@ void main() { }, ); }); - - flameTester.test( - 'moveDown applies downward velocity', - (game) async { - final flipper = Flipper(side: BoardSide.left); - await game.ensureAdd(flipper); - - expect(flipper.body.linearVelocity, equals(Vector2.zero())); - flipper.moveDown(); - - expect(flipper.body.linearVelocity.y, isPositive); - }, - ); - - flameTester.test( - 'moveUp applies upward velocity', - (game) async { - final flipper = Flipper(side: BoardSide.left); - await game.ensureAdd(flipper); - - expect(flipper.body.linearVelocity, equals(Vector2.zero())); - flipper.moveUp(); - - expect(flipper.body.linearVelocity.y, isNegative); - }, - ); }); } diff --git a/test/game/components/game_bloc_status_listener_test.dart b/test/game/components/game_bloc_status_listener_test.dart index 874f901c..47a273ba 100644 --- a/test/game/components/game_bloc_status_listener_test.dart +++ b/test/game/components/game_bloc_status_listener_test.dart @@ -86,6 +86,8 @@ class _MockPlungerCubit extends Mock implements PlungerCubit {} class _MockGoogleWordCubit extends Mock implements GoogleWordCubit {} +class _MockFlipperCubit extends Mock implements FlipperCubit {} + class _MockAppLocalizations extends Mock implements AppLocalizations { @override String get score => ''; @@ -199,7 +201,12 @@ void main() { final behavior = FlipperKeyControllingBehavior(); await game.pump([component, backbox, flipper]); - await flipper.ensureAdd(behavior); + await flipper.ensureAdd( + FlameBlocProvider( + create: _MockFlipperCubit.new, + children: [behavior], + ), + ); expect(state.status, GameStatus.gameOver); component.onNewState(state); @@ -366,12 +373,18 @@ void main() { final flipper = Flipper.test(side: BoardSide.left); await game.pump([component, backbox, flipper]); + await flipper.ensureAdd( + FlameBlocProvider( + create: _MockFlipperCubit.new, + ), + ); component.onNewState(state); await game.ready(); expect( - flipper.children + flipper + .descendants() .whereType() .length, equals(1), diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index a95f329b..b60ebbad 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -259,13 +259,19 @@ void main() { when(() => tapDownEvent.eventPosition).thenReturn(eventPosition); when(() => tapDownEvent.raw).thenReturn(raw); - final flippers = game.descendants().whereType().where( - (flipper) => flipper.side == BoardSide.left, - ); - game.onTapDown(0, tapDownEvent); + await Future.delayed(Duration.zero); - expect(flippers.first.body.linearVelocity.y, isNegative); + final flipperBloc = game + .descendants() + .whereType() + .where((flipper) => flipper.side == BoardSide.left) + .single + .descendants() + .whereType>() + .first + .bloc; + expect(flipperBloc.state, FlipperState.movingUp); }); flameTester.test('tap down moves right flipper up', (game) async { @@ -282,13 +288,19 @@ void main() { when(() => tapDownEvent.eventPosition).thenReturn(eventPosition); when(() => tapDownEvent.raw).thenReturn(raw); - final flippers = game.descendants().whereType().where( - (flipper) => flipper.side == BoardSide.right, - ); - game.onTapDown(0, tapDownEvent); + final flipperBloc = game + .descendants() + .whereType() + .where((flipper) => flipper.side == BoardSide.right) + .single + .descendants() + .whereType>() + .first + .bloc; - expect(flippers.first.body.linearVelocity.y, isNegative); + await Future.delayed(Duration.zero); + expect(flipperBloc.state, FlipperState.movingUp); }); flameTester.test('tap up moves flipper down', (game) async { @@ -298,28 +310,22 @@ void main() { when(() => eventPosition.game).thenReturn(Vector2.zero()); when(() => eventPosition.widget).thenReturn(Vector2.zero()); - final raw = _MockTapDownDetails(); - when(() => raw.kind).thenReturn(PointerDeviceKind.touch); - - final tapDownEvent = _MockTapDownInfo(); - when(() => tapDownEvent.eventPosition).thenReturn(eventPosition); - when(() => tapDownEvent.raw).thenReturn(raw); - - final flippers = game.descendants().whereType().where( - (flipper) => flipper.side == BoardSide.left, - ); - - game.onTapDown(0, tapDownEvent); - - expect(flippers.first.body.linearVelocity.y, isNegative); - final tapUpEvent = _MockTapUpInfo(); when(() => tapUpEvent.eventPosition).thenReturn(eventPosition); game.onTapUp(0, tapUpEvent); await game.ready(); - expect(flippers.first.body.linearVelocity.y, isPositive); + final flipperBloc = game + .descendants() + .whereType() + .where((flipper) => flipper.side == BoardSide.left) + .single + .descendants() + .whereType>() + .first + .bloc; + expect(flipperBloc.state, FlipperState.movingDown); }); flameTester.test('tap cancel moves flipper down', (game) async { @@ -336,17 +342,19 @@ void main() { when(() => tapDownEvent.eventPosition).thenReturn(eventPosition); when(() => tapDownEvent.raw).thenReturn(raw); - final flippers = game.descendants().whereType().where( - (flipper) => flipper.side == BoardSide.left, - ); + final flipperBloc = game + .descendants() + .whereType() + .where((flipper) => flipper.side == BoardSide.left) + .single + .descendants() + .whereType>() + .first + .bloc; game.onTapDown(0, tapDownEvent); - - expect(flippers.first.body.linearVelocity.y, isNegative); - game.onTapCancel(0); - - expect(flippers.first.body.linearVelocity.y, isPositive); + expect(flipperBloc.state, FlipperState.movingDown); }); flameTester.test( @@ -375,17 +383,25 @@ void main() { .thenReturn(rightEventPosition); when(() => rightTapDownEvent.raw).thenReturn(raw); - final flippers = game.descendants().whereType(); - final rightFlipper = flippers.elementAt(0); - final leftFlipper = flippers.elementAt(1); - game.onTapDown(0, leftTapDownEvent); game.onTapDown(1, rightTapDownEvent); - expect(leftFlipper.body.linearVelocity.y, isNegative); - expect(leftFlipper.side, equals(BoardSide.left)); - expect(rightFlipper.body.linearVelocity.y, isNegative); - expect(rightFlipper.side, equals(BoardSide.right)); + final flippers = game.descendants().whereType(); + final rightFlipper = flippers.elementAt(0); + final leftFlipper = flippers.elementAt(1); + final leftFlipperBloc = leftFlipper + .descendants() + .whereType>() + .first + .bloc; + final rightFlipperBloc = rightFlipper + .descendants() + .whereType>() + .first + .bloc; + + expect(leftFlipperBloc.state, equals(FlipperState.movingUp)); + expect(rightFlipperBloc.state, equals(FlipperState.movingUp)); expect( game.focusedBoardSide, @@ -396,7 +412,7 @@ void main() { }); group('plunger control', () { - flameTester.test('tap down emits plunging', (game) async { + flameTester.test('plunger control tap down emits plunging', (game) async { await game.ready(); final eventPosition = _MockEventPosition(); From 4ad59a795a32c6a320335ae055a5ee7adff2b7f2 Mon Sep 17 00:00:00 2001 From: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> Date: Mon, 9 May 2022 15:23:18 -0500 Subject: [PATCH 2/5] chore: fix sandbox and asset cache loading (#445) --- .../behaviors/ball_turbo_charging_behavior.dart | 2 +- .../lib/src/components/boundaries.dart | 4 ++-- .../lib/src/components/plunger/plunger.dart | 2 +- .../android_acres/android_spaceship_game.dart | 14 +++++++++----- .../lib/stories/launch_ramp/launch_ramp_game.dart | 5 +++++ .../sandbox/lib/stories/plunger/plunger_game.dart | 15 ++++++++++++++- packages/pinball_components/sandbox/pubspec.lock | 2 +- packages/pinball_components/sandbox/pubspec.yaml | 1 + .../ball_turbo_charging_behavior_test.dart | 7 +++++-- .../test/src/components/plunger/plunger_test.dart | 5 ++++- 10 files changed, 43 insertions(+), 14 deletions(-) diff --git a/packages/pinball_components/lib/src/components/ball/behaviors/ball_turbo_charging_behavior.dart b/packages/pinball_components/lib/src/components/ball/behaviors/ball_turbo_charging_behavior.dart index f1e5a855..006282ce 100644 --- a/packages/pinball_components/lib/src/components/ball/behaviors/ball_turbo_charging_behavior.dart +++ b/packages/pinball_components/lib/src/components/ball/behaviors/ball_turbo_charging_behavior.dart @@ -57,7 +57,7 @@ class _TurboChargeSpriteAnimationComponent extends SpriteAnimationComponent Future onLoad() async { await super.onLoad(); - final spriteSheet = await gameRef.images.load( + final spriteSheet = gameRef.images.fromCache( Assets.images.ball.flameEffect.keyName, ); diff --git a/packages/pinball_components/lib/src/components/boundaries.dart b/packages/pinball_components/lib/src/components/boundaries.dart index 4be27cef..d1ad34e0 100644 --- a/packages/pinball_components/lib/src/components/boundaries.dart +++ b/packages/pinball_components/lib/src/components/boundaries.dart @@ -109,7 +109,7 @@ class _OuterBoundary extends BodyComponent with InitialPosition, ZIndex { final topLeftCurve = BezierCurveShape( controlPoints: [ - topWall.vertex1, + topWall.vertex2, Vector2(-31.5, -69.9), Vector2(-32.3, -57.2), ], @@ -123,7 +123,7 @@ class _OuterBoundary extends BodyComponent with InitialPosition, ZIndex { final upperLeftWallCurve = BezierCurveShape( controlPoints: [ - topLeftWall.vertex1, + topLeftWall.vertex2, Vector2(-33.9, -40.7), Vector2(-32.5, -39), ], diff --git a/packages/pinball_components/lib/src/components/plunger/plunger.dart b/packages/pinball_components/lib/src/components/plunger/plunger.dart index 9f3b6873..fbb7a437 100644 --- a/packages/pinball_components/lib/src/components/plunger/plunger.dart +++ b/packages/pinball_components/lib/src/components/plunger/plunger.dart @@ -108,7 +108,7 @@ class _PlungerSpriteAnimationGroupComponent @override Future onLoad() async { await super.onLoad(); - final spriteSheet = await gameRef.images.load( + final spriteSheet = gameRef.images.fromCache( Assets.images.plunger.plunger.keyName, ); const amountPerRow = 20; diff --git a/packages/pinball_components/sandbox/lib/stories/android_acres/android_spaceship_game.dart b/packages/pinball_components/sandbox/lib/stories/android_acres/android_spaceship_game.dart index 185f5351..c7c9b76c 100644 --- a/packages/pinball_components/sandbox/lib/stories/android_acres/android_spaceship_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/android_acres/android_spaceship_game.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flame/input.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; @@ -29,11 +30,14 @@ class AndroidSpaceshipGame extends BallGame { await super.onLoad(); camera.followVector2(Vector2.zero()); - await addAll( - [ - AndroidSpaceship(position: Vector2.zero()), - AndroidAnimatronic(), - ], + await add( + FlameBlocProvider( + create: AndroidSpaceshipCubit.new, + children: [ + AndroidSpaceship(position: Vector2.zero()), + AndroidAnimatronic(), + ], + ), ); await traceAllBodies(); diff --git a/packages/pinball_components/sandbox/lib/stories/launch_ramp/launch_ramp_game.dart b/packages/pinball_components/sandbox/lib/stories/launch_ramp/launch_ramp_game.dart index c1d435d5..fef1a145 100644 --- a/packages/pinball_components/sandbox/lib/stories/launch_ramp/launch_ramp_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/launch_ramp/launch_ramp_game.dart @@ -10,6 +10,11 @@ class LaunchRampGame extends BallGame { : super( ballPriority: ZIndexes.ballOnLaunchRamp, ballLayer: Layer.launcher, + imagesFileNames: [ + Assets.images.launchRamp.ramp.keyName, + Assets.images.launchRamp.backgroundRailing.keyName, + Assets.images.launchRamp.foregroundRailing.keyName, + ], ); static const description = ''' 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 50af919f..328afce4 100644 --- a/packages/pinball_components/sandbox/lib/stories/plunger/plunger_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/plunger/plunger_game.dart @@ -1,10 +1,18 @@ import 'package:flame/input.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/common/common.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; class PlungerGame extends BallGame with HasKeyboardHandlerComponents, Traceable { + PlungerGame() + : super( + imagesFileNames: [ + Assets.images.plunger.plunger.keyName, + ], + ); + static const description = ''' Shows how Plunger is rendered. @@ -19,7 +27,12 @@ class PlungerGame extends BallGame final center = screenToWorld(camera.viewport.canvasSize! / 2); final plunger = Plunger() ..initialPosition = Vector2(center.x - 8.8, center.y); - await add(plunger); + await add( + FlameBlocProvider( + create: PlungerCubit.new, + children: [plunger], + ), + ); await plunger.add(PlungerKeyControllingBehavior()); await traceAllBodies(); diff --git a/packages/pinball_components/sandbox/pubspec.lock b/packages/pinball_components/sandbox/pubspec.lock index 9fcb0f89..5e4c6061 100644 --- a/packages/pinball_components/sandbox/pubspec.lock +++ b/packages/pinball_components/sandbox/pubspec.lock @@ -128,7 +128,7 @@ packages: source: hosted version: "1.0.2" flame_bloc: - dependency: transitive + dependency: "direct main" description: name: flame_bloc url: "https://pub.dartlang.org" diff --git a/packages/pinball_components/sandbox/pubspec.yaml b/packages/pinball_components/sandbox/pubspec.yaml index 791020d0..cbe1c7be 100644 --- a/packages/pinball_components/sandbox/pubspec.yaml +++ b/packages/pinball_components/sandbox/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: dashbook: ^0.1.7 flame: ^1.1.1 + flame_bloc: ^1.4.0 flame_forge2d: git: url: https://github.com/flame-engine/flame diff --git a/packages/pinball_components/test/src/components/ball/behaviors/ball_turbo_charging_behavior_test.dart b/packages/pinball_components/test/src/components/ball/behaviors/ball_turbo_charging_behavior_test.dart index 79eb030e..09eabe0e 100644 --- a/packages/pinball_components/test/src/components/ball/behaviors/ball_turbo_charging_behavior_test.dart +++ b/packages/pinball_components/test/src/components/ball/behaviors/ball_turbo_charging_behavior_test.dart @@ -14,8 +14,11 @@ void main() { group( 'BallTurboChargingBehavior', () { - final asset = theme.Assets.images.dash.ball.keyName; - final flameTester = FlameTester(() => TestGame([asset])); + final assets = [ + theme.Assets.images.dash.ball.keyName, + Assets.images.ball.flameEffect.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); test('can be instantiated', () { expect( diff --git a/packages/pinball_components/test/src/components/plunger/plunger_test.dart b/packages/pinball_components/test/src/components/plunger/plunger_test.dart index 32a6a45b..6017f255 100644 --- a/packages/pinball_components/test/src/components/plunger/plunger_test.dart +++ b/packages/pinball_components/test/src/components/plunger/plunger_test.dart @@ -10,7 +10,8 @@ import '../../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(TestGame.new); + final asset = Assets.images.plunger.plunger.keyName; + final flameTester = FlameTester(() => TestGame([asset])); group('Plunger', () { test('can be instantiated', () { @@ -69,6 +70,7 @@ void main() { flameTester.testGameWidget( 'pulling', setUp: (game, tester) async { + await game.images.load(asset); await game.ensureAdd(Plunger()); game.camera.followVector2(Vector2.zero()); game.camera.zoom = 4.1; @@ -92,6 +94,7 @@ void main() { flameTester.testGameWidget( 'releasing', setUp: (game, tester) async { + await game.images.load(asset); await game.ensureAdd(Plunger()); game.camera.followVector2(Vector2.zero()); game.camera.zoom = 4.1; From 5e8ac20d376b2c5ee8cbd6d0056f7a69e7d90363 Mon Sep 17 00:00:00 2001 From: Erick Date: Mon, 9 May 2022 17:38:20 -0300 Subject: [PATCH 3/5] feat: throttle assets loading for more stability (#443) * feat: throttle assets loading for more stability * lint * coveragE * chore: remove instances of e Co-authored-by: Allison Ryan --- .../cubit/assets_manager_cubit.dart | 34 +- .../cubit/assets_manager_state.dart | 32 +- lib/game/game_assets.dart | 330 ++++++++++-------- lib/game/view/widgets/bonus_animation.dart | 14 +- .../view/selected_character.dart | 10 +- .../pinball_audio/lib/src/pinball_audio.dart | 4 +- .../test/src/pinball_audio_test.dart | 92 +++-- test/app/view/app_test.dart | 2 +- .../cubit/assets_manager_state_test.dart | 79 ++--- .../views/assets_loading_page_test.dart | 6 +- test/game/pinball_game_test.dart | 7 +- test/game/view/pinball_game_page_test.dart | 24 +- test/helpers/pump_app.dart | 8 +- 13 files changed, 361 insertions(+), 281 deletions(-) diff --git a/lib/assets_manager/cubit/assets_manager_cubit.dart b/lib/assets_manager/cubit/assets_manager_cubit.dart index eb0f7e31..7932f194 100644 --- a/lib/assets_manager/cubit/assets_manager_cubit.dart +++ b/lib/assets_manager/cubit/assets_manager_cubit.dart @@ -19,21 +19,31 @@ class AssetsManagerCubit extends Cubit { /// do its job without adding too much delay for the user, we are letting /// the UI paint first, and then we start loading the assets. await Future.delayed(const Duration(seconds: 1)); + final loadables = Function()>[ + _game.preFetchLeaderboard, + ..._game.preLoadAssets(), + ..._audioPlayer.load(), + ...BonusAnimation.loadAssets(), + ...SelectedCharacter.loadAssets(), + ]; emit( state.copyWith( - loadables: [ - _game.preFetchLeaderboard(), - ..._game.preLoadAssets(), - ..._audioPlayer.load(), - ...BonusAnimation.loadAssets(), - ...SelectedCharacter.loadAssets(), - ], + assetsCount: loadables.length, ), ); - final all = state.loadables.map((loadable) async { - await loadable; - emit(state.copyWith(loaded: [...state.loaded, loadable])); - }).toList(); - await Future.wait(all); + + late void Function() _triggerLoad; + _triggerLoad = () async { + if (loadables.isEmpty) return; + final loadable = loadables.removeAt(0); + await loadable(); + _triggerLoad(); + emit(state.copyWith(loaded: state.loaded + 1)); + }; + + const _throttleSize = 3; + for (var i = 0; i < _throttleSize; i++) { + _triggerLoad(); + } } } diff --git a/lib/assets_manager/cubit/assets_manager_state.dart b/lib/assets_manager/cubit/assets_manager_state.dart index 4847adc6..9c1c5984 100644 --- a/lib/assets_manager/cubit/assets_manager_state.dart +++ b/lib/assets_manager/cubit/assets_manager_state.dart @@ -1,44 +1,42 @@ part of 'assets_manager_cubit.dart'; /// {@template assets_manager_state} -/// State used to load the game assets +/// State used to load the game assets. /// {@endtemplate} class AssetsManagerState extends Equatable { /// {@macro assets_manager_state} const AssetsManagerState({ - required this.loadables, + required this.assetsCount, required this.loaded, }); /// {@macro assets_manager_state} - const AssetsManagerState.initial() - : this(loadables: const [], loaded: const []); + const AssetsManagerState.initial() : this(assetsCount: 0, loaded: 0); - /// List of futures to load - final List loadables; + /// Number of assets to load. + final int assetsCount; - /// List of loaded futures - final List loaded; + /// Number of already loaded assets. + final int loaded; - /// Returns a value between 0 and 1 to indicate the loading progress - double get progress => - loadables.isEmpty ? 0 : loaded.length / loadables.length; + /// Returns a value between 0 and 1 to indicate the loading progress. + double get progress => loaded == 0 ? 0 : loaded / assetsCount; - /// Only returns false if all the assets have been loaded + /// Only returns false if all the assets have been loaded. bool get isLoading => progress != 1; /// Returns a copy of this instance with the given parameters - /// updated + /// updated. AssetsManagerState copyWith({ - List? loadables, - List? loaded, + int? assetsCount, + int? loaded, }) { return AssetsManagerState( - loadables: loadables ?? this.loadables, + assetsCount: assetsCount ?? this.assetsCount, loaded: loaded ?? this.loaded, ); } @override - List get props => [loaded, loadables]; + List get props => [loaded, assetsCount]; } diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index 0d0ef26a..4e786f12 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -6,158 +6,196 @@ import 'package:pinball_theme/pinball_theme.dart' hide Assets; /// Add methods to help loading and caching game assets. extension PinballGameAssetsX on PinballGame { /// Returns a list of assets to be loaded - List> preLoadAssets() { + List Function()> preLoadAssets() { const dashTheme = DashTheme(); const sparkyTheme = SparkyTheme(); const androidTheme = AndroidTheme(); const dinoTheme = DinoTheme(); return [ - images.load(components.Assets.images.boardBackground.keyName), - images.load(components.Assets.images.ball.flameEffect.keyName), - images.load(components.Assets.images.signpost.inactive.keyName), - images.load(components.Assets.images.signpost.active1.keyName), - images.load(components.Assets.images.signpost.active2.keyName), - images.load(components.Assets.images.signpost.active3.keyName), - images.load(components.Assets.images.flipper.left.keyName), - 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.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), - images.load( - components.Assets.images.launchRamp.foregroundRailing.keyName, - ), - images.load( - components.Assets.images.launchRamp.backgroundRailing.keyName, - ), - images.load(components.Assets.images.dino.bottomWall.keyName), - images.load(components.Assets.images.dino.topWall.keyName), - images.load(components.Assets.images.dino.topWallTunnel.keyName), - images.load(components.Assets.images.dino.animatronic.head.keyName), - images.load(components.Assets.images.dino.animatronic.mouth.keyName), - images.load(components.Assets.images.dash.animatronic.keyName), - images.load(components.Assets.images.dash.bumper.a.active.keyName), - images.load(components.Assets.images.dash.bumper.a.inactive.keyName), - images.load(components.Assets.images.dash.bumper.b.active.keyName), - images.load(components.Assets.images.dash.bumper.b.inactive.keyName), - images.load(components.Assets.images.dash.bumper.main.active.keyName), - images.load(components.Assets.images.dash.bumper.main.inactive.keyName), - images.load(components.Assets.images.plunger.plunger.keyName), - images.load(components.Assets.images.plunger.rocket.keyName), - images.load(components.Assets.images.boundary.bottom.keyName), - images.load(components.Assets.images.boundary.outer.keyName), - images.load(components.Assets.images.boundary.outerBottom.keyName), - images.load(components.Assets.images.android.spaceship.saucer.keyName), - images + () => images.load(components.Assets.images.boardBackground.keyName), + () => images.load(components.Assets.images.ball.flameEffect.keyName), + () => images.load(components.Assets.images.signpost.inactive.keyName), + () => images.load(components.Assets.images.signpost.active1.keyName), + () => images.load(components.Assets.images.signpost.active2.keyName), + () => images.load(components.Assets.images.signpost.active3.keyName), + () => images.load(components.Assets.images.flipper.left.keyName), + () => 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.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), + () => images.load( + components.Assets.images.launchRamp.foregroundRailing.keyName, + ), + () => images.load( + components.Assets.images.launchRamp.backgroundRailing.keyName, + ), + () => images.load(components.Assets.images.dino.bottomWall.keyName), + () => images.load(components.Assets.images.dino.topWall.keyName), + () => images.load(components.Assets.images.dino.topWallTunnel.keyName), + () => images.load(components.Assets.images.dino.animatronic.head.keyName), + () => + images.load(components.Assets.images.dino.animatronic.mouth.keyName), + () => images.load(components.Assets.images.dash.animatronic.keyName), + () => images.load(components.Assets.images.dash.bumper.a.active.keyName), + () => + images.load(components.Assets.images.dash.bumper.a.inactive.keyName), + () => images.load(components.Assets.images.dash.bumper.b.active.keyName), + () => + images.load(components.Assets.images.dash.bumper.b.inactive.keyName), + () => + images.load(components.Assets.images.dash.bumper.main.active.keyName), + () => images + .load(components.Assets.images.dash.bumper.main.inactive.keyName), + () => images.load(components.Assets.images.plunger.plunger.keyName), + () => images.load(components.Assets.images.plunger.rocket.keyName), + () => images.load(components.Assets.images.boundary.bottom.keyName), + () => images.load(components.Assets.images.boundary.outer.keyName), + () => images.load(components.Assets.images.boundary.outerBottom.keyName), + () => images + .load(components.Assets.images.android.spaceship.saucer.keyName), + () => images .load(components.Assets.images.android.spaceship.animatronic.keyName), - images.load(components.Assets.images.android.spaceship.lightBeam.keyName), - images.load(components.Assets.images.android.ramp.boardOpening.keyName), - images.load( - components.Assets.images.android.ramp.railingForeground.keyName, - ), - images.load( - components.Assets.images.android.ramp.railingBackground.keyName, - ), - images.load(components.Assets.images.android.ramp.main.keyName), - images.load(components.Assets.images.android.ramp.arrow.inactive.keyName), - images.load( - components.Assets.images.android.ramp.arrow.active1.keyName, - ), - images.load( - components.Assets.images.android.ramp.arrow.active2.keyName, - ), - images.load( - components.Assets.images.android.ramp.arrow.active3.keyName, - ), - images.load( - components.Assets.images.android.ramp.arrow.active4.keyName, - ), - images.load( - components.Assets.images.android.ramp.arrow.active5.keyName, - ), - images.load(components.Assets.images.android.rail.main.keyName), - images.load(components.Assets.images.android.rail.exit.keyName), - images.load(components.Assets.images.android.bumper.a.lit.keyName), - images.load(components.Assets.images.android.bumper.a.dimmed.keyName), - images.load(components.Assets.images.android.bumper.b.lit.keyName), - images.load(components.Assets.images.android.bumper.b.dimmed.keyName), - images.load(components.Assets.images.android.bumper.cow.lit.keyName), - images.load(components.Assets.images.android.bumper.cow.dimmed.keyName), - images.load(components.Assets.images.sparky.computer.top.keyName), - images.load(components.Assets.images.sparky.computer.base.keyName), - images.load(components.Assets.images.sparky.computer.glow.keyName), - images.load(components.Assets.images.sparky.animatronic.keyName), - images.load(components.Assets.images.sparky.bumper.a.lit.keyName), - images.load(components.Assets.images.sparky.bumper.a.dimmed.keyName), - images.load(components.Assets.images.sparky.bumper.b.lit.keyName), - images.load(components.Assets.images.sparky.bumper.b.dimmed.keyName), - images.load(components.Assets.images.sparky.bumper.c.lit.keyName), - images.load(components.Assets.images.sparky.bumper.c.dimmed.keyName), - images.load(components.Assets.images.backbox.marquee.keyName), - images.load(components.Assets.images.backbox.displayDivider.keyName), - images.load(components.Assets.images.backbox.button.facebook.keyName), - images.load(components.Assets.images.backbox.button.twitter.keyName), - images.load( - components.Assets.images.backbox.displayTitleDecoration.keyName, - ), - images.load(components.Assets.images.googleWord.letter1.lit.keyName), - images.load(components.Assets.images.googleWord.letter1.dimmed.keyName), - images.load(components.Assets.images.googleWord.letter2.lit.keyName), - images.load(components.Assets.images.googleWord.letter2.dimmed.keyName), - images.load(components.Assets.images.googleWord.letter3.lit.keyName), - images.load(components.Assets.images.googleWord.letter3.dimmed.keyName), - images.load(components.Assets.images.googleWord.letter4.lit.keyName), - images.load(components.Assets.images.googleWord.letter4.dimmed.keyName), - images.load(components.Assets.images.googleWord.letter5.lit.keyName), - images.load(components.Assets.images.googleWord.letter5.dimmed.keyName), - images.load(components.Assets.images.googleWord.letter6.lit.keyName), - images.load(components.Assets.images.googleWord.letter6.dimmed.keyName), - images.load(components.Assets.images.googleRollover.left.decal.keyName), - images.load(components.Assets.images.googleRollover.left.pin.keyName), - images.load(components.Assets.images.googleRollover.right.decal.keyName), - images.load(components.Assets.images.googleRollover.right.pin.keyName), - images.load(components.Assets.images.multiball.lit.keyName), - images.load(components.Assets.images.multiball.dimmed.keyName), - images.load(components.Assets.images.multiplier.x2.lit.keyName), - images.load(components.Assets.images.multiplier.x2.dimmed.keyName), - images.load(components.Assets.images.multiplier.x3.lit.keyName), - images.load(components.Assets.images.multiplier.x3.dimmed.keyName), - images.load(components.Assets.images.multiplier.x4.lit.keyName), - images.load(components.Assets.images.multiplier.x4.dimmed.keyName), - images.load(components.Assets.images.multiplier.x5.lit.keyName), - images.load(components.Assets.images.multiplier.x5.dimmed.keyName), - images.load(components.Assets.images.multiplier.x6.lit.keyName), - images.load(components.Assets.images.multiplier.x6.dimmed.keyName), - images.load(components.Assets.images.score.fiveThousand.keyName), - images.load(components.Assets.images.score.twentyThousand.keyName), - images.load(components.Assets.images.score.twoHundredThousand.keyName), - images.load(components.Assets.images.score.oneMillion.keyName), - images.load(components.Assets.images.flapper.backSupport.keyName), - images.load(components.Assets.images.flapper.frontSupport.keyName), - images.load(components.Assets.images.flapper.flap.keyName), - images.load(components.Assets.images.skillShot.decal.keyName), - images.load(components.Assets.images.skillShot.pin.keyName), - images.load(components.Assets.images.skillShot.lit.keyName), - images.load(components.Assets.images.skillShot.dimmed.keyName), - images.load(components.Assets.images.displayArrows.arrowLeft.keyName), - images.load(components.Assets.images.displayArrows.arrowRight.keyName), - images.load(androidTheme.leaderboardIcon.keyName), - images.load(androidTheme.ball.keyName), - images.load(dashTheme.leaderboardIcon.keyName), - images.load(dashTheme.ball.keyName), - images.load(dinoTheme.leaderboardIcon.keyName), - images.load(dinoTheme.ball.keyName), - images.load(sparkyTheme.leaderboardIcon.keyName), - images.load(sparkyTheme.ball.keyName), - images.load(androidTheme.background.keyName), - images.load(dashTheme.background.keyName), - images.load(dinoTheme.background.keyName), - images.load(sparkyTheme.background.keyName), + () => images + .load(components.Assets.images.android.spaceship.lightBeam.keyName), + () => images + .load(components.Assets.images.android.ramp.boardOpening.keyName), + () => images.load( + components.Assets.images.android.ramp.railingForeground.keyName, + ), + () => images.load( + components.Assets.images.android.ramp.railingBackground.keyName, + ), + () => images.load(components.Assets.images.android.ramp.main.keyName), + () => images + .load(components.Assets.images.android.ramp.arrow.inactive.keyName), + () => images.load( + components.Assets.images.android.ramp.arrow.active1.keyName, + ), + () => images.load( + components.Assets.images.android.ramp.arrow.active2.keyName, + ), + () => images.load( + components.Assets.images.android.ramp.arrow.active3.keyName, + ), + () => images.load( + components.Assets.images.android.ramp.arrow.active4.keyName, + ), + () => images.load( + components.Assets.images.android.ramp.arrow.active5.keyName, + ), + () => images.load(components.Assets.images.android.rail.main.keyName), + () => images.load(components.Assets.images.android.rail.exit.keyName), + () => images.load(components.Assets.images.android.bumper.a.lit.keyName), + () => + images.load(components.Assets.images.android.bumper.a.dimmed.keyName), + () => images.load(components.Assets.images.android.bumper.b.lit.keyName), + () => + images.load(components.Assets.images.android.bumper.b.dimmed.keyName), + () => + images.load(components.Assets.images.android.bumper.cow.lit.keyName), + () => images + .load(components.Assets.images.android.bumper.cow.dimmed.keyName), + () => images.load(components.Assets.images.sparky.computer.top.keyName), + () => images.load(components.Assets.images.sparky.computer.base.keyName), + () => images.load(components.Assets.images.sparky.computer.glow.keyName), + () => images.load(components.Assets.images.sparky.animatronic.keyName), + () => images.load(components.Assets.images.sparky.bumper.a.lit.keyName), + () => + images.load(components.Assets.images.sparky.bumper.a.dimmed.keyName), + () => images.load(components.Assets.images.sparky.bumper.b.lit.keyName), + () => + images.load(components.Assets.images.sparky.bumper.b.dimmed.keyName), + () => images.load(components.Assets.images.sparky.bumper.c.lit.keyName), + () => + images.load(components.Assets.images.sparky.bumper.c.dimmed.keyName), + () => images.load(components.Assets.images.backbox.marquee.keyName), + () => + images.load(components.Assets.images.backbox.displayDivider.keyName), + () => + images.load(components.Assets.images.backbox.button.facebook.keyName), + () => + images.load(components.Assets.images.backbox.button.twitter.keyName), + () => images.load( + components.Assets.images.backbox.displayTitleDecoration.keyName, + ), + () => + images.load(components.Assets.images.googleWord.letter1.lit.keyName), + () => images + .load(components.Assets.images.googleWord.letter1.dimmed.keyName), + () => + images.load(components.Assets.images.googleWord.letter2.lit.keyName), + () => images + .load(components.Assets.images.googleWord.letter2.dimmed.keyName), + () => + images.load(components.Assets.images.googleWord.letter3.lit.keyName), + () => images + .load(components.Assets.images.googleWord.letter3.dimmed.keyName), + () => + images.load(components.Assets.images.googleWord.letter4.lit.keyName), + () => images + .load(components.Assets.images.googleWord.letter4.dimmed.keyName), + () => + images.load(components.Assets.images.googleWord.letter5.lit.keyName), + () => images + .load(components.Assets.images.googleWord.letter5.dimmed.keyName), + () => + images.load(components.Assets.images.googleWord.letter6.lit.keyName), + () => images + .load(components.Assets.images.googleWord.letter6.dimmed.keyName), + () => images + .load(components.Assets.images.googleRollover.left.decal.keyName), + () => + images.load(components.Assets.images.googleRollover.left.pin.keyName), + () => images + .load(components.Assets.images.googleRollover.right.decal.keyName), + () => images + .load(components.Assets.images.googleRollover.right.pin.keyName), + () => images.load(components.Assets.images.multiball.lit.keyName), + () => images.load(components.Assets.images.multiball.dimmed.keyName), + () => images.load(components.Assets.images.multiplier.x2.lit.keyName), + () => images.load(components.Assets.images.multiplier.x2.dimmed.keyName), + () => images.load(components.Assets.images.multiplier.x3.lit.keyName), + () => images.load(components.Assets.images.multiplier.x3.dimmed.keyName), + () => images.load(components.Assets.images.multiplier.x4.lit.keyName), + () => images.load(components.Assets.images.multiplier.x4.dimmed.keyName), + () => images.load(components.Assets.images.multiplier.x5.lit.keyName), + () => images.load(components.Assets.images.multiplier.x5.dimmed.keyName), + () => images.load(components.Assets.images.multiplier.x6.lit.keyName), + () => images.load(components.Assets.images.multiplier.x6.dimmed.keyName), + () => images.load(components.Assets.images.score.fiveThousand.keyName), + () => images.load(components.Assets.images.score.twentyThousand.keyName), + () => images + .load(components.Assets.images.score.twoHundredThousand.keyName), + () => images.load(components.Assets.images.score.oneMillion.keyName), + () => images.load(components.Assets.images.flapper.backSupport.keyName), + () => images.load(components.Assets.images.flapper.frontSupport.keyName), + () => images.load(components.Assets.images.flapper.flap.keyName), + () => images.load(components.Assets.images.skillShot.decal.keyName), + () => images.load(components.Assets.images.skillShot.pin.keyName), + () => images.load(components.Assets.images.skillShot.lit.keyName), + () => images.load(components.Assets.images.skillShot.dimmed.keyName), + () => + images.load(components.Assets.images.displayArrows.arrowLeft.keyName), + () => images + .load(components.Assets.images.displayArrows.arrowRight.keyName), + () => images.load(androidTheme.leaderboardIcon.keyName), + () => images.load(androidTheme.ball.keyName), + () => images.load(dashTheme.leaderboardIcon.keyName), + () => images.load(dashTheme.ball.keyName), + () => images.load(dinoTheme.leaderboardIcon.keyName), + () => images.load(dinoTheme.ball.keyName), + () => images.load(sparkyTheme.leaderboardIcon.keyName), + () => images.load(sparkyTheme.ball.keyName), + () => images.load(androidTheme.background.keyName), + () => images.load(dashTheme.background.keyName), + () => images.load(dinoTheme.background.keyName), + () => images.load(sparkyTheme.background.keyName), ]; } } diff --git a/lib/game/view/widgets/bonus_animation.dart b/lib/game/view/widgets/bonus_animation.dart index 35e600f2..a52aafc0 100644 --- a/lib/game/view/widgets/bonus_animation.dart +++ b/lib/game/view/widgets/bonus_animation.dart @@ -72,14 +72,16 @@ class BonusAnimation extends StatefulWidget { final VoidCallback? _onCompleted; /// Returns a list of assets to be loaded for animations. - static List loadAssets() { + static List loadAssets() { Flame.images.prefix = ''; return [ - Flame.images.load(Assets.images.bonusAnimation.dashNest.keyName), - Flame.images.load(Assets.images.bonusAnimation.sparkyTurboCharge.keyName), - Flame.images.load(Assets.images.bonusAnimation.dinoChomp.keyName), - Flame.images.load(Assets.images.bonusAnimation.androidSpaceship.keyName), - Flame.images.load(Assets.images.bonusAnimation.googleWord.keyName), + () => Flame.images.load(Assets.images.bonusAnimation.dashNest.keyName), + () => Flame.images + .load(Assets.images.bonusAnimation.sparkyTurboCharge.keyName), + () => Flame.images.load(Assets.images.bonusAnimation.dinoChomp.keyName), + () => Flame.images + .load(Assets.images.bonusAnimation.androidSpaceship.keyName), + () => Flame.images.load(Assets.images.bonusAnimation.googleWord.keyName), ]; } diff --git a/lib/select_character/view/selected_character.dart b/lib/select_character/view/selected_character.dart index a061b05b..ed95297f 100644 --- a/lib/select_character/view/selected_character.dart +++ b/lib/select_character/view/selected_character.dart @@ -22,12 +22,12 @@ class SelectedCharacter extends StatefulWidget { State createState() => _SelectedCharacterState(); /// Returns a list of assets to be loaded. - static List loadAssets() { + static List loadAssets() { return [ - Flame.images.load(const DashTheme().animation.keyName), - Flame.images.load(const AndroidTheme().animation.keyName), - Flame.images.load(const DinoTheme().animation.keyName), - Flame.images.load(const SparkyTheme().animation.keyName), + () => Flame.images.load(const DashTheme().animation.keyName), + () => Flame.images.load(const AndroidTheme().animation.keyName), + () => Flame.images.load(const DinoTheme().animation.keyName), + () => Flame.images.load(const SparkyTheme().animation.keyName), ]; } } diff --git a/packages/pinball_audio/lib/src/pinball_audio.dart b/packages/pinball_audio/lib/src/pinball_audio.dart index e0e69988..1e1a7688 100644 --- a/packages/pinball_audio/lib/src/pinball_audio.dart +++ b/packages/pinball_audio/lib/src/pinball_audio.dart @@ -334,10 +334,10 @@ class PinballAudioPlayer { late final Map audios; /// Loads the sounds effects into the memory. - List> load() { + List Function()> load() { _configureAudioCache(FlameAudio.audioCache); - return audios.values.map((a) => a.load()).toList(); + return audios.values.map((a) => a.load).toList(); } /// Plays the received audio. diff --git a/packages/pinball_audio/test/src/pinball_audio_test.dart b/packages/pinball_audio/test/src/pinball_audio_test.dart index 769a880d..47d788bf 100644 --- a/packages/pinball_audio/test/src/pinball_audio_test.dart +++ b/packages/pinball_audio/test/src/pinball_audio_test.dart @@ -102,7 +102,9 @@ void main() { group('load', () { test('creates the bumpers pools', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); verify( () => createAudioPool.onCall( @@ -122,7 +124,9 @@ void main() { }); test('creates the kicker pools', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); verify( () => createAudioPool.onCall( @@ -142,7 +146,9 @@ void main() { }); test('configures the audio cache instance', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); verify(() => configureAudioCache.onCall(FlameAudio.audioCache)) .called(1); @@ -154,13 +160,17 @@ void main() { playSingleAudio: playSingleAudio.onCall, preCacheSingleAudio: preCacheSingleAudio.onCall, ); - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); expect(FlameAudio.audioCache.prefix, equals('')); }); test('pre cache the assets', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); verify( () => preCacheSingleAudio @@ -242,7 +252,9 @@ void main() { group('when seed is true', () { test('plays the bumper A sound pool', () async { when(seed.nextBool).thenReturn(true); - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.bumper); verify(() => bumperAPool.start(volume: 0.6)).called(1); @@ -252,7 +264,9 @@ void main() { group('when seed is false', () { test('plays the bumper B sound pool', () async { when(seed.nextBool).thenReturn(false); - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.bumper); verify(() => bumperBPool.start(volume: 0.6)).called(1); @@ -291,7 +305,9 @@ void main() { group('when seed is true', () { test('plays the kicker A sound pool', () async { when(seed.nextBool).thenReturn(true); - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.kicker); verify(() => kickerAPool.start(volume: 0.6)).called(1); @@ -301,7 +317,9 @@ void main() { group('when seed is false', () { test('plays the kicker B sound pool', () async { when(seed.nextBool).thenReturn(false); - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.kicker); verify(() => kickerBPool.start(volume: 0.6)).called(1); @@ -311,7 +329,9 @@ void main() { group('cow moo', () { test('plays the correct file', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.cowMoo); verify( @@ -324,7 +344,9 @@ void main() { final clock = _MockClock(); await withClock(clock, () async { when(clock.now).thenReturn(DateTime(2022)); - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer ..play(PinballAudio.cowMoo) ..play(PinballAudio.cowMoo); @@ -347,7 +369,9 @@ void main() { group('google', () { test('plays the correct file', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.google); verify( @@ -361,7 +385,9 @@ void main() { group('sparky', () { test('plays the correct file', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.sparky); verify( @@ -375,7 +401,9 @@ void main() { group('dino', () { test('plays the correct file', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.dino); verify( @@ -389,7 +417,9 @@ void main() { group('android', () { test('plays the correct file', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.android); verify( @@ -403,7 +433,9 @@ void main() { group('dash', () { test('plays the correct file', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.dash); verify( @@ -417,7 +449,9 @@ void main() { group('launcher', () { test('plays the correct file', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.launcher); verify( @@ -431,7 +465,9 @@ void main() { group('rollover', () { test('plays the correct file', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.rollover); verify( @@ -445,7 +481,9 @@ void main() { group('ioPinballVoiceOver', () { test('plays the correct file', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.ioPinballVoiceOver); verify( @@ -459,7 +497,9 @@ void main() { group('gameOverVoiceOver', () { test('plays the correct file', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.gameOverVoiceOver); verify( @@ -473,7 +513,9 @@ void main() { group('backgroundMusic', () { test('plays the correct file', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer.play(PinballAudio.backgroundMusic); verify( @@ -485,7 +527,9 @@ void main() { }); test('plays only once', () async { - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); audioPlayer ..play(PinballAudio.backgroundMusic) ..play(PinballAudio.backgroundMusic); @@ -503,7 +547,9 @@ void main() { 'throws assertions error when playing an unregistered audio', () async { audioPlayer.audios.remove(PinballAudio.google); - await Future.wait(audioPlayer.load()); + await Future.wait( + audioPlayer.load().map((loadableBuilder) => loadableBuilder()), + ); expect( () => audioPlayer.play(PinballAudio.google), diff --git a/test/app/view/app_test.dart b/test/app/view/app_test.dart index b9f2ef16..d719767e 100644 --- a/test/app/view/app_test.dart +++ b/test/app/view/app_test.dart @@ -37,7 +37,7 @@ void main() { shareRepository = _MockShareRepository(); pinballAudioPlayer = _MockPinballAudioPlayer(); platformHelper = _MockPlatformHelper(); - when(pinballAudioPlayer.load).thenAnswer((_) => [Future.value()]); + when(pinballAudioPlayer.load).thenAnswer((_) => [Future.value]); }); testWidgets('renders PinballGamePage', (tester) async { diff --git a/test/assets_manager/cubit/assets_manager_state_test.dart b/test/assets_manager/cubit/assets_manager_state_test.dart index 41e94add..6891ef9b 100644 --- a/test/assets_manager/cubit/assets_manager_state_test.dart +++ b/test/assets_manager/cubit/assets_manager_state_test.dart @@ -7,7 +7,7 @@ void main() { group('AssetsManagerState', () { test('can be instantiated', () { expect( - AssetsManagerState(loadables: const [], loaded: const []), + AssetsManagerState(assetsCount: 0, loaded: 0), isNotNull, ); }); @@ -17,22 +17,19 @@ void main() { AssetsManagerState.initial(), equals( AssetsManagerState( - loadables: const [], - loaded: const [], + assetsCount: 0, + loaded: 0, ), ), ); }); group('progress', () { - final future1 = Future.value(); - final future2 = Future.value(); - test('returns 0 when no future is loaded', () { expect( AssetsManagerState( - loadables: [future1, future2], - loaded: const [], + assetsCount: 2, + loaded: 0, ).progress, equals(0), ); @@ -41,8 +38,8 @@ void main() { test('returns the correct value when some of the futures are loaded', () { expect( AssetsManagerState( - loadables: [future1, future2], - loaded: [future1], + assetsCount: 2, + loaded: 1, ).progress, equals(0.5), ); @@ -51,8 +48,8 @@ void main() { test('returns the 1 when all futures are loaded', () { expect( AssetsManagerState( - loadables: [future1, future2], - loaded: [future1, future2], + assetsCount: 2, + loaded: 2, ).progress, equals(1), ); @@ -60,18 +57,16 @@ void main() { }); group('copyWith', () { - final future = Future.value(); - - test('returns a copy with the updated loadables', () { + test('returns a copy with the updated assetsCount', () { expect( AssetsManagerState( - loadables: const [], - loaded: const [], - ).copyWith(loadables: [future]), + assetsCount: 0, + loaded: 0, + ).copyWith(assetsCount: 1), equals( AssetsManagerState( - loadables: [future], - loaded: const [], + assetsCount: 1, + loaded: 0, ), ), ); @@ -80,13 +75,13 @@ void main() { test('returns a copy with the updated loaded', () { expect( AssetsManagerState( - loadables: const [], - loaded: const [], - ).copyWith(loaded: [future]), + assetsCount: 0, + loaded: 0, + ).copyWith(loaded: 1), equals( AssetsManagerState( - loadables: const [], - loaded: [future], + assetsCount: 0, + loaded: 1, ), ), ); @@ -94,47 +89,29 @@ void main() { }); test('supports value comparison', () { - final future1 = Future.value(); - final future2 = Future.value(); - expect( AssetsManagerState( - loadables: const [], - loaded: const [], + assetsCount: 0, + loaded: 0, ), equals( AssetsManagerState( - loadables: const [], - loaded: const [], - ), - ), - ); - - expect( - AssetsManagerState( - loadables: [future1], - loaded: const [], - ), - isNot( - equals( - AssetsManagerState( - loadables: [future2], - loaded: const [], - ), + assetsCount: 0, + loaded: 0, ), ), ); expect( AssetsManagerState( - loadables: const [], - loaded: [future1], + assetsCount: 1, + loaded: 0, ), isNot( equals( AssetsManagerState( - loadables: const [], - loaded: [future2], + assetsCount: 1, + loaded: 1, ), ), ), diff --git a/test/assets_manager/views/assets_loading_page_test.dart b/test/assets_manager/views/assets_loading_page_test.dart index a6210e0c..7a457e4b 100644 --- a/test/assets_manager/views/assets_loading_page_test.dart +++ b/test/assets_manager/views/assets_loading_page_test.dart @@ -12,9 +12,9 @@ void main() { late AssetsManagerCubit assetsManagerCubit; setUp(() { - final initialAssetsState = AssetsManagerState( - loadables: [Future.value()], - loaded: const [], + const initialAssetsState = AssetsManagerState( + assetsCount: 1, + loaded: 0, ); assetsManagerCubit = _MockAssetsManagerCubit(); whenListen( diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index b60ebbad..05b9442c 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -35,7 +35,7 @@ class _TestPinballGame extends PinballGame { @override Future onLoad() async { images.prefix = ''; - final futures = preLoadAssets(); + final futures = preLoadAssets().map((loadableBuilder) => loadableBuilder()); await Future.wait(futures); await super.onLoad(); } @@ -56,7 +56,7 @@ class _TestDebugPinballGame extends DebugPinballGame { @override Future onLoad() async { images.prefix = ''; - final futures = preLoadAssets(); + final futures = preLoadAssets().map((loadableBuilder) => loadableBuilder()); await Future.wait(futures); await super.onLoad(); } @@ -215,7 +215,8 @@ void main() { 'paints sprites with FilterQuality.medium', setUp: (game, tester) async { game.images.prefix = ''; - final futures = game.preLoadAssets(); + final futures = + game.preLoadAssets().map((loadableBuilder) => loadableBuilder()); await Future.wait(futures); await game.ready(); diff --git a/test/game/view/pinball_game_page_test.dart b/test/game/view/pinball_game_page_test.dart index 669117ed..da52bbd0 100644 --- a/test/game/view/pinball_game_page_test.dart +++ b/test/game/view/pinball_game_page_test.dart @@ -37,9 +37,13 @@ class _TestPinballGame extends PinballGame { images.prefix = ''; final futures = [ ...preLoadAssets(), - preFetchLeaderboard(), + ...BonusAnimation.loadAssets(), + ...SelectedCharacter.loadAssets(), + preFetchLeaderboard, ]; - await Future.wait(futures); + await Future.wait( + futures.map((loadableBuilder) => loadableBuilder()).toList(), + ); return super.onLoad(); } @@ -78,7 +82,9 @@ void main() { late GameBloc gameBloc; setUp(() async { - await Future.wait(game.preLoadAssets()); + await Future.wait( + game.preLoadAssets().map((loadableBuilder) => loadableBuilder()), + ); characterThemeCubit = _MockCharacterThemeCubit(); gameBloc = _MockGameBloc(); @@ -122,8 +128,8 @@ void main() { (tester) async { final assetsManagerCubit = _MockAssetsManagerCubit(); final initialAssetsState = AssetsManagerState( - loadables: [Future.value()], - loaded: const [], + assetsCount: 1, + loaded: 0, ); whenListen( assetsManagerCubit, @@ -146,8 +152,8 @@ void main() { final startGameBloc = _MockStartGameBloc(); final loadedAssetsState = AssetsManagerState( - loadables: [Future.value()], - loaded: [Future.value()], + assetsCount: 1, + loaded: 1, ); whenListen( assetsManagerCubit, @@ -179,7 +185,9 @@ void main() { final startGameBloc = _MockStartGameBloc(); setUp(() async { - await Future.wait(game.preLoadAssets()); + await Future.wait( + game.preLoadAssets().map((loadableBuilder) => loadableBuilder()), + ); whenListen( gameBloc, diff --git a/test/helpers/pump_app.dart b/test/helpers/pump_app.dart index d136487c..c057bc83 100644 --- a/test/helpers/pump_app.dart +++ b/test/helpers/pump_app.dart @@ -34,15 +34,15 @@ class _MockPlatformHelper extends Mock implements PlatformHelper {} PinballAudioPlayer _buildDefaultPinballAudioPlayer() { final audioPlayer = _MockPinballAudioPlayer(); - when(audioPlayer.load).thenAnswer((_) => [Future.value()]); + when(audioPlayer.load).thenAnswer((_) => [Future.value]); return audioPlayer; } AssetsManagerCubit _buildDefaultAssetsManagerCubit() { final cubit = _MockAssetsManagerCubit(); - final state = AssetsManagerState( - loadables: [Future.value()], - loaded: [Future.value()], + const state = AssetsManagerState( + assetsCount: 1, + loaded: 1, ); whenListen( cubit, From 98ac639493472d6bd6ba8e0968850e0dad30a973 Mon Sep 17 00:00:00 2001 From: Jochum van der Ploeg Date: Tue, 10 May 2022 00:07:28 +0200 Subject: [PATCH 4/5] fix: handle game resizing (#446) * fix: handle game resizing * Update lib/game/behaviors/camera_focusing_behavior.dart Co-authored-by: Erick Co-authored-by: Erick Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> --- .../behaviors/camera_focusing_behavior.dart | 87 +++++++++++-------- .../camera_focusing_behavior_test.dart | 14 +++ 2 files changed, 66 insertions(+), 35 deletions(-) diff --git a/lib/game/behaviors/camera_focusing_behavior.dart b/lib/game/behaviors/camera_focusing_behavior.dart index 8a13821d..1d454f02 100644 --- a/lib/game/behaviors/camera_focusing_behavior.dart +++ b/lib/game/behaviors/camera_focusing_behavior.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flame/components.dart'; import 'package:flame/game.dart'; import 'package:flame_bloc/flame_bloc.dart'; @@ -7,9 +9,9 @@ import 'package:pinball_components/pinball_components.dart'; /// {@template focus_data} /// Defines a [Camera] focus point. /// {@endtemplate} -class FocusData { - /// {@template focus_data} - FocusData({ +class _FocusData { + /// {@macro focus_data} + const _FocusData({ required this.zoom, required this.position, }); @@ -24,7 +26,11 @@ class FocusData { /// Changes the game focus when the [GameBloc] status changes. class CameraFocusingBehavior extends Component with FlameBlocListenable, HasGameRef { - late final Map _foci; + final Map _foci = {}; + + GameStatus? _activeFocus; + + final _previousSize = Vector2.zero(); @override bool listenWhen(GameState? previousState, GameState newState) { @@ -32,51 +38,62 @@ class CameraFocusingBehavior extends Component } @override - void onNewState(GameState state) { - switch (state.status) { - case GameStatus.waiting: - break; - case GameStatus.playing: - _zoom(_foci['game']!); - break; - case GameStatus.gameOver: - _zoom(_foci['backbox']!); - break; - } - } + void onNewState(GameState state) => _zoomTo(state.status); @override - Future onLoad() async { - await super.onLoad(); - _foci = { - 'game': FocusData( - zoom: gameRef.size.y / 16, - position: Vector2(0, -7.8), - ), - 'waiting': FocusData( - zoom: gameRef.size.y / 18, + void onGameResize(Vector2 size) { + super.onGameResize(size); + if (size == _previousSize) { + return; + } + _previousSize.setFrom(size); + + final maxWidth = size.x / 90; + final maxHeight = size.y / 160; + + final scale = min(maxHeight, maxWidth); + + _foci.addAll({ + GameStatus.waiting: _FocusData( + zoom: scale + (maxWidth > maxHeight ? 0.3 : -0.5), position: Vector2(0, -112), ), - 'backbox': FocusData( - zoom: gameRef.size.y / 10, + GameStatus.playing: _FocusData( + zoom: scale + (maxWidth > maxHeight ? 0.5 : -0.2), + position: Vector2(0, -7.8), + ), + GameStatus.gameOver: _FocusData( + zoom: scale + (maxWidth > maxHeight ? 2.8 : 3.3), position: Vector2(0, -111), ), - }; + }); + + if (_activeFocus != null) { + _snap(_activeFocus!); + } + } - _snap(_foci['waiting']!); + @override + Future onLoad() async { + await super.onLoad(); + _snap(GameStatus.waiting); } - void _snap(FocusData data) { + void _snap(GameStatus focusKey) { + final focusData = _foci[_activeFocus = focusKey]!; + gameRef.camera ..speed = 100 - ..followVector2(data.position) - ..zoom = data.zoom; + ..followVector2(focusData.position) + ..zoom = focusData.zoom; } - void _zoom(FocusData data) { - final zoom = CameraZoom(value: data.zoom); + void _zoomTo(GameStatus focusKey) { + final focusData = _foci[_activeFocus = focusKey]!; + + final zoom = CameraZoom(value: focusData.zoom); zoom.completed.then((_) { - gameRef.camera.moveTo(data.position); + gameRef.camera.moveTo(focusData.position); }); add(zoom); } diff --git a/test/game/behaviors/camera_focusing_behavior_test.dart b/test/game/behaviors/camera_focusing_behavior_test.dart index a856b392..092f3efe 100644 --- a/test/game/behaviors/camera_focusing_behavior_test.dart +++ b/test/game/behaviors/camera_focusing_behavior_test.dart @@ -53,6 +53,20 @@ void main() { }, ); + flameTester.test('sets zoom on resize', (game) async { + final behavior = CameraFocusingBehavior(); + + await game.ensureAdd( + FlameBlocProvider.value( + value: GameBloc(), + children: [behavior], + ), + ); + + game.onGameResize(game.canvasSize * 2); + expect(game.camera.zoom, equals(6.55)); + }); + flameTester.test( 'listenWhen only listens when status changes', (game) async { From 394e1fe72439701bdad0e35fe2f113d4f421cdce Mon Sep 17 00:00:00 2001 From: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> Date: Mon, 9 May 2022 17:39:41 -0500 Subject: [PATCH 5/5] fix: revert to invisible ink wells (#447) --- packages/pinball_ui/lib/src/widgets/pinball_button.dart | 4 +++- packages/pinball_ui/lib/src/widgets/pinball_dpad_button.dart | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/pinball_ui/lib/src/widgets/pinball_button.dart b/packages/pinball_ui/lib/src/widgets/pinball_button.dart index ee9b5c54..febedf4c 100644 --- a/packages/pinball_ui/lib/src/widgets/pinball_button.dart +++ b/packages/pinball_ui/lib/src/widgets/pinball_button.dart @@ -30,8 +30,10 @@ class PinballButton extends StatelessWidget { ), ), child: Center( - child: GestureDetector( + child: InkWell( onTap: onTap, + highlightColor: PinballColors.transparent, + splashColor: PinballColors.transparent, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 32, diff --git a/packages/pinball_ui/lib/src/widgets/pinball_dpad_button.dart b/packages/pinball_ui/lib/src/widgets/pinball_dpad_button.dart index c122f5b3..6fe27b77 100644 --- a/packages/pinball_ui/lib/src/widgets/pinball_dpad_button.dart +++ b/packages/pinball_ui/lib/src/widgets/pinball_dpad_button.dart @@ -53,8 +53,10 @@ class PinballDpadButton extends StatelessWidget { Widget build(BuildContext context) { return Material( color: PinballColors.transparent, - child: GestureDetector( + child: InkWell( onTap: onTap, + highlightColor: PinballColors.transparent, + splashColor: PinballColors.transparent, child: Image.asset( direction.toAsset(), width: 60,