From bf0596846b5748fa704f961bbe59a0346216e7da Mon Sep 17 00:00:00 2001 From: Rui Miguel Alonso Date: Tue, 12 Apr 2022 11:27:49 +0200 Subject: [PATCH] refactor: `Plunger` controls (#152) * feat: added plunger asset and move * feat: fixed plunger assets and position * fix: set limits to plunger compression correctly * chore: unused import * fix: placed plunger correctly * test: refactor test game * refactor: changed spawnBall initialPosition * chore: plunger golden test * refactor: moved plunger to pinball_components * feat: plunger story * refactor: plunger assets preload * chore: assets for plunger * refactor: added key events to plunger game * refactor: make pull and release public to call from outside plunger * chore: update to retry tests * refactor: added Traceable to Plunger sandbox story * refactor: add Traceable * fix: removed body from PlungerAnchor * refactor: removed keyevents from plunger * test: plunger tests refactored without keyevents * refactor: removed unused keyevents * refactor: plunger spritecomponent * refactor: keyevents to gamecontroller * feat: added plunger controller * test: tests for plunger controller * test: tested plunger controller * fix: assets gen * chore: removed export from barrel * refactor: removed unnecessary keyhandler * refactor: sprite size * test: anchor at setup * refactor: plunger const keys * Update lib/game/components/controlled_plunger.dart Co-authored-by: Alejandro Santiago Co-authored-by: Alejandro Santiago --- lib/game/components/components.dart | 2 +- lib/game/components/controlled_plunger.dart | 49 +++++ lib/game/game_assets.dart | 1 + lib/game/pinball_game.dart | 2 +- lib/gen/assets.gen.dart | 5 + .../assets/images/plunger/plunger.png | Bin 0 -> 11655 bytes .../lib/gen/assets.gen.dart | 9 + .../pinball_components/lib/gen/fonts.gen.dart | 5 + .../lib/src/components/components.dart | 1 + .../lib/src}/components/plunger.dart | 67 ++----- packages/pinball_components/pubspec.yaml | 1 + .../pinball_components/sandbox/lib/main.dart | 1 + .../lib/stories/plunger/plunger_game.dart | 54 ++++++ .../sandbox/lib/stories/plunger/stories.dart | 15 ++ .../sandbox/lib/stories/stories.dart | 1 + .../test/src}/components/plunger_test.dart | 176 ++++++++---------- .../components/controlled_plunger_test.dart | 78 ++++++++ 17 files changed, 312 insertions(+), 155 deletions(-) create mode 100644 lib/game/components/controlled_plunger.dart create mode 100644 packages/pinball_components/assets/images/plunger/plunger.png rename {lib/game => packages/pinball_components/lib/src}/components/plunger.dart (73%) create mode 100644 packages/pinball_components/sandbox/lib/stories/plunger/plunger_game.dart create mode 100644 packages/pinball_components/sandbox/lib/stories/plunger/stories.dart rename {test/game => packages/pinball_components/test/src}/components/plunger_test.dart (62%) create mode 100644 test/game/components/controlled_plunger_test.dart diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index f91d9baf..a5540614 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -3,10 +3,10 @@ export 'bonus_word.dart'; export 'camera_controller.dart'; export 'controlled_ball.dart'; export 'controlled_flipper.dart'; +export 'controlled_plunger.dart'; export 'controlled_sparky_computer.dart'; export 'flutter_forest.dart'; export 'game_flow_controller.dart'; -export 'plunger.dart'; export 'score_effect_controller.dart'; export 'score_points.dart'; export 'sparky_fire_zone.dart'; diff --git a/lib/game/components/controlled_plunger.dart b/lib/game/components/controlled_plunger.dart new file mode 100644 index 00000000..167f129e --- /dev/null +++ b/lib/game/components/controlled_plunger.dart @@ -0,0 +1,49 @@ +import 'package:flame/components.dart'; +import 'package:flutter/services.dart'; +import 'package:pinball/flame/flame.dart'; +import 'package:pinball_components/pinball_components.dart'; + +/// {@template controlled_plunger} +/// A [Plunger] with a [PlungerController] attached. +/// {@endtemplate} +class ControlledPlunger extends Plunger with Controls { + /// {@macro controlled_plunger} + ControlledPlunger({required double compressionDistance}) + : super(compressionDistance: compressionDistance) { + controller = PlungerController(this); + } +} + +/// {@template plunger_controller} +/// A [ComponentController] that controls a [Plunger]s movement. +/// {@endtemplate} +class PlungerController extends ComponentController + with KeyboardHandler { + /// {@macro plunger_controller} + PlungerController(Plunger plunger) : super(plunger); + + /// The [LogicalKeyboardKey]s that will control the [Flipper]. + /// + /// [onKeyEvent] method listens to when one of these keys is pressed. + static const List _keys = [ + LogicalKeyboardKey.arrowDown, + LogicalKeyboardKey.space, + LogicalKeyboardKey.keyS, + ]; + + @override + bool onKeyEvent( + RawKeyEvent event, + Set keysPressed, + ) { + if (!_keys.contains(event.logicalKey)) return true; + + if (event is RawKeyDownEvent) { + component.pull(); + } else if (event is RawKeyUpEvent) { + component.release(); + } + + return false; + } +} diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index 22605904..9abfa01c 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -46,6 +46,7 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.spaceship.rail.foreground.keyName), images.load(components.Assets.images.chromeDino.mouth.keyName), images.load(components.Assets.images.chromeDino.head.keyName), + images.load(components.Assets.images.plunger.plunger.keyName), images.load(components.Assets.images.sparky.computer.base.keyName), images.load(components.Assets.images.sparky.computer.top.keyName), images.load(components.Assets.images.sparky.bumper.a.active.keyName), diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 8d080b22..df602184 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -48,7 +48,7 @@ class PinballGame extends Forge2DGame unawaited(addFromBlueprint(LaunchRamp())); unawaited(addFromBlueprint(ControlledSparkyComputer())); - final plunger = Plunger(compressionDistance: 29) + final plunger = ControlledPlunger(compressionDistance: 29) ..initialPosition = Vector2(38, -19); await add(plunger); diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index b3b964f3..90013646 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -3,6 +3,8 @@ /// FlutterGen /// ***************************************************** +// ignore_for_file: directives_ordering,unnecessary_import + import 'package:flutter/widgets.dart'; class $AssetsImagesGen { @@ -15,8 +17,11 @@ class $AssetsImagesGen { class $AssetsImagesComponentsGen { const $AssetsImagesComponentsGen(); + /// File path: assets/images/components/background.png AssetGenImage get background => const AssetGenImage('assets/images/components/background.png'); + + /// File path: assets/images/components/plunger.png AssetGenImage get plunger => const AssetGenImage('assets/images/components/plunger.png'); } diff --git a/packages/pinball_components/assets/images/plunger/plunger.png b/packages/pinball_components/assets/images/plunger/plunger.png new file mode 100644 index 0000000000000000000000000000000000000000..f3cbdf0f004f617615e882c44d3522b3a2d88bd9 GIT binary patch literal 11655 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 G?Ee5&CbES9 literal 0 HcmV?d00001 diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index b52ca694..ab64439f 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -33,6 +33,7 @@ class $AssetsImagesGen { $AssetsImagesKickerGen get kicker => const $AssetsImagesKickerGen(); $AssetsImagesLaunchRampGen get launchRamp => const $AssetsImagesLaunchRampGen(); + $AssetsImagesPlungerGen get plunger => const $AssetsImagesPlungerGen(); $AssetsImagesSlingshotGen get slingshot => const $AssetsImagesSlingshotGen(); $AssetsImagesSpaceshipGen get spaceship => const $AssetsImagesSpaceshipGen(); $AssetsImagesSparkyGen get sparky => const $AssetsImagesSparkyGen(); @@ -171,6 +172,14 @@ class $AssetsImagesLaunchRampGen { const AssetGenImage('assets/images/launch_ramp/ramp.png'); } +class $AssetsImagesPlungerGen { + const $AssetsImagesPlungerGen(); + + /// File path: assets/images/plunger/plunger.png + AssetGenImage get plunger => + const AssetGenImage('assets/images/plunger/plunger.png'); +} + class $AssetsImagesSlingshotGen { const $AssetsImagesSlingshotGen(); diff --git a/packages/pinball_components/lib/gen/fonts.gen.dart b/packages/pinball_components/lib/gen/fonts.gen.dart index b15f2dd0..5f77da16 100644 --- a/packages/pinball_components/lib/gen/fonts.gen.dart +++ b/packages/pinball_components/lib/gen/fonts.gen.dart @@ -3,9 +3,14 @@ /// FlutterGen /// ***************************************************** +// ignore_for_file: directives_ordering,unnecessary_import + class FontFamily { FontFamily._(); + /// Font family: PixeloidMono static const String pixeloidMono = 'PixeloidMono'; + + /// Font family: PixeloidSans static const String pixeloidSans = 'PixeloidSans'; } diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index 8dca7549..b5e9392b 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -17,6 +17,7 @@ export 'joint_anchor.dart'; export 'kicker.dart'; export 'launch_ramp.dart'; export 'layer.dart'; +export 'plunger.dart'; export 'ramp_opening.dart'; export 'score_text.dart'; export 'shapes/shapes.dart'; diff --git a/lib/game/components/plunger.dart b/packages/pinball_components/lib/src/components/plunger.dart similarity index 73% rename from lib/game/components/plunger.dart rename to packages/pinball_components/lib/src/components/plunger.dart index b8c079b5..7e0ba5ba 100644 --- a/lib/game/components/plunger.dart +++ b/packages/pinball_components/lib/src/components/plunger.dart @@ -1,16 +1,14 @@ import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flutter/services.dart'; -import 'package:pinball/gen/assets.gen.dart'; -import 'package:pinball_components/pinball_components.dart' hide Assets; +import 'package:pinball_components/pinball_components.dart'; /// {@template plunger} /// [Plunger] serves as a spring, that shoots the ball on the right side of the /// playfield. /// -/// [Plunger] ignores gravity so the player controls its downward [_pull]. +/// [Plunger] ignores gravity so the player controls its downward [pull]. /// {@endtemplate} -class Plunger extends BodyComponent with KeyboardHandler, InitialPosition { +class Plunger extends BodyComponent with InitialPosition { /// {@macro plunger} Plunger({ required this.compressionDistance, @@ -43,7 +41,7 @@ class Plunger extends BodyComponent with KeyboardHandler, InitialPosition { } /// Set a constant downward velocity on the [Plunger]. - void _pull() { + void pull() { body.linearVelocity = Vector2(0, -7); } @@ -51,32 +49,11 @@ class Plunger extends BodyComponent with KeyboardHandler, InitialPosition { /// /// The velocity's magnitude depends on how far the [Plunger] has been pulled /// from its original [initialPosition]. - void _release() { + void release() { final velocity = (initialPosition.y - body.position.y) * 5; body.linearVelocity = Vector2(0, velocity); } - @override - bool onKeyEvent( - RawKeyEvent event, - Set keysPressed, - ) { - final keys = [ - LogicalKeyboardKey.space, - LogicalKeyboardKey.arrowDown, - LogicalKeyboardKey.keyS, - ]; - if (!keys.contains(event.logicalKey)) return true; - - if (event is RawKeyDownEvent) { - _pull(); - } else if (event is RawKeyUpEvent) { - _release(); - } - - return false; - } - /// Anchors the [Plunger] to the [PrismaticJoint] that controls its vertical /// motion. Future _anchorToJoint() async { @@ -97,26 +74,24 @@ class Plunger extends BodyComponent with KeyboardHandler, InitialPosition { Future onLoad() async { await super.onLoad(); await _anchorToJoint(); - renderBody = false; - - await _loadSprite(); + await add(_PlungerSpriteComponent()); } +} - Future _loadSprite() async { +class _PlungerSpriteComponent extends SpriteComponent with HasGameRef { + @override + Future onLoad() async { + await super.onLoad(); final sprite = await gameRef.loadSprite( - Assets.images.components.plunger.path, + Assets.images.plunger.plunger.keyName, ); - await add( - SpriteComponent( - sprite: sprite, - size: Vector2(5.5, 40), - anchor: Anchor.center, - position: Vector2(2, 19), - angle: -0.033, - ), - ); + this.sprite = sprite; + size = sprite.originalSize / 10; + anchor = Anchor.center; + position = Vector2(2, 19); + angle = -0.033; } } @@ -133,14 +108,6 @@ class PlungerAnchor extends JointAnchor { -plunger.compressionDistance, ); } - - @override - Body createBody() { - final bodyDef = BodyDef() - ..position = initialPosition - ..type = BodyType.static; - return world.createBody(bodyDef); - } } /// {@template plunger_anchor_prismatic_joint_def} diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index 72263d1d..d63e2312 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -49,6 +49,7 @@ flutter: - assets/images/spaceship/ramp/ - assets/images/chrome_dino/ - assets/images/kicker/ + - assets/images/plunger/ - assets/images/slingshot/ - assets/images/sparky/computer/ - assets/images/sparky/bumper/a/ diff --git a/packages/pinball_components/sandbox/lib/main.dart b/packages/pinball_components/sandbox/lib/main.dart index 193a0d8f..1e4aab5e 100644 --- a/packages/pinball_components/sandbox/lib/main.dart +++ b/packages/pinball_components/sandbox/lib/main.dart @@ -21,6 +21,7 @@ void main() { addChromeDinoStories(dashbook); addDashNestBumperStories(dashbook); addKickerStories(dashbook); + addPlungerStories(dashbook); addSlingshotStories(dashbook); addSparkyBumperStories(dashbook); addZoomStories(dashbook); diff --git a/packages/pinball_components/sandbox/lib/stories/plunger/plunger_game.dart b/packages/pinball_components/sandbox/lib/stories/plunger/plunger_game.dart new file mode 100644 index 00000000..baaab21b --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/plunger/plunger_game.dart @@ -0,0 +1,54 @@ +import 'package:flame/input.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.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 BasicBallGame with KeyboardEvents, Traceable { + PlungerGame() : super(color: const Color(0xFFFF0000)); + + static const info = ''' + Shows how Plunger is rendered. + + - Activate the "trace" parameter to overlay the body. + - Tap anywhere on the screen to spawn a ball into the game. +'''; + + static const _downKeys = [ + LogicalKeyboardKey.arrowDown, + LogicalKeyboardKey.space, + ]; + + late Plunger plunger; + + @override + Future onLoad() async { + await super.onLoad(); + + final center = screenToWorld(camera.viewport.canvasSize! / 2); + + plunger = Plunger(compressionDistance: 29) + ..initialPosition = Vector2(center.x - (Kicker.size.x * 2), center.y); + await add(plunger); + + await traceAllBodies(); + } + + @override + KeyEventResult onKeyEvent( + RawKeyEvent event, + Set keysPressed, + ) { + final movedPlungerDown = _downKeys.contains(event.logicalKey); + if (movedPlungerDown) { + if (event is RawKeyDownEvent) { + plunger.pull(); + } else if (event is RawKeyUpEvent) { + plunger.release(); + } + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + } +} diff --git a/packages/pinball_components/sandbox/lib/stories/plunger/stories.dart b/packages/pinball_components/sandbox/lib/stories/plunger/stories.dart new file mode 100644 index 00000000..86061dc2 --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/plunger/stories.dart @@ -0,0 +1,15 @@ +import 'package:dashbook/dashbook.dart'; +import 'package:flame/game.dart'; +import 'package:sandbox/common/common.dart'; +import 'package:sandbox/stories/plunger/plunger_game.dart'; + +void addPlungerStories(Dashbook dashbook) { + dashbook.storiesOf('Plunger').add( + 'Basic', + (context) => GameWidget( + game: PlungerGame()..trace = context.boolProperty('Trace', true), + ), + codeLink: buildSourceLink('plunger_game/basic.dart'), + info: PlungerGame.info, + ); +} diff --git a/packages/pinball_components/sandbox/lib/stories/stories.dart b/packages/pinball_components/sandbox/lib/stories/stories.dart index 0a795ec7..7dd02878 100644 --- a/packages/pinball_components/sandbox/lib/stories/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/stories.dart @@ -8,6 +8,7 @@ export 'flutter_forest/stories.dart'; export 'google_word/stories.dart'; export 'launch_ramp/stories.dart'; export 'layer/stories.dart'; +export 'plunger/stories.dart'; export 'score_text/stories.dart'; export 'slingshot/stories.dart'; export 'spaceship/stories.dart'; diff --git a/test/game/components/plunger_test.dart b/packages/pinball_components/test/src/components/plunger_test.dart similarity index 62% rename from test/game/components/plunger_test.dart rename to packages/pinball_components/test/src/components/plunger_test.dart index 65789ae0..8f8a26db 100644 --- a/test/game/components/plunger_test.dart +++ b/packages/pinball_components/test/src/components/plunger_test.dart @@ -1,12 +1,9 @@ // ignore_for_file: cascade_invocations -import 'dart:collection'; - import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; import '../../helpers/helpers.dart'; @@ -117,13 +114,23 @@ void main() { ); }); - group('onKeyEvent', () { - final keys = UnmodifiableListView([ - LogicalKeyboardKey.space, - LogicalKeyboardKey.arrowDown, - LogicalKeyboardKey.keyS, - ]); + group('pull', () { + flameTester.test( + 'moves downwards when pull is called', + (game) async { + final plunger = Plunger( + compressionDistance: compressionDistance, + ); + await game.ensureAdd(plunger); + plunger.pull(); + expect(plunger.body.linearVelocity.y, isNegative); + expect(plunger.body.linearVelocity.x, isZero); + }, + ); + }); + + group('release', () { late Plunger plunger; setUp(() { @@ -132,56 +139,28 @@ void main() { ); }); - testRawKeyUpEvents(keys, (event) { - final keyLabel = (event.logicalKey != LogicalKeyboardKey.space) - ? event.logicalKey.keyLabel - : 'Space'; - flameTester.test( - 'moves upwards when $keyLabel is released ' - 'and plunger is below its starting position', - (game) async { - await game.ensureAdd(plunger); - plunger.body.setTransform(Vector2(0, -1), 0); - plunger.onKeyEvent(event, {}); - - expect(plunger.body.linearVelocity.y, isPositive); - expect(plunger.body.linearVelocity.x, isZero); - }, - ); - }); + flameTester.test( + 'moves upwards when release is called ' + 'and plunger is below its starting position', (game) async { + await game.ensureAdd(plunger); + plunger.body.setTransform(Vector2(0, -1), 0); + plunger.release(); - testRawKeyUpEvents(keys, (event) { - final keyLabel = (event.logicalKey != LogicalKeyboardKey.space) - ? event.logicalKey.keyLabel - : 'Space'; - flameTester.test( - 'does not move when $keyLabel is released ' - 'and plunger is in its starting position', - (game) async { - await game.ensureAdd(plunger); - plunger.onKeyEvent(event, {}); - - expect(plunger.body.linearVelocity.y, isZero); - expect(plunger.body.linearVelocity.x, isZero); - }, - ); + expect(plunger.body.linearVelocity.y, isPositive); + expect(plunger.body.linearVelocity.x, isZero); }); - testRawKeyDownEvents(keys, (event) { - final keyLabel = (event.logicalKey != LogicalKeyboardKey.space) - ? event.logicalKey.keyLabel - : 'Space'; - flameTester.test( - 'moves downwards when $keyLabel is pressed', - (game) async { - await game.ensureAdd(plunger); - plunger.onKeyEvent(event, {}); - - expect(plunger.body.linearVelocity.y, isNegative); - expect(plunger.body.linearVelocity.x, isZero); - }, - ); - }); + flameTester.test( + 'does not move when release is called ' + 'and plunger is in its starting position', + (game) async { + await game.ensureAdd(plunger); + plunger.release(); + + expect(plunger.body.linearVelocity.y, isZero); + expect(plunger.body.linearVelocity.x, isZero); + }, + ); }); }); @@ -210,11 +189,13 @@ void main() { group('PlungerAnchorPrismaticJointDef', () { const compressionDistance = 10.0; late Plunger plunger; + late PlungerAnchor anchor; setUp(() { plunger = Plunger( compressionDistance: compressionDistance, ); + anchor = PlungerAnchor(plunger: plunger); }); group('initializes with', () { @@ -222,7 +203,6 @@ void main() { 'plunger body as bodyA', (game) async { await game.ensureAdd(plunger); - final anchor = PlungerAnchor(plunger: plunger); await game.ensureAdd(anchor); final jointDef = PlungerAnchorPrismaticJointDef( @@ -238,7 +218,6 @@ void main() { 'anchor body as bodyB', (game) async { await game.ensureAdd(plunger); - final anchor = PlungerAnchor(plunger: plunger); await game.ensureAdd(anchor); final jointDef = PlungerAnchorPrismaticJointDef( @@ -255,7 +234,6 @@ void main() { 'limits enabled', (game) async { await game.ensureAdd(plunger); - final anchor = PlungerAnchor(plunger: plunger); await game.ensureAdd(anchor); final jointDef = PlungerAnchorPrismaticJointDef( @@ -272,7 +250,6 @@ void main() { 'lower translation limit as negative infinity', (game) async { await game.ensureAdd(plunger); - final anchor = PlungerAnchor(plunger: plunger); await game.ensureAdd(anchor); final jointDef = PlungerAnchorPrismaticJointDef( @@ -289,7 +266,6 @@ void main() { 'connected body collison enabled', (game) async { await game.ensureAdd(plunger); - final anchor = PlungerAnchor(plunger: plunger); await game.ensureAdd(anchor); final jointDef = PlungerAnchorPrismaticJointDef( @@ -303,53 +279,47 @@ void main() { ); }); - testRawKeyUpEvents([LogicalKeyboardKey.space], (event) { - late final anchor = PlungerAnchor(plunger: plunger); - flameTester.testGameWidget( - 'plunger cannot go below anchor', - setUp: (game, tester) async { - await game.ensureAdd(plunger); - await game.ensureAdd(anchor); + flameTester.testGameWidget( + 'plunger cannot go below anchor', + setUp: (game, tester) async { + await game.ensureAdd(plunger); + await game.ensureAdd(anchor); - // Giving anchor a shape for the plunger to collide with. - anchor.body.createFixtureFromShape(PolygonShape()..setAsBoxXY(2, 1)); + // Giving anchor a shape for the plunger to collide with. + anchor.body.createFixtureFromShape(PolygonShape()..setAsBoxXY(2, 1)); - final jointDef = PlungerAnchorPrismaticJointDef( - plunger: plunger, - anchor: anchor, - ); - game.world.createJoint(PrismaticJoint(jointDef)); + final jointDef = PlungerAnchorPrismaticJointDef( + plunger: plunger, + anchor: anchor, + ); + game.world.createJoint(PrismaticJoint(jointDef)); - await tester.pump(const Duration(seconds: 1)); - }, - verify: (game, tester) async { - expect(plunger.body.position.y > anchor.body.position.y, isTrue); - }, - ); - }); + await tester.pump(const Duration(seconds: 1)); + }, + verify: (game, tester) async { + expect(plunger.body.position.y > anchor.body.position.y, isTrue); + }, + ); - testRawKeyUpEvents([LogicalKeyboardKey.space], (event) { - flameTester.testGameWidget( - 'plunger cannot excessively exceed starting position', - setUp: (game, tester) async { - await game.ensureAdd(plunger); - final anchor = PlungerAnchor(plunger: plunger); - await game.ensureAdd(anchor); + flameTester.testGameWidget( + 'plunger cannot excessively exceed starting position', + setUp: (game, tester) async { + await game.ensureAdd(plunger); + await game.ensureAdd(anchor); - final jointDef = PlungerAnchorPrismaticJointDef( - plunger: plunger, - anchor: anchor, - ); - game.world.createJoint(PrismaticJoint(jointDef)); + final jointDef = PlungerAnchorPrismaticJointDef( + plunger: plunger, + anchor: anchor, + ); + game.world.createJoint(PrismaticJoint(jointDef)); - plunger.body.setTransform(Vector2(0, -1), 0); + plunger.body.setTransform(Vector2(0, -1), 0); - await tester.pump(const Duration(seconds: 1)); - }, - verify: (game, tester) async { - expect(plunger.body.position.y < 1, isTrue); - }, - ); - }); + await tester.pump(const Duration(seconds: 1)); + }, + verify: (game, tester) async { + expect(plunger.body.position.y < 1, isTrue); + }, + ); }); } diff --git a/test/game/components/controlled_plunger_test.dart b/test/game/components/controlled_plunger_test.dart new file mode 100644 index 00000000..bb965fc1 --- /dev/null +++ b/test/game/components/controlled_plunger_test.dart @@ -0,0 +1,78 @@ +import 'dart:collection'; + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(EmptyPinballGameTest.new); + + group('PlungerController', () { + group('onKeyEvent', () { + final downKeys = UnmodifiableListView([ + LogicalKeyboardKey.arrowDown, + LogicalKeyboardKey.space, + LogicalKeyboardKey.keyS, + ]); + + late Plunger plunger; + late PlungerController controller; + + setUp(() { + plunger = Plunger(compressionDistance: 10); + controller = PlungerController(plunger); + plunger.add(controller); + }); + + testRawKeyDownEvents(downKeys, (event) { + flameTester.test( + 'moves down ' + 'when ${event.logicalKey.keyLabel} is pressed', + (game) async { + await game.ensureAdd(plunger); + controller.onKeyEvent(event, {}); + + expect(plunger.body.linearVelocity.y, isNegative); + expect(plunger.body.linearVelocity.x, isZero); + }, + ); + }); + + testRawKeyUpEvents(downKeys, (event) { + flameTester.test( + 'moves up ' + 'when ${event.logicalKey.keyLabel} is released ' + 'and plunger is below its starting position', + (game) async { + await game.ensureAdd(plunger); + plunger.body.setTransform(Vector2(0, -1), 0); + controller.onKeyEvent(event, {}); + + expect(plunger.body.linearVelocity.y, isPositive); + expect(plunger.body.linearVelocity.x, isZero); + }, + ); + }); + + testRawKeyUpEvents(downKeys, (event) { + flameTester.test( + 'does not move when ${event.logicalKey.keyLabel} is released ' + 'and plunger is in its starting position', + (game) async { + await game.ensureAdd(plunger); + controller.onKeyEvent(event, {}); + + expect(plunger.body.linearVelocity.y, isZero); + expect(plunger.body.linearVelocity.x, isZero); + }, + ); + }); + }); + }); +}