diff --git a/lib/game/components/ball.dart b/lib/game/components/ball.dart index e166bd40..6540caee 100644 --- a/lib/game/components/ball.dart +++ b/lib/game/components/ball.dart @@ -6,17 +6,17 @@ import 'package:pinball/game/game.dart'; /// A solid, [BodyType.dynamic] sphere that rolls and bounces along the /// [PinballGame]. /// {@endtemplate} -class Ball extends BodyComponent with Layer { +class Ball extends BodyComponent with Layered { /// {@macro ball} Ball({ required Vector2 position, - RampType? layer, + Layer? layer, }) : _position = position, - _layer = layer ?? RampType.all; + _layer = layer ?? Layer.all; /// The initial position of the [Ball] body. final Vector2 _position; - final RampType _layer; + final Layer _layer; /// The size of the [Ball] final Vector2 size = Vector2.all(2); diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index 2cfc3cd4..918112d9 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -2,10 +2,10 @@ export 'ball.dart'; export 'baseboard.dart'; export 'board_side.dart'; export 'bonus_word.dart'; -export 'crossing_ramp.dart'; export 'flipper.dart'; export 'jetpack_ramp.dart'; export 'joint_anchor.dart'; +export 'layer.dart'; export 'pathway.dart'; export 'plunger.dart'; export 'round_bumper.dart'; diff --git a/lib/game/components/jetpack_ramp.dart b/lib/game/components/jetpack_ramp.dart index ed08e540..9039fe3a 100644 --- a/lib/game/components/jetpack_ramp.dart +++ b/lib/game/components/jetpack_ramp.dart @@ -35,7 +35,7 @@ class JetpackRamp extends Component with HasGameRef { radius: _radius, angle: _angle, rotation: _rotation, - categoryBits: RampType.jetpack.maskBits, + layer: Layer.jetpack, ), ); @@ -59,9 +59,7 @@ class JetpackRamp extends Component with HasGameRef { } /// {@template jetpack_ramp_opening} -/// Implementation of [RampOpening] for sensors in [JetpackRamp]. -/// -/// [RampOpening] with [RampType.jetpack] to filter [Ball]s collisions +/// [RampOpening] with [Layer.jetpack] to filter [Ball] collisions /// inside [JetpackRamp]. /// {@endtemplate} class JetpackRampOpening extends RampOpening { @@ -74,7 +72,7 @@ class JetpackRampOpening extends RampOpening { _orientation = orientation, super( position: position, - layer: RampType.jetpack, + layer: Layer.jetpack, ); /// Orientation of entrance/exit of [JetpackRamp] where @@ -102,8 +100,8 @@ class JetpackRampOpening extends RampOpening { } /// {@template jetpack_ramp_opening_ball_contact_callback} -/// Implementation of [RampOpeningBallContactCallback] to listen when a [Ball] -/// gets into a [JetpackRampOpening]. +/// Detects when a [Ball] enters or exits the [JetpackRamp] through a +/// [JetpackRampOpening]. /// {@endtemplate} class JetpackRampOpeningBallContactCallback extends RampOpeningBallContactCallback { diff --git a/lib/game/components/crossing_ramp.dart b/lib/game/components/layer.dart similarity index 55% rename from lib/game/components/crossing_ramp.dart rename to lib/game/components/layer.dart index 65a5db43..558ad9dd 100644 --- a/lib/game/components/crossing_ramp.dart +++ b/lib/game/components/layer.dart @@ -3,15 +3,16 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/game/game.dart'; -/// {@template layer} -/// Modifies maskBits of [BodyComponent] for collisions. +/// Modifies maskBits of [BodyComponent] to control what other bodies it can +/// have physical interactions with. /// -/// Changes the [Filter] data for category and maskBits of -/// the [BodyComponent] to collide with other objects of -/// same bits and ignore others. +/// Changes the [Filter] data for category and maskBits of the [BodyComponent] +/// so it will only collide with bodies having the same bit value and ignore +/// bodies with a different bit value. /// {@endtemplate} -mixin Layer on BodyComponent { - void setLayer(RampType layer) { +mixin Layered on BodyComponent { + /// Sets [Filter] category and mask bits for the [BodyComponent] + set layer(Layer layer) { for (final fixture in body.fixtures) { fixture ..filterData.categoryBits = layer.maskBits @@ -20,21 +21,11 @@ mixin Layer on BodyComponent { } } -/// Indicates a orientation of the ramp entrance/exit. +/// Indicates the type of a layer. /// -/// Used to know if ramps are looking up or down of the board. -enum RampOrientation { - /// Looking up of the board. - up, - - /// Looking down of the board. - down, -} - -/// Indicates a type of the ramp. -/// -/// Used to set the maskBits of the ramp to determine their possible collisions. -enum RampType { +/// Each layer type is associated with a maskBits value to define possible +/// collisions within that plane. +enum Layer { /// Collide with all elements. all, @@ -45,37 +36,43 @@ enum RampType { sparky, } -/// Utility methods for [RampType]. -extension RampTypeX on RampType { - /// Mask of bits for each [RampType]. - int get maskBits => _getRampMaskBits(this); - - /// Mask of bits for each [RampType]. - int _getRampMaskBits(RampType type) { - switch (type) { - case RampType.all: - return Filter().maskBits; - case RampType.jetpack: - return 0x010; - case RampType.sparky: +/// Utility methods for [Layer]. +extension LayerX on Layer { + /// Mask of bits for each [Layer]. + int get maskBits { + switch (this) { + case Layer.all: + return 0xFFFF; + case Layer.jetpack: + return 0x0010; + case Layer.sparky: return 0x0100; } } } +/// Indicates the orientation of a ramp entrance/exit. +/// +/// Used to know if a ramp is facing up or down on the board. +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. -/// Collisions with [RampOpening] are listened -/// by [RampOpeningBallContactCallback]. /// {@endtemplate} abstract class RampOpening extends BodyComponent { /// {@macro ramp_opening} RampOpening({ required Vector2 position, - required RampType layer, + required Layer layer, }) : _position = position, _layer = layer { // TODO(ruialonso): remove paint color for BodyComponent. @@ -83,10 +80,10 @@ abstract class RampOpening extends BodyComponent { } final Vector2 _position; - final RampType _layer; + final Layer _layer; /// Mask of category bits for collision with [RampOpening] - RampType get layer => _layer; + Layer get layer => _layer; /// The [Shape] of the [RampOpening] Shape get shape; @@ -112,51 +109,52 @@ abstract class RampOpening extends BodyComponent { /// {@template ramp_opening_ball_contact_callback} /// Detects when a [Ball] enters or exits a [Pathway] ramp through a /// [RampOpening]. -/// Modifies [Ball]'s maskBits while is inside the ramp. When [Ball] exits, +/// +/// Modifies [Ball]'s maskBits while it is inside the ramp. When [Ball] exits, /// sets maskBits to collide with all elements. /// {@endtemplate} -abstract class RampOpeningBallContactCallback - extends ContactCallback { +abstract class RampOpeningBallContactCallback + extends ContactCallback { /// Collection of balls inside ramp pathway. Set get ballsInside; @override void begin( Ball ball, - Area area, + Opening opening, Contact _, ) { - RampType layer; + Layer layer; if (!ballsInside.contains(ball)) { - layer = area.layer; + layer = opening.layer; ballsInside.add(ball); } else { - layer = RampType.all; + layer = Layer.all; ballsInside.remove(ball); } - ball.setLayer(layer); + ball.layer = layer; } @override - void end(Ball ball, Area area, Contact contact) { - RampType? layer; + void end(Ball ball, Opening opening, Contact contact) { + Layer? layer; - switch (area.orientation) { + switch (opening.orientation) { case RampOrientation.up: - if (ball.body.position.y > area._position.y) { - layer = RampType.all; + if (ball.body.position.y > opening._position.y) { + layer = Layer.all; } break; case RampOrientation.down: - if (ball.body.position.y < area._position.y) { - layer = RampType.all; + if (ball.body.position.y < opening._position.y) { + layer = Layer.all; } break; } if (layer != null) { - ball.setLayer(layer); + ball.layer = layer; } } } diff --git a/lib/game/components/pathway.dart b/lib/game/components/pathway.dart index 8546a9ed..5e4cadf6 100644 --- a/lib/game/components/pathway.dart +++ b/lib/game/components/pathway.dart @@ -2,6 +2,7 @@ import 'package:flame/extensions.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:geometry/geometry.dart'; +import 'package:pinball/game/components/components.dart'; /// {@template pathway} /// [Pathway] creates lines of various shapes. @@ -14,10 +15,10 @@ class Pathway extends BodyComponent { Color? color, required Vector2 position, required List> paths, - int? categoryBits, + Layer? layer, }) : _position = position, _paths = paths, - _categoryBits = categoryBits ?? Filter().categoryBits { + _layer = layer { paint = Paint() ..color = color ?? const Color.fromARGB(0, 0, 0, 0) ..style = PaintingStyle.stroke; @@ -38,7 +39,7 @@ class Pathway extends BodyComponent { required double width, double rotation = 0, bool singleWall = false, - int? categoryBits, + Layer? layer, }) { final paths = >[]; @@ -61,7 +62,7 @@ class Pathway extends BodyComponent { color: color, position: position, paths: paths, - categoryBits: categoryBits, + layer: layer, ); } @@ -87,7 +88,7 @@ class Pathway extends BodyComponent { required double angle, double rotation = 0, bool singleWall = false, - int? categoryBits, + Layer? layer, }) { final paths = >[]; @@ -114,7 +115,7 @@ class Pathway extends BodyComponent { color: color, position: position, paths: paths, - categoryBits: categoryBits, + layer: layer, ); } @@ -134,7 +135,7 @@ class Pathway extends BodyComponent { required double width, double rotation = 0, bool singleWall = false, - int? categoryBits, + Layer? layer, }) { final paths = >[]; @@ -157,13 +158,13 @@ class Pathway extends BodyComponent { color: color, position: position, paths: paths, - categoryBits: categoryBits, + layer: layer, ); } Vector2 _position; List> _paths; - int _categoryBits; + Layer? _layer; @override Body createBody() { @@ -179,7 +180,8 @@ class Pathway extends BodyComponent { ); final fixtureDef = FixtureDef(chain); - body.createFixture(fixtureDef).filterData.categoryBits = _categoryBits; + body.createFixture(fixtureDef).filterData.categoryBits = + _layer?.maskBits ?? Filter().categoryBits; } return body; diff --git a/lib/game/components/sparky_ramp.dart b/lib/game/components/sparky_ramp.dart index bae9ca0c..54aaf33d 100644 --- a/lib/game/components/sparky_ramp.dart +++ b/lib/game/components/sparky_ramp.dart @@ -32,7 +32,7 @@ class SparkyRamp extends Component with HasGameRef { radius: _radius, angle: _angle, width: _width, - categoryBits: RampType.sparky.maskBits, + layer: Layer.sparky, ), ); await add( @@ -54,9 +54,7 @@ class SparkyRamp extends Component with HasGameRef { } /// {@template sparky_ramp_opening} -/// Implementation of [RampOpening] for sensors in [SparkyRamp]. -/// -/// [RampOpening] with [RampType.sparky] to filter [Ball]s collisions +/// [RampOpening] with [Layer.sparky] to filter [Ball]s collisions /// inside [SparkyRamp]. /// {@endtemplate} class SparkyRampOpening extends RampOpening { @@ -69,7 +67,7 @@ class SparkyRampOpening extends RampOpening { _orientation = orientation, super( position: position, - layer: RampType.sparky, + layer: Layer.sparky, ); /// Orientation of entrance/exit of [SparkyRamp] where @@ -97,8 +95,8 @@ class SparkyRampOpening extends RampOpening { } /// {@template sparky_ramp_opening_ball_contact_callback} -/// Implementation of [RampOpeningBallContactCallback] to listen when a [Ball] -/// gets into a [SparkyRampOpening]. +/// Detects when a [Ball] enters or exits the [SparkyRamp] through a +/// [SparkyRampOpening]. /// {@endtemplate} class SparkyRampOpeningBallContactCallback extends RampOpeningBallContactCallback { diff --git a/test/game/components/ball_test.dart b/test/game/components/ball_test.dart index 8bdcc99b..de15c24c 100644 --- a/test/game/components/ball_test.dart +++ b/test/game/components/ball_test.dart @@ -172,7 +172,7 @@ void main() { flameTester.test( 'modifies layer correctly', (game) async { - const newLayer = RampType.jetpack; + const newLayer = Layer.jetpack; final ball = Ball(position: Vector2.zero()); await game.ensureAdd(ball); @@ -182,7 +182,7 @@ void main() { expect(fixture.filterData.categoryBits, equals(1)); expect(fixture.filterData.maskBits, equals(Filter().maskBits)); - ball.setLayer(newLayer); + ball.layer = newLayer; expect(fixture.filterData.categoryBits, equals(newLayer.maskBits)); expect(fixture.filterData.maskBits, equals(newLayer.maskBits)); diff --git a/test/game/components/jetpack_ramp_test.dart b/test/game/components/jetpack_ramp_test.dart index 26779aaf..e0037156 100644 --- a/test/game/components/jetpack_ramp_test.dart +++ b/test/game/components/jetpack_ramp_test.dart @@ -80,7 +80,7 @@ void main() { }); }); - group('JetpackRampArea', () { + group('JetpackRampOpening', () { flameTester.test( 'orientation is down', (game) async { @@ -97,7 +97,7 @@ void main() { ); }); - group('JetpackRampAreaCallback', () { + group('JetpackRampOpeningBallContactCallback', () { test('has no ball inside on creation', () { expect( JetpackRampOpeningBallContactCallback().ballsInside, diff --git a/test/game/components/crossing_ramp_test.dart b/test/game/components/layer_test.dart similarity index 74% rename from test/game/components/crossing_ramp_test.dart rename to test/game/components/layer_test.dart index b4f76579..f8df221d 100644 --- a/test/game/components/crossing_ramp_test.dart +++ b/test/game/components/layer_test.dart @@ -7,11 +7,11 @@ import 'package:pinball/game/game.dart'; import '../../helpers/helpers.dart'; -class TestRampArea extends RampOpening { - TestRampArea({ +class TestRampOpening extends RampOpening { + TestRampOpening({ required Vector2 position, required RampOrientation orientation, - required RampType layer, + required Layer layer, }) : _orientation = orientation, super(position: position, layer: layer); @@ -30,9 +30,9 @@ class TestRampArea extends RampOpening { ]); } -class TestRampAreaCallback - extends RampOpeningBallContactCallback { - TestRampAreaCallback() : super(); +class TestRampOpeningBallContactCallback + extends RampOpeningBallContactCallback { + TestRampOpeningBallContactCallback() : super(); final _ballsInside = {}; @@ -41,37 +41,43 @@ class TestRampAreaCallback } void main() { - group('RampType', () { + group('Layer', () { test('has three values', () { - expect(RampType.values.length, equals(3)); + expect(Layer.values.length, equals(3)); }); }); - group('RampTypeX', () { + group('LayerX', () { + test('all types are different', () { + expect(Layer.all.maskBits, isNot(equals(Layer.jetpack.maskBits))); + expect(Layer.jetpack.maskBits, isNot(equals(Layer.sparky.maskBits))); + expect(Layer.sparky.maskBits, isNot(equals(Layer.all.maskBits))); + }); + test('all type has default maskBits', () { - expect(RampType.all.maskBits, equals(Filter().maskBits)); + expect(Layer.all.maskBits, equals(0xFFFF)); }); test('jetpack type has 0x010 maskBits', () { - expect(RampType.jetpack.maskBits, equals(0x010)); + expect(Layer.jetpack.maskBits, equals(0x0010)); }); test('sparky type has 0x0100 maskBits', () { - expect(RampType.sparky.maskBits, equals(0x0100)); + expect(Layer.sparky.maskBits, equals(0x0100)); }); }); - group('RampArea', () { + group('RampOpening', () { TestWidgetsFlutterBinding.ensureInitialized(); final flameTester = FlameTester(PinballGameTest.create); flameTester.test( 'loads correctly', (game) async { - final ramp = TestRampArea( + final ramp = TestRampOpening( position: Vector2.zero(), orientation: RampOrientation.down, - layer: RampType.all, + layer: Layer.all, ); await game.ready(); await game.ensureAdd(ramp); @@ -85,10 +91,10 @@ void main() { 'positions correctly', (game) async { final position = Vector2.all(10); - final ramp = TestRampArea( + final ramp = TestRampOpening( position: position, orientation: RampOrientation.down, - layer: RampType.all, + layer: Layer.all, ); await game.ensureAdd(ramp); @@ -100,10 +106,10 @@ void main() { flameTester.test( 'is static', (game) async { - final ramp = TestRampArea( + final ramp = TestRampOpening( position: Vector2.zero(), orientation: RampOrientation.down, - layer: RampType.all, + layer: Layer.all, ); await game.ensureAdd(ramp); @@ -111,13 +117,13 @@ void main() { }, ); - group('fixtures', () { - const layer = RampType.jetpack; + group('fixture', () { + const layer = Layer.jetpack; flameTester.test( 'exists', (game) async { - final ramp = TestRampArea( + final ramp = TestRampOpening( position: Vector2.zero(), orientation: RampOrientation.down, layer: layer, @@ -131,7 +137,7 @@ void main() { flameTester.test( 'shape is a polygon', (game) async { - final ramp = TestRampArea( + final ramp = TestRampOpening( position: Vector2.zero(), orientation: RampOrientation.down, layer: layer, @@ -146,7 +152,7 @@ void main() { flameTester.test( 'is sensor', (game) async { - final ramp = TestRampArea( + final ramp = TestRampOpening( position: Vector2.zero(), orientation: RampOrientation.down, layer: layer, @@ -161,7 +167,7 @@ void main() { flameTester.test( 'sets filter categoryBits correctly', (game) async { - final ramp = TestRampArea( + final ramp = TestRampOpening( position: Vector2.zero(), orientation: RampOrientation.down, layer: layer, @@ -180,20 +186,20 @@ void main() { }); }); - group('RampAreaCallback', () { - const layer = RampType.jetpack; + group('RampOpeningBallContactCallback', () { + const layer = Layer.jetpack; test( 'a ball enters from bottom into a down oriented path and keeps inside, ' 'is saved into collection and set maskBits to path', () { final ball = MockBall(); final body = MockBody(); - final area = TestRampArea( + final area = TestRampOpening( position: Vector2(0, 10), orientation: RampOrientation.down, layer: layer, ); - final callback = TestRampAreaCallback(); + final callback = TestRampOpeningBallContactCallback(); when(() => body.position).thenReturn(Vector2(0, 20)); when(() => ball.body).thenReturn(body); @@ -203,11 +209,11 @@ void main() { expect(callback._ballsInside.length, equals(1)); expect(callback._ballsInside.first, ball); - verify(() => ball.setLayer(layer)).called(1); + verify(() => ball.layer = layer).called(1); callback.end(ball, area, MockContact()); - verifyNever(() => ball.setLayer(RampType.all)); + verifyNever(() => ball.layer = Layer.all); }); test( @@ -215,12 +221,12 @@ void main() { 'is saved into collection and set maskBits to path', () { final ball = MockBall(); final body = MockBody(); - final area = TestRampArea( + final area = TestRampOpening( position: Vector2(0, 10), orientation: RampOrientation.up, layer: layer, ); - final callback = TestRampAreaCallback(); + final callback = TestRampOpeningBallContactCallback(); when(() => body.position).thenReturn(Vector2.zero()); when(() => ball.body).thenReturn(body); @@ -230,11 +236,11 @@ void main() { expect(callback._ballsInside.length, equals(1)); expect(callback._ballsInside.first, ball); - verify(() => ball.setLayer(layer)).called(1); + verify(() => ball.layer = layer).called(1); callback.end(ball, area, MockContact()); - verifyNever(() => ball.setLayer(RampType.all)); + verifyNever(() => ball.layer = Layer.all); }); test( @@ -242,12 +248,12 @@ void main() { 'is removed from collection and set maskBits to collide all', () { final ball = MockBall(); final body = MockBody(); - final area = TestRampArea( + final area = TestRampOpening( position: Vector2(0, 10), orientation: RampOrientation.down, layer: layer, ); - final callback = TestRampAreaCallback(); + final callback = TestRampOpeningBallContactCallback(); when(() => body.position).thenReturn(Vector2.zero()); when(() => ball.body).thenReturn(body); @@ -257,11 +263,11 @@ void main() { expect(callback._ballsInside.length, equals(1)); expect(callback._ballsInside.first, ball); - verify(() => ball.setLayer(layer)).called(1); + verify(() => ball.layer = layer).called(1); callback.end(ball, area, MockContact()); - verify(() => ball.setLayer(RampType.all)); + verify(() => ball.layer = Layer.all); }); test( @@ -269,12 +275,13 @@ void main() { 'is removed from collection and set maskBits to collide all', () { final ball = MockBall(); final body = MockBody(); - final area = TestRampArea( + final area = TestRampOpening( position: Vector2(0, 10), orientation: RampOrientation.down, layer: layer, ); - final callback = TestRampAreaCallback()..ballsInside.add(ball); + final callback = TestRampOpeningBallContactCallback() + ..ballsInside.add(ball); when(() => body.position).thenReturn(Vector2.zero()); when(() => ball.body).thenReturn(body); @@ -282,11 +289,11 @@ void main() { callback.begin(ball, area, MockContact()); expect(callback._ballsInside.isEmpty, isTrue); - verify(() => ball.setLayer(RampType.all)).called(1); + verify(() => ball.layer = Layer.all).called(1); callback.end(ball, area, MockContact()); - verify(() => ball.setLayer(RampType.all)).called(1); + verify(() => ball.layer = Layer.all).called(1); }); test( @@ -294,12 +301,13 @@ void main() { 'is removed from collection and set maskBits to collide all', () { final ball = MockBall(); final body = MockBody(); - final area = TestRampArea( + final area = TestRampOpening( position: Vector2(0, 10), orientation: RampOrientation.up, layer: layer, ); - final callback = TestRampAreaCallback()..ballsInside.add(ball); + final callback = TestRampOpeningBallContactCallback() + ..ballsInside.add(ball); when(() => body.position).thenReturn(Vector2(0, 20)); when(() => ball.body).thenReturn(body); @@ -307,11 +315,11 @@ void main() { callback.begin(ball, area, MockContact()); expect(callback._ballsInside.isEmpty, isTrue); - verify(() => ball.setLayer(RampType.all)).called(1); + verify(() => ball.layer = Layer.all).called(1); callback.end(ball, area, MockContact()); - verify(() => ball.setLayer(RampType.all)).called(1); + verify(() => ball.layer = Layer.all).called(1); }); }); } diff --git a/test/game/components/pathway_test.dart b/test/game/components/pathway_test.dart index f1142b71..443c1f96 100644 --- a/test/game/components/pathway_test.dart +++ b/test/game/components/pathway_test.dart @@ -176,13 +176,13 @@ void main() { flameTester.test( 'sets filter categoryBits correctly', (game) async { - const maskBits = 1234; + const layer = Layer.jetpack; final pathway = Pathway.straight( position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), width: width, - categoryBits: maskBits, + layer: layer, ); await game.ready(); await game.ensureAdd(pathway); @@ -191,7 +191,7 @@ void main() { expect(fixture, isA()); expect( fixture.filterData.categoryBits, - equals(maskBits), + equals(layer.maskBits), ); } }, diff --git a/test/game/components/sparky_ramp_test.dart b/test/game/components/sparky_ramp_test.dart index 366d150d..32ce0845 100644 --- a/test/game/components/sparky_ramp_test.dart +++ b/test/game/components/sparky_ramp_test.dart @@ -80,7 +80,7 @@ void main() { }); }); - group('SparkyRampArea', () { + group('SparkyRampOpening', () { flameTester.test( 'orientation is down', (game) async { @@ -97,7 +97,7 @@ void main() { ); }); - group('SparkyRampAreaCallback', () { + group('SparkyRampOpeningBallContactCallback', () { test('has no ball inside on creation', () { expect( SparkyRampOpeningBallContactCallback().ballsInside,