diff --git a/.github/workflows/pinball_components.yaml b/.github/workflows/pinball_components.yaml index cab60a54..bf1907f8 100644 --- a/.github/workflows/pinball_components.yaml +++ b/.github/workflows/pinball_components.yaml @@ -16,4 +16,4 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: working_directory: packages/pinball_components - coverage_excludes: "lib/src/generated/*.dart" + coverage_excludes: "lib/gen/*.dart" diff --git a/lib/flame/blueprint.dart b/lib/flame/blueprint.dart index 9f2a68f6..d536d650 100644 --- a/lib/flame/blueprint.dart +++ b/lib/flame/blueprint.dart @@ -12,19 +12,19 @@ const _attachedErrorMessage = "Can't add to attached Blueprints"; /// A [Blueprint] is a virtual way of grouping [Component]s /// that are related, but they need to be added directly on /// the [FlameGame] level. -abstract class Blueprint { +abstract class Blueprint { final List _components = []; bool _isAttached = false; /// Called before the the [Component]s managed /// by this blueprint is added to the [FlameGame] - void build(); + void build(T gameRef); /// Attach the [Component]s built on [build] to the [game] /// instance @mustCallSuper - Future attach(FlameGame game) async { - build(); + Future attach(T game) async { + build(game); await game.addAll(_components); _isAttached = true; } @@ -47,7 +47,7 @@ abstract class Blueprint { /// A [Blueprint] that provides additional /// structures specific to flame_forge2d -abstract class Forge2DBlueprint extends Blueprint { +abstract class Forge2DBlueprint extends Blueprint { final List _callbacks = []; /// Adds a single [ContactCallback] to this blueprint @@ -63,13 +63,11 @@ abstract class Forge2DBlueprint extends Blueprint { } @override - Future attach(FlameGame game) async { + Future attach(Forge2DGame game) async { await super.attach(game); - assert(game is Forge2DGame, 'Forge2DBlueprint used outside a Forge2DGame'); - for (final callback in _callbacks) { - (game as Forge2DGame).addContactCallback(callback); + game.addContactCallback(callback); } } diff --git a/lib/game/components/ball.dart b/lib/game/components/ball.dart index def21929..756a8a97 100644 --- a/lib/game/components/ball.dart +++ b/lib/game/components/ball.dart @@ -1,61 +1,40 @@ import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball/flame/blueprint.dart'; import 'package:pinball/game/game.dart'; -import 'package:pinball/gen/assets.gen.dart'; +import 'package:pinball_components/pinball_components.dart'; -/// {@template ball} -/// A solid, [BodyType.dynamic] sphere that rolls and bounces along the -/// [PinballGame]. +/// {@template ball_blueprint} +/// [Blueprint] which cretes a ball game object /// {@endtemplate} -class Ball extends BodyComponent with InitialPosition, Layered { - /// {@macro ball} - Ball() { - // TODO(ruimiguel): while developing Ball can be launched by clicking mouse, - // and default layer is Layer.all. But on final game Ball will be always be - // be launched from Plunger and LauncherRamp will modify it to Layer.board. - // We need to see what happens if Ball appears from other place like nest - // bumper, it will need to explicit change layer to Layer.board then. - layer = Layer.board; - } - - /// The size of the [Ball] - final Vector2 size = Vector2.all(2); +class BallBlueprint extends Blueprint { + /// {@macro ball_blueprint} + BallBlueprint({required this.position}); - @override - Future onLoad() async { - await super.onLoad(); - final sprite = await gameRef.loadSprite(Assets.images.components.ball.path); - final tint = gameRef.theme.characterTheme.ballColor.withOpacity(0.5); - await add( - SpriteComponent( - sprite: sprite, - size: size, - anchor: Anchor.center, - )..tint(tint), - ); - } + /// The initial position of the [Ball] + final Vector2 position; @override - Body createBody() { - final shape = CircleShape()..radius = size.x / 2; - - final fixtureDef = FixtureDef(shape)..density = 1; + void build(PinballGame gameRef) { + final baseColor = gameRef.theme.characterTheme.ballColor; + final ball = Ball(baseColor: baseColor)..add(BallController()); - final bodyDef = BodyDef() - ..position = initialPosition - ..userData = this - ..type = BodyType.dynamic; - - return world.createBody(bodyDef)..createFixture(fixtureDef); + add(ball..initialPosition = position + Vector2(0, ball.size.y / 2)); } +} +/// {@template ball} +/// A solid, [BodyType.dynamic] sphere that rolls and bounces along the +/// [PinballGame]. +/// {@endtemplate} +class BallController extends Component with HasGameRef { /// Removes the [Ball] from a [PinballGame]; spawning a new [Ball] if /// any are left. /// /// Triggered by [BottomWallBallContactCallback] when the [Ball] falls into /// a [BottomWall]. void lost() { - shouldRemove = true; + parent?.shouldRemove = true; final bloc = gameRef.read()..add(const BallLost()); @@ -64,19 +43,13 @@ class Ball extends BodyComponent with InitialPosition, Layered { gameRef.spawnBall(); } } +} - /// Immediatly and completly [stop]s the ball. - /// - /// The [Ball] will no longer be affected by any forces, including it's - /// weight and those emitted from collisions. - void stop() { - body.setType(BodyType.static); - } - - /// Allows the [Ball] to be affected by forces. - /// - /// If previously [stop]ed, the previous ball's velocity is not kept. - void resume() { - body.setType(BodyType.dynamic); +/// Adds helper methods to the [Ball] +extension BallX on Ball { + /// Returns the controller instance of the ball + // TODO(erickzanardo): Remove the need of an extension. + BallController get controller { + return children.whereType().first; } } diff --git a/lib/game/components/baseboard.dart b/lib/game/components/baseboard.dart index 48d38497..60d0ebe7 100644 --- a/lib/game/components/baseboard.dart +++ b/lib/game/components/baseboard.dart @@ -2,6 +2,7 @@ import 'dart:math' as math; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; /// {@template baseboard} /// Straight, angled board piece to corral the [Ball] towards the [Flipper]s. diff --git a/lib/game/components/board.dart b/lib/game/components/board.dart index efa3f137..6e895d6e 100644 --- a/lib/game/components/board.dart +++ b/lib/game/components/board.dart @@ -1,5 +1,6 @@ import 'package:flame/components.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; /// {@template board} /// The main flat surface of the [PinballGame], where the [Flipper]s, diff --git a/lib/game/components/bonus_word.dart b/lib/game/components/bonus_word.dart index cc6391e8..d97a9bd1 100644 --- a/lib/game/components/bonus_word.dart +++ b/lib/game/components/bonus_word.dart @@ -8,6 +8,7 @@ import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; /// {@template bonus_word} /// Loads all [BonusLetter]s to compose a [BonusWord]. diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index a255e652..1ed293da 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -4,12 +4,10 @@ export 'board.dart'; export 'board_side.dart'; export 'bonus_word.dart'; export 'flipper.dart'; -export 'initial_position.dart'; export 'jetpack_ramp.dart'; export 'joint_anchor.dart'; export 'kicker.dart'; export 'launcher_ramp.dart'; -export 'layer.dart'; export 'pathway.dart'; export 'plunger.dart'; export 'ramp_opening.dart'; diff --git a/lib/game/components/flipper.dart b/lib/game/components/flipper.dart index 48913934..6e64c781 100644 --- a/lib/game/components/flipper.dart +++ b/lib/game/components/flipper.dart @@ -6,6 +6,7 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/services.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball/gen/assets.gen.dart'; +import 'package:pinball_components/pinball_components.dart' hide Assets; const _leftFlipperKeys = [ LogicalKeyboardKey.arrowLeft, diff --git a/lib/game/components/jetpack_ramp.dart b/lib/game/components/jetpack_ramp.dart index 4afc36d1..a69b2111 100644 --- a/lib/game/components/jetpack_ramp.dart +++ b/lib/game/components/jetpack_ramp.dart @@ -4,6 +4,7 @@ import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; /// {@template jetpack_ramp} /// Represents the upper left blue ramp of the [Board]. diff --git a/lib/game/components/joint_anchor.dart b/lib/game/components/joint_anchor.dart index e945bd72..98b80ece 100644 --- a/lib/game/components/joint_anchor.dart +++ b/lib/game/components/joint_anchor.dart @@ -1,5 +1,5 @@ import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; /// {@template joint_anchor} /// Non visual [BodyComponent] used to hold a [BodyType.dynamic] in [Joint]s diff --git a/lib/game/components/kicker.dart b/lib/game/components/kicker.dart index e4b2824d..dc55a52f 100644 --- a/lib/game/components/kicker.dart +++ b/lib/game/components/kicker.dart @@ -5,6 +5,7 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:geometry/geometry.dart' as geometry show centroid; import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; /// {@template kicker} /// Triangular [BodyType.static] body that propels the [Ball] towards the diff --git a/lib/game/components/launcher_ramp.dart b/lib/game/components/launcher_ramp.dart index 0b6e4dbf..aae9265f 100644 --- a/lib/game/components/launcher_ramp.dart +++ b/lib/game/components/launcher_ramp.dart @@ -4,6 +4,7 @@ import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; /// {@template launcher_ramp} /// The yellow left ramp, where the [Ball] goes through when launched from the diff --git a/lib/game/components/pathway.dart b/lib/game/components/pathway.dart index 819ed5f4..54c81464 100644 --- a/lib/game/components/pathway.dart +++ b/lib/game/components/pathway.dart @@ -2,7 +2,7 @@ import 'package:flame/extensions.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:geometry/geometry.dart'; -import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; /// {@template pathway} /// [Pathway] creates lines of various shapes. diff --git a/lib/game/components/plunger.dart b/lib/game/components/plunger.dart index 934cd8ac..9791ec66 100644 --- a/lib/game/components/plunger.dart +++ b/lib/game/components/plunger.dart @@ -2,6 +2,7 @@ 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} /// [Plunger] serves as a spring, that shoots the ball on the right side of the diff --git a/lib/game/components/ramp_opening.dart b/lib/game/components/ramp_opening.dart index 2d8454dd..d8ddcc35 100644 --- a/lib/game/components/ramp_opening.dart +++ b/lib/game/components/ramp_opening.dart @@ -2,6 +2,7 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; /// {@template ramp_orientation} /// Determines if a ramp is facing [up] or [down] on the [Board]. diff --git a/lib/game/components/round_bumper.dart b/lib/game/components/round_bumper.dart index 2f43a35b..969bddbe 100644 --- a/lib/game/components/round_bumper.dart +++ b/lib/game/components/round_bumper.dart @@ -1,5 +1,6 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; /// {@template round_bumper} /// Circular body that repels a [Ball] on contact, increasing the score. diff --git a/lib/game/components/score_points.dart b/lib/game/components/score_points.dart index 97cea82a..12649137 100644 --- a/lib/game/components/score_points.dart +++ b/lib/game/components/score_points.dart @@ -2,6 +2,7 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; /// {@template score_points} /// Specifies the amount of points received on [Ball] collision. @@ -26,6 +27,8 @@ class BallScorePointsCallback extends ContactCallback { ScorePoints scorePoints, Contact _, ) { - ball.gameRef.read().add(Scored(points: scorePoints.points)); + ball.controller.gameRef.read().add( + Scored(points: scorePoints.points), + ); } } diff --git a/lib/game/components/spaceship.dart b/lib/game/components/spaceship.dart index 8243a974..847e4ac8 100644 --- a/lib/game/components/spaceship.dart +++ b/lib/game/components/spaceship.dart @@ -8,6 +8,7 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/flame/blueprint.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball/gen/assets.gen.dart'; +import 'package:pinball_components/pinball_components.dart' hide Assets; /// A [Blueprint] which creates the spaceship feature. class Spaceship extends Forge2DBlueprint { @@ -15,7 +16,7 @@ class Spaceship extends Forge2DBlueprint { static const radius = 10.0; @override - void build() { + void build(_) { final position = Vector2( PinballGame.boardBounds.left + radius + 15, PinballGame.boardBounds.center.dy + 30, diff --git a/lib/game/components/wall.dart b/lib/game/components/wall.dart index 7475715e..0fb57a41 100644 --- a/lib/game/components/wall.dart +++ b/lib/game/components/wall.dart @@ -4,6 +4,7 @@ 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'; /// {@template wall} /// A continuous generic and [BodyType.static] barrier that divides a game area. @@ -77,6 +78,6 @@ class BottomWall extends Wall { class BottomWallBallContactCallback extends ContactCallback { @override void begin(Ball ball, BottomWall wall, Contact contact) { - ball.lost(); + ball.controller.lost(); } } diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index d19ef177..fdc1f332 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -1,12 +1,13 @@ import 'package:pinball/game/game.dart'; import 'package:pinball/gen/assets.gen.dart'; +import 'package:pinball_components/pinball_components.dart' as components; /// Add methods to help loading and caching game assets. extension PinballGameAssetsX on PinballGame { /// Pre load the initial assets of the game. Future preLoadAssets() async { await Future.wait([ - images.load(Assets.images.components.ball.path), + images.load(components.Assets.images.ball.keyName), images.load(Assets.images.components.flipper.path), images.load(Assets.images.components.spaceship.androidTop.path), images.load(Assets.images.components.spaceship.androidBottom.path), diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 3ce7fd77..681a6431 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -102,11 +102,7 @@ class PinballGame extends Forge2DGame } void spawnBall() { - final ball = Ball(); - add( - ball - ..initialPosition = plunger.body.position + Vector2(0, ball.size.y / 2), - ); + addFromBlueprint(BallBlueprint(position: plunger.body.position)); } } @@ -115,8 +111,6 @@ class DebugPinballGame extends PinballGame with TapDetector { @override void onTapUp(TapUpInfo info) { - add( - Ball()..initialPosition = info.eventPosition.game, - ); + addFromBlueprint(BallBlueprint(position: info.eventPosition.game)); } } diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index fe4bad8b..ba75412b 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -15,8 +15,6 @@ class $AssetsImagesGen { class $AssetsImagesComponentsGen { const $AssetsImagesComponentsGen(); - AssetGenImage get ball => - const AssetGenImage('assets/images/components/ball.png'); AssetGenImage get flipper => const AssetGenImage('assets/images/components/flipper.png'); AssetGenImage get sauce => diff --git a/packages/pinball_components/analysis_options.yaml b/packages/pinball_components/analysis_options.yaml index 3742fc3d..f8155aa6 100644 --- a/packages/pinball_components/analysis_options.yaml +++ b/packages/pinball_components/analysis_options.yaml @@ -1 +1,4 @@ -include: package:very_good_analysis/analysis_options.2.4.0.yaml \ No newline at end of file +include: package:very_good_analysis/analysis_options.2.4.0.yaml +analyzer: + exclude: + - lib/**/*.gen.dart diff --git a/assets/images/components/ball.png b/packages/pinball_components/assets/images/ball.png similarity index 100% rename from assets/images/components/ball.png rename to packages/pinball_components/assets/images/ball.png diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart new file mode 100644 index 00000000..c4ed6ca0 --- /dev/null +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -0,0 +1,68 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +import 'package:flutter/widgets.dart'; + +class $AssetsImagesGen { + const $AssetsImagesGen(); + + AssetGenImage get ball => const AssetGenImage('assets/images/ball.png'); +} + +class Assets { + Assets._(); + + static const $AssetsImagesGen images = $AssetsImagesGen(); +} + +class AssetGenImage extends AssetImage { + const AssetGenImage(String assetName) + : super(assetName, package: 'pinball_components'); + + Image image({ + Key? key, + ImageFrameBuilder? frameBuilder, + ImageLoadingBuilder? loadingBuilder, + ImageErrorWidgetBuilder? errorBuilder, + String? semanticLabel, + bool excludeFromSemantics = false, + double? width, + double? height, + Color? color, + BlendMode? colorBlendMode, + BoxFit? fit, + AlignmentGeometry alignment = Alignment.center, + ImageRepeat repeat = ImageRepeat.noRepeat, + Rect? centerSlice, + bool matchTextDirection = false, + bool gaplessPlayback = false, + bool isAntiAlias = false, + FilterQuality filterQuality = FilterQuality.low, + }) { + return Image( + key: key, + image: this, + frameBuilder: frameBuilder, + loadingBuilder: loadingBuilder, + errorBuilder: errorBuilder, + semanticLabel: semanticLabel, + excludeFromSemantics: excludeFromSemantics, + width: width, + height: height, + color: color, + colorBlendMode: colorBlendMode, + fit: fit, + alignment: alignment, + repeat: repeat, + centerSlice: centerSlice, + matchTextDirection: matchTextDirection, + gaplessPlayback: gaplessPlayback, + isAntiAlias: isAntiAlias, + filterQuality: filterQuality, + ); + } + + String get path => assetName; +} diff --git a/packages/pinball_components/lib/pinball_components.dart b/packages/pinball_components/lib/pinball_components.dart index a08579e5..b00b9d5b 100644 --- a/packages/pinball_components/lib/pinball_components.dart +++ b/packages/pinball_components/lib/pinball_components.dart @@ -1,3 +1,4 @@ library pinball_components; +export 'gen/assets.gen.dart'; export 'src/pinball_components.dart'; diff --git a/packages/pinball_components/lib/src/components/ball.dart b/packages/pinball_components/lib/src/components/ball.dart new file mode 100644 index 00000000..ec51cb47 --- /dev/null +++ b/packages/pinball_components/lib/src/components/ball.dart @@ -0,0 +1,72 @@ +import 'dart:ui'; + +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_components/pinball_components.dart'; + +/// {@template ball} +/// A solid, [BodyType.dynamic] sphere that rolls and bounces around +/// {@endtemplate} +class Ball extends BodyComponent + with Layered, InitialPosition { + /// {@macro ball_body} + Ball({ + required this.baseColor, + }) { + // TODO(ruimiguel): while developing Ball can be launched by clicking mouse, + // and default layer is Layer.all. But on final game Ball will be always be + // be launched from Plunger and LauncherRamp will modify it to Layer.board. + // We need to see what happens if Ball appears from other place like nest + // bumper, it will need to explicit change layer to Layer.board then. + layer = Layer.board; + } + + /// The size of the [Ball] + final Vector2 size = Vector2.all(2); + + /// The base [Color] used to tint this [Ball] + final Color baseColor; + + @override + Future onLoad() async { + await super.onLoad(); + final sprite = await gameRef.loadSprite(Assets.images.ball.keyName); + final tint = baseColor.withOpacity(0.5); + await add( + SpriteComponent( + sprite: sprite, + size: size, + anchor: Anchor.center, + )..tint(tint), + ); + } + + @override + Body createBody() { + final shape = CircleShape()..radius = size.x / 2; + + final fixtureDef = FixtureDef(shape)..density = 1; + + final bodyDef = BodyDef() + ..position = initialPosition + ..userData = this + ..type = BodyType.dynamic; + + return world.createBody(bodyDef)..createFixture(fixtureDef); + } + + /// Immediatly and completly [stop]s the ball. + /// + /// The [Ball] will no longer be affected by any forces, including it's + /// weight and those emitted from collisions. + void stop() { + body.setType(BodyType.static); + } + + /// Allows the [Ball] to be affected by forces. + /// + /// If previously [stop]ed, the previous ball's velocity is not kept. + void resume() { + body.setType(BodyType.dynamic); + } +} diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart new file mode 100644 index 00000000..677bbd0c --- /dev/null +++ b/packages/pinball_components/lib/src/components/components.dart @@ -0,0 +1,3 @@ +export 'ball.dart'; +export 'initial_position.dart'; +export 'layer.dart'; diff --git a/lib/game/components/initial_position.dart b/packages/pinball_components/lib/src/components/initial_position.dart similarity index 100% rename from lib/game/components/initial_position.dart rename to packages/pinball_components/lib/src/components/initial_position.dart diff --git a/lib/game/components/layer.dart b/packages/pinball_components/lib/src/components/layer.dart similarity index 100% rename from lib/game/components/layer.dart rename to packages/pinball_components/lib/src/components/layer.dart diff --git a/packages/pinball_components/lib/src/pinball_components.dart b/packages/pinball_components/lib/src/pinball_components.dart index dbc4d6fd..003c1c49 100644 --- a/packages/pinball_components/lib/src/pinball_components.dart +++ b/packages/pinball_components/lib/src/pinball_components.dart @@ -1,7 +1 @@ -/// {@template pinball_components} -/// Package with the UI game components for the Pinball Game -/// {@endtemplate} -class PinballComponents { - /// {@macro pinball_components} - const PinballComponents(); -} +export 'components/components.dart'; diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index a3a990a0..56645a30 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -7,10 +7,24 @@ environment: sdk: ">=2.16.0 <3.0.0" dependencies: + flame: ^1.1.0-releasecandidate.6 + flame_forge2d: ^0.9.0-releasecandidate.6 flutter: sdk: flutter dev_dependencies: + flame_test: ^1.1.0 flutter_test: sdk: flutter - very_good_analysis: ^2.4.0 \ No newline at end of file + mocktail: ^0.2.0 + very_good_analysis: ^2.4.0 + +flutter: + generate: true + assets: + - assets/images/ + +flutter_gen: + line_length: 80 + assets: + package_parameter_enabled: true diff --git a/packages/pinball_components/test/helpers/helpers.dart b/packages/pinball_components/test/helpers/helpers.dart new file mode 100644 index 00000000..a8b9f7ff --- /dev/null +++ b/packages/pinball_components/test/helpers/helpers.dart @@ -0,0 +1 @@ +export 'test_game.dart'; diff --git a/packages/pinball_components/test/helpers/test_game.dart b/packages/pinball_components/test/helpers/test_game.dart new file mode 100644 index 00000000..a1219868 --- /dev/null +++ b/packages/pinball_components/test/helpers/test_game.dart @@ -0,0 +1,7 @@ +import 'package:flame_forge2d/flame_forge2d.dart'; + +class TestGame extends Forge2DGame { + TestGame() { + images.prefix = ''; + } +} diff --git a/packages/pinball_components/test/src/components/ball_test.dart b/packages/pinball_components/test/src/components/ball_test.dart new file mode 100644 index 00000000..682f1f73 --- /dev/null +++ b/packages/pinball_components/test/src/components/ball_test.dart @@ -0,0 +1,162 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter/material.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('Ball', () { + flameTester.test( + 'loads correctly', + (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ready(); + await game.ensureAdd(ball); + + expect(game.contains(ball), isTrue); + }, + ); + + group('body', () { + flameTester.test( + 'is dynamic', + (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ensureAdd(ball); + + expect(ball.body.bodyType, equals(BodyType.dynamic)); + }, + ); + + group('can be moved', () { + flameTester.test('by its weight', (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ensureAdd(ball); + + game.update(1); + expect(ball.body.position, isNot(equals(ball.initialPosition))); + }); + + flameTester.test('by applying velocity', (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ensureAdd(ball); + + ball.body.gravityScale = 0; + ball.body.linearVelocity.setValues(10, 10); + game.update(1); + expect(ball.body.position, isNot(equals(ball.initialPosition))); + }); + }); + }); + + group('fixture', () { + flameTester.test( + 'exists', + (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ensureAdd(ball); + + expect(ball.body.fixtures[0], isA()); + }, + ); + + flameTester.test( + 'is dense', + (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ensureAdd(ball); + + final fixture = ball.body.fixtures[0]; + expect(fixture.density, greaterThan(0)); + }, + ); + + flameTester.test( + 'shape is circular', + (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ensureAdd(ball); + + final fixture = ball.body.fixtures[0]; + expect(fixture.shape.shapeType, equals(ShapeType.circle)); + expect(fixture.shape.radius, equals(1)); + }, + ); + + flameTester.test( + 'has Layer.all as default filter maskBits', + (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ready(); + await game.ensureAdd(ball); + await game.ready(); + + final fixture = ball.body.fixtures[0]; + expect(fixture.filterData.maskBits, equals(Layer.board.maskBits)); + }, + ); + }); + + group('stop', () { + group("can't be moved", () { + flameTester.test('by its weight', (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ensureAdd(ball); + ball.stop(); + + game.update(1); + expect(ball.body.position, equals(ball.initialPosition)); + }); + }); + + flameTester.test('by applying velocity', (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ensureAdd(ball); + ball.stop(); + + ball.body.linearVelocity.setValues(10, 10); + game.update(1); + expect(ball.body.position, equals(ball.initialPosition)); + }); + }); + + group('resume', () { + group('can move', () { + flameTester.test( + 'by its weight when previously stopped', + (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ensureAdd(ball); + ball.stop(); + ball.resume(); + + game.update(1); + expect(ball.body.position, isNot(equals(ball.initialPosition))); + }, + ); + + flameTester.test( + 'by applying velocity when previously stopped', + (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ensureAdd(ball); + ball.stop(); + ball.resume(); + + ball.body.gravityScale = 0; + ball.body.linearVelocity.setValues(10, 10); + game.update(1); + expect(ball.body.position, isNot(equals(ball.initialPosition))); + }, + ); + }); + }); + }); +} diff --git a/test/game/components/initial_position_test.dart b/packages/pinball_components/test/src/components/initial_position_test.dart similarity index 97% rename from test/game/components/initial_position_test.dart rename to packages/pinball_components/test/src/components/initial_position_test.dart index ece083cc..9f015150 100644 --- a/test/game/components/initial_position_test.dart +++ b/packages/pinball_components/test/src/components/initial_position_test.dart @@ -3,7 +3,7 @@ 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'; class TestBodyComponent extends BodyComponent with InitialPosition { @override diff --git a/test/game/components/layer_test.dart b/packages/pinball_components/test/src/components/layer_test.dart similarity index 98% rename from test/game/components/layer_test.dart rename to packages/pinball_components/test/src/components/layer_test.dart index 3aacdb49..1eaa9135 100644 --- a/test/game/components/layer_test.dart +++ b/packages/pinball_components/test/src/components/layer_test.dart @@ -4,7 +4,7 @@ import 'dart:math' as math; 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'; class TestBodyComponent extends BodyComponent with Layered { @override diff --git a/packages/pinball_components/test/src/pinball_components_test.dart b/packages/pinball_components/test/src/pinball_components_test.dart deleted file mode 100644 index 7359ddcb..00000000 --- a/packages/pinball_components/test/src/pinball_components_test.dart +++ /dev/null @@ -1,11 +0,0 @@ -// ignore_for_file: prefer_const_constructors -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball_components/pinball_components.dart'; - -void main() { - group('PinballComponents', () { - test('can be instantiated', () { - expect(PinballComponents(), isNotNull); - }); - }); -} diff --git a/pubspec.lock b/pubspec.lock index ac5fa36d..067559c4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -392,6 +392,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" + pinball_components: + dependency: "direct main" + description: + path: "packages/pinball_components" + relative: true + source: path + version: "1.0.0+1" pinball_theme: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 838dd9ed..1efe9281 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,8 @@ dependencies: intl: ^0.17.0 leaderboard_repository: path: packages/leaderboard_repository + pinball_components: + path: packages/pinball_components pinball_theme: path: packages/pinball_theme diff --git a/test/flame/blueprint_test.dart b/test/flame/blueprint_test.dart index e521a83c..3a9f5ed3 100644 --- a/test/flame/blueprint_test.dart +++ b/test/flame/blueprint_test.dart @@ -1,5 +1,4 @@ import 'package:flame/components.dart'; -import 'package:flame/game.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/flame/blueprint.dart'; @@ -9,7 +8,7 @@ import '../helpers/helpers.dart'; class MyBlueprint extends Blueprint { @override - void build() { + void build(_) { add(Component()); addAll([Component(), Component()]); } @@ -17,7 +16,7 @@ class MyBlueprint extends Blueprint { class MyForge2dBlueprint extends Forge2DBlueprint { @override - void build() { + void build(_) { addContactCallback(MockContactCallback()); addAllContactCallback([MockContactCallback(), MockContactCallback()]); } @@ -26,7 +25,7 @@ class MyForge2dBlueprint extends Forge2DBlueprint { void main() { group('Blueprint', () { test('components can be added to it', () { - final blueprint = MyBlueprint()..build(); + final blueprint = MyBlueprint()..build(MockPinballGame()); expect(blueprint.components.length, equals(3)); }); @@ -59,7 +58,7 @@ void main() { }); test('callbacks can be added to it', () { - final blueprint = MyForge2dBlueprint()..build(); + final blueprint = MyForge2dBlueprint()..build(MockPinballGame()); expect(blueprint.callbacks.length, equals(3)); }); @@ -92,12 +91,5 @@ void main() { ); }, ); - - test('throws assertion error when used on a non Forge2dGame', () { - expect( - () => MyForge2dBlueprint().attach(FlameGame()), - throwsAssertionError, - ); - }); }); } diff --git a/test/game/components/ball_test.dart b/test/game/components/ball_test.dart index f94c1526..6419eef2 100644 --- a/test/game/components/ball_test.dart +++ b/test/game/components/ball_test.dart @@ -1,110 +1,17 @@ // 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_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(PinballGameTest.create); group('Ball', () { - flameTester.test( - 'loads correctly', - (game) async { - final ball = Ball(); - await game.ready(); - await game.ensureAdd(ball); - - expect(game.contains(ball), isTrue); - }, - ); - - group('body', () { - flameTester.test( - 'is dynamic', - (game) async { - final ball = Ball(); - await game.ensureAdd(ball); - - expect(ball.body.bodyType, equals(BodyType.dynamic)); - }, - ); - - group('can be moved', () { - flameTester.test('by its weight', (game) async { - final ball = Ball(); - await game.ensureAdd(ball); - - game.update(1); - expect(ball.body.position, isNot(equals(ball.initialPosition))); - }); - - flameTester.test('by applying velocity', (game) async { - final ball = Ball(); - await game.ensureAdd(ball); - - ball.body.gravityScale = 0; - ball.body.linearVelocity.setValues(10, 10); - game.update(1); - expect(ball.body.position, isNot(equals(ball.initialPosition))); - }); - }); - }); - - group('fixture', () { - flameTester.test( - 'exists', - (game) async { - final ball = Ball(); - await game.ensureAdd(ball); - - expect(ball.body.fixtures[0], isA()); - }, - ); - - flameTester.test( - 'is dense', - (game) async { - final ball = Ball(); - await game.ensureAdd(ball); - - final fixture = ball.body.fixtures[0]; - expect(fixture.density, greaterThan(0)); - }, - ); - - flameTester.test( - 'shape is circular', - (game) async { - final ball = Ball(); - await game.ensureAdd(ball); - - final fixture = ball.body.fixtures[0]; - expect(fixture.shape.shapeType, equals(ShapeType.circle)); - expect(fixture.shape.radius, equals(1)); - }, - ); - - flameTester.test( - 'has Layer.all as default filter maskBits', - (game) async { - final ball = Ball(); - await game.ready(); - await game.ensureAdd(ball); - await game.ready(); - - final fixture = ball.body.fixtures[0]; - expect(fixture.filterData.maskBits, equals(Layer.board.maskBits)); - }, - ); - }); - group('lost', () { late GameBloc gameBloc; @@ -124,7 +31,7 @@ void main() { (game, tester) async { await game.ready(); - game.children.whereType().first.lost(); + game.children.whereType().first.controller.lost(); await tester.pump(); verify(() => gameBloc.add(const BallLost())).called(1); @@ -136,7 +43,7 @@ void main() { (game, tester) async { await game.ready(); - game.children.whereType().first.lost(); + game.children.whereType().first.controller.lost(); await game.ready(); // Making sure that all additions are done expect( @@ -162,7 +69,7 @@ void main() { ); await game.ready(); - game.children.whereType().first.lost(); + game.children.whereType().first.controller.lost(); await tester.pump(); expect( @@ -172,60 +79,5 @@ void main() { }, ); }); - - group('stop', () { - group("can't be moved", () { - flameTester.test('by its weight', (game) async { - final ball = Ball(); - await game.ensureAdd(ball); - ball.stop(); - - game.update(1); - expect(ball.body.position, equals(ball.initialPosition)); - }); - }); - - flameTester.test('by applying velocity', (game) async { - final ball = Ball(); - await game.ensureAdd(ball); - ball.stop(); - - ball.body.linearVelocity.setValues(10, 10); - game.update(1); - expect(ball.body.position, equals(ball.initialPosition)); - }); - }); - - group('resume', () { - group('can move', () { - flameTester.test( - 'by its weight when previously stopped', - (game) async { - final ball = Ball(); - await game.ensureAdd(ball); - ball.stop(); - ball.resume(); - - game.update(1); - expect(ball.body.position, isNot(equals(ball.initialPosition))); - }, - ); - - flameTester.test( - 'by applying velocity when previously stopped', - (game) async { - final ball = Ball(); - await game.ensureAdd(ball); - ball.stop(); - ball.resume(); - - ball.body.gravityScale = 0; - ball.body.linearVelocity.setValues(10, 10); - game.update(1); - expect(ball.body.position, isNot(equals(ball.initialPosition))); - }, - ); - }); - }); }); } diff --git a/test/game/components/flipper_test.dart b/test/game/components/flipper_test.dart index 3c12e37e..3e6429df 100644 --- a/test/game/components/flipper_test.dart +++ b/test/game/components/flipper_test.dart @@ -4,9 +4,11 @@ import 'dart:collection'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.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'; @@ -81,7 +83,7 @@ void main() { final flipper = Flipper( side: BoardSide.left, ); - final ball = Ball(); + final ball = Ball(baseColor: Colors.white); await game.ready(); await game.ensureAddAll([flipper, ball]); diff --git a/test/game/components/ramp_opening_test.dart b/test/game/components/ramp_opening_test.dart index 1876f8ae..11cf8ddc 100644 --- a/test/game/components/ramp_opening_test.dart +++ b/test/game/components/ramp_opening_test.dart @@ -4,6 +4,7 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockingjay/mockingjay.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; import '../../helpers/helpers.dart'; diff --git a/test/game/components/score_points_test.dart b/test/game/components/score_points_test.dart index 9a93c078..30ec70db 100644 --- a/test/game/components/score_points_test.dart +++ b/test/game/components/score_points_test.dart @@ -1,9 +1,11 @@ +import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; -class MockBall extends Mock implements Ball {} +import '../../helpers/helpers.dart'; class MockGameBloc extends Mock implements GameBloc {} @@ -28,12 +30,16 @@ void main() { late PinballGame game; late GameBloc bloc; late Ball ball; + late ComponentSet componentSet; + late BallController ballController; late FakeScorePoints fakeScorePoints; setUp(() { game = MockPinballGame(); bloc = MockGameBloc(); ball = MockBall(); + componentSet = MockComponentSet(); + ballController = MockBallController(); fakeScorePoints = FakeScorePoints(); }); @@ -45,7 +51,10 @@ void main() { test( 'emits Scored event with points', () { - when(() => ball.gameRef).thenReturn(game); + when(() => componentSet.whereType()) + .thenReturn([ballController]); + when(() => ball.children).thenReturn(componentSet); + when(() => ballController.gameRef).thenReturn(game); when(game.read).thenReturn(bloc); BallScorePointsCallback().begin( diff --git a/test/game/components/spaceship_test.dart b/test/game/components/spaceship_test.dart index c7eb24b2..52b12609 100644 --- a/test/game/components/spaceship_test.dart +++ b/test/game/components/spaceship_test.dart @@ -2,6 +2,7 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; import '../../helpers/helpers.dart'; diff --git a/test/game/components/wall_test.dart b/test/game/components/wall_test.dart index 53d387fa..18c7ea5b 100644 --- a/test/game/components/wall_test.dart +++ b/test/game/components/wall_test.dart @@ -17,11 +17,14 @@ void main() { test( 'removes the ball on begin contact when the wall is a bottom one', () { - final game = MockPinballGame(); final wall = MockBottomWall(); + final ballController = MockBallController(); final ball = MockBall(); + final componentSet = MockComponentSet(); - when(() => ball.gameRef).thenReturn(game); + when(() => componentSet.whereType()) + .thenReturn([ballController]); + when(() => ball.children).thenReturn(componentSet); BottomWallBallContactCallback() // Remove once https://github.com/flame-engine/flame/pull/1415 @@ -29,7 +32,7 @@ void main() { ..end(MockBall(), MockBottomWall(), MockContact()) ..begin(ball, wall, MockContact()); - verify(ball.lost).called(1); + verify(ballController.lost).called(1); }, ); }); diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index b9e0ac7d..65732608 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -5,6 +5,7 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; import '../helpers/helpers.dart'; diff --git a/test/helpers/mocks.dart b/test/helpers/mocks.dart index 1e6b7289..bd9f82cf 100644 --- a/test/helpers/mocks.dart +++ b/test/helpers/mocks.dart @@ -1,3 +1,4 @@ +import 'package:flame/components.dart'; import 'package:flame/input.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/foundation.dart'; @@ -6,6 +7,7 @@ import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball/theme/theme.dart'; +import 'package:pinball_components/pinball_components.dart'; class MockPinballGame extends Mock implements PinballGame {} @@ -17,6 +19,8 @@ class MockBody extends Mock implements Body {} class MockBall extends Mock implements Ball {} +class MockBallController extends Mock implements BallController {} + class MockContact extends Mock implements Contact {} class MockContactCallback extends Mock @@ -62,3 +66,5 @@ class MockFixture extends Mock implements Fixture {} class MockSpaceshipEntrance extends Mock implements SpaceshipEntrance {} class MockSpaceshipHole extends Mock implements SpaceshipHole {} + +class MockComponentSet extends Mock implements ComponentSet {}