From 7338b95ef55a05a031a3e81dee470dd353a7ec87 Mon Sep 17 00:00:00 2001 From: Allison Ryan Date: Wed, 2 Mar 2022 08:42:11 -0600 Subject: [PATCH 01/11] feat: plunger --- lib/game/components/boundaries.dart | 41 ++++++++++++++++++++++ lib/game/components/components.dart | 2 ++ lib/game/components/plunger.dart | 31 +++++++++++++++++ lib/game/game.dart | 1 + lib/game/pinball_game.dart | 54 ++++++++++++++++++++++++++++- 5 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 lib/game/components/boundaries.dart create mode 100644 lib/game/components/components.dart create mode 100644 lib/game/components/plunger.dart diff --git a/lib/game/components/boundaries.dart b/lib/game/components/boundaries.dart new file mode 100644 index 00000000..08e9a2c3 --- /dev/null +++ b/lib/game/components/boundaries.dart @@ -0,0 +1,41 @@ +import 'package:flame_forge2d/body_component.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_forge2d/forge2d_game.dart'; +import 'package:forge2d/forge2d.dart'; + +List createBoundaries(Forge2DGame game) { + final topLeft = Vector2.zero(); + final bottomRight = game.screenToWorld(game.camera.viewport.effectiveSize); + final topRight = Vector2(bottomRight.x, topLeft.y); + final bottomLeft = Vector2(topLeft.x, bottomRight.y); + + return [ + Wall(topLeft, topRight), + Wall(topRight, bottomRight), + Wall(bottomRight, bottomLeft), + Wall(bottomLeft, topLeft), + ]; +} + +class Wall extends BodyComponent { + Wall(this.start, this.end); + + final Vector2 start; + final Vector2 end; + + @override + Body createBody() { + final shape = EdgeShape()..set(start, end); + + final fixtureDef = FixtureDef(shape) + ..restitution = 0.0 + ..friction = 0.3; + + final bodyDef = BodyDef() + ..userData = this + ..position = Vector2.zero() + ..type = BodyType.static; + + return world.createBody(bodyDef)..createFixture(fixtureDef); + } +} diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart new file mode 100644 index 00000000..85ec5ae3 --- /dev/null +++ b/lib/game/components/components.dart @@ -0,0 +1,2 @@ +export 'boundaries.dart'; +export 'plunger.dart'; diff --git a/lib/game/components/plunger.dart b/lib/game/components/plunger.dart new file mode 100644 index 00000000..7b168629 --- /dev/null +++ b/lib/game/components/plunger.dart @@ -0,0 +1,31 @@ +import 'package:flame_forge2d/body_component.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; + +class Plunger extends BodyComponent { + Plunger(this._position); + + final Vector2 _position; + + @override + Body createBody() { + final shape = PolygonShape()..setAsBoxXY(2.5, 1.5); + + final fixtureDef = FixtureDef(shape)..friction = 0.1; + + final bodyDef = BodyDef() + ..userData = this + ..position = _position + ..type = BodyType.dynamic; + + return world.createBody(bodyDef)..createFixture(fixtureDef); + } + + void pull() { + body.linearVelocity = Vector2(0, -5); + } + + void release() { + final velocity = (_position.y - body.position.y) * 9; + body.linearVelocity = Vector2(0, velocity); + } +} diff --git a/lib/game/game.dart b/lib/game/game.dart index ec8e0824..0a8dac1b 100644 --- a/lib/game/game.dart +++ b/lib/game/game.dart @@ -1,2 +1,3 @@ +export 'components/components.dart'; export 'pinball_game.dart'; export 'view/pinball_game_page.dart'; diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 306d03f0..031e51d7 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -1,3 +1,55 @@ +import 'package:flame/input.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/forge2d_game.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:pinball/game/game.dart'; -class PinballGame extends Forge2DGame {} +class PinballGame extends Forge2DGame with KeyboardEvents { + late Plunger plunger; + + @override + Future onLoad() async { + await super.onLoad(); + + final boundaries = createBoundaries(this)..forEach(add); + final bottomWall = boundaries[2]; + + final center = screenToWorld(camera.viewport.effectiveSize / 2); + + await add(plunger = Plunger(Vector2(center.x, center.y - 50))); + + final prismaticJointDef = PrismaticJointDef() + ..initialize( + plunger.body, + bottomWall.body, + bottomWall.body.position, + Vector2(0, 0), + ) + ..localAnchorA.setFrom(Vector2(0, 0)) + ..enableLimit = true + ..upperTranslation = 0 + ..lowerTranslation = -5 + ..collideConnected = true; + + world.createJoint(prismaticJointDef); + print(prismaticJointDef.localAnchorA); + print(prismaticJointDef.localAnchorB); + } + + @override + KeyEventResult onKeyEvent( + RawKeyEvent event, + Set keysPressed, + ) { + if (event is RawKeyUpEvent && + event.data.logicalKey == LogicalKeyboardKey.space) { + plunger.release(); + } + if (event is RawKeyDownEvent && + event.data.logicalKey == LogicalKeyboardKey.space) { + plunger.pull(); + } + return KeyEventResult.handled; + } +} From 92590b9c827521613e20d029ddb550dabc7b1002 Mon Sep 17 00:00:00 2001 From: Allison Ryan Date: Wed, 2 Mar 2022 15:23:07 -0600 Subject: [PATCH 02/11] docs: plunger issue comments --- lib/game/components/plunger.dart | 3 ++- lib/game/pinball_game.dart | 37 +++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/lib/game/components/plunger.dart b/lib/game/components/plunger.dart index 7b168629..d52c7c31 100644 --- a/lib/game/components/plunger.dart +++ b/lib/game/components/plunger.dart @@ -1,4 +1,3 @@ -import 'package:flame_forge2d/body_component.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; class Plunger extends BodyComponent { @@ -20,10 +19,12 @@ class Plunger extends BodyComponent { return world.createBody(bodyDef)..createFixture(fixtureDef); } + // Unused for now - from the previous kinematic plunger implementation. void pull() { body.linearVelocity = Vector2(0, -5); } + // Unused for now - from the previous kinematic plunger implementation. void release() { final velocity = (_position.y - body.position.y) * 9; body.linearVelocity = Vector2(0, velocity); diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 597c53ca..ff112a43 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -7,6 +7,7 @@ import 'package:pinball/game/game.dart'; class PinballGame extends Forge2DGame with FlameBloc, KeyboardEvents { late Plunger plunger; + late PrismaticJointDef prismaticJointDef; @override Future onLoad() async { @@ -20,22 +21,30 @@ class PinballGame extends Forge2DGame with FlameBloc, KeyboardEvents { await add(plunger = Plunger(Vector2(center.x, center.y - 50))); - final prismaticJointDef = PrismaticJointDef() + prismaticJointDef = PrismaticJointDef() ..initialize( plunger.body, bottomWall.body, - bottomWall.body.position, - Vector2(0, 0), + plunger.body.position, + // Logically, I feel like this should be (0, 1), but it has to be + // negative for lowerTranslation limit to work as expected. + Vector2(0, -1), ) - ..localAnchorA.setFrom(Vector2(0, 0)) ..enableLimit = true - ..upperTranslation = 0 - ..lowerTranslation = -5 + // Given the above inverted vertical axis, the lowerTranslation works as + // expected and this lets the plunger fall down 10 units before being + // stopped. + // + // Ideally, we shouldn't need to set any limits here - this is just for + // demo purposes to see how the limits work. We should be leaving this at + // 0 and altering it as the user holds the space bar. The longer they hold + // it, the lower the lowerTranslation becomes - allowing the plunger to + // slowly fall down (see key event handlers below). + ..lowerTranslation = -10 + // This prevents the plunger from falling through the bottom wall. ..collideConnected = true; world.createJoint(prismaticJointDef); - print(prismaticJointDef.localAnchorA); - print(prismaticJointDef.localAnchorB); } @override @@ -45,11 +54,19 @@ class PinballGame extends Forge2DGame with FlameBloc, KeyboardEvents { ) { if (event is RawKeyUpEvent && event.data.logicalKey == LogicalKeyboardKey.space) { - plunger.release(); + // I haven't been able to successfully pull down the plunger, so this is + // completely untested. I imagine we could calculate the distance between + // the prismaticJoinDef.upperTranslation (plunger starting position) and + // the ground, then use that value as a multiplier on the speed so the + // ball moves faster when you pull the plunger farther down. + prismaticJointDef.motorSpeed = 5; } if (event is RawKeyDownEvent && event.data.logicalKey == LogicalKeyboardKey.space) { - plunger.pull(); + // This was my attempt to decrement the lower limit but it doesn't seem to + // render. If you debug, you can see that this value is being lowered, + // but the game isn't reflecting these value changes. + prismaticJointDef.lowerTranslation--; } return KeyEventResult.handled; } From 7048618ce5e68cdd2da8584df7a205d373eb1c57 Mon Sep 17 00:00:00 2001 From: Allison Ryan Date: Thu, 3 Mar 2022 13:16:52 -0600 Subject: [PATCH 03/11] test: plunger and joint --- lib/game/components/plunger.dart | 46 +++++- lib/game/pinball_game.dart | 42 +----- test/game/components/plunger_test.dart | 192 +++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 41 deletions(-) create mode 100644 test/game/components/plunger_test.dart diff --git a/lib/game/components/plunger.dart b/lib/game/components/plunger.dart index d52c7c31..2b55cdc7 100644 --- a/lib/game/components/plunger.dart +++ b/lib/game/components/plunger.dart @@ -1,6 +1,14 @@ import 'package:flame_forge2d/flame_forge2d.dart'; +/// {@template plunger} +/// Plunger body component to be pulled and released by the player to launch +/// the pinball. +/// +/// The plunger body ignores gravity so the player can control its downward +/// pull. +/// {@endtemplate} class Plunger extends BodyComponent { + /// {@macro plunger} Plunger(this._position); final Vector2 _position; @@ -9,24 +17,52 @@ class Plunger extends BodyComponent { Body createBody() { final shape = PolygonShape()..setAsBoxXY(2.5, 1.5); - final fixtureDef = FixtureDef(shape)..friction = 0.1; + final fixtureDef = FixtureDef(shape); final bodyDef = BodyDef() ..userData = this ..position = _position - ..type = BodyType.dynamic; + ..type = BodyType.dynamic + ..gravityScale = 0; return world.createBody(bodyDef)..createFixture(fixtureDef); } - // Unused for now - from the previous kinematic plunger implementation. + /// Set a contstant downward velocity on the plunger body. void pull() { - body.linearVelocity = Vector2(0, -5); + body.linearVelocity = Vector2(0, -7); } - // Unused for now - from the previous kinematic plunger implementation. + /// Set an upward velocity on the plunger body. The velocity's magnitude + /// depends on how far the plunger has been pulled from its original position. void release() { final velocity = (_position.y - body.position.y) * 9; body.linearVelocity = Vector2(0, velocity); } } + +/// {@template plunger_anchor_prismatic_joint_def} +/// Prismatic joint def between a [Plunger] and an anchor body given motion on +/// the vertical axis. +/// +/// The [Plunger] is constrained to vertical motion between its starting +/// position and the anchor body. The anchor needs to be below the plunger for +/// this joint to function properly. +/// {@endtemplate} +class PlungerAnchorPrismaticJointDef extends PrismaticJointDef { + /// {@macro plunger_anchor_prismatic_joint_def} + PlungerAnchorPrismaticJointDef({ + required Plunger plunger, + required BodyComponent anchor, + }) { + initialize( + plunger.body, + anchor.body, + anchor.body.position, + Vector2(0, -1), + ); + enableLimit = true; + lowerTranslation = double.negativeInfinity; + collideConnected = true; + } +} diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index ff112a43..fbcea02f 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -7,7 +7,6 @@ import 'package:pinball/game/game.dart'; class PinballGame extends Forge2DGame with FlameBloc, KeyboardEvents { late Plunger plunger; - late PrismaticJointDef prismaticJointDef; @override Future onLoad() async { @@ -19,32 +18,11 @@ class PinballGame extends Forge2DGame with FlameBloc, KeyboardEvents { final center = screenToWorld(camera.viewport.effectiveSize / 2); - await add(plunger = Plunger(Vector2(center.x, center.y - 50))); + await add(plunger = Plunger(Vector2(center.x, center.y))); - prismaticJointDef = PrismaticJointDef() - ..initialize( - plunger.body, - bottomWall.body, - plunger.body.position, - // Logically, I feel like this should be (0, 1), but it has to be - // negative for lowerTranslation limit to work as expected. - Vector2(0, -1), - ) - ..enableLimit = true - // Given the above inverted vertical axis, the lowerTranslation works as - // expected and this lets the plunger fall down 10 units before being - // stopped. - // - // Ideally, we shouldn't need to set any limits here - this is just for - // demo purposes to see how the limits work. We should be leaving this at - // 0 and altering it as the user holds the space bar. The longer they hold - // it, the lower the lowerTranslation becomes - allowing the plunger to - // slowly fall down (see key event handlers below). - ..lowerTranslation = -10 - // This prevents the plunger from falling through the bottom wall. - ..collideConnected = true; - - world.createJoint(prismaticJointDef); + world.createJoint( + PlungerAnchorPrismaticJointDef(plunger: plunger, anchor: bottomWall), + ); } @override @@ -54,19 +32,11 @@ class PinballGame extends Forge2DGame with FlameBloc, KeyboardEvents { ) { if (event is RawKeyUpEvent && event.data.logicalKey == LogicalKeyboardKey.space) { - // I haven't been able to successfully pull down the plunger, so this is - // completely untested. I imagine we could calculate the distance between - // the prismaticJoinDef.upperTranslation (plunger starting position) and - // the ground, then use that value as a multiplier on the speed so the - // ball moves faster when you pull the plunger farther down. - prismaticJointDef.motorSpeed = 5; + plunger.release(); } if (event is RawKeyDownEvent && event.data.logicalKey == LogicalKeyboardKey.space) { - // This was my attempt to decrement the lower limit but it doesn't seem to - // render. If you debug, you can see that this value is being lowered, - // but the game isn't reflecting these value changes. - prismaticJointDef.lowerTranslation--; + plunger.pull(); } return KeyEventResult.handled; } diff --git a/test/game/components/plunger_test.dart b/test/game/components/plunger_test.dart new file mode 100644 index 00000000..6156c5ff --- /dev/null +++ b/test/game/components/plunger_test.dart @@ -0,0 +1,192 @@ +// 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/game/game.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(PinballGame.new); + + group('Plunger', () { + flameTester.test( + 'loads correctly', + (game) async { + final plunger = Plunger(Vector2.zero()); + await game.ensureAdd(plunger); + + expect(game.contains(plunger), isTrue); + }, + ); + + group('body', () { + flameTester.test( + 'positions correctly', + (game) async { + final position = Vector2.all(10); + final plunger = Plunger(position); + await game.ensureAdd(plunger); + game.contains(plunger); + + expect(plunger.body.position, position); + }, + ); + + flameTester.test( + 'is dynamic', + (game) async { + final plunger = Plunger(Vector2.zero()); + await game.ensureAdd(plunger); + + expect(plunger.body.bodyType, equals(BodyType.dynamic)); + }, + ); + + flameTester.test( + 'ignores gravity', + (game) async { + final plunger = Plunger(Vector2.zero()); + await game.ensureAdd(plunger); + + expect(plunger.body.gravityScale, isZero); + }, + ); + }); + + group('first fixture', () { + flameTester.test( + 'exists', + (game) async { + final plunger = Plunger(Vector2.zero()); + await game.ensureAdd(plunger); + + expect(plunger.body.fixtures[0], isA()); + }, + ); + + flameTester.test( + 'shape is a polygon', + (game) async { + final plunger = Plunger(Vector2.zero()); + await game.ensureAdd(plunger); + + final fixture = plunger.body.fixtures[0]; + expect(fixture.shape.shapeType, equals(ShapeType.polygon)); + }, + ); + }); + + flameTester.test( + 'pull sets a negative linear velocity', + (game) async { + final plunger = Plunger(Vector2.zero()); + await game.ensureAdd(plunger); + + plunger.pull(); + + expect(plunger.body.linearVelocity.y, isNegative); + }, + ); + + group('release', () { + flameTester.test( + 'does not set a linear velocity ' + 'when plunger is in starting position', + (game) async { + final plunger = Plunger(Vector2.zero()); + await game.ensureAdd(plunger); + + plunger.release(); + + expect(plunger.body.linearVelocity.y, isZero); + }, + ); + + flameTester.test( + 'sets a positive linear velocity ' + 'when plunger is below starting position', + (game) async { + final plunger = Plunger(Vector2.zero()); + await game.ensureAdd(plunger); + + plunger.body.setTransform(Vector2(0, -1), 0); + plunger.release(); + + expect(plunger.body.linearVelocity.y, isPositive); + }, + ); + }); + }); + + group('PlungerAnchorPrismaticJointDef', () { + final plunger = Plunger(Vector2.zero())..createBody(); + final anchor = Plunger(Vector2(0, -5))..createBody(); + + group('initializes with', () { + flameTester.test( + 'plunger as bodyA', + (game) async { + final jointDef = PlungerAnchorPrismaticJointDef( + plunger: plunger, + anchor: anchor, + ); + + expect(jointDef.bodyA, equals(plunger)); + }, + ); + + flameTester.test( + 'anchor as bodyB', + (game) async { + final jointDef = PlungerAnchorPrismaticJointDef( + plunger: plunger, + anchor: anchor, + ); + game.world.createJoint(jointDef); + + expect(jointDef.bodyB, equals(anchor)); + }, + ); + + flameTester.test( + 'limits enabled', + (game) async { + final jointDef = PlungerAnchorPrismaticJointDef( + plunger: plunger, + anchor: anchor, + ); + game.world.createJoint(jointDef); + + expect(jointDef.enableLimit, isTrue); + }, + ); + + flameTester.test( + 'lower translation limit as negative infinity', + (game) async { + final jointDef = PlungerAnchorPrismaticJointDef( + plunger: plunger, + anchor: anchor, + ); + game.world.createJoint(jointDef); + + expect(jointDef.lowerTranslation, equals(double.negativeInfinity)); + }, + ); + + flameTester.test( + 'connected body collison enabled', + (game) async { + final jointDef = PlungerAnchorPrismaticJointDef( + plunger: plunger, + anchor: anchor, + ); + game.world.createJoint(jointDef); + + expect(jointDef.collideConnected, isTrue); + }, + ); + }); + }); +} From 902d3092ee3aeffa4ff072051f14671a36bf8373 Mon Sep 17 00:00:00 2001 From: Erick Zanardo Date: Thu, 3 Mar 2022 16:40:27 -0300 Subject: [PATCH 04/11] fix: plunger tests --- test/game/components/plunger_test.dart | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/test/game/components/plunger_test.dart b/test/game/components/plunger_test.dart index 6156c5ff..f310874d 100644 --- a/test/game/components/plunger_test.dart +++ b/test/game/components/plunger_test.dart @@ -120,13 +120,20 @@ void main() { }); group('PlungerAnchorPrismaticJointDef', () { - final plunger = Plunger(Vector2.zero())..createBody(); - final anchor = Plunger(Vector2(0, -5))..createBody(); + late Plunger plunger; + late Plunger anchor; + + setUp(() { + plunger = Plunger(Vector2.zero()); + anchor = Plunger(Vector2(0, -5)); + }); group('initializes with', () { flameTester.test( 'plunger as bodyA', (game) async { + await game.ensureAddAll([plunger, anchor]); + final jointDef = PlungerAnchorPrismaticJointDef( plunger: plunger, anchor: anchor, @@ -139,6 +146,8 @@ void main() { flameTester.test( 'anchor as bodyB', (game) async { + await game.ensureAddAll([plunger, anchor]); + final jointDef = PlungerAnchorPrismaticJointDef( plunger: plunger, anchor: anchor, @@ -152,6 +161,8 @@ void main() { flameTester.test( 'limits enabled', (game) async { + await game.ensureAddAll([plunger, anchor]); + final jointDef = PlungerAnchorPrismaticJointDef( plunger: plunger, anchor: anchor, @@ -165,6 +176,8 @@ void main() { flameTester.test( 'lower translation limit as negative infinity', (game) async { + await game.ensureAddAll([plunger, anchor]); + final jointDef = PlungerAnchorPrismaticJointDef( plunger: plunger, anchor: anchor, @@ -178,6 +191,8 @@ void main() { flameTester.test( 'connected body collison enabled', (game) async { + await game.ensureAddAll([plunger, anchor]); + final jointDef = PlungerAnchorPrismaticJointDef( plunger: plunger, anchor: anchor, From 85936df74e8c0de7905ccbaec233b1a5de6ab76d Mon Sep 17 00:00:00 2001 From: Allison Ryan Date: Thu, 3 Mar 2022 14:18:29 -0600 Subject: [PATCH 05/11] test: joint tests --- lib/game/components/boundaries.dart | 41 --------------------- lib/game/components/components.dart | 1 - lib/game/pinball_game.dart | 35 ++---------------- test/game/components/plunger_test.dart | 49 +++++++++++++++++++++++--- 4 files changed, 46 insertions(+), 80 deletions(-) delete mode 100644 lib/game/components/boundaries.dart diff --git a/lib/game/components/boundaries.dart b/lib/game/components/boundaries.dart deleted file mode 100644 index 08e9a2c3..00000000 --- a/lib/game/components/boundaries.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flame_forge2d/body_component.dart'; -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flame_forge2d/forge2d_game.dart'; -import 'package:forge2d/forge2d.dart'; - -List createBoundaries(Forge2DGame game) { - final topLeft = Vector2.zero(); - final bottomRight = game.screenToWorld(game.camera.viewport.effectiveSize); - final topRight = Vector2(bottomRight.x, topLeft.y); - final bottomLeft = Vector2(topLeft.x, bottomRight.y); - - return [ - Wall(topLeft, topRight), - Wall(topRight, bottomRight), - Wall(bottomRight, bottomLeft), - Wall(bottomLeft, topLeft), - ]; -} - -class Wall extends BodyComponent { - Wall(this.start, this.end); - - final Vector2 start; - final Vector2 end; - - @override - Body createBody() { - final shape = EdgeShape()..set(start, end); - - final fixtureDef = FixtureDef(shape) - ..restitution = 0.0 - ..friction = 0.3; - - final bodyDef = BodyDef() - ..userData = this - ..position = Vector2.zero() - ..type = BodyType.static; - - return world.createBody(bodyDef)..createFixture(fixtureDef); - } -} diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index 97f7eb69..28541aed 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -1,4 +1,3 @@ export 'ball.dart'; -export 'boundaries.dart'; export 'plunger.dart'; export 'score_points.dart'; diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index fbcea02f..80b4ffea 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -1,43 +1,12 @@ -import 'package:flame/input.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:pinball/game/game.dart'; -class PinballGame extends Forge2DGame with FlameBloc, KeyboardEvents { - late Plunger plunger; +import 'package:pinball/game/game.dart'; +class PinballGame extends Forge2DGame with FlameBloc { @override Future onLoad() async { await super.onLoad(); addContactCallback(BallScorePointsCallback()); - - final boundaries = createBoundaries(this)..forEach(add); - final bottomWall = boundaries[2]; - - final center = screenToWorld(camera.viewport.effectiveSize / 2); - - await add(plunger = Plunger(Vector2(center.x, center.y))); - - world.createJoint( - PlungerAnchorPrismaticJointDef(plunger: plunger, anchor: bottomWall), - ); - } - - @override - KeyEventResult onKeyEvent( - RawKeyEvent event, - Set keysPressed, - ) { - if (event is RawKeyUpEvent && - event.data.logicalKey == LogicalKeyboardKey.space) { - plunger.release(); - } - if (event is RawKeyDownEvent && - event.data.logicalKey == LogicalKeyboardKey.space) { - plunger.pull(); - } - return KeyEventResult.handled; } } diff --git a/test/game/components/plunger_test.dart b/test/game/components/plunger_test.dart index f310874d..54a477e3 100644 --- a/test/game/components/plunger_test.dart +++ b/test/game/components/plunger_test.dart @@ -125,12 +125,12 @@ void main() { setUp(() { plunger = Plunger(Vector2.zero()); - anchor = Plunger(Vector2(0, -5)); + anchor = Plunger(Vector2(0, -1)); }); group('initializes with', () { flameTester.test( - 'plunger as bodyA', + 'plunger body as bodyA', (game) async { await game.ensureAddAll([plunger, anchor]); @@ -139,12 +139,12 @@ void main() { anchor: anchor, ); - expect(jointDef.bodyA, equals(plunger)); + expect(jointDef.bodyA, equals(plunger.body)); }, ); flameTester.test( - 'anchor as bodyB', + 'anchor body as bodyB', (game) async { await game.ensureAddAll([plunger, anchor]); @@ -154,7 +154,7 @@ void main() { ); game.world.createJoint(jointDef); - expect(jointDef.bodyB, equals(anchor)); + expect(jointDef.bodyB, equals(anchor.body)); }, ); @@ -203,5 +203,44 @@ void main() { }, ); }); + + flameTester.widgetTest( + 'plunger cannot go below anchor', + (game, tester) async { + await game.ensureAddAll([plunger, anchor]); + + final jointDef = PlungerAnchorPrismaticJointDef( + plunger: plunger, + anchor: anchor, + ); + game.world.createJoint(jointDef); + + plunger.pull(); + await tester.pump(const Duration(seconds: 1)); + + expect(plunger.body.position.y > anchor.body.position.y, isTrue); + }, + ); + + flameTester.widgetTest( + 'plunger cannot excessively exceed starting position', + (game, tester) async { + await game.ensureAddAll([plunger, anchor]); + + final jointDef = PlungerAnchorPrismaticJointDef( + plunger: plunger, + anchor: anchor, + ); + game.world.createJoint(jointDef); + + plunger.pull(); + await tester.pump(const Duration(seconds: 1)); + + plunger.release(); + await tester.pump(const Duration(seconds: 1)); + + expect(plunger.body.position.y < 1, isTrue); + }, + ); }); } From 64d4677b3f5077f76e12cd1da23b56e0cbc8975d Mon Sep 17 00:00:00 2001 From: Allison Ryan Date: Thu, 3 Mar 2022 14:20:03 -0600 Subject: [PATCH 06/11] chore: remove modifications to game --- lib/game/pinball_game.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 80b4ffea..f7b2777f 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -1,12 +1,10 @@ import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; - import 'package:pinball/game/game.dart'; class PinballGame extends Forge2DGame with FlameBloc { @override Future onLoad() async { - await super.onLoad(); addContactCallback(BallScorePointsCallback()); } } From 2e28cd1a1d9ec52576b6b289578486316da0a4b6 Mon Sep 17 00:00:00 2001 From: Allison Ryan Date: Thu, 3 Mar 2022 14:30:01 -0600 Subject: [PATCH 07/11] docs: fix typo --- lib/game/components/plunger.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/game/components/plunger.dart b/lib/game/components/plunger.dart index 2b55cdc7..8af6b167 100644 --- a/lib/game/components/plunger.dart +++ b/lib/game/components/plunger.dart @@ -28,7 +28,7 @@ class Plunger extends BodyComponent { return world.createBody(bodyDef)..createFixture(fixtureDef); } - /// Set a contstant downward velocity on the plunger body. + /// Set a constant downward velocity on the plunger body. void pull() { body.linearVelocity = Vector2(0, -7); } From bb1663111f858609a7fab866b9bdfa59ee016053 Mon Sep 17 00:00:00 2001 From: Allison Ryan Date: Fri, 4 Mar 2022 08:39:29 -0600 Subject: [PATCH 08/11] refactor: suggestions --- lib/game/components/plunger.dart | 32 ++++++++++-------- test/game/components/plunger_test.dart | 45 +++++++++++++++++++------- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/lib/game/components/plunger.dart b/lib/game/components/plunger.dart index 8af6b167..7e6dbd89 100644 --- a/lib/game/components/plunger.dart +++ b/lib/game/components/plunger.dart @@ -1,15 +1,15 @@ import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball/game/game.dart'; /// {@template plunger} -/// Plunger body component to be pulled and released by the player to launch -/// the pinball. +/// [Plunger] serves as a spring, that shoots the ball on the right side of the +/// playfield. /// -/// The plunger body ignores gravity so the player can control its downward -/// pull. +/// [Plunger] ignores gravity so the player controls its downward [pull]. /// {@endtemplate} class Plunger extends BodyComponent { /// {@macro plunger} - Plunger(this._position); + Plunger({required Vector2 position}) : _position = position; final Vector2 _position; @@ -28,13 +28,15 @@ class Plunger extends BodyComponent { return world.createBody(bodyDef)..createFixture(fixtureDef); } - /// Set a constant downward velocity on the plunger body. + /// Set a constant downward velocity on the [Plunger]. void pull() { body.linearVelocity = Vector2(0, -7); } - /// Set an upward velocity on the plunger body. The velocity's magnitude - /// depends on how far the plunger has been pulled from its original position. + /// Set an upward velocity on the [Plunger]. + /// + /// The velocity's magnitude depends on how far the [Plunger] has been pulled + /// from its original [_position]. void release() { final velocity = (_position.y - body.position.y) * 9; body.linearVelocity = Vector2(0, velocity); @@ -42,19 +44,21 @@ class Plunger extends BodyComponent { } /// {@template plunger_anchor_prismatic_joint_def} -/// Prismatic joint def between a [Plunger] and an anchor body given motion on +/// [PrismaticJointDef] between a [Plunger] and an [Anchor] with motion on /// the vertical axis. /// -/// The [Plunger] is constrained to vertical motion between its starting -/// position and the anchor body. The anchor needs to be below the plunger for -/// this joint to function properly. +/// The [Plunger] is constrained to vertically between its starting position and +/// the [Anchor]. The [Anchor] must be below the [Plunger]. /// {@endtemplate} class PlungerAnchorPrismaticJointDef extends PrismaticJointDef { /// {@macro plunger_anchor_prismatic_joint_def} PlungerAnchorPrismaticJointDef({ required Plunger plunger, - required BodyComponent anchor, - }) { + required Anchor anchor, + }) : assert( + anchor.body.position.y < plunger.body.position.y, + "Anchor can't be positioned above the Plunger", + ) { initialize( plunger.body, anchor.body, diff --git a/test/game/components/plunger_test.dart b/test/game/components/plunger_test.dart index 54a477e3..913cfa82 100644 --- a/test/game/components/plunger_test.dart +++ b/test/game/components/plunger_test.dart @@ -1,5 +1,6 @@ // ignore_for_file: cascade_invocations +import 'package:flame/extensions.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -13,7 +14,7 @@ void main() { flameTester.test( 'loads correctly', (game) async { - final plunger = Plunger(Vector2.zero()); + final plunger = Plunger(position: Vector2.zero()); await game.ensureAdd(plunger); expect(game.contains(plunger), isTrue); @@ -25,7 +26,7 @@ void main() { 'positions correctly', (game) async { final position = Vector2.all(10); - final plunger = Plunger(position); + final plunger = Plunger(position: position); await game.ensureAdd(plunger); game.contains(plunger); @@ -36,7 +37,7 @@ void main() { flameTester.test( 'is dynamic', (game) async { - final plunger = Plunger(Vector2.zero()); + final plunger = Plunger(position: Vector2.zero()); await game.ensureAdd(plunger); expect(plunger.body.bodyType, equals(BodyType.dynamic)); @@ -46,7 +47,7 @@ void main() { flameTester.test( 'ignores gravity', (game) async { - final plunger = Plunger(Vector2.zero()); + final plunger = Plunger(position: Vector2.zero()); await game.ensureAdd(plunger); expect(plunger.body.gravityScale, isZero); @@ -58,7 +59,7 @@ void main() { flameTester.test( 'exists', (game) async { - final plunger = Plunger(Vector2.zero()); + final plunger = Plunger(position: Vector2.zero()); await game.ensureAdd(plunger); expect(plunger.body.fixtures[0], isA()); @@ -68,7 +69,7 @@ void main() { flameTester.test( 'shape is a polygon', (game) async { - final plunger = Plunger(Vector2.zero()); + final plunger = Plunger(position: Vector2.zero()); await game.ensureAdd(plunger); final fixture = plunger.body.fixtures[0]; @@ -80,12 +81,13 @@ void main() { flameTester.test( 'pull sets a negative linear velocity', (game) async { - final plunger = Plunger(Vector2.zero()); + final plunger = Plunger(position: Vector2.zero()); await game.ensureAdd(plunger); plunger.pull(); expect(plunger.body.linearVelocity.y, isNegative); + expect(plunger.body.linearVelocity.x, isZero); }, ); @@ -94,12 +96,13 @@ void main() { 'does not set a linear velocity ' 'when plunger is in starting position', (game) async { - final plunger = Plunger(Vector2.zero()); + final plunger = Plunger(position: Vector2.zero()); await game.ensureAdd(plunger); plunger.release(); expect(plunger.body.linearVelocity.y, isZero); + expect(plunger.body.linearVelocity.x, isZero); }, ); @@ -107,13 +110,14 @@ void main() { 'sets a positive linear velocity ' 'when plunger is below starting position', (game) async { - final plunger = Plunger(Vector2.zero()); + final plunger = Plunger(position: Vector2.zero()); await game.ensureAdd(plunger); plunger.body.setTransform(Vector2(0, -1), 0); plunger.release(); expect(plunger.body.linearVelocity.y, isPositive); + expect(plunger.body.linearVelocity.x, isZero); }, ); }); @@ -121,13 +125,30 @@ void main() { group('PlungerAnchorPrismaticJointDef', () { late Plunger plunger; - late Plunger anchor; + late Anchor anchor; setUp(() { - plunger = Plunger(Vector2.zero()); - anchor = Plunger(Vector2(0, -1)); + plunger = Plunger(position: Vector2.zero()); + anchor = Anchor(position: Vector2(0, -1)); }); + flameTester.test( + 'throws AssertionError ' + 'when anchor is above plunger', + (game) async { + final anchor = Anchor(position: Vector2(0, 1)); + await game.ensureAddAll([plunger, anchor]); + + expect( + () => PlungerAnchorPrismaticJointDef( + plunger: plunger, + anchor: anchor, + ), + throwsAssertionError, + ); + }, + ); + group('initializes with', () { flameTester.test( 'plunger body as bodyA', From 3ef9cb878b2966f2ad6c8546bdc6e01d8eb9ac18 Mon Sep 17 00:00:00 2001 From: Allison Ryan Date: Fri, 4 Mar 2022 10:52:23 -0600 Subject: [PATCH 09/11] test: provide mock game bloc --- test/game/components/plunger_test.dart | 20 +++++++++++++++++++- test/helpers/builders.dart | 6 ++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/test/game/components/plunger_test.dart b/test/game/components/plunger_test.dart index 913cfa82..5d3c3a55 100644 --- a/test/game/components/plunger_test.dart +++ b/test/game/components/plunger_test.dart @@ -1,11 +1,13 @@ // ignore_for_file: cascade_invocations -import 'package:flame/extensions.dart'; +import 'package:bloc_test/bloc_test.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pinball/game/game.dart'; +import '../../helpers/helpers.dart'; + void main() { TestWidgetsFlutterBinding.ensureInitialized(); final flameTester = FlameTester(PinballGame.new); @@ -124,14 +126,27 @@ void main() { }); group('PlungerAnchorPrismaticJointDef', () { + late GameBloc gameBloc; late Plunger plunger; late Anchor anchor; setUp(() { + gameBloc = MockGameBloc(); + whenListen( + gameBloc, + const Stream.empty(), + initialState: const GameState.initial(), + ); plunger = Plunger(position: Vector2.zero()); anchor = Anchor(position: Vector2(0, -1)); }); + final flameTester = flameBlocTester( + gameBlocBuilder: () { + return gameBloc; + }, + ); + flameTester.test( 'throws AssertionError ' 'when anchor is above plunger', @@ -230,6 +245,9 @@ void main() { (game, tester) async { await game.ensureAddAll([plunger, anchor]); + // Giving anchor a shape for the plunger to collide with. + anchor.body.createFixtureFromShape(PolygonShape()..setAsBoxXY(2, 1)); + final jointDef = PlungerAnchorPrismaticJointDef( plunger: plunger, anchor: anchor, diff --git a/test/helpers/builders.dart b/test/helpers/builders.dart index 5ef98226..e124052e 100644 --- a/test/helpers/builders.dart +++ b/test/helpers/builders.dart @@ -2,8 +2,10 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pinball/game/game.dart'; -FlameTester flameBlocTester({required GameBloc Function() gameBlocBuilder}) { - return FlameTester( +FlameTester flameBlocTester({ + required GameBloc Function() gameBlocBuilder, +}) { + return FlameTester( PinballGame.new, pumpWidget: (gameWidget, tester) async { await tester.pumpWidget( From ea3b126b6255b6ea61e7d33267faf45ca6a4fae7 Mon Sep 17 00:00:00 2001 From: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> Date: Fri, 4 Mar 2022 11:06:09 -0600 Subject: [PATCH 10/11] Update lib/game/components/plunger.dart Co-authored-by: Alejandro Santiago --- lib/game/components/plunger.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/game/components/plunger.dart b/lib/game/components/plunger.dart index 7e6dbd89..caef1670 100644 --- a/lib/game/components/plunger.dart +++ b/lib/game/components/plunger.dart @@ -47,7 +47,7 @@ class Plunger extends BodyComponent { /// [PrismaticJointDef] between a [Plunger] and an [Anchor] with motion on /// the vertical axis. /// -/// The [Plunger] is constrained to vertically between its starting position and +/// The [Plunger] is constrained vertically between its starting position and /// the [Anchor]. The [Anchor] must be below the [Plunger]. /// {@endtemplate} class PlungerAnchorPrismaticJointDef extends PrismaticJointDef { From 16516764628864da5c5aef85355e0476c773f235 Mon Sep 17 00:00:00 2001 From: Allison Ryan Date: Fri, 4 Mar 2022 11:12:13 -0600 Subject: [PATCH 11/11] test: same position --- lib/game/components/plunger.dart | 2 +- test/game/components/plunger_test.dart | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/game/components/plunger.dart b/lib/game/components/plunger.dart index caef1670..ed1ef36f 100644 --- a/lib/game/components/plunger.dart +++ b/lib/game/components/plunger.dart @@ -57,7 +57,7 @@ class PlungerAnchorPrismaticJointDef extends PrismaticJointDef { required Anchor anchor, }) : assert( anchor.body.position.y < plunger.body.position.y, - "Anchor can't be positioned above the Plunger", + 'Anchor must be below the Plunger', ) { initialize( plunger.body, diff --git a/test/game/components/plunger_test.dart b/test/game/components/plunger_test.dart index 5d3c3a55..67e215fd 100644 --- a/test/game/components/plunger_test.dart +++ b/test/game/components/plunger_test.dart @@ -164,6 +164,23 @@ void main() { }, ); + flameTester.test( + 'throws AssertionError ' + 'when anchor is in same position as plunger', + (game) async { + final anchor = Anchor(position: Vector2.zero()); + await game.ensureAddAll([plunger, anchor]); + + expect( + () => PlungerAnchorPrismaticJointDef( + plunger: plunger, + anchor: anchor, + ), + throwsAssertionError, + ); + }, + ); + group('initializes with', () { flameTester.test( 'plunger body as bodyA',