diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index 7b4e1ddd..ffada30b 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -19,8 +19,8 @@ export 'joint_anchor.dart'; export 'kicker.dart'; export 'launch_ramp.dart'; export 'layer.dart'; +export 'layer_sensor.dart'; export 'plunger.dart'; -export 'ramp_opening.dart'; export 'rocket.dart'; export 'score_text.dart'; export 'shapes/shapes.dart'; diff --git a/packages/pinball_components/lib/src/components/launch_ramp.dart b/packages/pinball_components/lib/src/components/launch_ramp.dart index df59b4ce..c3d7624d 100644 --- a/packages/pinball_components/lib/src/components/launch_ramp.dart +++ b/packages/pinball_components/lib/src/components/launch_ramp.dart @@ -15,7 +15,7 @@ class LaunchRamp extends Forge2DBlueprint { @override void build(_) { addAllContactCallback([ - RampOpeningBallContactCallback<_LaunchRampExit>(), + LayerSensorBallContactCallback<_LaunchRampExit>(), ]); final launchRampBase = _LaunchRampBase(); @@ -236,10 +236,10 @@ class _LaunchRampCloseWall extends BodyComponent with InitialPosition, Layered { } /// {@template launch_ramp_exit} -/// [RampOpening] with [Layer.launcher] to filter [Ball]s exiting the +/// [LayerSensor] with [Layer.launcher] to filter [Ball]s exiting the /// [LaunchRamp]. /// {@endtemplate} -class _LaunchRampExit extends RampOpening { +class _LaunchRampExit extends LayerSensor { /// {@macro launch_ramp_exit} _LaunchRampExit({ required double rotation, @@ -247,7 +247,7 @@ class _LaunchRampExit extends RampOpening { super( insideLayer: Layer.launcher, outsideLayer: Layer.board, - orientation: RampOrientation.down, + orientation: LayerEntranceOrientation.down, insidePriority: Ball.launchRampPriority, outsidePriority: 0, ) { diff --git a/packages/pinball_components/lib/src/components/layer_sensor.dart b/packages/pinball_components/lib/src/components/layer_sensor.dart new file mode 100644 index 00000000..05f9a98d --- /dev/null +++ b/packages/pinball_components/lib/src/components/layer_sensor.dart @@ -0,0 +1,110 @@ +// ignore_for_file: avoid_renaming_method_parameters + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_components/pinball_components.dart'; + +/// {@template layer_entrance_orientation} +/// Determines if a layer entrance is oriented [up] or [down] on the board. +/// {@endtemplate} +enum LayerEntranceOrientation { + /// Facing up on the Board. + up, + + /// Facing down on the Board. + down, +} + +/// {@template layer_sensor} +/// [BodyComponent] located at the entrance and exit of a [Layer]. +/// +/// [LayerSensorBallContactCallback] detects when a [Ball] passes +/// through this sensor. +/// +/// By default the base [layer] is set to [Layer.board] and the +/// [outsidePriority] is set to the lowest possible [Layer]. +/// {@endtemplate} +abstract class LayerSensor extends BodyComponent with InitialPosition, Layered { + /// {@macro layer_sensor} + LayerSensor({ + required Layer insideLayer, + Layer? outsideLayer, + required int insidePriority, + int? outsidePriority, + required this.orientation, + }) : _insideLayer = insideLayer, + _outsideLayer = outsideLayer ?? Layer.board, + _insidePriority = insidePriority, + _outsidePriority = outsidePriority ?? Ball.boardPriority { + layer = Layer.opening; + } + final Layer _insideLayer; + final Layer _outsideLayer; + final int _insidePriority; + final int _outsidePriority; + + /// Mask bits value for collisions on [Layer]. + Layer get insideLayer => _insideLayer; + + /// Mask bits value for collisions outside of [Layer]. + Layer get outsideLayer => _outsideLayer; + + /// Render priority for the [Ball] on [Layer]. + int get insidePriority => _insidePriority; + + /// Render priority for the [Ball] outside of [Layer]. + int get outsidePriority => _outsidePriority; + + /// The [Shape] of the [LayerSensor]. + Shape get shape; + + /// {@macro layer_entrance_orientation} + // TODO(ruimiguel): Try to remove the need of [LayerEntranceOrientation] for + // collision calculations. + final LayerEntranceOrientation orientation; + + @override + Body createBody() { + final fixtureDef = FixtureDef( + shape, + isSensor: true, + ); + final bodyDef = BodyDef( + position: initialPosition, + userData: this, + ); + + return world.createBody(bodyDef)..createFixture(fixtureDef); + } +} + +/// {@template layer_sensor_ball_contact_callback} +/// Detects when a [Ball] enters or exits a [Layer] through a [LayerSensor]. +/// +/// Modifies [Ball]'s [Layer] and render priority depending on whether the +/// [Ball] is on or outside of a [Layer]. +/// {@endtemplate} +class LayerSensorBallContactCallback + extends ContactCallback { + @override + void begin(Ball ball, LayerEntrance layerEntrance, Contact _) { + if (ball.layer != layerEntrance.insideLayer) { + final isBallEnteringOpening = + (layerEntrance.orientation == LayerEntranceOrientation.down && + ball.body.linearVelocity.y < 0) || + (layerEntrance.orientation == LayerEntranceOrientation.up && + ball.body.linearVelocity.y > 0); + + if (isBallEnteringOpening) { + ball + ..layer = layerEntrance.insideLayer + ..priority = layerEntrance.insidePriority + ..reorderChildren(); + } + } else { + ball + ..layer = layerEntrance.outsideLayer + ..priority = layerEntrance.outsidePriority + ..reorderChildren(); + } + } +} diff --git a/packages/pinball_components/lib/src/components/ramp_opening.dart b/packages/pinball_components/lib/src/components/ramp_opening.dart deleted file mode 100644 index 9a7f4162..00000000 --- a/packages/pinball_components/lib/src/components/ramp_opening.dart +++ /dev/null @@ -1,130 +0,0 @@ -// ignore_for_file: avoid_renaming_method_parameters - -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; - -/// {@template ramp_orientation} -/// Determines if a ramp is facing [up] or [down] on the Board. -/// {@endtemplate} -enum RampOrientation { - /// Facing up on the Board. - up, - - /// Facing down on the Board. - down, -} - -/// {@template ramp_opening} -/// [BodyComponent] located at the entrance and exit of a ramp. -/// -/// [RampOpeningBallContactCallback] detects when a [Ball] passes -/// through this opening. -/// -/// By default the base [layer] is set to [Layer.board] and the -/// [outsidePriority] is set to the lowest possible [Layer]. -/// {@endtemplate} -// TODO(ruialonso): Consider renaming the class. -abstract class RampOpening extends BodyComponent with InitialPosition, Layered { - /// {@macro ramp_opening} - RampOpening({ - required Layer insideLayer, - Layer? outsideLayer, - required int insidePriority, - int? outsidePriority, - required this.orientation, - }) : _insideLayer = insideLayer, - _outsideLayer = outsideLayer ?? Layer.board, - _insidePriority = insidePriority, - _outsidePriority = outsidePriority ?? Ball.boardPriority { - layer = Layer.opening; - } - final Layer _insideLayer; - final Layer _outsideLayer; - final int _insidePriority; - final int _outsidePriority; - - /// Mask of category bits for collision inside ramp. - Layer get insideLayer => _insideLayer; - - /// Mask of category bits for collision outside ramp. - Layer get outsideLayer => _outsideLayer; - - /// Priority for the [Ball] inside ramp. - int get insidePriority => _insidePriority; - - /// Priority for the [Ball] outside ramp. - int get outsidePriority => _outsidePriority; - - /// The [Shape] of the [RampOpening]. - Shape get shape; - - /// {@macro ramp_orientation} - // TODO(ruimiguel): Try to remove the need of [RampOrientation] for collision - // calculations. - final RampOrientation orientation; - - @override - Body createBody() { - final fixtureDef = FixtureDef( - shape, - isSensor: true, - ); - final bodyDef = BodyDef( - position: initialPosition, - userData: this, - ); - - return world.createBody(bodyDef)..createFixture(fixtureDef); - } -} - -/// {@template ramp_opening_ball_contact_callback} -/// Detects when a [Ball] enters or exits a ramp through a [RampOpening]. -/// -/// Modifies [Ball]'s [Layer] accordingly depending on whether the [Ball] is -/// outside or inside a ramp. -/// {@endtemplate} -class RampOpeningBallContactCallback - extends ContactCallback { - /// [Ball]s currently inside the ramp. - final _ballsInside = {}; - - @override - void begin(Ball ball, Opening opening, Contact _) { - Layer layer; - - if (!_ballsInside.contains(ball)) { - layer = opening.insideLayer; - _ballsInside.add(ball); - ball - ..sendTo(opening.insidePriority) - ..layer = layer; - } else { - _ballsInside.remove(ball); - } - } - - @override - void end(Ball ball, Opening opening, Contact _) { - if (!_ballsInside.contains(ball)) { - ball.layer = opening.outsideLayer; - } else { - // TODO(ruimiguel): change this code. Check what happens with ball that - // slightly touch Opening and goes out again. With InitialPosition change - // now doesn't work position.y comparison - final isBallOutsideOpening = - (opening.orientation == RampOrientation.down && - ball.body.linearVelocity.y > 0) || - (opening.orientation == RampOrientation.up && - ball.body.linearVelocity.y < 0); - - if (isBallOutsideOpening) { - ball - ..sendTo(opening.outsidePriority) - ..layer = opening.outsideLayer; - _ballsInside.remove(ball); - } - } - } -} diff --git a/packages/pinball_components/lib/src/components/spaceship.dart b/packages/pinball_components/lib/src/components/spaceship.dart index 7de430a6..4beb0367 100644 --- a/packages/pinball_components/lib/src/components/spaceship.dart +++ b/packages/pinball_components/lib/src/components/spaceship.dart @@ -25,19 +25,19 @@ class Spaceship extends Forge2DBlueprint { @override void build(_) { addAllContactCallback([ - SpaceshipHoleBallContactCallback(), - SpaceshipEntranceBallContactCallback(), + LayerSensorBallContactCallback<_SpaceshipEntrance>(), + LayerSensorBallContactCallback<_SpaceshipHole>(), ]); addAll([ SpaceshipSaucer()..initialPosition = position, - SpaceshipEntrance()..initialPosition = position, + _SpaceshipEntrance()..initialPosition = position, AndroidHead()..initialPosition = position, - SpaceshipHole( + _SpaceshipHole( outsideLayer: Layer.spaceshipExitRail, outsidePriority: Ball.spaceshipRailPriority, )..initialPosition = position - Vector2(5.2, -4.8), - SpaceshipHole()..initialPosition = position - Vector2(-7.2, -0.8), + _SpaceshipHole()..initialPosition = position - Vector2(-7.2, -0.8), SpaceshipWall()..initialPosition = position, ]); } @@ -140,17 +140,11 @@ class _AndroidHeadSpriteAnimation extends SpriteAnimationComponent } } -/// {@template spaceship_entrance} -/// A sensor [BodyComponent] used to detect when the ball enters the -/// the spaceship area in order to modify its filter data so the ball -/// can correctly collide only with the Spaceship -/// {@endtemplate} -class SpaceshipEntrance extends RampOpening { - /// {@macro spaceship_entrance} - SpaceshipEntrance() +class _SpaceshipEntrance extends LayerSensor { + _SpaceshipEntrance() : super( insideLayer: Layer.spaceship, - orientation: RampOrientation.up, + orientation: LayerEntranceOrientation.up, insidePriority: Ball.spaceshipPriority, ) { layer = Layer.spaceship; @@ -174,17 +168,12 @@ class SpaceshipEntrance extends RampOpening { } } -/// {@template spaceship_hole} -/// A sensor [BodyComponent] responsible for sending the [Ball] -/// out from the [Spaceship]. -/// {@endtemplate} -class SpaceshipHole extends RampOpening { - /// {@macro spaceship_hole} - SpaceshipHole({Layer? outsideLayer, int? outsidePriority = 1}) +class _SpaceshipHole extends LayerSensor { + _SpaceshipHole({Layer? outsideLayer, int? outsidePriority = 1}) : super( insideLayer: Layer.spaceship, outsideLayer: outsideLayer, - orientation: RampOrientation.up, + orientation: LayerEntranceOrientation.down, insidePriority: 4, outsidePriority: outsidePriority, ) { @@ -256,33 +245,3 @@ class SpaceshipWall extends BodyComponent with InitialPosition, Layered { return world.createBody(bodyDef)..createFixture(fixtureDef); } } - -/// [ContactCallback] that handles the contact between the [Ball] -/// and the [SpaceshipEntrance]. -/// -/// It modifies the [Ball] priority and filter data so it can appear on top of -/// the spaceship and also only collide with the spaceship. -class SpaceshipEntranceBallContactCallback - extends ContactCallback { - @override - void begin(SpaceshipEntrance entrance, Ball ball, _) { - ball - ..sendTo(entrance.insidePriority) - ..layer = Layer.spaceship; - } -} - -/// [ContactCallback] that handles the contact between the [Ball] -/// and a [SpaceshipHole]. -/// -/// It sets the [Ball] priority and filter data so it will outside of the -/// [Spaceship]. -class SpaceshipHoleBallContactCallback - extends ContactCallback { - @override - void begin(SpaceshipHole hole, Ball ball, _) { - ball - ..sendTo(hole.outsidePriority) - ..layer = hole.outsideLayer; - } -} diff --git a/packages/pinball_components/lib/src/components/spaceship_rail.dart b/packages/pinball_components/lib/src/components/spaceship_rail.dart index 406cc340..cc308c77 100644 --- a/packages/pinball_components/lib/src/components/spaceship_rail.dart +++ b/packages/pinball_components/lib/src/components/spaceship_rail.dart @@ -18,11 +18,11 @@ class SpaceshipRail extends Forge2DBlueprint { @override void build(_) { addAllContactCallback([ - SpaceshipRailExitBallContactCallback(), + LayerSensorBallContactCallback<_SpaceshipRailExit>(), ]); final railRamp = _SpaceshipRailRamp(); - final railEnd = SpaceshipRailExit(); + final railEnd = _SpaceshipRailExit(); final topBase = _SpaceshipRailBase(radius: 0.55) ..initialPosition = Vector2(-26.15, -18.65); final bottomBase = _SpaceshipRailBase(radius: 0.8) @@ -201,15 +201,10 @@ class _SpaceshipRailBase extends BodyComponent with InitialPosition, Layered { } } -/// {@template spaceship_rail_exit} -/// A sensor [BodyComponent] responsible for sending the [Ball] -/// back to the board. -/// {@endtemplate} -class SpaceshipRailExit extends RampOpening { - /// {@macro spaceship_rail_exit} - SpaceshipRailExit() +class _SpaceshipRailExit extends LayerSensor { + _SpaceshipRailExit() : super( - orientation: RampOrientation.down, + orientation: LayerEntranceOrientation.down, insideLayer: Layer.spaceshipExitRail, insidePriority: Ball.spaceshipRailPriority, ) { @@ -227,18 +222,3 @@ class SpaceshipRailExit extends RampOpening { ); } } - -/// [ContactCallback] that handles the contact between the [Ball] -/// and a [SpaceshipRailExit]. -/// -/// It resets the [Ball] priority and filter data so it will "be back" on the -/// board. -class SpaceshipRailExitBallContactCallback - extends ContactCallback { - @override - void begin(SpaceshipRailExit exitRail, Ball ball, _) { - ball - ..sendTo(exitRail.outsidePriority) - ..layer = exitRail.outsideLayer; - } -} diff --git a/packages/pinball_components/lib/src/components/spaceship_ramp.dart b/packages/pinball_components/lib/src/components/spaceship_ramp.dart index 5be55193..b0adb8cb 100644 --- a/packages/pinball_components/lib/src/components/spaceship_ramp.dart +++ b/packages/pinball_components/lib/src/components/spaceship_ramp.dart @@ -18,7 +18,7 @@ class SpaceshipRamp extends Forge2DBlueprint { @override void build(_) { addAllContactCallback([ - RampOpeningBallContactCallback<_SpaceshipRampOpening>(), + LayerSensorBallContactCallback<_SpaceshipRampOpening>(), ]); final rightOpening = _SpaceshipRampOpening( @@ -273,10 +273,10 @@ class _SpaceshipRampBase extends BodyComponent with InitialPosition, Layered { } /// {@template spaceship_ramp_opening} -/// [RampOpening] with [Layer.spaceshipEntranceRamp] to filter [Ball] collisions +/// [LayerSensor] with [Layer.spaceshipEntranceRamp] to filter [Ball] collisions /// inside [_SpaceshipRampBackground]. /// {@endtemplate} -class _SpaceshipRampOpening extends RampOpening { +class _SpaceshipRampOpening extends LayerSensor { /// {@macro spaceship_ramp_opening} _SpaceshipRampOpening({ Layer? outsideLayer, @@ -286,7 +286,7 @@ class _SpaceshipRampOpening extends RampOpening { super( insideLayer: Layer.spaceshipEntranceRamp, outsideLayer: outsideLayer, - orientation: RampOrientation.down, + orientation: LayerEntranceOrientation.down, insidePriority: Ball.spaceshipRampPriority, outsidePriority: outsidePriority, ) { diff --git a/packages/pinball_components/test/helpers/mocks.dart b/packages/pinball_components/test/helpers/mocks.dart index 21d5d01a..520555df 100644 --- a/packages/pinball_components/test/helpers/mocks.dart +++ b/packages/pinball_components/test/helpers/mocks.dart @@ -13,12 +13,6 @@ class MockBall extends Mock implements Ball {} class MockGame extends Mock implements Forge2DGame {} -class MockSpaceshipEntrance extends Mock implements SpaceshipEntrance {} - -class MockSpaceshipHole extends Mock implements SpaceshipHole {} - -class MockSpaceshipRailExit extends Mock implements SpaceshipRailExit {} - class MockContact extends Mock implements Contact {} class MockContactCallback extends Mock diff --git a/packages/pinball_components/test/src/components/layer_sensor_test.dart b/packages/pinball_components/test/src/components/layer_sensor_test.dart new file mode 100644 index 00000000..c87f02b3 --- /dev/null +++ b/packages/pinball_components/test/src/components/layer_sensor_test.dart @@ -0,0 +1,181 @@ +// 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:mocktail/mocktail.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../helpers/helpers.dart'; + +class TestLayerSensor extends LayerSensor { + TestLayerSensor({ + required LayerEntranceOrientation orientation, + required int insidePriority, + required Layer insideLayer, + }) : super( + insideLayer: insideLayer, + insidePriority: insidePriority, + orientation: orientation, + ); + + @override + Shape get shape => PolygonShape()..setAsBoxXY(1, 1); +} + +class TestLayerSensorBallContactCallback + extends LayerSensorBallContactCallback { + TestLayerSensorBallContactCallback() : super(); +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(TestGame.new); + const insidePriority = 1; + + group('LayerSensor', () { + flameTester.test( + 'loads correctly', + (game) async { + final layerSensor = TestLayerSensor( + orientation: LayerEntranceOrientation.down, + insidePriority: insidePriority, + insideLayer: Layer.spaceshipEntranceRamp, + ); + await game.ensureAdd(layerSensor); + + expect(game.contains(layerSensor), isTrue); + }, + ); + + group('body', () { + flameTester.test( + 'is static', + (game) async { + final layerSensor = TestLayerSensor( + orientation: LayerEntranceOrientation.down, + insidePriority: insidePriority, + insideLayer: Layer.spaceshipEntranceRamp, + ); + await game.ensureAdd(layerSensor); + + expect(layerSensor.body.bodyType, equals(BodyType.static)); + }, + ); + + group('first fixture', () { + const pathwayLayer = Layer.spaceshipEntranceRamp; + const openingLayer = Layer.opening; + + flameTester.test( + 'exists', + (game) async { + final layerSensor = TestLayerSensor( + orientation: LayerEntranceOrientation.down, + insidePriority: insidePriority, + insideLayer: pathwayLayer, + )..layer = openingLayer; + await game.ensureAdd(layerSensor); + + expect(layerSensor.body.fixtures[0], isA()); + }, + ); + + flameTester.test( + 'shape is a polygon', + (game) async { + final layerSensor = TestLayerSensor( + orientation: LayerEntranceOrientation.down, + insidePriority: insidePriority, + insideLayer: pathwayLayer, + )..layer = openingLayer; + await game.ensureAdd(layerSensor); + + final fixture = layerSensor.body.fixtures[0]; + expect(fixture.shape.shapeType, equals(ShapeType.polygon)); + }, + ); + + flameTester.test( + 'is sensor', + (game) async { + final layerSensor = TestLayerSensor( + orientation: LayerEntranceOrientation.down, + insidePriority: insidePriority, + insideLayer: pathwayLayer, + )..layer = openingLayer; + await game.ensureAdd(layerSensor); + + final fixture = layerSensor.body.fixtures[0]; + expect(fixture.isSensor, isTrue); + }, + ); + }); + }); + }); + + group('LayerSensorBallContactCallback', () { + late Ball ball; + late Body body; + + setUp(() { + ball = MockBall(); + body = MockBody(); + + when(() => ball.body).thenReturn(body); + when(() => ball.priority).thenReturn(1); + when(() => ball.layer).thenReturn(Layer.board); + }); + + flameTester.test( + 'changes ball layer and priority ' + 'when a ball enters and exits a downward oriented LayerSensor', + (game) async { + final sensor = TestLayerSensor( + orientation: LayerEntranceOrientation.down, + insidePriority: insidePriority, + insideLayer: Layer.spaceshipEntranceRamp, + )..initialPosition = Vector2(0, 10); + final callback = TestLayerSensorBallContactCallback(); + + when(() => body.linearVelocity).thenReturn(Vector2(0, -1)); + + callback.begin(ball, sensor, MockContact()); + verify(() => ball.layer = sensor.insideLayer).called(1); + verify(() => ball.priority = sensor.insidePriority).called(1); + verify(ball.reorderChildren).called(1); + + when(() => ball.layer).thenReturn(sensor.insideLayer); + + callback.begin(ball, sensor, MockContact()); + verify(() => ball.layer = Layer.board); + verify(() => ball.priority = Ball.boardPriority).called(1); + verify(ball.reorderChildren).called(1); + }); + + flameTester.test( + 'changes ball layer and priority ' + 'when a ball enters and exits an upward oriented LayerSensor', + (game) async { + final sensor = TestLayerSensor( + orientation: LayerEntranceOrientation.up, + insidePriority: insidePriority, + insideLayer: Layer.spaceshipEntranceRamp, + )..initialPosition = Vector2(0, 10); + final callback = TestLayerSensorBallContactCallback(); + + when(() => body.linearVelocity).thenReturn(Vector2(0, 1)); + + callback.begin(ball, sensor, MockContact()); + verify(() => ball.layer = sensor.insideLayer).called(1); + verify(() => ball.priority = sensor.insidePriority).called(1); + verify(ball.reorderChildren).called(1); + + when(() => ball.layer).thenReturn(sensor.insideLayer); + + callback.begin(ball, sensor, MockContact()); + verify(() => ball.layer = Layer.board); + verify(() => ball.priority = Ball.boardPriority).called(1); + verify(ball.reorderChildren).called(1); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/ramp_opening_test.dart b/packages/pinball_components/test/src/components/ramp_opening_test.dart deleted file mode 100644 index 37b8edb0..00000000 --- a/packages/pinball_components/test/src/components/ramp_opening_test.dart +++ /dev/null @@ -1,249 +0,0 @@ -// 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:mocktail/mocktail.dart'; -import 'package:pinball_components/pinball_components.dart'; - -import '../../helpers/helpers.dart'; - -class TestRampOpening extends RampOpening { - TestRampOpening({ - required RampOrientation orientation, - required int insidePriority, - required Layer pathwayLayer, - }) : super( - insideLayer: pathwayLayer, - insidePriority: insidePriority, - orientation: orientation, - ); - - @override - Shape get shape => PolygonShape() - ..set([ - Vector2(0, 0), - Vector2(0, 1), - Vector2(1, 1), - Vector2(1, 0), - ]); -} - -class TestRampOpeningBallContactCallback - extends RampOpeningBallContactCallback { - TestRampOpeningBallContactCallback() : super(); -} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(TestGame.new); - const insidePriority = 1; - - group('RampOpening', () { - flameTester.test( - 'loads correctly', - (game) async { - final ramp = TestRampOpening( - orientation: RampOrientation.down, - insidePriority: insidePriority, - pathwayLayer: Layer.spaceshipEntranceRamp, - ); - await game.ready(); - await game.ensureAdd(ramp); - - expect(game.contains(ramp), isTrue); - }, - ); - - group('body', () { - flameTester.test( - 'is static', - (game) async { - final ramp = TestRampOpening( - orientation: RampOrientation.down, - insidePriority: insidePriority, - pathwayLayer: Layer.spaceshipEntranceRamp, - ); - await game.ensureAdd(ramp); - - expect(ramp.body.bodyType, equals(BodyType.static)); - }, - ); - - group('first fixture', () { - const pathwayLayer = Layer.spaceshipEntranceRamp; - const openingLayer = Layer.opening; - - flameTester.test( - 'exists', - (game) async { - final ramp = TestRampOpening( - orientation: RampOrientation.down, - insidePriority: insidePriority, - pathwayLayer: pathwayLayer, - )..layer = openingLayer; - await game.ensureAdd(ramp); - - expect(ramp.body.fixtures[0], isA()); - }, - ); - - flameTester.test( - 'shape is a polygon', - (game) async { - final ramp = TestRampOpening( - orientation: RampOrientation.down, - insidePriority: insidePriority, - pathwayLayer: pathwayLayer, - )..layer = openingLayer; - await game.ensureAdd(ramp); - - final fixture = ramp.body.fixtures[0]; - expect(fixture.shape.shapeType, equals(ShapeType.polygon)); - }, - ); - - flameTester.test( - 'is sensor', - (game) async { - final ramp = TestRampOpening( - orientation: RampOrientation.down, - insidePriority: insidePriority, - pathwayLayer: pathwayLayer, - )..layer = openingLayer; - await game.ensureAdd(ramp); - - final fixture = ramp.body.fixtures[0]; - expect(fixture.isSensor, isTrue); - }, - ); - }); - }); - }); - - group('RampOpeningBallContactCallback', () { - flameTester.test( - 'changes ball layer ' - 'when a ball enters upwards into a downward ramp opening', - (game) async { - final ball = MockBall(); - final body = MockBody(); - final area = TestRampOpening( - orientation: RampOrientation.down, - insidePriority: insidePriority, - pathwayLayer: Layer.spaceshipEntranceRamp, - ); - final callback = TestRampOpeningBallContactCallback(); - - when(() => ball.body).thenReturn(body); - when(() => ball.priority).thenReturn(1); - when(() => body.position).thenReturn(Vector2.zero()); - when(() => ball.layer).thenReturn(Layer.board); - - callback.begin(ball, area, MockContact()); - verify(() => ball.layer = area.insideLayer).called(1); - }); - - flameTester.test( - 'changes ball layer ' - 'when a ball enters downwards into a upward ramp opening', - (game) async { - final ball = MockBall(); - final body = MockBody(); - final area = TestRampOpening( - orientation: RampOrientation.up, - insidePriority: insidePriority, - pathwayLayer: Layer.spaceshipEntranceRamp, - ); - final callback = TestRampOpeningBallContactCallback(); - - when(() => ball.body).thenReturn(body); - when(() => ball.priority).thenReturn(1); - when(() => body.position).thenReturn(Vector2.zero()); - when(() => ball.layer).thenReturn(Layer.board); - - callback.begin(ball, area, MockContact()); - verify(() => ball.layer = area.insideLayer).called(1); - }); - - flameTester.test( - 'changes ball layer ' - 'when a ball exits from a downward oriented ramp', (game) async { - final ball = MockBall(); - final body = MockBody(); - final area = TestRampOpening( - orientation: RampOrientation.down, - insidePriority: insidePriority, - pathwayLayer: Layer.spaceshipEntranceRamp, - )..initialPosition = Vector2(0, 10); - final callback = TestRampOpeningBallContactCallback(); - - when(() => ball.body).thenReturn(body); - when(() => ball.priority).thenReturn(1); - when(() => body.position).thenReturn(Vector2.zero()); - when(() => body.linearVelocity).thenReturn(Vector2(0, 1)); - when(() => ball.layer).thenReturn(Layer.board); - - callback.begin(ball, area, MockContact()); - verify(() => ball.layer = area.insideLayer).called(1); - - callback.end(ball, area, MockContact()); - verify(() => ball.layer = Layer.board); - }); - - flameTester.test( - 'changes ball layer ' - 'when a ball exits from a upward oriented ramp', (game) async { - final ball = MockBall(); - final body = MockBody(); - final area = TestRampOpening( - orientation: RampOrientation.up, - insidePriority: insidePriority, - pathwayLayer: Layer.spaceshipEntranceRamp, - )..initialPosition = Vector2(0, 10); - final callback = TestRampOpeningBallContactCallback(); - - when(() => ball.body).thenReturn(body); - when(() => ball.priority).thenReturn(1); - when(() => body.position).thenReturn(Vector2.zero()); - when(() => body.linearVelocity).thenReturn(Vector2(0, -1)); - when(() => ball.layer).thenReturn(Layer.board); - - callback.begin(ball, area, MockContact()); - verify(() => ball.layer = area.insideLayer).called(1); - - callback.end(ball, area, MockContact()); - verify(() => ball.layer = Layer.board); - }); - - flameTester.test( - 'change ball layer from pathwayLayer to Layer.board ' - 'when a ball enters and exits from ramp', (game) async { - final ball = MockBall(); - final body = MockBody(); - final area = TestRampOpening( - orientation: RampOrientation.down, - insidePriority: insidePriority, - pathwayLayer: Layer.spaceshipEntranceRamp, - )..initialPosition = Vector2(0, 10); - final callback = TestRampOpeningBallContactCallback(); - - when(() => ball.body).thenReturn(body); - when(() => ball.priority).thenReturn(1); - when(() => body.position).thenReturn(Vector2.zero()); - when(() => body.linearVelocity).thenReturn(Vector2(0, -1)); - when(() => ball.layer).thenReturn(Layer.board); - - callback.begin(ball, area, MockContact()); - verify(() => ball.layer = area.insideLayer).called(1); - - callback.end(ball, area, MockContact()); - verifyNever(() => ball.layer = Layer.board); - - callback.begin(ball, area, MockContact()); - verifyNever(() => ball.layer = area.insideLayer); - - callback.end(ball, area, MockContact()); - verify(() => ball.layer = Layer.board); - }); - }); -} diff --git a/packages/pinball_components/test/src/components/spaceship_rail_test.dart b/packages/pinball_components/test/src/components/spaceship_rail_test.dart index c9d49918..c73c8dee 100644 --- a/packages/pinball_components/test/src/components/spaceship_rail_test.dart +++ b/packages/pinball_components/test/src/components/spaceship_rail_test.dart @@ -3,7 +3,6 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; @@ -42,57 +41,4 @@ void main() { }, ); }); - - // TODO(alestiago): Make ContactCallback private and use `beginContact` - // instead. - group('SpaceshipRailExitBallContactCallback', () { - late Forge2DGame game; - late SpaceshipRailExit railExit; - late Ball ball; - late Body body; - late Fixture fixture; - late Filter filterData; - - setUp(() { - game = MockGame(); - - railExit = MockSpaceshipRailExit(); - - ball = MockBall(); - body = MockBody(); - when(() => ball.gameRef).thenReturn(game); - when(() => ball.body).thenReturn(body); - - fixture = MockFixture(); - filterData = MockFilter(); - when(() => body.fixtures).thenReturn([fixture]); - when(() => fixture.filterData).thenReturn(filterData); - }); - - setUp(() { - when(() => ball.priority).thenReturn(1); - when(() => railExit.outsideLayer).thenReturn(Layer.board); - when(() => railExit.outsidePriority).thenReturn(0); - }); - - test('changes the ball priority on contact', () { - SpaceshipRailExitBallContactCallback().begin( - railExit, - ball, - MockContact(), - ); - - verify(() => ball.sendTo(railExit.outsidePriority)).called(1); - }); - - test('changes the ball layer on contact', () { - SpaceshipRailExitBallContactCallback().begin( - railExit, - ball, - MockContact(), - ); - - verify(() => ball.layer = railExit.outsideLayer).called(1); - }); - }); } diff --git a/packages/pinball_components/test/src/components/spaceship_test.dart b/packages/pinball_components/test/src/components/spaceship_test.dart index e6c38476..c9a90746 100644 --- a/packages/pinball_components/test/src/components/spaceship_test.dart +++ b/packages/pinball_components/test/src/components/spaceship_test.dart @@ -15,8 +15,6 @@ void main() { late Fixture fixture; late Body body; late Ball ball; - late SpaceshipEntrance entrance; - late SpaceshipHole hole; late Forge2DGame game; setUp(() { @@ -33,9 +31,6 @@ void main() { ball = MockBall(); when(() => ball.gameRef).thenReturn(game); when(() => ball.body).thenReturn(body); - - entrance = MockSpaceshipEntrance(); - hole = MockSpaceshipHole(); }); group('Spaceship', () { @@ -57,36 +52,5 @@ void main() { }, ); }); - - group('SpaceshipEntranceBallContactCallback', () { - test('changes the ball priority on contact', () { - when(() => ball.priority).thenReturn(2); - when(() => entrance.insidePriority).thenReturn(3); - - SpaceshipEntranceBallContactCallback().begin( - entrance, - ball, - MockContact(), - ); - - verify(() => ball.sendTo(entrance.insidePriority)).called(1); - }); - }); - - group('SpaceshipHoleBallContactCallback', () { - test('changes the ball priority on contact', () { - when(() => ball.priority).thenReturn(2); - when(() => hole.outsideLayer).thenReturn(Layer.board); - when(() => hole.outsidePriority).thenReturn(1); - - SpaceshipHoleBallContactCallback().begin( - hole, - ball, - MockContact(), - ); - - verify(() => ball.sendTo(hole.outsidePriority)).called(1); - }); - }); }); } diff --git a/packages/pinball_flame/lib/pinball_flame.dart b/packages/pinball_flame/lib/pinball_flame.dart index e8f8624b..2d2e760b 100644 --- a/packages/pinball_flame/lib/pinball_flame.dart +++ b/packages/pinball_flame/lib/pinball_flame.dart @@ -3,4 +3,3 @@ library pinball_flame; export 'src/blueprint.dart'; export 'src/component_controller.dart'; export 'src/keyboard_input_controller.dart'; -export 'src/priority.dart'; diff --git a/packages/pinball_flame/lib/src/priority.dart b/packages/pinball_flame/lib/src/priority.dart deleted file mode 100644 index f4dccabf..00000000 --- a/packages/pinball_flame/lib/src/priority.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'dart:math' as math; -import 'package:flame/components.dart'; - -/// Helper methods to change the [priority] of a [Component]. -extension ComponentPriorityX on Component { - static const _lowestPriority = 0; - - /// Changes the priority to a specific one. - void sendTo(int destinationPriority) { - if (priority != destinationPriority) { - priority = math.max(destinationPriority, _lowestPriority); - reorderChildren(); - } - } - - /// Changes the priority to the lowest possible. - void sendToBack() { - if (priority != _lowestPriority) { - priority = _lowestPriority; - reorderChildren(); - } - } - - /// Decreases the priority to be lower than another [Component]. - void showBehindOf(Component other) { - if (priority >= other.priority) { - priority = math.max(other.priority - 1, _lowestPriority); - reorderChildren(); - } - } - - /// Increases the priority to be higher than another [Component]. - void showInFrontOf(Component other) { - if (priority <= other.priority) { - priority = other.priority + 1; - reorderChildren(); - } - } -} diff --git a/packages/pinball_flame/test/src/priority_test.dart b/packages/pinball_flame/test/src/priority_test.dart deleted file mode 100644 index 41e362d8..00000000 --- a/packages/pinball_flame/test/src/priority_test.dart +++ /dev/null @@ -1,221 +0,0 @@ -// 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:mocktail/mocktail.dart'; -import 'package:pinball_flame/pinball_flame.dart'; - -import '../helpers/helpers.dart'; - -class TestBodyComponent extends BodyComponent { - @override - Body createBody() { - final fixtureDef = FixtureDef(CircleShape()); - return world.createBody(BodyDef())..createFixture(fixtureDef); - } -} - -void main() { - final flameTester = FlameTester(Forge2DGame.new); - - group('ComponentPriorityX', () { - group('sendTo', () { - flameTester.test( - 'changes the priority correctly to other level', - (game) async { - const newPriority = 5; - final component = TestBodyComponent()..priority = 4; - - component.sendTo(newPriority); - - expect(component.priority, equals(newPriority)); - }, - ); - - flameTester.test( - 'calls reorderChildren if the new priority is different', - (game) async { - const newPriority = 5; - final component = MockComponent(); - when(() => component.priority).thenReturn(4); - - component.sendTo(newPriority); - - verify(component.reorderChildren).called(1); - }, - ); - - flameTester.test( - "doesn't call reorderChildren if the priority is the same", - (game) async { - const newPriority = 5; - final component = MockComponent(); - when(() => component.priority).thenReturn(newPriority); - - component.sendTo(newPriority); - - verifyNever(component.reorderChildren); - }, - ); - }); - - group('sendToBack', () { - flameTester.test( - 'changes the priority correctly to board level', - (game) async { - final component = TestBodyComponent()..priority = 4; - - component.sendToBack(); - - expect(component.priority, equals(0)); - }, - ); - - flameTester.test( - 'calls reorderChildren if the priority is greater than lowest level', - (game) async { - final component = MockComponent(); - when(() => component.priority).thenReturn(4); - - component.sendToBack(); - - verify(component.reorderChildren).called(1); - }, - ); - - flameTester.test( - "doesn't call reorderChildren if the priority is the lowest level", - (game) async { - final component = MockComponent(); - when(() => component.priority).thenReturn(0); - - component.sendToBack(); - - verifyNever(component.reorderChildren); - }, - ); - }); - - group('showBehindOf', () { - flameTester.test( - 'changes the priority if it is greater than other component', - (game) async { - const startPriority = 2; - final component = TestBodyComponent()..priority = startPriority; - final otherComponent = TestBodyComponent() - ..priority = startPriority - 1; - - component.showBehindOf(otherComponent); - - expect(component.priority, equals(otherComponent.priority - 1)); - }, - ); - - flameTester.test( - "doesn't change the priority if it is lower than other component", - (game) async { - const startPriority = 2; - final component = TestBodyComponent()..priority = startPriority; - final otherComponent = TestBodyComponent() - ..priority = startPriority + 1; - - component.showBehindOf(otherComponent); - - expect(component.priority, equals(startPriority)); - }, - ); - - flameTester.test( - 'calls reorderChildren if the priority is greater than other component', - (game) async { - const startPriority = 2; - final component = MockComponent(); - final otherComponent = MockComponent(); - when(() => component.priority).thenReturn(startPriority); - when(() => otherComponent.priority).thenReturn(startPriority - 1); - - component.showBehindOf(otherComponent); - - verify(component.reorderChildren).called(1); - }, - ); - - flameTester.test( - "doesn't call reorderChildren if the priority is lower than other " - 'component', - (game) async { - const startPriority = 2; - final component = MockComponent(); - final otherComponent = MockComponent(); - when(() => component.priority).thenReturn(startPriority); - when(() => otherComponent.priority).thenReturn(startPriority + 1); - - component.showBehindOf(otherComponent); - - verifyNever(component.reorderChildren); - }, - ); - }); - - group('showInFrontOf', () { - flameTester.test( - 'changes the priority if it is lower than other component', - (game) async { - const startPriority = 2; - final component = TestBodyComponent()..priority = startPriority; - final otherComponent = TestBodyComponent() - ..priority = startPriority + 1; - - component.showInFrontOf(otherComponent); - - expect(component.priority, equals(otherComponent.priority + 1)); - }, - ); - - flameTester.test( - "doesn't change the priority if it is greater than other component", - (game) async { - const startPriority = 2; - final component = TestBodyComponent()..priority = startPriority; - final otherComponent = TestBodyComponent() - ..priority = startPriority - 1; - - component.showInFrontOf(otherComponent); - - expect(component.priority, equals(startPriority)); - }, - ); - - flameTester.test( - 'calls reorderChildren if the priority is lower than other component', - (game) async { - const startPriority = 2; - final component = MockComponent(); - final otherComponent = MockComponent(); - when(() => component.priority).thenReturn(startPriority); - when(() => otherComponent.priority).thenReturn(startPriority + 1); - - component.showInFrontOf(otherComponent); - - verify(component.reorderChildren).called(1); - }, - ); - - flameTester.test( - "doesn't call reorderChildren if the priority is greater than other " - 'component', - (game) async { - const startPriority = 2; - final component = MockComponent(); - final otherComponent = MockComponent(); - when(() => component.priority).thenReturn(startPriority); - when(() => otherComponent.priority).thenReturn(startPriority - 1); - - component.showInFrontOf(otherComponent); - - verifyNever(component.reorderChildren); - }, - ); - }); - }); -} diff --git a/test/helpers/mocks.dart b/test/helpers/mocks.dart index 12e6d366..fd01a509 100644 --- a/test/helpers/mocks.dart +++ b/test/helpers/mocks.dart @@ -31,11 +31,6 @@ class MockContact extends Mock implements Contact {} class MockContactCallback extends Mock implements ContactCallback {} -class MockRampOpening extends Mock implements RampOpening {} - -class MockRampOpeningBallContactCallback extends Mock - implements RampOpeningBallContactCallback {} - class MockGameBloc extends Mock implements GameBloc {} class MockGameState extends Mock implements GameState {}