From aa1a2d76748b53de641673a898b993fc4d962eec Mon Sep 17 00:00:00 2001 From: Rui Miguel Alonso Date: Mon, 21 Mar 2022 20:20:31 +0100 Subject: [PATCH] feat: connect spaceship ramp (#65) * refactor: improve layer and ramp to allow connection between different layers outside from board * refactor: modified spaceship to be Layered and RampOpening * refactor: moved ramp and game components to connect jetpack ramp with spaceship * test: test coverage and removed layer unnecessary tests for spaceship * refactor: jetpack ramp removed rotation * refactor: hardcoded layer spaceship inside each component Co-authored-by: Erick --- lib/game/components/jetpack_ramp.dart | 40 ++++----- lib/game/components/layer.dart | 5 ++ lib/game/components/ramp_opening.dart | 12 ++- lib/game/components/spaceship.dart | 107 +++++++++-------------- lib/game/pinball_game.dart | 89 +++++++------------ test/game/components/spaceship_test.dart | 22 ----- test/game/pinball_game_test.dart | 4 +- 7 files changed, 104 insertions(+), 175 deletions(-) diff --git a/lib/game/components/jetpack_ramp.dart b/lib/game/components/jetpack_ramp.dart index a11ac40c..985c8f7d 100644 --- a/lib/game/components/jetpack_ramp.dart +++ b/lib/game/components/jetpack_ramp.dart @@ -30,22 +30,18 @@ class JetpackRamp extends Component with HasGameRef { // TODO(ruialonso): Use a bezier curve once control points are defined. color: const Color.fromARGB(255, 8, 218, 241), center: position, - width: 80, + width: 62, radius: 200, - angle: 7 * math.pi / 6, - rotation: -math.pi / 18, + angle: math.pi, ) ..initialPosition = position ..layer = layer; - final leftOpening = _JetpackRampOpening( - rotation: 15 * math.pi / 180, - ) - ..initialPosition = position + Vector2(-27, 21) - ..layer = Layer.opening; - final rightOpening = _JetpackRampOpening( - rotation: -math.pi / 20, - ) - ..initialPosition = position + Vector2(-11.2, 22.5) + final leftOpening = _JetpackRampOpening(outsideLayer: Layer.spaceship) + ..initialPosition = position + Vector2(-27.6, 25.3) + ..layer = Layer.jetpack; + + final rightOpening = _JetpackRampOpening() + ..initialPosition = position + Vector2(-10.6, 25.3) ..layer = Layer.opening; await addAll([ @@ -63,25 +59,21 @@ class JetpackRamp extends Component with HasGameRef { class _JetpackRampOpening extends RampOpening { /// {@macro jetpack_ramp_opening} _JetpackRampOpening({ - required double rotation, - }) : _rotation = rotation, - super( + Layer? outsideLayer, + }) : super( pathwayLayer: Layer.jetpack, + outsideLayer: outsideLayer, orientation: RampOrientation.down, ); - final double _rotation; - - // TODO(ruialonso): Avoid magic number 3, should be propotional to + // TODO(ruialonso): Avoid magic number 2, should be proportional to // [JetpackRamp]. - static final Vector2 _size = Vector2(3, .1); + static const _size = 2; @override Shape get shape => PolygonShape() - ..setAsBox( - _size.x, - _size.y, - initialPosition, - _rotation, + ..setAsEdge( + Vector2(initialPosition.x - _size, initialPosition.y), + Vector2(initialPosition.x + _size, initialPosition.y), ); } diff --git a/lib/game/components/layer.dart b/lib/game/components/layer.dart index d5df0698..3cc0471f 100644 --- a/lib/game/components/layer.dart +++ b/lib/game/components/layer.dart @@ -55,6 +55,9 @@ enum Layer { /// Collide only with Launcher group elements. launcher, + + /// Collide only with Spaceship group elements. + spaceship, } /// {@template layer_mask_bits} @@ -81,6 +84,8 @@ extension LayerMaskBits on Layer { return 0x0002; case Layer.launcher: return 0x0005; + case Layer.spaceship: + return 0x000A; } } } diff --git a/lib/game/components/ramp_opening.dart b/lib/game/components/ramp_opening.dart index 4ff8f8c9..2d8454dd 100644 --- a/lib/game/components/ramp_opening.dart +++ b/lib/game/components/ramp_opening.dart @@ -27,15 +27,21 @@ abstract class RampOpening extends BodyComponent with InitialPosition, Layered { /// {@macro ramp_opening} RampOpening({ required Layer pathwayLayer, + Layer? outsideLayer, required this.orientation, - }) : _pathwayLayer = pathwayLayer { + }) : _pathwayLayer = pathwayLayer, + _outsideLayer = outsideLayer ?? Layer.board { layer = Layer.board; } final Layer _pathwayLayer; + final Layer _outsideLayer; /// Mask of category bits for collision inside [Pathway]. Layer get pathwayLayer => _pathwayLayer; + /// Mask of category bits for collision outside [Pathway]. + Layer get outsideLayer => _outsideLayer; + /// The [Shape] of the [RampOpening]. Shape get shape; @@ -85,7 +91,7 @@ class RampOpeningBallContactCallback @override void end(Ball ball, Opening opening, Contact _) { if (!_ballsInside.contains(ball)) { - ball.layer = Layer.board; + 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 @@ -97,7 +103,7 @@ class RampOpeningBallContactCallback ball.body.linearVelocity.y > 0); if (isBallOutsideOpening) { - ball.layer = Layer.board; + ball.layer = opening.outsideLayer; _ballsInside.remove(ball); } } diff --git a/lib/game/components/spaceship.dart b/lib/game/components/spaceship.dart index adfca9a5..f934d943 100644 --- a/lib/game/components/spaceship.dart +++ b/lib/game/components/spaceship.dart @@ -8,10 +8,6 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/flame/blueprint.dart'; import 'package:pinball/game/game.dart'; -// TODO(erickzanardo): change this to use the layer class -// that will be introduced on the path PR -const _spaceShipBits = 0x0002; - /// A [Blueprint] which creates the spaceship feature. class Spaceship extends Forge2DBlueprint { /// Total size of the spaceship @@ -19,7 +15,7 @@ class Spaceship extends Forge2DBlueprint { @override void build() { - final position = Vector2(20, -24); + final position = Vector2(30, -50); addAllContactCallback([ SpaceshipHoleBallContactCallback(), @@ -41,9 +37,11 @@ class Spaceship extends Forge2DBlueprint { /// {@template spaceship_saucer} /// A [BodyComponent] for the base, or the saucer of the spaceship /// {@endtemplate} -class SpaceshipSaucer extends BodyComponent with InitialPosition { +class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered { /// {@macro spaceship_saucer} - SpaceshipSaucer() : super(priority: 2); + SpaceshipSaucer() : super(priority: 2) { + layer = Layer.spaceship; + } /// Path for the base sprite static const saucerSpritePath = 'components/spaceship/saucer.png'; @@ -90,10 +88,7 @@ class SpaceshipSaucer extends BodyComponent with InitialPosition { return world.createBody(bodyDef) ..createFixture( - FixtureDef(circleShape) - ..isSensor = true - ..filter.maskBits = _spaceShipBits - ..filter.categoryBits = _spaceShipBits, + FixtureDef(circleShape)..isSensor = true, ); } } @@ -138,9 +133,11 @@ class SpaceshipBridgeTop extends BodyComponent with InitialPosition { /// The main part of the [SpaceshipBridge], this [BodyComponent] /// provides both the collision and the rotation animation for the bridge. /// {@endtemplate} -class SpaceshipBridge extends BodyComponent with InitialPosition { +class SpaceshipBridge extends BodyComponent with InitialPosition, Layered { /// {@macro spaceship_bridge} - SpaceshipBridge() : super(priority: 3); + SpaceshipBridge() : super(priority: 3) { + layer = Layer.spaceship; + } /// Path to the spaceship bridge static const spritePath = 'components/spaceship/android-bottom.png'; @@ -177,10 +174,7 @@ class SpaceshipBridge extends BodyComponent with InitialPosition { return world.createBody(bodyDef) ..createFixture( - FixtureDef(circleShape) - ..restitution = 0.4 - ..filter.maskBits = _spaceShipBits - ..filter.categoryBits = _spaceShipBits, + FixtureDef(circleShape)..restitution = 0.4, ); } } @@ -190,34 +184,29 @@ class SpaceshipBridge extends BodyComponent with InitialPosition { /// the spaceship area in order to modify its filter data so the ball /// can correctly collide only with the Spaceship /// {@endtemplate} -// TODO(erickzanardo): Use RampOpening once provided. -class SpaceshipEntrance extends BodyComponent with InitialPosition { +class SpaceshipEntrance extends RampOpening { /// {@macro spaceship_entrance} - SpaceshipEntrance(); + SpaceshipEntrance() + : super( + pathwayLayer: Layer.spaceship, + orientation: RampOrientation.up, + ) { + layer = Layer.spaceship; + } @override - Body createBody() { - final entranceShape = PolygonShape() + Shape get shape { + const radius = Spaceship.radius * 2; + return PolygonShape() ..setAsEdge( Vector2( - Spaceship.radius * cos(20 * pi / 180), - Spaceship.radius * sin(20 * pi / 180), - ), + radius * cos(20 * pi / 180), + radius * sin(20 * pi / 180), + )..rotate(90 * pi / 180), Vector2( - Spaceship.radius * cos(340 * pi / 180), - Spaceship.radius * sin(340 * pi / 180), - ), - ); - - final bodyDef = BodyDef() - ..userData = this - ..position = initialPosition - ..angle = 90 * pi / 180 - ..type = BodyType.static; - - return world.createBody(bodyDef) - ..createFixture( - FixtureDef(entranceShape)..isSensor = true, + radius * cos(340 * pi / 180), + radius * sin(340 * pi / 180), + )..rotate(90 * pi / 180), ); } } @@ -226,9 +215,11 @@ class SpaceshipEntrance extends BodyComponent with InitialPosition { /// A sensor [BodyComponent] responsible for sending the [Ball] /// back to the board. /// {@endtemplate} -class SpaceshipHole extends BodyComponent with InitialPosition { +class SpaceshipHole extends BodyComponent with InitialPosition, Layered { /// {@macro spaceship_hole} - SpaceshipHole(); + SpaceshipHole() { + layer = Layer.spaceship; + } @override Body createBody() { @@ -242,10 +233,7 @@ class SpaceshipHole extends BodyComponent with InitialPosition { return world.createBody(bodyDef) ..createFixture( - FixtureDef(circleShape) - ..isSensor = true - ..filter.maskBits = _spaceShipBits - ..filter.categoryBits = _spaceShipBits, + FixtureDef(circleShape)..isSensor = true, ); } } @@ -256,9 +244,11 @@ class SpaceshipHole extends BodyComponent with InitialPosition { /// [Ball] to get inside the spaceship saucer. /// It also contains the [SpriteComponent] for the lower wall /// {@endtemplate} -class SpaceshipWall extends BodyComponent with InitialPosition { +class SpaceshipWall extends BodyComponent with InitialPosition, Layered { /// {@macro spaceship_wall} - SpaceshipWall() : super(priority: 4); + SpaceshipWall() : super(priority: 4) { + layer = Layer.spaceship; + } /// Sprite path for the lower wall static const lowerWallPath = 'components/spaceship/lower.png'; @@ -303,10 +293,7 @@ class SpaceshipWall extends BodyComponent with InitialPosition { return world.createBody(bodyDef) ..createFixture( - FixtureDef(wallShape) - ..restitution = 1 - ..filter.maskBits = _spaceShipBits - ..filter.categoryBits = _spaceShipBits, + FixtureDef(wallShape)..restitution = 1, ); } } @@ -316,19 +303,14 @@ class SpaceshipWall extends BodyComponent with InitialPosition { /// /// It modifies the [Ball] priority and filter data so it can appear on top of /// the spaceship and also only collide with the spaceship. -// TODO(alestiago): modify once Layer is implemented in Spaceship. class SpaceshipEntranceBallContactCallback extends ContactCallback { @override void begin(SpaceshipEntrance entrance, Ball ball, _) { ball ..priority = 3 - ..gameRef.reorderChildren(); - - for (final fixture in ball.body.fixtures) { - fixture.filterData.categoryBits = _spaceShipBits; - fixture.filterData.maskBits = _spaceShipBits; - } + ..gameRef.reorderChildren() + ..layer = Layer.spaceship; } } @@ -337,18 +319,13 @@ class SpaceshipEntranceBallContactCallback /// /// It resets the [Ball] priority and filter data so it will "be back" on the /// board. -// TODO(alestiago): modify once Layer is implemented in Spaceship. class SpaceshipHoleBallContactCallback extends ContactCallback { @override void begin(SpaceshipHole hole, Ball ball, _) { ball ..priority = 1 - ..gameRef.reorderChildren(); - - for (final fixture in ball.body.fixtures) { - fixture.filterData.categoryBits = 0xFFFF; - fixture.filterData.maskBits = 0x0001; - } + ..gameRef.reorderChildren() + ..layer = Layer.board; } } diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index ef5cb3b1..86bceef6 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -27,33 +27,22 @@ class PinballGame extends Forge2DGame _addContactCallbacks(); await _addGameBoundaries(); + unawaited(_addBoard()); unawaited(_addPlunger()); + unawaited(_addBonusWord()); unawaited(_addPaths()); - unawaited(addFromBlueprint(Spaceship())); + } - // Corner wall above plunger so the ball deflects into the rest of the - // board. - // TODO(allisonryan0002): remove once we have the launch track for the ball. - await add( - Wall( - start: screenToWorld( - Vector2( - camera.viewport.effectiveSize.x, - 100, - ), - ), - end: screenToWorld( - Vector2( - camera.viewport.effectiveSize.x - 100, - 0, - ), - ), - ), - ); + void _addContactCallbacks() { + addContactCallback(BallScorePointsCallback()); + addContactCallback(BottomWallBallContactCallback()); + addContactCallback(BonusLetterBallContactCallback()); + } - unawaited(_addBonusWord()); - unawaited(_addBoard()); + Future _addGameBoundaries() async { + await add(BottomWall(this)); + createBoundaries(this).forEach(add); } Future _addBoard() async { @@ -68,6 +57,20 @@ class PinballGame extends Forge2DGame await add(board); } + Future _addPlunger() async { + plunger = Plunger( + compressionDistance: camera.viewport.effectiveSize.y / 12, + ); + plunger.initialPosition = screenToWorld( + Vector2( + camera.viewport.effectiveSize.x / 2 + 450, + camera.viewport.effectiveSize.y - plunger.compressionDistance, + ), + ); + + await add(plunger); + } + Future _addBonusWord() async { await add( BonusWord( @@ -81,33 +84,9 @@ class PinballGame extends Forge2DGame ); } - void spawnBall() { - final ball = Ball(); - add( - ball - ..initialPosition = plunger.body.position + Vector2(0, ball.size.y / 2), - ); - } - - void _addContactCallbacks() { - addContactCallback(BallScorePointsCallback()); - addContactCallback(BottomWallBallContactCallback()); - addContactCallback(BonusLetterBallContactCallback()); - } - - Future _addGameBoundaries() async { - await add(BottomWall(this)); - createBoundaries(this).forEach(add); - } - Future _addPaths() async { final jetpackRamp = JetpackRamp( - position: screenToWorld( - Vector2( - camera.viewport.effectiveSize.x / 2 - 150, - camera.viewport.effectiveSize.y / 2 - 250, - ), - ), + position: Vector2(42.6, -45), ); final launcherRamp = LauncherRamp( position: screenToWorld( @@ -121,18 +100,12 @@ class PinballGame extends Forge2DGame await addAll([jetpackRamp, launcherRamp]); } - Future _addPlunger() async { - plunger = Plunger( - compressionDistance: camera.viewport.effectiveSize.y / 12, - ); - plunger.initialPosition = screenToWorld( - Vector2( - camera.viewport.effectiveSize.x / 2 + 450, - camera.viewport.effectiveSize.y - plunger.compressionDistance, - ), + void spawnBall() { + final ball = Ball(); + add( + ball + ..initialPosition = plunger.body.position + Vector2(0, ball.size.y / 2), ); - - await add(plunger); } } diff --git a/test/game/components/spaceship_test.dart b/test/game/components/spaceship_test.dart index 7e16edd8..c7eb24b2 100644 --- a/test/game/components/spaceship_test.dart +++ b/test/game/components/spaceship_test.dart @@ -54,17 +54,6 @@ void main() { verify(game.reorderChildren).called(1); }); - - test('changes the filter data from the ball fixtures', () { - SpaceshipEntranceBallContactCallback().begin( - entrance, - ball, - MockContact(), - ); - - verify(() => filterData.maskBits = 0x0002).called(1); - verify(() => filterData.categoryBits = 0x0002).called(1); - }); }); group('SpaceshipHoleBallContactCallback', () { @@ -87,17 +76,6 @@ void main() { verify(game.reorderChildren).called(1); }); - - test('changes the filter data from the ball fixtures', () { - SpaceshipHoleBallContactCallback().begin( - hole, - ball, - MockContact(), - ); - - verify(() => filterData.categoryBits = 0xFFFF).called(1); - verify(() => filterData.maskBits = 0x0001).called(1); - }); }); }); } diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index c145e2e4..b9e0ac7d 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -25,9 +25,7 @@ void main() { final walls = game.children.where( (component) => component is Wall && component is! BottomWall, ); - // TODO(allisonryan0002): expect 3 when launch track is added and - // temporary wall is removed. - expect(walls.length, 4); + expect(walls.length, 3); }, );