diff --git a/lib/flame/component_controller.dart b/lib/flame/component_controller.dart index d1da90a6..1d6e0173 100644 --- a/lib/flame/component_controller.dart +++ b/lib/flame/component_controller.dart @@ -1,5 +1,6 @@ import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flutter/foundation.dart'; /// {@template component_controller} /// A [ComponentController] is a [Component] in charge of handling the logic @@ -35,6 +36,7 @@ mixin Controls on Component { late final T controller; @override + @mustCallSuper Future onLoad() async { await super.onLoad(); await add(controller); diff --git a/lib/game/components/board.dart b/lib/game/components/board.dart index f022862c..e71d5ede 100644 --- a/lib/game/components/board.dart +++ b/lib/game/components/board.dart @@ -12,22 +12,15 @@ class Board extends Component { @override Future onLoad() async { - // TODO(alestiago): adjust positioning once sprites are added. - final bottomGroup = _BottomGroup( - position: Vector2( - PinballGame.boardBounds.center.dx, - PinballGame.boardBounds.bottom + 10, - ), - spacing: 2, - ); + final bottomGroup = _BottomGroup(); final flutterForest = FlutterForest(); // TODO(alestiago): adjust positioning to real design. final dino = ChromeDino() ..initialPosition = Vector2( - PinballGame.boardBounds.center.dx + 25, - PinballGame.boardBounds.center.dy + 10, + BoardDimensions.bounds.center.dx + 25, + BoardDimensions.bounds.center.dy + 10, ); await addAll([ @@ -46,27 +39,15 @@ class Board extends Component { // TODO(alestiago): Consider renaming once entire Board is defined. class _BottomGroup extends Component { /// {@macro bottom_group} - _BottomGroup({ - required this.position, - required this.spacing, - }); - - /// The amount of space between the line of symmetry. - final double spacing; - - /// The position of this [_BottomGroup]. - final Vector2 position; + _BottomGroup(); @override Future onLoad() async { - final spacing = this.spacing + Flipper.size.x / 2; final rightSide = _BottomGroupSide( side: BoardSide.right, - position: position + Vector2(spacing, 0), ); final leftSide = _BottomGroupSide( side: BoardSide.left, - position: position + Vector2(-spacing, 0), ); await addAll([rightSide, leftSide]); @@ -82,36 +63,29 @@ class _BottomGroupSide extends Component { /// {@macro bottom_group_side} _BottomGroupSide({ required BoardSide side, - required Vector2 position, - }) : _side = side, - _position = position; + }) : _side = side; final BoardSide _side; - final Vector2 _position; - @override Future onLoad() async { final direction = _side.direction; + final centerXAdjustment = _side.isLeft ? 0 : -6.5; final flipper = ControlledFlipper( side: _side, - )..initialPosition = _position; - + )..initialPosition = Vector2((11.0 * direction) + centerXAdjustment, -42.4); final baseboard = Baseboard(side: _side) - ..initialPosition = _position + - Vector2( - (Baseboard.size.x / 1.6 * direction), - Baseboard.size.y - 2, - ); - + ..initialPosition = Vector2( + (25.58 * direction) + centerXAdjustment, + -28.69, + ); final kicker = Kicker( side: _side, - )..initialPosition = _position + - Vector2( - (Flipper.size.x) * direction, - Flipper.size.y + Kicker.size.y, - ); + )..initialPosition = Vector2( + (22.0 * direction) + centerXAdjustment, + -26, + ); await addAll([flipper, baseboard, kicker]); } diff --git a/lib/game/components/chrome_dino.dart b/lib/game/components/chrome_dino.dart index dc280350..af086e0e 100644 --- a/lib/game/components/chrome_dino.dart +++ b/lib/game/components/chrome_dino.dart @@ -31,7 +31,7 @@ class ChromeDino extends BodyComponent with InitialPosition { anchor: anchor, ); final joint = _ChromeDinoJoint(jointDef); - world.createJoint2(joint); + world.createJoint(joint); return joint; } @@ -154,15 +154,3 @@ class _ChromeDinoJoint extends RevoluteJoint { setMotorSpeed(-motorSpeed); } } - -extension on World { - // TODO(alestiago): Remove once Forge2D supports custom joints. - void createJoint2(Joint joint) { - assert(!isLocked, ''); - - joints.add(joint); - - joint.bodyA.joints.add(joint); - joint.bodyB.joints.add(joint); - } -} diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index 1f1f1ce5..e19c607c 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -1,4 +1,3 @@ -export 'baseboard.dart'; export 'board.dart'; export 'bonus_word.dart'; export 'chrome_dino.dart'; diff --git a/lib/game/components/flutter_forest.dart b/lib/game/components/flutter_forest.dart index 6eb3ce7d..2d7bdf33 100644 --- a/lib/game/components/flutter_forest.dart +++ b/lib/game/components/flutter_forest.dart @@ -1,11 +1,10 @@ // ignore_for_file: avoid_renaming_method_parameters -import 'dart:math' as math; - import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; +import 'package:pinball/flame/flame.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -16,10 +15,47 @@ import 'package:pinball_components/pinball_components.dart'; /// When all [DashNestBumper]s are hit at least once, the [GameBonus.dashNest] /// is awarded, and the [BigDashNestBumper] releases a new [Ball]. /// {@endtemplate} -// TODO(alestiago): Make a [Blueprint] once nesting [Blueprint] is implemented. -class FlutterForest extends Component - with HasGameRef, BlocComponent { +// TODO(alestiago): Make a [Blueprint] once [Blueprint] inherits from +// [Component]. +class FlutterForest extends Component with Controls<_FlutterForestController> { /// {@macro flutter_forest} + FlutterForest() { + controller = _FlutterForestController(this); + } + + @override + Future onLoad() async { + await super.onLoad(); + final signPost = FlutterSignPost()..initialPosition = Vector2(8.35, 58.3); + + final bigNest = _ControlledBigDashNestBumper( + id: 'big_nest_bumper', + )..initialPosition = Vector2(18.55, 59.35); + final smallLeftNest = _ControlledSmallDashNestBumper.a( + id: 'small_nest_bumper_a', + )..initialPosition = Vector2(8.95, 51.95); + final smallRightNest = _ControlledSmallDashNestBumper.b( + id: 'small_nest_bumper_b', + )..initialPosition = Vector2(23.3, 46.75); + + await addAll([ + signPost, + smallLeftNest, + smallRightNest, + bigNest, + ]); + } +} + +class _FlutterForestController extends ComponentController + with BlocComponent, HasGameRef { + _FlutterForestController(FlutterForest flutterForest) : super(flutterForest); + + @override + Future onLoad() async { + await super.onLoad(); + gameRef.addContactCallback(_ControlledDashNestBumperBallContactCallback()); + } @override bool listenWhen(GameState? previousState, GameState newState) { @@ -32,117 +68,90 @@ class FlutterForest extends Component void onNewState(GameState state) { super.onNewState(state); - add( - ControlledBall.bonus( - theme: gameRef.theme, - )..initialPosition = Vector2(17.2, 52.7), + component.add( + ControlledBall.bonus(theme: gameRef.theme) + ..initialPosition = Vector2(17.2, 52.7), ); } +} - @override - Future onLoad() async { - gameRef.addContactCallback(DashNestBumperBallContactCallback()); +class _ControlledBigDashNestBumper extends BigDashNestBumper + with Controls, ScorePoints { + _ControlledBigDashNestBumper({required String id}) : super() { + controller = DashNestBumperController(this, id: id); + } - final signPost = FlutterSignPost()..initialPosition = Vector2(8.35, 58.3); + @override + int get points => 20; +} - // TODO(alestiago): adjust positioning once sprites are added. - final smallLeftNest = SmallDashNestBumper(id: 'small_left_nest') - ..initialPosition = Vector2(8.95, 51.95); - final smallRightNest = SmallDashNestBumper(id: 'small_right_nest') - ..initialPosition = Vector2(23.3, 46.75); - final bigNest = BigDashNestBumper(id: 'big_nest') - ..initialPosition = Vector2(18.55, 59.35); +class _ControlledSmallDashNestBumper extends SmallDashNestBumper + with Controls, ScorePoints { + _ControlledSmallDashNestBumper.a({required String id}) : super.a() { + controller = DashNestBumperController(this, id: id); + } - await addAll([ - signPost, - smallLeftNest, - smallRightNest, - bigNest, - ]); + _ControlledSmallDashNestBumper.b({required String id}) : super.b() { + controller = DashNestBumperController(this, id: id); } + + @override + int get points => 10; } -/// {@template dash_nest_bumper} -/// Bumper located in the [FlutterForest]. +/// {@template dash_nest_bumper_controller} +/// Controls a [DashNestBumper]. /// {@endtemplate} @visibleForTesting -abstract class DashNestBumper extends BodyComponent - with ScorePoints, InitialPosition { - /// {@macro dash_nest_bumper} - DashNestBumper({required this.id}) { - paint = Paint() - ..color = Colors.blue.withOpacity(0.5) - ..style = PaintingStyle.fill; - } - - /// Unique identifier for this [DashNestBumper]. +class DashNestBumperController extends ComponentController + with BlocComponent, HasGameRef { + /// {@macro dash_nest_bumper_controller} + DashNestBumperController( + DashNestBumper dashNestBumper, { + required this.id, + }) : super(dashNestBumper); + + /// Unique identifier for the controlled [DashNestBumper]. /// /// Used to identify [DashNestBumper]s in [GameState.activatedDashNests]. final String id; -} -/// Listens when a [Ball] bounces bounces against a [DashNestBumper]. -@visibleForTesting -class DashNestBumperBallContactCallback - extends ContactCallback { @override - void begin(DashNestBumper dashNestBumper, Ball ball, Contact _) { - dashNestBumper.gameRef.read().add( - DashNestActivated(dashNestBumper.id), - ); - } -} + bool listenWhen(GameState? previousState, GameState newState) { + final wasActive = previousState?.activatedDashNests.contains(id) ?? false; + final isActive = newState.activatedDashNests.contains(id); -/// {@macro dash_nest_bumper} -@visibleForTesting -class BigDashNestBumper extends DashNestBumper { - /// {@macro dash_nest_bumper} - BigDashNestBumper({required String id}) : super(id: id); + return wasActive != isActive; + } @override - int get points => 20; + void onNewState(GameState state) { + super.onNewState(state); - @override - Body createBody() { - final shape = EllipseShape( - center: Vector2.zero(), - majorRadius: 4.85, - minorRadius: 3.95, - )..rotate(math.pi / 2); - final fixtureDef = FixtureDef(shape); - - final bodyDef = BodyDef() - ..position = initialPosition - ..userData = this; - - return world.createBody(bodyDef)..createFixture(fixtureDef); + if (state.activatedDashNests.contains(id)) { + component.activate(); + } else { + component.deactivate(); + } } -} -/// {@macro dash_nest_bumper} -@visibleForTesting -class SmallDashNestBumper extends DashNestBumper { - /// {@macro dash_nest_bumper} - SmallDashNestBumper({required String id}) : super(id: id); - - @override - int get points => 10; + /// Registers when a [DashNestBumper] is hit by a [Ball]. + /// + /// Triggered by [_ControlledDashNestBumperBallContactCallback]. + void hit() { + gameRef.read().add(DashNestActivated(id)); + } +} +/// Listens when a [Ball] bounces bounces against a [DashNestBumper]. +class _ControlledDashNestBumperBallContactCallback + extends ContactCallback, Ball> { @override - Body createBody() { - final shape = EllipseShape( - center: Vector2.zero(), - majorRadius: 3, - minorRadius: 2.25, - )..rotate(math.pi / 2); - final fixtureDef = FixtureDef(shape) - ..friction = 0 - ..restitution = 4; - - final bodyDef = BodyDef() - ..position = initialPosition - ..userData = this; - - return world.createBody(bodyDef)..createFixture(fixtureDef); + void begin( + Controls controlledDashNestBumper, + Ball _, + Contact __, + ) { + controlledDashNestBumper.controller.hit(); } } diff --git a/lib/game/components/jetpack_ramp.dart b/lib/game/components/jetpack_ramp.dart index b58ddfa6..aa5a9dbd 100644 --- a/lib/game/components/jetpack_ramp.dart +++ b/lib/game/components/jetpack_ramp.dart @@ -13,8 +13,8 @@ class Jetpack extends Forge2DBlueprint { @override void build(_) { final position = Vector2( - PinballGame.boardBounds.left + 40.5, - PinballGame.boardBounds.top - 31.5, + BoardDimensions.bounds.left + 40.5, + BoardDimensions.bounds.top - 31.5, ); addAllContactCallback([ @@ -107,7 +107,7 @@ class _JetpackRampOpening extends RampOpening { required double rotation, }) : _rotation = rotation, super( - pathwayLayer: Layer.jetpack, + insideLayer: Layer.jetpack, outsideLayer: outsideLayer, orientation: RampOrientation.down, ); diff --git a/lib/game/components/launcher_ramp.dart b/lib/game/components/launcher_ramp.dart index b3f3cb23..c05f8aa2 100644 --- a/lib/game/components/launcher_ramp.dart +++ b/lib/game/components/launcher_ramp.dart @@ -13,8 +13,8 @@ class Launcher extends Forge2DBlueprint { @override void build(_) { final position = Vector2( - PinballGame.boardBounds.right - 31.3, - PinballGame.boardBounds.bottom + 33, + BoardDimensions.bounds.right - 31.3, + BoardDimensions.bounds.bottom + 33, ); addAllContactCallback([ @@ -67,8 +67,8 @@ class LauncherRamp extends BodyComponent with InitialPosition, Layered { final rightStraightShape = EdgeShape() ..set( - startPosition..rotate(PinballGame.boardPerspectiveAngle), - endPosition..rotate(PinballGame.boardPerspectiveAngle), + startPosition..rotate(BoardDimensions.perspectiveAngle), + endPosition..rotate(BoardDimensions.perspectiveAngle), ); final rightStraightFixtureDef = FixtureDef(rightStraightShape); fixturesDef.add(rightStraightFixtureDef); @@ -122,7 +122,7 @@ class _LauncherRampOpening extends RampOpening { required double rotation, }) : _rotation = rotation, super( - pathwayLayer: Layer.launcher, + insideLayer: Layer.launcher, orientation: RampOrientation.down, ); diff --git a/lib/game/components/plunger.dart b/lib/game/components/plunger.dart index 5703e525..60e29a4d 100644 --- a/lib/game/components/plunger.dart +++ b/lib/game/components/plunger.dart @@ -1,7 +1,6 @@ import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/services.dart'; -import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template plunger} @@ -26,10 +25,10 @@ class Plunger extends BodyComponent with KeyboardHandler, InitialPosition { 1.35, 0.5, Vector2.zero(), - PinballGame.boardPerspectiveAngle, + BoardDimensions.perspectiveAngle, ); - final fixtureDef = FixtureDef(shape)..density = 20; + final fixtureDef = FixtureDef(shape)..density = 80; final bodyDef = BodyDef() ..position = initialPosition @@ -50,7 +49,7 @@ class Plunger extends BodyComponent with KeyboardHandler, InitialPosition { /// The velocity's magnitude depends on how far the [Plunger] has been pulled /// from its original [initialPosition]. void _release() { - final velocity = (initialPosition.y - body.position.y) * 4; + final velocity = (initialPosition.y - body.position.y) * 5; body.linearVelocity = Vector2(0, velocity); } @@ -127,12 +126,12 @@ class PlungerAnchorPrismaticJointDef extends PrismaticJointDef { plunger.body, anchor.body, anchor.body.position, - Vector2(18.6, PinballGame.boardBounds.height), + Vector2(18.6, BoardDimensions.bounds.height), ); enableLimit = true; lowerTranslation = double.negativeInfinity; enableMotor = true; - motorSpeed = 80; + motorSpeed = 1000; maxMotorForce = motorSpeed; collideConnected = true; } diff --git a/lib/game/components/spaceship_exit_rail.dart b/lib/game/components/spaceship_exit_rail.dart index 0dc38322..4a6c44cd 100644 --- a/lib/game/components/spaceship_exit_rail.dart +++ b/lib/game/components/spaceship_exit_rail.dart @@ -169,7 +169,7 @@ class SpaceshipExitRailEnd extends RampOpening { /// {@macro spaceship_exit_rail_end} SpaceshipExitRailEnd() : super( - pathwayLayer: Layer.spaceshipExitRail, + insideLayer: Layer.spaceshipExitRail, orientation: RampOrientation.down, ) { layer = Layer.spaceshipExitRail; diff --git a/lib/game/components/wall.dart b/lib/game/components/wall.dart index 96522cbd..030edc50 100644 --- a/lib/game/components/wall.dart +++ b/lib/game/components/wall.dart @@ -2,9 +2,8 @@ import 'package:flame/extensions.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:pinball/game/components/components.dart'; -import 'package:pinball/game/pinball_game.dart'; -import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart' hide Assets; /// {@template wall} /// A continuous generic and [BodyType.static] barrier that divides a game area. @@ -42,13 +41,12 @@ class Wall extends BodyComponent { /// Create top, left, and right [Wall]s for the game board. List createBoundaries(Forge2DGame game) { - final topLeft = - PinballGame.boardBounds.topLeft.toVector2() + Vector2(18.6, 0); - final bottomRight = PinballGame.boardBounds.bottomRight.toVector2(); + final topLeft = BoardDimensions.bounds.topLeft.toVector2() + Vector2(18.6, 0); + final bottomRight = BoardDimensions.bounds.bottomRight.toVector2(); final topRight = - PinballGame.boardBounds.topRight.toVector2() - Vector2(18.6, 0); - final bottomLeft = PinballGame.boardBounds.bottomLeft.toVector2(); + BoardDimensions.bounds.topRight.toVector2() - Vector2(18.6, 0); + final bottomLeft = BoardDimensions.bounds.bottomLeft.toVector2(); return [ Wall(start: topLeft, end: topRight), @@ -67,8 +65,8 @@ class BottomWall extends Wall { /// {@macro bottom_wall} BottomWall() : super( - start: PinballGame.boardBounds.bottomLeft.toVector2(), - end: PinballGame.boardBounds.bottomRight.toVector2(), + start: BoardDimensions.bounds.bottomLeft.toVector2(), + end: BoardDimensions.bounds.bottomRight.toVector2(), ); } diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index 73aae25f..cc8aac9c 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -11,6 +11,16 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.flutterSignPost.keyName), images.load(components.Assets.images.flipper.left.keyName), images.load(components.Assets.images.flipper.right.keyName), + images.load(components.Assets.images.baseboard.left.keyName), + images.load(components.Assets.images.baseboard.right.keyName), + images.load(components.Assets.images.dino.dinoLandTop.keyName), + images.load(components.Assets.images.dino.dinoLandBottom.keyName), + images.load(components.Assets.images.dashBumper.a.active.keyName), + images.load(components.Assets.images.dashBumper.a.inactive.keyName), + images.load(components.Assets.images.dashBumper.b.active.keyName), + images.load(components.Assets.images.dashBumper.b.inactive.keyName), + images.load(components.Assets.images.dashBumper.main.active.keyName), + images.load(components.Assets.images.dashBumper.main.inactive.keyName), images.load(Assets.images.components.background.path), ]); } diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 514c589c..b5162053 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -1,6 +1,5 @@ // ignore_for_file: public_member_api_docs import 'dart:async'; -import 'dart:math' as math; import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; @@ -22,15 +21,6 @@ class PinballGame extends Forge2DGame late final Plunger plunger; - static final boardSize = Vector2(101.6, 143.8); - static final boardBounds = Rect.fromCenter( - center: Offset.zero, - width: boardSize.x, - height: -boardSize.y, - ); - static final boardPerspectiveAngle = - -math.atan(18.6 / PinballGame.boardBounds.height); - @override void onAttach() { super.onAttach(); @@ -76,11 +66,19 @@ class PinballGame extends Forge2DGame Future _addGameBoundaries() async { await add(BottomWall()); createBoundaries(this).forEach(add); + unawaited( + addFromBlueprint( + DinoWalls( + position: Vector2(-2.4, 0), + ), + ), + ); } Future _addPlunger() async { plunger = Plunger(compressionDistance: 29) - ..initialPosition = boardBounds.center.toVector2() + Vector2(41.5, -49); + ..initialPosition = + BoardDimensions.bounds.center.toVector2() + Vector2(41.5, -49); await add(plunger); } @@ -88,8 +86,8 @@ class PinballGame extends Forge2DGame await add( BonusWord( position: Vector2( - boardBounds.center.dx - 3.07, - boardBounds.center.dy - 2.4, + BoardDimensions.bounds.center.dx - 3.07, + BoardDimensions.bounds.center.dy - 2.4, ), ), ); diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index 370d8fcf..97be7f3e 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -3,6 +3,8 @@ /// FlutterGen /// ***************************************************** +// ignore_for_file: directives_ordering,unnecessary_import + import 'package:flutter/widgets.dart'; class $AssetsImagesGen { @@ -15,6 +17,7 @@ class $AssetsImagesGen { class $AssetsImagesComponentsGen { const $AssetsImagesComponentsGen(); + /// File path: assets/images/components/background.png AssetGenImage get background => const AssetGenImage('assets/images/components/background.png'); } diff --git a/packages/geometry/lib/src/geometry.dart b/packages/geometry/lib/src/geometry.dart index 6975f8cb..edc176e7 100644 --- a/packages/geometry/lib/src/geometry.dart +++ b/packages/geometry/lib/src/geometry.dart @@ -82,7 +82,7 @@ List calculateEllipse({ /// For more information read: https://en.wikipedia.org/wiki/B%C3%A9zier_curve List calculateBezierCurve({ required List controlPoints, - double step = 0.001, + double step = 0.01, }) { assert( 0 <= step && step <= 1, diff --git a/packages/geometry/test/src/geometry_test.dart b/packages/geometry/test/src/geometry_test.dart index 7a49b2b2..e702f043 100644 --- a/packages/geometry/test/src/geometry_test.dart +++ b/packages/geometry/test/src/geometry_test.dart @@ -98,14 +98,14 @@ void main() { ); }); - test('returns by default 1000 points as indicated by step', () { + test('returns by default 100 points as indicated by step', () { final points = calculateBezierCurve( controlPoints: [ Vector2(0, 0), Vector2(10, 10), ], ); - expect(points.length, 1000); + expect(points.length, 100); }); test('returns as many points as indicated by step', () { @@ -114,9 +114,9 @@ void main() { Vector2(0, 0), Vector2(10, 10), ], - step: 0.01, + step: 0.001, ); - expect(points.length, 100); + expect(points.length, 1000); }); }); diff --git a/packages/pinball_components/assets/images/ball.png b/packages/pinball_components/assets/images/ball.png index af80811b..43332c9a 100644 Binary files a/packages/pinball_components/assets/images/ball.png and b/packages/pinball_components/assets/images/ball.png differ diff --git a/packages/pinball_components/assets/images/baseboard/left.png b/packages/pinball_components/assets/images/baseboard/left.png new file mode 100644 index 00000000..17253554 Binary files /dev/null and b/packages/pinball_components/assets/images/baseboard/left.png differ diff --git a/packages/pinball_components/assets/images/baseboard/right.png b/packages/pinball_components/assets/images/baseboard/right.png new file mode 100644 index 00000000..081a1782 Binary files /dev/null and b/packages/pinball_components/assets/images/baseboard/right.png differ diff --git a/packages/pinball_components/assets/images/dash_bumper/a/active.png b/packages/pinball_components/assets/images/dash_bumper/a/active.png new file mode 100644 index 00000000..feeee11f Binary files /dev/null and b/packages/pinball_components/assets/images/dash_bumper/a/active.png differ diff --git a/packages/pinball_components/assets/images/dash_bumper/a/inactive.png b/packages/pinball_components/assets/images/dash_bumper/a/inactive.png new file mode 100644 index 00000000..58ab8c56 Binary files /dev/null and b/packages/pinball_components/assets/images/dash_bumper/a/inactive.png differ diff --git a/packages/pinball_components/assets/images/dash_bumper/b/active.png b/packages/pinball_components/assets/images/dash_bumper/b/active.png new file mode 100644 index 00000000..4bc2897f Binary files /dev/null and b/packages/pinball_components/assets/images/dash_bumper/b/active.png differ diff --git a/packages/pinball_components/assets/images/dash_bumper/b/inactive.png b/packages/pinball_components/assets/images/dash_bumper/b/inactive.png new file mode 100644 index 00000000..eddc7693 Binary files /dev/null and b/packages/pinball_components/assets/images/dash_bumper/b/inactive.png differ diff --git a/packages/pinball_components/assets/images/dash_bumper/main/active.png b/packages/pinball_components/assets/images/dash_bumper/main/active.png new file mode 100644 index 00000000..bef56684 Binary files /dev/null and b/packages/pinball_components/assets/images/dash_bumper/main/active.png differ diff --git a/packages/pinball_components/assets/images/dash_bumper/main/inactive.png b/packages/pinball_components/assets/images/dash_bumper/main/inactive.png new file mode 100644 index 00000000..e6f15b38 Binary files /dev/null and b/packages/pinball_components/assets/images/dash_bumper/main/inactive.png differ diff --git a/packages/pinball_components/assets/images/dino/dino-land-bottom.png b/packages/pinball_components/assets/images/dino/dino-land-bottom.png new file mode 100644 index 00000000..1839dda3 Binary files /dev/null and b/packages/pinball_components/assets/images/dino/dino-land-bottom.png differ diff --git a/packages/pinball_components/assets/images/dino/dino-land-top.png b/packages/pinball_components/assets/images/dino/dino-land-top.png new file mode 100644 index 00000000..85c2619a Binary files /dev/null and b/packages/pinball_components/assets/images/dino/dino-land-top.png differ diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index 54b0ff53..cf32e986 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -13,6 +13,10 @@ class $AssetsImagesGen { /// File path: assets/images/ball.png AssetGenImage get ball => const AssetGenImage('assets/images/ball.png'); + $AssetsImagesBaseboardGen get baseboard => const $AssetsImagesBaseboardGen(); + $AssetsImagesDashBumperGen get dashBumper => + const $AssetsImagesDashBumperGen(); + $AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen(); $AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen(); /// File path: assets/images/flutter_sign_post.png @@ -28,6 +32,39 @@ class $AssetsImagesGen { const AssetGenImage('assets/images/spaceship_saucer.png'); } +class $AssetsImagesBaseboardGen { + const $AssetsImagesBaseboardGen(); + + /// File path: assets/images/baseboard/left.png + AssetGenImage get left => + const AssetGenImage('assets/images/baseboard/left.png'); + + /// File path: assets/images/baseboard/right.png + AssetGenImage get right => + const AssetGenImage('assets/images/baseboard/right.png'); +} + +class $AssetsImagesDashBumperGen { + const $AssetsImagesDashBumperGen(); + + $AssetsImagesDashBumperAGen get a => const $AssetsImagesDashBumperAGen(); + $AssetsImagesDashBumperBGen get b => const $AssetsImagesDashBumperBGen(); + $AssetsImagesDashBumperMainGen get main => + const $AssetsImagesDashBumperMainGen(); +} + +class $AssetsImagesDinoGen { + const $AssetsImagesDinoGen(); + + /// File path: assets/images/dino/dino-land-bottom.png + AssetGenImage get dinoLandBottom => + const AssetGenImage('assets/images/dino/dino-land-bottom.png'); + + /// File path: assets/images/dino/dino-land-top.png + AssetGenImage get dinoLandTop => + const AssetGenImage('assets/images/dino/dino-land-top.png'); +} + class $AssetsImagesFlipperGen { const $AssetsImagesFlipperGen(); @@ -40,6 +77,42 @@ class $AssetsImagesFlipperGen { const AssetGenImage('assets/images/flipper/right.png'); } +class $AssetsImagesDashBumperAGen { + const $AssetsImagesDashBumperAGen(); + + /// File path: assets/images/dash_bumper/a/active.png + AssetGenImage get active => + const AssetGenImage('assets/images/dash_bumper/a/active.png'); + + /// File path: assets/images/dash_bumper/a/inactive.png + AssetGenImage get inactive => + const AssetGenImage('assets/images/dash_bumper/a/inactive.png'); +} + +class $AssetsImagesDashBumperBGen { + const $AssetsImagesDashBumperBGen(); + + /// File path: assets/images/dash_bumper/b/active.png + AssetGenImage get active => + const AssetGenImage('assets/images/dash_bumper/b/active.png'); + + /// File path: assets/images/dash_bumper/b/inactive.png + AssetGenImage get inactive => + const AssetGenImage('assets/images/dash_bumper/b/inactive.png'); +} + +class $AssetsImagesDashBumperMainGen { + const $AssetsImagesDashBumperMainGen(); + + /// File path: assets/images/dash_bumper/main/active.png + AssetGenImage get active => + const AssetGenImage('assets/images/dash_bumper/main/active.png'); + + /// File path: assets/images/dash_bumper/main/inactive.png + AssetGenImage get inactive => + const AssetGenImage('assets/images/dash_bumper/main/inactive.png'); +} + class Assets { Assets._(); diff --git a/packages/pinball_components/lib/src/components/ball.dart b/packages/pinball_components/lib/src/components/ball.dart index 96e0bf9d..892936f9 100644 --- a/packages/pinball_components/lib/src/components/ball.dart +++ b/packages/pinball_components/lib/src/components/ball.dart @@ -23,13 +23,14 @@ class Ball extends BodyComponent } /// The size of the [Ball] - static final Vector2 size = Vector2.all(3); + static final Vector2 size = Vector2.all(4.5); /// The base [Color] used to tint this [Ball] final Color baseColor; double _boostTimer = 0; static const _boostDuration = 2.0; + late SpriteComponent _spriteComponent; @override Future onLoad() async { @@ -37,9 +38,9 @@ class Ball extends BodyComponent final sprite = await gameRef.loadSprite(Assets.images.ball.keyName); final tint = baseColor.withOpacity(0.5); await add( - SpriteComponent( + _spriteComponent = SpriteComponent( sprite: sprite, - size: size, + size: size * 1.15, anchor: Anchor.center, )..tint(tint), ); @@ -82,12 +83,15 @@ class Ball extends BodyComponent final direction = body.linearVelocity.normalized(); final effect = FireEffect( burstPower: _boostTimer, - direction: direction, - position: body.position, + direction: -direction, + position: Vector2(body.position.x, -body.position.y), + priority: priority - 1, ); unawaited(gameRef.add(effect)); } + + _rescale(); } /// Applies a boost on this [Ball]. @@ -95,4 +99,18 @@ class Ball extends BodyComponent body.applyLinearImpulse(impulse); _boostTimer = _boostDuration; } + + void _rescale() { + final boardHeight = BoardDimensions.size.y; + const maxShrinkAmount = BoardDimensions.perspectiveShrinkFactor; + + final adjustedYPosition = body.position.y + (boardHeight / 2); + + final scaleFactor = ((boardHeight - adjustedYPosition) / + BoardDimensions.shrinkAdjustedHeight) + + maxShrinkAmount; + + body.fixtures.first.shape.radius = (size.x / 2) * scaleFactor; + _spriteComponent.scale = Vector2.all(scaleFactor); + } } diff --git a/lib/game/components/baseboard.dart b/packages/pinball_components/lib/src/components/baseboard.dart similarity index 59% rename from lib/game/components/baseboard.dart rename to packages/pinball_components/lib/src/components/baseboard.dart index cdad23fc..0a6bcc91 100644 --- a/lib/game/components/baseboard.dart +++ b/packages/pinball_components/lib/src/components/baseboard.dart @@ -1,5 +1,6 @@ import 'dart:math' as math; +import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -12,9 +13,6 @@ class Baseboard extends BodyComponent with InitialPosition { required BoardSide side, }) : _side = side; - /// The size of the [Baseboard]. - static final size = Vector2(24.2, 13.5); - /// Whether the [Baseboard] is on the left or right side of the board. final BoardSide _side; @@ -24,50 +22,55 @@ class Baseboard extends BodyComponent with InitialPosition { final arcsAngle = -1.11 * direction; const arcsRotation = math.pi / 2.08; + final pegBumperShape = CircleShape()..radius = 0.7; + pegBumperShape.position.setValues(11.11 * direction, 7.15); + final pegBumperFixtureDef = FixtureDef(pegBumperShape); + fixturesDef.add(pegBumperFixtureDef); + final topCircleShape = CircleShape()..radius = 0.7; - topCircleShape.position.setValues(11.39 * direction, 6.05); + topCircleShape.position.setValues(9.71 * direction, 4.95); final topCircleFixtureDef = FixtureDef(topCircleShape); fixturesDef.add(topCircleFixtureDef); final innerEdgeShape = EdgeShape() ..set( - Vector2(10.86 * direction, 6.45), - Vector2(6.96 * direction, 0.25), + Vector2(9.01 * direction, 5.35), + Vector2(5.29 * direction, -0.95), ); final innerEdgeShapeFixtureDef = FixtureDef(innerEdgeShape); fixturesDef.add(innerEdgeShapeFixtureDef); final outerEdgeShape = EdgeShape() ..set( - Vector2(11.96 * direction, 5.85), - Vector2(5.48 * direction, -4.85), + Vector2(10.41 * direction, 4.75), + Vector2(3.79 * direction, -5.95), ); final outerEdgeShapeFixtureDef = FixtureDef(outerEdgeShape); fixturesDef.add(outerEdgeShapeFixtureDef); final upperArcShape = ArcShape( - center: Vector2(1.76 * direction, 3.25), + center: Vector2(0.09 * direction, 2.15), arcRadius: 6.1, angle: arcsAngle, rotation: arcsRotation, ); - final upperArcFixtureDefs = FixtureDef(upperArcShape); - fixturesDef.add(upperArcFixtureDefs); + final upperArcFixtureDef = FixtureDef(upperArcShape); + fixturesDef.add(upperArcFixtureDef); final lowerArcShape = ArcShape( - center: Vector2(1.85 * direction, -2.15), + center: Vector2(0.09 * direction, -3.35), arcRadius: 4.5, angle: arcsAngle, rotation: arcsRotation, ); - final lowerArcFixtureDefs = FixtureDef(lowerArcShape); - fixturesDef.add(lowerArcFixtureDefs); + final lowerArcFixtureDef = FixtureDef(lowerArcShape); + fixturesDef.add(lowerArcFixtureDef); final bottomRectangle = PolygonShape() ..setAsBox( - 7, + 6.8, 2, - Vector2(-5.14 * direction, -4.75), + Vector2(-6.3 * direction, -5.85), 0, ); final bottomRectangleFixtureDef = FixtureDef(bottomRectangle); @@ -76,11 +79,31 @@ class Baseboard extends BodyComponent with InitialPosition { return fixturesDef; } + @override + Future onLoad() async { + await super.onLoad(); + + final sprite = await gameRef.loadSprite( + (_side.isLeft) + ? Assets.images.baseboard.left.keyName + : Assets.images.baseboard.right.keyName, + ); + + await add( + SpriteComponent( + sprite: sprite, + size: Vector2(27.5, 17.9), + anchor: Anchor.center, + position: Vector2(_side.isLeft ? 0.4 : -0.4, 0), + ), + ); + + renderBody = false; + } + @override Body createBody() { - // TODO(allisonryan0002): share sweeping angle with flipper when components - // are grouped. - const angle = math.pi / 5; + const angle = 37.1 * (math.pi / 180); final bodyDef = BodyDef() ..position = initialPosition diff --git a/packages/pinball_components/lib/src/components/board_dimensions.dart b/packages/pinball_components/lib/src/components/board_dimensions.dart new file mode 100644 index 00000000..b4db8c3c --- /dev/null +++ b/packages/pinball_components/lib/src/components/board_dimensions.dart @@ -0,0 +1,29 @@ +import 'dart:math' as math; + +import 'package:flame/extensions.dart'; + +/// {@template board_dimensions} +/// Contains various board properties and dimensions for global use. +/// {@endtemplate} +// TODO(allisonryan0002): consider alternatives for global dimensions. +class BoardDimensions { + /// Width and height of the board. + static final size = Vector2(101.6, 143.8); + + /// [Rect] for easier access to board boundaries. + static final bounds = Rect.fromCenter( + center: Offset.zero, + width: size.x, + height: -size.y, + ); + + /// 3D perspective angle of the board in radians. + static final perspectiveAngle = -math.atan(18.6 / bounds.height); + + /// Factor the board shrinks by from the closest point to the farthest. + static const perspectiveShrinkFactor = 0.63; + + /// Board height based on the [perspectiveShrinkFactor]. + static final shrinkAdjustedHeight = + (1 / (1 - perspectiveShrinkFactor)) * size.y; +} diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index c29f91a3..bbb2c29c 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -1,5 +1,9 @@ export 'ball.dart'; +export 'baseboard.dart'; +export 'board_dimensions.dart'; export 'board_side.dart'; +export 'dash_nest_bumper.dart'; +export 'dino_walls.dart'; export 'fire_effect.dart'; export 'flipper.dart'; export 'flutter_sign_post.dart'; diff --git a/packages/pinball_components/lib/src/components/dash_nest_bumper.dart b/packages/pinball_components/lib/src/components/dash_nest_bumper.dart new file mode 100644 index 00000000..a2b9b982 --- /dev/null +++ b/packages/pinball_components/lib/src/components/dash_nest_bumper.dart @@ -0,0 +1,142 @@ +import 'dart:math' as math; + +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_components/pinball_components.dart'; + +/// {@template dash_nest_bumper} +/// Bumper with a nest appearance. +/// {@endtemplate} +abstract class DashNestBumper extends BodyComponent with InitialPosition { + /// {@macro dash_nest_bumper} + DashNestBumper._({ + required String activeAssetPath, + required String inactiveAssetPath, + required SpriteComponent spriteComponent, + }) : _activeAssetPath = activeAssetPath, + _inactiveAssetPath = inactiveAssetPath, + _spriteComponent = spriteComponent; + + final String _activeAssetPath; + late final Sprite _activeSprite; + final String _inactiveAssetPath; + late final Sprite _inactiveSprite; + final SpriteComponent _spriteComponent; + + Future _loadSprites() async { + // TODO(alestiago): I think ideally we would like to do: + // Sprite(path).load so we don't require to store the activeAssetPath and + // the inactive assetPath. + _inactiveSprite = await gameRef.loadSprite(_inactiveAssetPath); + _activeSprite = await gameRef.loadSprite(_activeAssetPath); + } + + /// Activates the [DashNestBumper]. + void activate() { + _spriteComponent + ..sprite = _activeSprite + ..size = _activeSprite.originalSize / 10; + } + + /// Deactivates the [DashNestBumper]. + void deactivate() { + _spriteComponent + ..sprite = _inactiveSprite + ..size = _inactiveSprite.originalSize / 10; + } + + @override + Future onLoad() async { + await super.onLoad(); + await _loadSprites(); + + // TODO(erickzanardo): Look into using onNewState instead. + // Currently doing: onNewState(gameRef.read()) will throw an + // `Exception: build context is not available yet` + deactivate(); + await add(_spriteComponent); + } +} + +/// {@macro dash_nest_bumper} +class BigDashNestBumper extends DashNestBumper { + /// {@macro dash_nest_bumper} + BigDashNestBumper() + : super._( + activeAssetPath: Assets.images.dashBumper.main.active.keyName, + inactiveAssetPath: Assets.images.dashBumper.main.inactive.keyName, + spriteComponent: SpriteComponent( + anchor: Anchor.center, + ), + ); + + @override + Body createBody() { + final shape = EllipseShape( + center: Vector2.zero(), + majorRadius: 4.85, + minorRadius: 3.95, + )..rotate(math.pi / 2); + final fixtureDef = FixtureDef(shape); + + final bodyDef = BodyDef() + ..position = initialPosition + ..userData = this; + + return world.createBody(bodyDef)..createFixture(fixtureDef); + } +} + +/// {@macro dash_nest_bumper} +class SmallDashNestBumper extends DashNestBumper { + /// {@macro dash_nest_bumper} + SmallDashNestBumper._({ + required String activeAssetPath, + required String inactiveAssetPath, + required SpriteComponent spriteComponent, + }) : super._( + activeAssetPath: activeAssetPath, + inactiveAssetPath: inactiveAssetPath, + spriteComponent: spriteComponent, + ); + + /// {@macro dash_nest_bumper} + SmallDashNestBumper.a() + : this._( + activeAssetPath: Assets.images.dashBumper.a.active.keyName, + inactiveAssetPath: Assets.images.dashBumper.a.inactive.keyName, + spriteComponent: SpriteComponent( + anchor: Anchor.center, + position: Vector2(0.35, -1.2), + ), + ); + + /// {@macro dash_nest_bumper} + SmallDashNestBumper.b() + : this._( + activeAssetPath: Assets.images.dashBumper.b.active.keyName, + inactiveAssetPath: Assets.images.dashBumper.b.inactive.keyName, + spriteComponent: SpriteComponent( + anchor: Anchor.center, + position: Vector2(0.35, -1.2), + ), + ); + + @override + Body createBody() { + final shape = EllipseShape( + center: Vector2.zero(), + majorRadius: 3, + minorRadius: 2.25, + )..rotate(math.pi / 2); + final fixtureDef = FixtureDef(shape) + ..friction = 0 + ..restitution = 4; + + final bodyDef = BodyDef() + ..position = initialPosition + ..userData = this; + + return world.createBody(bodyDef)..createFixture(fixtureDef); + } +} diff --git a/packages/pinball_components/lib/src/components/dino_walls.dart b/packages/pinball_components/lib/src/components/dino_walls.dart new file mode 100644 index 00000000..13f56ff3 --- /dev/null +++ b/packages/pinball_components/lib/src/components/dino_walls.dart @@ -0,0 +1,225 @@ +// ignore_for_file: comment_references, avoid_renaming_method_parameters + +import 'dart:async'; + +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_components/gen/assets.gen.dart'; +import 'package:pinball_components/pinball_components.dart' hide Assets; + +/// {@template dinowalls} +/// A [Blueprint] which creates walls for the [ChromeDino]. +/// {@endtemplate} +class DinoWalls extends Forge2DBlueprint { + /// {@macro dinowalls} + DinoWalls({required this.position}); + + /// The [position] where the elements will be created + final Vector2 position; + + @override + void build(_) { + addAll([ + _DinoTopWall()..initialPosition = position, + _DinoBottomWall()..initialPosition = position, + ]); + } +} + +/// {@template dino_top_wall} +/// Wall segment located above [ChromeDino]. +/// {@endtemplate} +class _DinoTopWall extends BodyComponent with InitialPosition { + ///{@macro dino_top_wall} + _DinoTopWall() : super(priority: 2); + + List _createFixtureDefs() { + final fixturesDef = []; + + final topStraightShape = EdgeShape() + ..set( + Vector2(29.5, 35.1), + Vector2(28.4, 35.1), + ); + final topStraightFixtureDef = FixtureDef(topStraightShape); + fixturesDef.add(topStraightFixtureDef); + + final topCurveShape = BezierCurveShape( + controlPoints: [ + topStraightShape.vertex1, + Vector2(17.4, 26.38), + Vector2(25.5, 20.7), + ], + ); + fixturesDef.add(FixtureDef(topCurveShape)); + + final middleCurveShape = BezierCurveShape( + controlPoints: [ + topCurveShape.vertices.last, + Vector2(27.8, 20.1), + Vector2(26.8, 19.5), + ], + ); + fixturesDef.add(FixtureDef(middleCurveShape)); + + final bottomCurveShape = BezierCurveShape( + controlPoints: [ + middleCurveShape.vertices.last, + Vector2(21.15, 16), + Vector2(25.6, 15.2), + ], + ); + fixturesDef.add(FixtureDef(bottomCurveShape)); + + final bottomStraightShape = EdgeShape() + ..set( + bottomCurveShape.vertices.last, + Vector2(31, 14.5), + ); + final bottomStraightFixtureDef = FixtureDef(bottomStraightShape); + fixturesDef.add(bottomStraightFixtureDef); + + return fixturesDef; + } + + @override + Body createBody() { + renderBody = false; + + final bodyDef = BodyDef() + ..userData = this + ..position = initialPosition + ..type = BodyType.static; + + final body = world.createBody(bodyDef); + _createFixtureDefs().forEach( + (fixture) => body.createFixture( + fixture + ..restitution = 0.1 + ..friction = 0, + ), + ); + + return body; + } + + @override + Future onLoad() async { + await super.onLoad(); + await _loadSprite(); + } + + Future _loadSprite() async { + final sprite = await gameRef.loadSprite( + Assets.images.dino.dinoLandTop.keyName, + ); + final spriteComponent = SpriteComponent( + sprite: sprite, + size: Vector2(10.6, 27.7), + anchor: Anchor.center, + position: Vector2(27, -28.2), + ); + + await add(spriteComponent); + } +} + +/// {@template dino_bottom_wall} +/// Wall segment located below [ChromeDino]. +/// {@endtemplate} +class _DinoBottomWall extends BodyComponent with InitialPosition { + ///{@macro dino_top_wall} + _DinoBottomWall() : super(priority: 2); + + List _createFixtureDefs() { + final fixturesDef = []; + + final topStraightControlPoints = [ + Vector2(32.4, 8.3), + Vector2(25, 7.7), + ]; + final topStraightShape = EdgeShape() + ..set( + topStraightControlPoints.first, + topStraightControlPoints.last, + ); + final topStraightFixtureDef = FixtureDef(topStraightShape); + fixturesDef.add(topStraightFixtureDef); + + final topLeftCurveControlPoints = [ + topStraightControlPoints.last, + Vector2(21.8, 7), + Vector2(29.5, -13.8), + ]; + final topLeftCurveShape = BezierCurveShape( + controlPoints: topLeftCurveControlPoints, + ); + fixturesDef.add(FixtureDef(topLeftCurveShape)); + + final bottomLeftStraightControlPoints = [ + topLeftCurveControlPoints.last, + Vector2(31.8, -44.1), + ]; + final bottomLeftStraightShape = EdgeShape() + ..set( + bottomLeftStraightControlPoints.first, + bottomLeftStraightControlPoints.last, + ); + final bottomLeftStraightFixtureDef = FixtureDef(bottomLeftStraightShape); + fixturesDef.add(bottomLeftStraightFixtureDef); + + final bottomStraightControlPoints = [ + bottomLeftStraightControlPoints.last, + Vector2(37.8, -44.1), + ]; + final bottomStraightShape = EdgeShape() + ..set( + bottomStraightControlPoints.first, + bottomStraightControlPoints.last, + ); + final bottomStraightFixtureDef = FixtureDef(bottomStraightShape); + fixturesDef.add(bottomStraightFixtureDef); + + return fixturesDef; + } + + @override + Body createBody() { + renderBody = false; + + final bodyDef = BodyDef() + ..userData = this + ..position = initialPosition + ..type = BodyType.static; + + final body = world.createBody(bodyDef); + _createFixtureDefs().forEach( + (fixture) => body.createFixture( + fixture + ..restitution = 0.1 + ..friction = 0, + ), + ); + + return body; + } + + @override + Future onLoad() async { + await super.onLoad(); + await _loadSprite(); + } + + Future _loadSprite() async { + final sprite = await gameRef.loadSprite( + Assets.images.dino.dinoLandBottom.keyName, + ); + final spriteComponent = SpriteComponent( + sprite: sprite, + size: Vector2(15.6, 54.8), + anchor: Anchor.center, + )..position = Vector2(31.7, 18); + + await add(spriteComponent); + } +} diff --git a/packages/pinball_components/lib/src/components/fire_effect.dart b/packages/pinball_components/lib/src/components/fire_effect.dart index 0a7cef2b..cf8c3707 100644 --- a/packages/pinball_components/lib/src/components/fire_effect.dart +++ b/packages/pinball_components/lib/src/components/fire_effect.dart @@ -1,5 +1,6 @@ import 'dart:math' as math; +import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame/particles.dart'; import 'package:flame_forge2d/flame_forge2d.dart' hide Particle; @@ -19,33 +20,24 @@ const _particleRadius = 0.25; /// A [BodyComponent] which creates a fire trail effect using the given /// parameters /// {@endtemplate} -class FireEffect extends BodyComponent { +class FireEffect extends ParticleSystemComponent { /// {@macro fire_effect} FireEffect({ required this.burstPower, - required this.position, required this.direction, - }); + Vector2? position, + int? priority, + }) : super( + position: position, + priority: priority, + ); /// A [double] value that will define how "strong" the burst of particles - /// will be + /// will be. final double burstPower; - /// The position of the burst - final Vector2 position; - - /// Which direction the burst will aim + /// Which direction the burst will aim. final Vector2 direction; - late Particle _particle; - - @override - Body createBody() { - final bodyDef = BodyDef()..position = position; - - final fixtureDef = FixtureDef(CircleShape()..radius = 0)..isSensor = true; - - return world.createBody(bodyDef)..createFixture(fixtureDef); - } @override Future onLoad() async { @@ -71,15 +63,15 @@ class FireEffect extends BodyComponent { ); }), ]; - final rng = math.Random(); + final random = math.Random(); final spreadTween = Tween(begin: -0.2, end: 0.2); - _particle = Particle.generate( - count: (rng.nextDouble() * (burstPower * 10)).toInt(), + particle = Particle.generate( + count: math.max((random.nextDouble() * (burstPower * 10)).toInt(), 1), generator: (_) { final spread = Vector2( - spreadTween.transform(rng.nextDouble()), - spreadTween.transform(rng.nextDouble()), + spreadTween.transform(random.nextDouble()), + spreadTween.transform(random.nextDouble()), ); final finalDirection = Vector2(direction.x, -direction.y) + spread; final speed = finalDirection * (burstPower * 20); @@ -88,26 +80,9 @@ class FireEffect extends BodyComponent { lifespan: 5 / burstPower, position: Vector2.zero(), speed: speed, - child: children[rng.nextInt(children.length)], + child: children[random.nextInt(children.length)], ); }, ); } - - @override - void update(double dt) { - super.update(dt); - _particle.update(dt); - - if (_particle.shouldRemove) { - removeFromParent(); - } - } - - @override - void render(Canvas canvas) { - super.render(canvas); - - _particle.render(canvas); - } } diff --git a/packages/pinball_components/lib/src/components/flipper.dart b/packages/pinball_components/lib/src/components/flipper.dart index de5f18c8..49bd6d6f 100644 --- a/packages/pinball_components/lib/src/components/flipper.dart +++ b/packages/pinball_components/lib/src/components/flipper.dart @@ -68,7 +68,7 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition { anchor: anchor, ); final joint = _FlipperJoint(jointDef); - world.createJoint2(joint); + world.createJoint(joint); unawaited(mounted.whenComplete(joint.unlock)); } @@ -219,15 +219,3 @@ class _FlipperJoint extends RevoluteJoint { setLimits(-angle, angle); } } - -// TODO(alestiago): Remove once Forge2D supports custom joints. -extension on World { - void createJoint2(Joint joint) { - assert(!isLocked, ''); - - joints.add(joint); - - joint.bodyA.joints.add(joint); - joint.bodyB.joints.add(joint); - } -} diff --git a/packages/pinball_components/lib/src/components/ramp_opening.dart b/packages/pinball_components/lib/src/components/ramp_opening.dart index 8f33e813..cb6066f2 100644 --- a/packages/pinball_components/lib/src/components/ramp_opening.dart +++ b/packages/pinball_components/lib/src/components/ramp_opening.dart @@ -20,28 +20,41 @@ enum RampOrientation { /// [RampOpeningBallContactCallback] detects when a [Ball] passes /// through this opening. /// -/// By default the base [layer] is set to [Layer.board]. +/// 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 pathwayLayer, + required Layer insideLayer, Layer? outsideLayer, + int? insidePriority, + int? outsidePriority, required this.orientation, - }) : _pathwayLayer = pathwayLayer, - _outsideLayer = outsideLayer ?? Layer.board { - layer = Layer.board; + }) : _insideLayer = insideLayer, + _outsideLayer = outsideLayer ?? Layer.board, + _insidePriority = insidePriority ?? 0, + _outsidePriority = outsidePriority ?? 0 { + layer = Layer.opening; } - final Layer _pathwayLayer; + final Layer _insideLayer; final Layer _outsideLayer; + final int _insidePriority; + final int _outsidePriority; - /// Mask of category bits for collision inside pathway. - Layer get pathwayLayer => _pathwayLayer; + /// Mask of category bits for collision inside ramp. + Layer get insideLayer => _insideLayer; - /// Mask of category bits for collision outside pathway. + /// 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; @@ -64,8 +77,7 @@ abstract class RampOpening extends BodyComponent with InitialPosition, Layered { } /// {@template ramp_opening_ball_contact_callback} -/// Detects when a [Ball] enters or exits a pathway ramp through a -/// [RampOpening]. +/// 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. @@ -80,9 +92,11 @@ class RampOpeningBallContactCallback Layer layer; if (!_ballsInside.contains(ball)) { - layer = opening.pathwayLayer; + layer = opening.insideLayer; _ballsInside.add(ball); - ball.layer = layer; + ball + ..sendTo(opening.insidePriority) + ..layer = layer; } else { _ballsInside.remove(ball); } @@ -103,7 +117,9 @@ class RampOpeningBallContactCallback ball.body.linearVelocity.y > 0); if (isBallOutsideOpening) { - ball.layer = opening.outsideLayer; + 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 7e9d097e..4d84eb68 100644 --- a/packages/pinball_components/lib/src/components/spaceship.dart +++ b/packages/pinball_components/lib/src/components/spaceship.dart @@ -21,6 +21,9 @@ class Spaceship extends Forge2DBlueprint { /// The [position] where the elements will be created final Vector2 position; + /// Base priority for wall while be on spaceship. + static const ballPriorityWhenOnSpaceship = 4; + @override void build(_) { addAllContactCallback([ @@ -33,8 +36,8 @@ class Spaceship extends Forge2DBlueprint { SpaceshipEntrance()..initialPosition = position, AndroidHead()..initialPosition = position, SpaceshipHole( - onExitLayer: Layer.spaceshipExitRail, - onExitElevation: 2, + outsideLayer: Layer.spaceshipExitRail, + outsidePriority: 2, )..initialPosition = position - Vector2(5.2, 4.8), SpaceshipHole()..initialPosition = position - Vector2(-7.2, 0.8), SpaceshipWall()..initialPosition = position, @@ -47,8 +50,8 @@ class Spaceship extends Forge2DBlueprint { /// {@endtemplate} class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered { /// {@macro spaceship_saucer} - // TODO(ruimiguel): apply Elevated when PR merged. - SpaceshipSaucer() : super(priority: 3) { + SpaceshipSaucer() + : super(priority: Spaceship.ballPriorityWhenOnSpaceship - 1) { layer = Layer.spaceship; } @@ -92,7 +95,7 @@ class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered { /// {@endtemplate} class AndroidHead extends BodyComponent with InitialPosition, Layered { /// {@macro spaceship_bridge} - AndroidHead() : super(priority: 4) { + AndroidHead() : super(priority: Spaceship.ballPriorityWhenOnSpaceship + 1) { layer = Layer.spaceship; } @@ -147,16 +150,13 @@ class SpaceshipEntrance extends RampOpening { /// {@macro spaceship_entrance} SpaceshipEntrance() : super( - pathwayLayer: Layer.spaceship, + insideLayer: Layer.spaceship, orientation: RampOrientation.up, + insidePriority: Spaceship.ballPriorityWhenOnSpaceship, ) { layer = Layer.spaceship; } - /// Priority order for [SpaceshipHole] on enter. - // TODO(ruimiguel): apply Elevated when PR merged. - final int onEnterElevation = 4; - @override Shape get shape { renderBody = false; @@ -181,19 +181,17 @@ class SpaceshipEntrance extends RampOpening { /// {@endtemplate} class SpaceshipHole extends RampOpening { /// {@macro spaceship_hole} - SpaceshipHole({Layer? onExitLayer, this.onExitElevation = 1}) + SpaceshipHole({Layer? outsideLayer, int? outsidePriority = 1}) : super( - pathwayLayer: Layer.spaceship, - outsideLayer: onExitLayer, + insideLayer: Layer.spaceship, + outsideLayer: outsideLayer, + outsidePriority: outsidePriority, orientation: RampOrientation.up, ) { + renderBody = false; layer = Layer.spaceship; } - /// Priority order for [SpaceshipHole] on exit. - // TODO(ruimiguel): apply Elevated when PR merged. - final int onExitElevation; - @override Shape get shape { return ArcShape( @@ -235,8 +233,7 @@ class _SpaceshipWallShape extends ChainShape { /// {@endtemplate} class SpaceshipWall extends BodyComponent with InitialPosition, Layered { /// {@macro spaceship_wall} - // TODO(ruimiguel): apply Elevated when PR merged - SpaceshipWall() : super(priority: 4) { + SpaceshipWall() : super(priority: Spaceship.ballPriorityWhenOnSpaceship + 1) { layer = Layer.spaceship; } @@ -269,9 +266,7 @@ class SpaceshipEntranceBallContactCallback @override void begin(SpaceshipEntrance entrance, Ball ball, _) { ball - // TODO(ruimiguel): apply Elevated when PR merged. - ..priority = entrance.onEnterElevation - ..gameRef.reorderChildren() + ..sendTo(entrance.insidePriority) ..layer = Layer.spaceship; } } @@ -279,16 +274,14 @@ class SpaceshipEntranceBallContactCallback /// [ContactCallback] that handles the contact between the [Ball] /// and a [SpaceshipHole]. /// -/// It sets the [Ball] priority and filter data so it will "be back" on the -/// board. +/// 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 - // TODO(ruimiguel): apply Elevated when PR merged. - ..priority = hole.onExitElevation - ..gameRef.reorderChildren() + ..sendTo(hole.outsidePriority) ..layer = hole.outsideLayer; } } diff --git a/packages/pinball_components/lib/src/flame/flame.dart b/packages/pinball_components/lib/src/flame/flame.dart index c46a6fd2..9af8dba6 100644 --- a/packages/pinball_components/lib/src/flame/flame.dart +++ b/packages/pinball_components/lib/src/flame/flame.dart @@ -1 +1,2 @@ export 'blueprint.dart'; +export 'priority.dart'; diff --git a/packages/pinball_components/lib/src/flame/priority.dart b/packages/pinball_components/lib/src/flame/priority.dart new file mode 100644 index 00000000..f4dccabf --- /dev/null +++ b/packages/pinball_components/lib/src/flame/priority.dart @@ -0,0 +1,39 @@ +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_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index aa1eb224..8fc9c6f8 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -26,7 +26,12 @@ flutter: generate: true assets: - assets/images/ + - assets/images/baseboard/ + - assets/images/dino/ - assets/images/flipper/ + - assets/images/dash_bumper/a/ + - assets/images/dash_bumper/b/ + - assets/images/dash_bumper/main/ flutter_gen: line_length: 80 diff --git a/packages/pinball_components/sandbox/lib/main.dart b/packages/pinball_components/sandbox/lib/main.dart index 2df3c16c..62ff7022 100644 --- a/packages/pinball_components/sandbox/lib/main.dart +++ b/packages/pinball_components/sandbox/lib/main.dart @@ -6,7 +6,6 @@ // https://opensource.org/licenses/MIT. import 'package:dashbook/dashbook.dart'; import 'package:flutter/material.dart'; -import 'package:sandbox/stories/effects/effects.dart'; import 'package:sandbox/stories/spaceship/spaceship.dart'; import 'package:sandbox/stories/stories.dart'; @@ -18,5 +17,6 @@ void main() { addEffectsStories(dashbook); addFlipperStories(dashbook); addSpaceshipStories(dashbook); + addBaseboardStories(dashbook); runApp(dashbook); } diff --git a/packages/pinball_components/sandbox/lib/stories/baseboard/baseboard.dart b/packages/pinball_components/sandbox/lib/stories/baseboard/baseboard.dart new file mode 100644 index 00000000..96d89928 --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/baseboard/baseboard.dart @@ -0,0 +1,15 @@ +import 'package:dashbook/dashbook.dart'; +import 'package:flame/game.dart'; +import 'package:sandbox/common/common.dart'; +import 'package:sandbox/stories/baseboard/basic.dart'; + +void addBaseboardStories(Dashbook dashbook) { + dashbook.storiesOf('Baseboard').add( + 'Basic', + (context) => GameWidget( + game: BasicBaseboardGame(), + ), + codeLink: buildSourceLink('baseboard/basic.dart'), + info: BasicBaseboardGame.info, + ); +} diff --git a/packages/pinball_components/sandbox/lib/stories/baseboard/basic.dart b/packages/pinball_components/sandbox/lib/stories/baseboard/basic.dart new file mode 100644 index 00000000..127c1dec --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/baseboard/basic.dart @@ -0,0 +1,26 @@ +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:sandbox/common/common.dart'; + +class BasicBaseboardGame extends BasicGame { + static const info = ''' + Basic example of how a Baseboard works. +'''; + + @override + Future onLoad() async { + await super.onLoad(); + + final center = screenToWorld(camera.viewport.canvasSize! / 2); + + final leftBaseboard = Baseboard(side: BoardSide.left) + ..initialPosition = center - Vector2(25, 0); + final rightBaseboard = Baseboard(side: BoardSide.right) + ..initialPosition = center + Vector2(25, 0); + + await addAll([ + leftBaseboard, + rightBaseboard, + ]); + } +} diff --git a/packages/pinball_components/sandbox/lib/stories/effects/fire_effect.dart b/packages/pinball_components/sandbox/lib/stories/effects/fire_effect.dart index 9f066952..1262af11 100644 --- a/packages/pinball_components/sandbox/lib/stories/effects/fire_effect.dart +++ b/packages/pinball_components/sandbox/lib/stories/effects/fire_effect.dart @@ -34,7 +34,6 @@ class _EffectEmitter extends Component { add( FireEffect( burstPower: (_timer / _timerLimit) * _force, - position: Vector2.zero(), direction: _direction, ), ); diff --git a/packages/pinball_components/sandbox/lib/stories/stories.dart b/packages/pinball_components/sandbox/lib/stories/stories.dart index 9f861bde..90b93723 100644 --- a/packages/pinball_components/sandbox/lib/stories/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/stories.dart @@ -1,3 +1,5 @@ export 'ball/ball.dart'; +export 'baseboard/baseboard.dart'; +export 'effects/effects.dart'; export 'flipper/flipper.dart'; export 'layer/layer.dart'; diff --git a/packages/pinball_components/test/helpers/mocks.dart b/packages/pinball_components/test/helpers/mocks.dart index 8d6f45b3..7771d1e1 100644 --- a/packages/pinball_components/test/helpers/mocks.dart +++ b/packages/pinball_components/test/helpers/mocks.dart @@ -1,11 +1,8 @@ -import 'dart:ui'; - +import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball_components/pinball_components.dart'; -class MockCanvas extends Mock implements Canvas {} - class MockFilter extends Mock implements Filter {} class MockFixture extends Mock implements Fixture {} @@ -24,3 +21,5 @@ class MockContact extends Mock implements Contact {} class MockContactCallback extends Mock implements ContactCallback {} + +class MockComponent extends Mock implements Component {} diff --git a/packages/pinball_components/test/src/components/ball_test.dart b/packages/pinball_components/test/src/components/ball_test.dart index a9eb05ad..f2a54c68 100644 --- a/packages/pinball_components/test/src/components/ball_test.dart +++ b/packages/pinball_components/test/src/components/ball_test.dart @@ -86,7 +86,7 @@ void main() { final fixture = ball.body.fixtures[0]; expect(fixture.shape.shapeType, equals(ShapeType.circle)); - expect(fixture.shape.radius, equals(1.5)); + expect(fixture.shape.radius, equals(2.25)); }, ); diff --git a/test/game/components/baseboard_test.dart b/packages/pinball_components/test/src/components/baseboard_test.dart similarity index 89% rename from test/game/components/baseboard_test.dart rename to packages/pinball_components/test/src/components/baseboard_test.dart index 37c3c978..b1ce58e2 100644 --- a/test/game/components/baseboard_test.dart +++ b/packages/pinball_components/test/src/components/baseboard_test.dart @@ -3,13 +3,16 @@ 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 'package:pinball_components/pinball_components.dart'; +import '../../helpers/helpers.dart'; + void main() { group('Baseboard', () { + // TODO(allisonryan0002): Add golden tests. + TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(Forge2DGame.new); + final flameTester = FlameTester(TestGame.new); flameTester.test( 'loads correctly', @@ -62,14 +65,14 @@ void main() { group('fixtures', () { flameTester.test( - 'has six', + 'has seven', (game) async { final baseboard = Baseboard( side: BoardSide.left, ); await game.ensureAdd(baseboard); - expect(baseboard.body.fixtures.length, equals(6)); + expect(baseboard.body.fixtures.length, equals(7)); }, ); }); diff --git a/packages/pinball_components/test/src/components/board_dimensions_test.dart b/packages/pinball_components/test/src/components/board_dimensions_test.dart new file mode 100644 index 00000000..afd4a2d8 --- /dev/null +++ b/packages/pinball_components/test/src/components/board_dimensions_test.dart @@ -0,0 +1,27 @@ +import 'package:flame/extensions.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +void main() { + group('BoardDimensions', () { + test('has size', () { + expect(BoardDimensions.size, equals(Vector2(101.6, 143.8))); + }); + + test('has bounds', () { + expect(BoardDimensions.bounds, isNotNull); + }); + + test('has perspectiveAngle', () { + expect(BoardDimensions.perspectiveAngle, isNotNull); + }); + + test('has perspectiveShrinkFactor', () { + expect(BoardDimensions.perspectiveShrinkFactor, equals(0.63)); + }); + + test('has shrinkAdjustedHeight', () { + expect(BoardDimensions.shrinkAdjustedHeight, isNotNull); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/dash_nest_bumper_test.dart b/packages/pinball_components/test/src/components/dash_nest_bumper_test.dart new file mode 100644 index 00000000..2c6bb00c --- /dev/null +++ b/packages/pinball_components/test/src/components/dash_nest_bumper_test.dart @@ -0,0 +1,116 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(TestGame.new); + + group('BigDashNestBumper', () { + flameTester.test('loads correctly', (game) async { + final bumper = BigDashNestBumper(); + await game.ensureAdd(bumper); + expect(game.contains(bumper), isTrue); + }); + + flameTester.test('activate returns normally', (game) async { + final bumper = BigDashNestBumper(); + await game.ensureAdd(bumper); + + expect(bumper.activate, returnsNormally); + }); + + flameTester.test('deactivate returns normally', (game) async { + final bumper = BigDashNestBumper(); + await game.ensureAdd(bumper); + + expect(bumper.deactivate, returnsNormally); + }); + + flameTester.test('changes sprite', (game) async { + final bumper = BigDashNestBumper(); + await game.ensureAdd(bumper); + + final spriteComponent = bumper.firstChild()!; + + final deactivatedSprite = spriteComponent.sprite; + bumper.activate(); + expect( + spriteComponent.sprite, + isNot(equals(deactivatedSprite)), + ); + + final activatedSprite = spriteComponent.sprite; + bumper.deactivate(); + expect( + spriteComponent.sprite, + isNot(equals(activatedSprite)), + ); + + expect( + activatedSprite, + isNot(equals(deactivatedSprite)), + ); + }); + }); + + group('SmallDashNestBumper', () { + flameTester.test('"a" loads correctly', (game) async { + final bumper = SmallDashNestBumper.a(); + await game.ensureAdd(bumper); + + expect(game.contains(bumper), isTrue); + }); + + flameTester.test('"b" loads correctly', (game) async { + final bumper = SmallDashNestBumper.b(); + await game.ensureAdd(bumper); + expect(game.contains(bumper), isTrue); + }); + + flameTester.test('activate returns normally', (game) async { + final bumper = SmallDashNestBumper.a(); + await game.ensureAdd(bumper); + + expect(bumper.activate, returnsNormally); + }); + + flameTester.test('deactivate returns normally', (game) async { + final bumper = SmallDashNestBumper.a(); + await game.ensureAdd(bumper); + + expect(bumper.deactivate, returnsNormally); + }); + + flameTester.test('changes sprite', (game) async { + final bumper = SmallDashNestBumper.a(); + await game.ensureAdd(bumper); + + final spriteComponent = bumper.firstChild()!; + + final deactivatedSprite = spriteComponent.sprite; + bumper.activate(); + expect( + spriteComponent.sprite, + isNot(equals(deactivatedSprite)), + ); + + final activatedSprite = spriteComponent.sprite; + bumper.deactivate(); + expect( + spriteComponent.sprite, + isNot(equals(activatedSprite)), + ); + + expect( + activatedSprite, + isNot(equals(deactivatedSprite)), + ); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/dino_walls_test.dart b/packages/pinball_components/test/src/components/dino_walls_test.dart new file mode 100644 index 00000000..af80444b --- /dev/null +++ b/packages/pinball_components/test/src/components/dino_walls_test.dart @@ -0,0 +1,28 @@ +// 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_components/pinball_components.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + group('DinoWalls', () { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(TestGame.new); + + flameTester.test( + 'loads correctly', + (game) async { + final dinoWalls = DinoWalls(position: Vector2.zero()); + await game.addFromBlueprint(dinoWalls); + await game.ready(); + + for (final wall in dinoWalls.components) { + expect(game.contains(wall), isTrue); + } + }, + ); + }); +} diff --git a/packages/pinball_components/test/src/components/fire_effect_test.dart b/packages/pinball_components/test/src/components/fire_effect_test.dart index bc6baa4b..2c404747 100644 --- a/packages/pinball_components/test/src/components/fire_effect_test.dart +++ b/packages/pinball_components/test/src/components/fire_effect_test.dart @@ -1,11 +1,8 @@ // ignore_for_file: cascade_invocations -import 'dart:ui'; - import 'package:flame/components.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'; @@ -14,42 +11,16 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); final flameTester = FlameTester(TestGame.new); - setUpAll(() { - registerFallbackValue(Offset.zero); - registerFallbackValue(Paint()); - }); - - group('FireEffect', () { - flameTester.test('is removed once its particles are done', (game) async { - await game.ensureAdd( - FireEffect( - burstPower: 1, - position: Vector2.zero(), - direction: Vector2.all(2), - ), - ); - await game.ready(); - expect(game.children.whereType().length, equals(1)); - game.update(5); - - await game.ready(); - expect(game.children.whereType().length, equals(0)); - }); - - flameTester.test('render circles on the canvas', (game) async { - final effect = FireEffect( + flameTester.test( + 'loads correctly', + (game) async { + final fireEffect = FireEffect( burstPower: 1, - position: Vector2.zero(), - direction: Vector2.all(2), + direction: Vector2.zero(), ); - await game.ensureAdd(effect); - await game.ready(); - - final canvas = MockCanvas(); - effect.render(canvas); + await game.ensureAdd(fireEffect); - verify(() => canvas.drawCircle(any(), any(), any())) - .called(greaterThan(0)); - }); - }); + expect(game.contains(fireEffect), isTrue); + }, + ); } diff --git a/packages/pinball_components/test/src/components/golden/dino-bottom-wall.png b/packages/pinball_components/test/src/components/golden/dino-bottom-wall.png new file mode 100644 index 00000000..14ae9c0d Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/dino-bottom-wall.png differ diff --git a/packages/pinball_components/test/src/components/golden/dino-top-wall.png b/packages/pinball_components/test/src/components/golden/dino-top-wall.png new file mode 100644 index 00000000..0d434d69 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/dino-top-wall.png differ diff --git a/packages/pinball_components/test/src/components/ramp_opening_test.dart b/packages/pinball_components/test/src/components/ramp_opening_test.dart index c49e9164..cb42203a 100644 --- a/packages/pinball_components/test/src/components/ramp_opening_test.dart +++ b/packages/pinball_components/test/src/components/ramp_opening_test.dart @@ -12,7 +12,7 @@ class TestRampOpening extends RampOpening { required RampOrientation orientation, required Layer pathwayLayer, }) : super( - pathwayLayer: pathwayLayer, + insideLayer: pathwayLayer, orientation: orientation, ); @@ -129,14 +129,12 @@ void main() { 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); - await game.ready(); - await game.ensureAdd(area); - callback.begin(ball, area, MockContact()); - verify(() => ball.layer = area.pathwayLayer).called(1); + verify(() => ball.layer = area.insideLayer).called(1); }); flameTester.test( @@ -152,14 +150,12 @@ void main() { 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); - await game.ready(); - await game.ensureAdd(area); - callback.begin(ball, area, MockContact()); - verify(() => ball.layer = area.pathwayLayer).called(1); + verify(() => ball.layer = area.insideLayer).called(1); }); flameTester.test( @@ -174,15 +170,13 @@ void main() { 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); - await game.ready(); - await game.ensureAdd(area); - callback.begin(ball, area, MockContact()); - verify(() => ball.layer = area.pathwayLayer).called(1); + verify(() => ball.layer = area.insideLayer).called(1); callback.end(ball, area, MockContact()); verify(() => ball.layer = Layer.board); @@ -200,15 +194,13 @@ void main() { 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); - await game.ready(); - await game.ensureAdd(area); - callback.begin(ball, area, MockContact()); - verify(() => ball.layer = area.pathwayLayer).called(1); + verify(() => ball.layer = area.insideLayer).called(1); callback.end(ball, area, MockContact()); verify(() => ball.layer = Layer.board); @@ -226,21 +218,19 @@ void main() { 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); - await game.ready(); - await game.ensureAdd(area); - callback.begin(ball, area, MockContact()); - verify(() => ball.layer = area.pathwayLayer).called(1); + 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.pathwayLayer); + 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_test.dart b/packages/pinball_components/test/src/components/spaceship_test.dart index f89408f7..4d980c69 100644 --- a/packages/pinball_components/test/src/components/spaceship_test.dart +++ b/packages/pinball_components/test/src/components/spaceship_test.dart @@ -59,7 +59,8 @@ void main() { group('SpaceshipEntranceBallContactCallback', () { test('changes the ball priority on contact', () { - when(() => entrance.onEnterElevation).thenReturn(3); + when(() => ball.priority).thenReturn(2); + when(() => entrance.insidePriority).thenReturn(3); SpaceshipEntranceBallContactCallback().begin( entrance, @@ -67,39 +68,15 @@ void main() { MockContact(), ); - verify(() => ball.priority = entrance.onEnterElevation).called(1); - }); - - test('re order the game children', () { - when(() => entrance.onEnterElevation).thenReturn(3); - - SpaceshipEntranceBallContactCallback().begin( - entrance, - ball, - MockContact(), - ); - - verify(game.reorderChildren).called(1); + 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.onExitElevation).thenReturn(1); - - SpaceshipHoleBallContactCallback().begin( - hole, - ball, - MockContact(), - ); - - verify(() => ball.priority = hole.onExitElevation).called(1); - }); - - test('re order the game children', () { - when(() => hole.outsideLayer).thenReturn(Layer.board); - when(() => hole.onExitElevation).thenReturn(1); + when(() => hole.outsidePriority).thenReturn(1); SpaceshipHoleBallContactCallback().begin( hole, @@ -107,7 +84,7 @@ void main() { MockContact(), ); - verify(game.reorderChildren).called(1); + verify(() => ball.sendTo(hole.outsidePriority)).called(1); }); }); }); diff --git a/packages/pinball_components/test/src/flame/priority_test.dart b/packages/pinball_components/test/src/flame/priority_test.dart new file mode 100644 index 00000000..231c7744 --- /dev/null +++ b/packages/pinball_components/test/src/flame/priority_test.dart @@ -0,0 +1,221 @@ +// 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/src/flame/priority.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/game/components/flutter_forest_test.dart b/test/game/components/flutter_forest_test.dart index 48586895..a0e1b81f 100644 --- a/test/game/components/flutter_forest_test.dart +++ b/test/game/components/flutter_forest_test.dart @@ -1,7 +1,9 @@ // ignore_for_file: cascade_invocations import 'package:bloc_test/bloc_test.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/game.dart'; @@ -9,6 +11,18 @@ import 'package:pinball_components/pinball_components.dart'; import '../../helpers/helpers.dart'; +void beginContact(Forge2DGame game, BodyComponent bodyA, BodyComponent bodyB) { + assert( + bodyA.body.fixtures.isNotEmpty && bodyB.body.fixtures.isNotEmpty, + 'Bodies require fixtures to contact each other.', + ); + + final fixtureA = bodyA.body.fixtures.first; + final fixtureB = bodyB.body.fixtures.first; + final contact = Contact.init(fixtureA, 0, fixtureB, 0); + game.world.contactManager.contactListener?.beginContact(contact); +} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); final flameTester = FlameTester(PinballGameTest.create); @@ -30,13 +44,73 @@ void main() { 'a FlutterSignPost', (game) async { await game.ready(); + final flutterForest = FlutterForest(); + await game.ensureAdd(flutterForest); expect( - game.descendants().whereType().length, + flutterForest.descendants().whereType().length, equals(1), ); }, ); + + flameTester.test( + 'a BigDashNestBumper', + (game) async { + await game.ready(); + final flutterForest = FlutterForest(); + await game.ensureAdd(flutterForest); + + expect( + flutterForest.descendants().whereType().length, + equals(1), + ); + }, + ); + + flameTester.test( + 'two SmallDashNestBumper', + (game) async { + await game.ready(); + final flutterForest = FlutterForest(); + await game.ensureAdd(flutterForest); + + expect( + flutterForest.descendants().whereType().length, + equals(2), + ); + }, + ); + }); + + group('controller', () { + group('listenWhen', () { + final gameBloc = MockGameBloc(); + final tester = flameBlocTester( + game: TestGame.new, + gameBloc: () => gameBloc, + ); + + tester.testGameWidget( + 'listens when a Bonus.dashNest is added', + verify: (game, tester) async { + final flutterForest = FlutterForest(); + + const state = GameState( + score: 0, + balls: 3, + activatedBonusLetters: [], + activatedDashNests: {}, + bonusHistory: [GameBonus.dashNest], + ); + expect( + flutterForest.controller + .listenWhen(const GameState.initial(), state), + isTrue, + ); + }, + ); + }); }); flameTester.test( @@ -47,7 +121,7 @@ void main() { await game.ensureAdd(flutterForest); final previousBalls = game.descendants().whereType().length; - flutterForest.onNewState(MockGameState()); + flutterForest.controller.onNewState(MockGameState()); await game.ready(); expect( @@ -57,14 +131,13 @@ void main() { }, ); - group('listenWhen', () { - final gameBloc = MockGameBloc(); - final tester = flameBlocTester( - game: TestGame.new, - gameBloc: () => gameBloc, - ); + group('bumpers', () { + late Ball ball; + late GameBloc gameBloc; setUp(() { + ball = Ball(baseColor: const Color(0xFF00FFFF)); + gameBloc = MockGameBloc(); whenListen( gameBloc, const Stream.empty(), @@ -72,73 +145,167 @@ void main() { ); }); + final tester = flameBlocTester( + game: PinballGameTest.create, + gameBloc: () => gameBloc, + ); + + tester.testGameWidget( + 'add DashNestActivated event', + setUp: (game, tester) async { + await game.ready(); + final flutterForest = + game.descendants().whereType().first; + await game.ensureAdd(ball); + + final bumpers = + flutterForest.descendants().whereType(); + + for (final bumper in bumpers) { + beginContact(game, bumper, ball); + final controller = bumper.firstChild()!; + verify( + () => gameBloc.add(DashNestActivated(controller.id)), + ).called(1); + } + }, + ); + tester.testGameWidget( - 'listens when a Bonus.dashNest is added', - verify: (game, tester) async { + 'add Scored event', + setUp: (game, tester) async { final flutterForest = FlutterForest(); + await game.ensureAdd(flutterForest); + await game.ensureAdd(ball); - const state = GameState( - score: 0, - balls: 3, - activatedBonusLetters: [], - activatedDashNests: {}, - bonusHistory: [GameBonus.dashNest], - ); - expect( - flutterForest.listenWhen(const GameState.initial(), state), - isTrue, - ); + final bumpers = + flutterForest.descendants().whereType(); + + for (final bumper in bumpers) { + beginContact(game, bumper, ball); + final points = (bumper as ScorePoints).points; + verify( + () => gameBloc.add(Scored(points: points)), + ).called(1); + } }, ); }); }); - group('DashNestBumperBallContactCallback', () { - final gameBloc = MockGameBloc(); - final tester = flameBlocTester( - // TODO(alestiago): Use TestGame.new once a controller is implemented. - game: PinballGameTest.create, - gameBloc: () => gameBloc, - ); + group('DashNestBumperController', () { + late DashNestBumper dashNestBumper; setUp(() { - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial(), - ); + dashNestBumper = MockDashNestBumper(); }); - final dashNestBumper = MockDashNestBumper(); - tester.testGameWidget( - 'adds a DashNestActivated event with DashNestBumper.id', - setUp: (game, tester) async { - const id = '0'; - when(() => dashNestBumper.id).thenReturn(id); - when(() => dashNestBumper.gameRef).thenReturn(game); - }, - verify: (game, tester) async { - final contactCallback = DashNestBumperBallContactCallback(); - contactCallback.begin(dashNestBumper, MockBall(), MockContact()); + group( + 'listensWhen', + () { + late GameState previousState; + late GameState newState; - verify( - () => gameBloc.add(DashNestActivated(dashNestBumper.id)), - ).called(1); + setUp( + () { + previousState = MockGameState(); + newState = MockGameState(); + }, + ); + + test('listens when the id is added to activatedDashNests', () { + const id = ''; + final controller = DashNestBumperController( + dashNestBumper, + id: id, + ); + + when(() => previousState.activatedDashNests).thenReturn({}); + when(() => newState.activatedDashNests).thenReturn({id}); + + expect(controller.listenWhen(previousState, newState), isTrue); + }); + + test('listens when the id is removed from activatedDashNests', () { + const id = ''; + final controller = DashNestBumperController( + dashNestBumper, + id: id, + ); + + when(() => previousState.activatedDashNests).thenReturn({id}); + when(() => newState.activatedDashNests).thenReturn({}); + + expect(controller.listenWhen(previousState, newState), isTrue); + }); + + test("doesn't listen when the id is never in activatedDashNests", () { + final controller = DashNestBumperController( + dashNestBumper, + id: '', + ); + + when(() => previousState.activatedDashNests).thenReturn({}); + when(() => newState.activatedDashNests).thenReturn({}); + + expect(controller.listenWhen(previousState, newState), isFalse); + }); + + test("doesn't listen when the id still in activatedDashNests", () { + const id = ''; + final controller = DashNestBumperController( + dashNestBumper, + id: id, + ); + + when(() => previousState.activatedDashNests).thenReturn({id}); + when(() => newState.activatedDashNests).thenReturn({id}); + + expect(controller.listenWhen(previousState, newState), isFalse); + }); }, ); - }); - group('BigDashNestBumper', () { - test('has points', () { - final dashNestBumper = BigDashNestBumper(id: ''); - expect(dashNestBumper.points, greaterThan(0)); - }); - }); + group( + 'onNewState', + () { + late GameState state; - group('SmallDashNestBumper', () { - test('has points', () { - final dashNestBumper = SmallDashNestBumper(id: ''); - expect(dashNestBumper.points, greaterThan(0)); - }); + setUp(() { + state = MockGameState(); + }); + + test( + 'activates the bumper when id in activatedDashNests', + () { + const id = ''; + final controller = DashNestBumperController( + dashNestBumper, + id: id, + ); + + when(() => state.activatedDashNests).thenReturn({id}); + controller.onNewState(state); + + verify(() => dashNestBumper.activate()).called(1); + }, + ); + + test( + 'deactivates the bumper when id not in activatedDashNests', + () { + final controller = DashNestBumperController( + dashNestBumper, + id: '', + ); + + when(() => state.activatedDashNests).thenReturn({}); + controller.onNewState(state); + + verify(() => dashNestBumper.deactivate()).called(1); + }, + ); + }, + ); }); } diff --git a/test/helpers/mocks.dart b/test/helpers/mocks.dart index 9453c93a..fbe8edfb 100644 --- a/test/helpers/mocks.dart +++ b/test/helpers/mocks.dart @@ -66,10 +66,6 @@ class MockFilter extends Mock implements Filter {} class MockFixture extends Mock implements Fixture {} -class MockSpaceshipEntrance extends Mock implements SpaceshipEntrance {} - -class MockSpaceshipHole extends Mock implements SpaceshipHole {} - class MockSpaceshipExitRailEnd extends Mock implements SpaceshipExitRailEnd {} class MockComponentSet extends Mock implements ComponentSet {}