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',