diff --git a/lib/game/components/android_acres.dart b/lib/game/components/android_acres.dart index 4a235619..da8e4949 100644 --- a/lib/game/components/android_acres.dart +++ b/lib/game/components/android_acres.dart @@ -6,8 +6,8 @@ import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; /// {@template android_acres} -/// Area positioned on the left side of the board containing the [Spaceship], -/// [SpaceshipRamp], [SpaceshipRail], and [AndroidBumper]s. +/// Area positioned on the left side of the board containing the +/// [AndroidSpaceship], [SpaceshipRamp], [SpaceshipRail], and [AndroidBumper]s. /// {@endtemplate} class AndroidAcres extends Blueprint { /// {@macro android_acres} @@ -18,16 +18,21 @@ class AndroidAcres extends Blueprint { children: [ ScoringBehavior(points: 20000), ], - )..initialPosition = Vector2(-32.52, -9.1), + )..initialPosition = Vector2(-25, 1.3), AndroidBumper.b( children: [ ScoringBehavior(points: 20000), ], - )..initialPosition = Vector2(-22.89, -17.35), + )..initialPosition = Vector2(-32.6, -9.2), + AndroidBumper.cow( + children: [ + ScoringBehavior(points: 20), + ], + )..initialPosition = Vector2(-20.5, -13.8), ], blueprints: [ SpaceshipRamp(), - Spaceship(position: Vector2(-26.5, -28.5)), + AndroidSpaceship(position: Vector2(-26.5, -28.5)), SpaceshipRail(), ], ); diff --git a/lib/game/components/bottom_group.dart b/lib/game/components/bottom_group.dart index 5d0cf97e..921a8e58 100644 --- a/lib/game/components/bottom_group.dart +++ b/lib/game/components/bottom_group.dart @@ -16,6 +16,7 @@ class BottomGroup extends Component { _BottomGroupSide(side: BoardSide.right), _BottomGroupSide(side: BoardSide.left), ], + priority: RenderPriority.bottomGroup, ); } @@ -28,8 +29,7 @@ class _BottomGroupSide extends Component { /// {@macro bottom_group_side} _BottomGroupSide({ required BoardSide side, - }) : _side = side, - super(priority: RenderPriority.bottomGroup); + }) : _side = side; final BoardSide _side; diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index 3fae6abd..afef04f0 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -10,5 +10,6 @@ export 'flutter_forest/flutter_forest.dart'; export 'game_flow_controller.dart'; export 'google_word/google_word.dart'; export 'launcher.dart'; +export 'multipliers/multipliers.dart'; export 'scoring_behavior.dart'; export 'sparky_fire_zone.dart'; diff --git a/lib/game/components/multipliers/behaviors/behaviors.dart b/lib/game/components/multipliers/behaviors/behaviors.dart new file mode 100644 index 00000000..70703bba --- /dev/null +++ b/lib/game/components/multipliers/behaviors/behaviors.dart @@ -0,0 +1 @@ +export 'multipliers_behavior.dart'; diff --git a/lib/game/components/multipliers/behaviors/multipliers_behavior.dart b/lib/game/components/multipliers/behaviors/multipliers_behavior.dart new file mode 100644 index 00000000..33a59a08 --- /dev/null +++ b/lib/game/components/multipliers/behaviors/multipliers_behavior.dart @@ -0,0 +1,25 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// Toggle each [Multiplier] when GameState.multiplier changes. +class MultipliersBehavior extends Component + with + HasGameRef, + ParentIsA, + BlocComponent { + @override + bool listenWhen(GameState? previousState, GameState newState) { + return previousState?.multiplier != newState.multiplier; + } + + @override + void onNewState(GameState state) { + final multipliers = parent.children.whereType(); + for (final multiplier in multipliers) { + multiplier.bloc.next(state.multiplier); + } + } +} diff --git a/lib/game/components/multipliers/multipliers.dart b/lib/game/components/multipliers/multipliers.dart new file mode 100644 index 00000000..6a6a1563 --- /dev/null +++ b/lib/game/components/multipliers/multipliers.dart @@ -0,0 +1,44 @@ +import 'dart:math' as math; +import 'package:flame/components.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball/game/components/multipliers/behaviors/behaviors.dart'; +import 'package:pinball_components/pinball_components.dart'; + +/// {@template multipliers} +/// A group for the multipliers on the board. +/// {@endtemplate} +class Multipliers extends Component { + /// {@macro multipliers} + Multipliers() + : super( + children: [ + Multiplier.x2( + position: Vector2(-19.5, -2), + angle: -15 * math.pi / 180, + ), + Multiplier.x3( + position: Vector2(13, -9.4), + angle: 15 * math.pi / 180, + ), + Multiplier.x4( + position: Vector2(0, -21.2), + angle: 0, + ), + Multiplier.x5( + position: Vector2(-8.5, -28), + angle: -3 * math.pi / 180, + ), + Multiplier.x6( + position: Vector2(10, -30.7), + angle: 8 * math.pi / 180, + ), + MultipliersBehavior(), + ], + ); + + /// Creates [Multipliers] without any children. + /// + /// This can be used for testing [Multipliers]'s behaviors in isolation. + @visibleForTesting + Multipliers.test(); +} diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index ab4e9860..ab7a6169 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -50,39 +50,42 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.boundary.bottom.keyName), images.load(components.Assets.images.boundary.outer.keyName), images.load(components.Assets.images.boundary.outerBottom.keyName), - images.load(components.Assets.images.spaceship.saucer.keyName), - images.load(components.Assets.images.spaceship.bridge.keyName), - images.load(components.Assets.images.spaceship.ramp.boardOpening.keyName), + images.load(components.Assets.images.android.spaceship.saucer.keyName), + images + .load(components.Assets.images.android.spaceship.animatronic.keyName), + images.load(components.Assets.images.android.spaceship.lightBeam.keyName), + images.load(components.Assets.images.android.ramp.boardOpening.keyName), images.load( - components.Assets.images.spaceship.ramp.railingForeground.keyName, + components.Assets.images.android.ramp.railingForeground.keyName, ), images.load( - components.Assets.images.spaceship.ramp.railingBackground.keyName, + components.Assets.images.android.ramp.railingBackground.keyName, ), - images.load(components.Assets.images.spaceship.ramp.main.keyName), - images - .load(components.Assets.images.spaceship.ramp.arrow.inactive.keyName), + images.load(components.Assets.images.android.ramp.main.keyName), + images.load(components.Assets.images.android.ramp.arrow.inactive.keyName), images.load( - components.Assets.images.spaceship.ramp.arrow.active1.keyName, + components.Assets.images.android.ramp.arrow.active1.keyName, ), images.load( - components.Assets.images.spaceship.ramp.arrow.active2.keyName, + components.Assets.images.android.ramp.arrow.active2.keyName, ), images.load( - components.Assets.images.spaceship.ramp.arrow.active3.keyName, + components.Assets.images.android.ramp.arrow.active3.keyName, ), images.load( - components.Assets.images.spaceship.ramp.arrow.active4.keyName, + components.Assets.images.android.ramp.arrow.active4.keyName, ), images.load( - components.Assets.images.spaceship.ramp.arrow.active5.keyName, + components.Assets.images.android.ramp.arrow.active5.keyName, ), - images.load(components.Assets.images.spaceship.rail.main.keyName), - images.load(components.Assets.images.spaceship.rail.exit.keyName), - images.load(components.Assets.images.androidBumper.a.lit.keyName), - images.load(components.Assets.images.androidBumper.a.dimmed.keyName), - images.load(components.Assets.images.androidBumper.b.lit.keyName), - images.load(components.Assets.images.androidBumper.b.dimmed.keyName), + images.load(components.Assets.images.android.rail.main.keyName), + images.load(components.Assets.images.android.rail.exit.keyName), + images.load(components.Assets.images.android.bumper.a.lit.keyName), + images.load(components.Assets.images.android.bumper.a.dimmed.keyName), + images.load(components.Assets.images.android.bumper.b.lit.keyName), + images.load(components.Assets.images.android.bumper.b.dimmed.keyName), + images.load(components.Assets.images.android.bumper.cow.lit.keyName), + images.load(components.Assets.images.android.bumper.cow.dimmed.keyName), images.load(components.Assets.images.sparky.computer.top.keyName), images.load(components.Assets.images.sparky.computer.base.keyName), images.load(components.Assets.images.sparky.animatronic.keyName), @@ -101,6 +104,16 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.googleWord.letter5.keyName), images.load(components.Assets.images.googleWord.letter6.keyName), images.load(components.Assets.images.backboard.display.keyName), + images.load(components.Assets.images.multiplier.x2.lit.keyName), + images.load(components.Assets.images.multiplier.x2.dimmed.keyName), + images.load(components.Assets.images.multiplier.x3.lit.keyName), + images.load(components.Assets.images.multiplier.x3.dimmed.keyName), + images.load(components.Assets.images.multiplier.x4.lit.keyName), + images.load(components.Assets.images.multiplier.x4.dimmed.keyName), + images.load(components.Assets.images.multiplier.x5.lit.keyName), + images.load(components.Assets.images.multiplier.x5.dimmed.keyName), + images.load(components.Assets.images.multiplier.x6.lit.keyName), + images.load(components.Assets.images.multiplier.x6.dimmed.keyName), images.load(dashTheme.leaderboardIcon.keyName), images.load(sparkyTheme.leaderboardIcon.keyName), images.load(androidTheme.leaderboardIcon.keyName), diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index a4b740fb..6933e649 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -49,10 +49,10 @@ class PinballGame extends Forge2DGame await add(Drain()); await add(BottomGroup()); unawaited(addFromBlueprint(Boundaries())); - unawaited(addFromBlueprint(LaunchRamp())); final launcher = Launcher(); unawaited(addFromBlueprint(launcher)); + await add(Multipliers()); await add(FlutterForest()); await addFromBlueprint(SparkyFireZone()); await addFromBlueprint(AndroidAcres()); @@ -67,7 +67,7 @@ class PinballGame extends Forge2DGame ), ); - controller.attachTo(launcher.components.whereType().first); + controller.attachTo(launcher.components.whereType().single); await super.onLoad(); } diff --git a/packages/pinball_components/assets/images/android_bumper/a/dimmed.png b/packages/pinball_components/assets/images/android/bumper/a/dimmed.png similarity index 100% rename from packages/pinball_components/assets/images/android_bumper/a/dimmed.png rename to packages/pinball_components/assets/images/android/bumper/a/dimmed.png diff --git a/packages/pinball_components/assets/images/android_bumper/a/lit.png b/packages/pinball_components/assets/images/android/bumper/a/lit.png similarity index 100% rename from packages/pinball_components/assets/images/android_bumper/a/lit.png rename to packages/pinball_components/assets/images/android/bumper/a/lit.png diff --git a/packages/pinball_components/assets/images/android_bumper/b/dimmed.png b/packages/pinball_components/assets/images/android/bumper/b/dimmed.png similarity index 100% rename from packages/pinball_components/assets/images/android_bumper/b/dimmed.png rename to packages/pinball_components/assets/images/android/bumper/b/dimmed.png diff --git a/packages/pinball_components/assets/images/android_bumper/b/lit.png b/packages/pinball_components/assets/images/android/bumper/b/lit.png similarity index 100% rename from packages/pinball_components/assets/images/android_bumper/b/lit.png rename to packages/pinball_components/assets/images/android/bumper/b/lit.png diff --git a/packages/pinball_components/assets/images/android/bumper/cow/dimmed.png b/packages/pinball_components/assets/images/android/bumper/cow/dimmed.png new file mode 100644 index 00000000..6a8bb146 Binary files /dev/null and b/packages/pinball_components/assets/images/android/bumper/cow/dimmed.png differ diff --git a/packages/pinball_components/assets/images/android/bumper/cow/lit.png b/packages/pinball_components/assets/images/android/bumper/cow/lit.png new file mode 100644 index 00000000..4909708b Binary files /dev/null and b/packages/pinball_components/assets/images/android/bumper/cow/lit.png differ diff --git a/packages/pinball_components/assets/images/spaceship/rail/exit.png b/packages/pinball_components/assets/images/android/rail/exit.png similarity index 100% rename from packages/pinball_components/assets/images/spaceship/rail/exit.png rename to packages/pinball_components/assets/images/android/rail/exit.png diff --git a/packages/pinball_components/assets/images/spaceship/rail/main.png b/packages/pinball_components/assets/images/android/rail/main.png similarity index 100% rename from packages/pinball_components/assets/images/spaceship/rail/main.png rename to packages/pinball_components/assets/images/android/rail/main.png diff --git a/packages/pinball_components/assets/images/spaceship/ramp/arrow/active1.png b/packages/pinball_components/assets/images/android/ramp/arrow/active1.png similarity index 100% rename from packages/pinball_components/assets/images/spaceship/ramp/arrow/active1.png rename to packages/pinball_components/assets/images/android/ramp/arrow/active1.png diff --git a/packages/pinball_components/assets/images/spaceship/ramp/arrow/active2.png b/packages/pinball_components/assets/images/android/ramp/arrow/active2.png similarity index 100% rename from packages/pinball_components/assets/images/spaceship/ramp/arrow/active2.png rename to packages/pinball_components/assets/images/android/ramp/arrow/active2.png diff --git a/packages/pinball_components/assets/images/spaceship/ramp/arrow/active3.png b/packages/pinball_components/assets/images/android/ramp/arrow/active3.png similarity index 100% rename from packages/pinball_components/assets/images/spaceship/ramp/arrow/active3.png rename to packages/pinball_components/assets/images/android/ramp/arrow/active3.png diff --git a/packages/pinball_components/assets/images/spaceship/ramp/arrow/active4.png b/packages/pinball_components/assets/images/android/ramp/arrow/active4.png similarity index 100% rename from packages/pinball_components/assets/images/spaceship/ramp/arrow/active4.png rename to packages/pinball_components/assets/images/android/ramp/arrow/active4.png diff --git a/packages/pinball_components/assets/images/spaceship/ramp/arrow/active5.png b/packages/pinball_components/assets/images/android/ramp/arrow/active5.png similarity index 100% rename from packages/pinball_components/assets/images/spaceship/ramp/arrow/active5.png rename to packages/pinball_components/assets/images/android/ramp/arrow/active5.png diff --git a/packages/pinball_components/assets/images/spaceship/ramp/arrow/inactive.png b/packages/pinball_components/assets/images/android/ramp/arrow/inactive.png similarity index 100% rename from packages/pinball_components/assets/images/spaceship/ramp/arrow/inactive.png rename to packages/pinball_components/assets/images/android/ramp/arrow/inactive.png diff --git a/packages/pinball_components/assets/images/spaceship/ramp/board-opening.png b/packages/pinball_components/assets/images/android/ramp/board-opening.png similarity index 100% rename from packages/pinball_components/assets/images/spaceship/ramp/board-opening.png rename to packages/pinball_components/assets/images/android/ramp/board-opening.png diff --git a/packages/pinball_components/assets/images/spaceship/ramp/main.png b/packages/pinball_components/assets/images/android/ramp/main.png similarity index 100% rename from packages/pinball_components/assets/images/spaceship/ramp/main.png rename to packages/pinball_components/assets/images/android/ramp/main.png diff --git a/packages/pinball_components/assets/images/spaceship/ramp/railing-background.png b/packages/pinball_components/assets/images/android/ramp/railing-background.png similarity index 100% rename from packages/pinball_components/assets/images/spaceship/ramp/railing-background.png rename to packages/pinball_components/assets/images/android/ramp/railing-background.png diff --git a/packages/pinball_components/assets/images/spaceship/ramp/railing-foreground.png b/packages/pinball_components/assets/images/android/ramp/railing-foreground.png similarity index 100% rename from packages/pinball_components/assets/images/spaceship/ramp/railing-foreground.png rename to packages/pinball_components/assets/images/android/ramp/railing-foreground.png diff --git a/packages/pinball_components/assets/images/android/spaceship/animatronic.png b/packages/pinball_components/assets/images/android/spaceship/animatronic.png new file mode 100644 index 00000000..d4b165f3 Binary files /dev/null and b/packages/pinball_components/assets/images/android/spaceship/animatronic.png differ diff --git a/packages/pinball_components/assets/images/android/spaceship/light-beam.png b/packages/pinball_components/assets/images/android/spaceship/light-beam.png new file mode 100644 index 00000000..eb33725d Binary files /dev/null and b/packages/pinball_components/assets/images/android/spaceship/light-beam.png differ diff --git a/packages/pinball_components/assets/images/android/spaceship/saucer.png b/packages/pinball_components/assets/images/android/spaceship/saucer.png new file mode 100644 index 00000000..6c77525a Binary files /dev/null and b/packages/pinball_components/assets/images/android/spaceship/saucer.png differ diff --git a/packages/pinball_components/assets/images/multiplier/x2/dimmed.png b/packages/pinball_components/assets/images/multiplier/x2/dimmed.png new file mode 100644 index 00000000..7cc9fc4f Binary files /dev/null and b/packages/pinball_components/assets/images/multiplier/x2/dimmed.png differ diff --git a/packages/pinball_components/assets/images/multiplier/x2/lit.png b/packages/pinball_components/assets/images/multiplier/x2/lit.png new file mode 100644 index 00000000..be2b3f08 Binary files /dev/null and b/packages/pinball_components/assets/images/multiplier/x2/lit.png differ diff --git a/packages/pinball_components/assets/images/multiplier/x3/dimmed.png b/packages/pinball_components/assets/images/multiplier/x3/dimmed.png new file mode 100644 index 00000000..460b1a0e Binary files /dev/null and b/packages/pinball_components/assets/images/multiplier/x3/dimmed.png differ diff --git a/packages/pinball_components/assets/images/multiplier/x3/lit.png b/packages/pinball_components/assets/images/multiplier/x3/lit.png new file mode 100644 index 00000000..7fdedbbe Binary files /dev/null and b/packages/pinball_components/assets/images/multiplier/x3/lit.png differ diff --git a/packages/pinball_components/assets/images/multiplier/x4/dimmed.png b/packages/pinball_components/assets/images/multiplier/x4/dimmed.png new file mode 100644 index 00000000..e8a6256e Binary files /dev/null and b/packages/pinball_components/assets/images/multiplier/x4/dimmed.png differ diff --git a/packages/pinball_components/assets/images/multiplier/x4/lit.png b/packages/pinball_components/assets/images/multiplier/x4/lit.png new file mode 100644 index 00000000..5beceabb Binary files /dev/null and b/packages/pinball_components/assets/images/multiplier/x4/lit.png differ diff --git a/packages/pinball_components/assets/images/multiplier/x5/dimmed.png b/packages/pinball_components/assets/images/multiplier/x5/dimmed.png new file mode 100644 index 00000000..96e018e4 Binary files /dev/null and b/packages/pinball_components/assets/images/multiplier/x5/dimmed.png differ diff --git a/packages/pinball_components/assets/images/multiplier/x5/lit.png b/packages/pinball_components/assets/images/multiplier/x5/lit.png new file mode 100644 index 00000000..23fd3aab Binary files /dev/null and b/packages/pinball_components/assets/images/multiplier/x5/lit.png differ diff --git a/packages/pinball_components/assets/images/multiplier/x6/dimmed.png b/packages/pinball_components/assets/images/multiplier/x6/dimmed.png new file mode 100644 index 00000000..d518e1eb Binary files /dev/null and b/packages/pinball_components/assets/images/multiplier/x6/dimmed.png differ diff --git a/packages/pinball_components/assets/images/multiplier/x6/lit.png b/packages/pinball_components/assets/images/multiplier/x6/lit.png new file mode 100644 index 00000000..54244bab Binary files /dev/null and b/packages/pinball_components/assets/images/multiplier/x6/lit.png differ diff --git a/packages/pinball_components/assets/images/spaceship/bridge.png b/packages/pinball_components/assets/images/spaceship/bridge.png deleted file mode 100644 index 6ebb143e..00000000 Binary files a/packages/pinball_components/assets/images/spaceship/bridge.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/spaceship/saucer.png b/packages/pinball_components/assets/images/spaceship/saucer.png deleted file mode 100644 index 4cd65522..00000000 Binary files a/packages/pinball_components/assets/images/spaceship/saucer.png and /dev/null differ diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index 71c01cfd..3c7b2454 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -10,8 +10,7 @@ import 'package:flutter/widgets.dart'; class $AssetsImagesGen { const $AssetsImagesGen(); - $AssetsImagesAndroidBumperGen get androidBumper => - const $AssetsImagesAndroidBumperGen(); + $AssetsImagesAndroidGen get android => const $AssetsImagesAndroidGen(); $AssetsImagesBackboardGen get backboard => const $AssetsImagesBackboardGen(); $AssetsImagesBallGen get ball => const $AssetsImagesBallGen(); $AssetsImagesBaseboardGen get baseboard => const $AssetsImagesBaseboardGen(); @@ -24,20 +23,23 @@ class $AssetsImagesGen { $AssetsImagesKickerGen get kicker => const $AssetsImagesKickerGen(); $AssetsImagesLaunchRampGen get launchRamp => const $AssetsImagesLaunchRampGen(); + $AssetsImagesMultiplierGen get multiplier => + const $AssetsImagesMultiplierGen(); $AssetsImagesPlungerGen get plunger => const $AssetsImagesPlungerGen(); $AssetsImagesSignpostGen get signpost => const $AssetsImagesSignpostGen(); $AssetsImagesSlingshotGen get slingshot => const $AssetsImagesSlingshotGen(); - $AssetsImagesSpaceshipGen get spaceship => const $AssetsImagesSpaceshipGen(); $AssetsImagesSparkyGen get sparky => const $AssetsImagesSparkyGen(); } -class $AssetsImagesAndroidBumperGen { - const $AssetsImagesAndroidBumperGen(); +class $AssetsImagesAndroidGen { + const $AssetsImagesAndroidGen(); - $AssetsImagesAndroidBumperAGen get a => - const $AssetsImagesAndroidBumperAGen(); - $AssetsImagesAndroidBumperBGen get b => - const $AssetsImagesAndroidBumperBGen(); + $AssetsImagesAndroidBumperGen get bumper => + const $AssetsImagesAndroidBumperGen(); + $AssetsImagesAndroidRailGen get rail => const $AssetsImagesAndroidRailGen(); + $AssetsImagesAndroidRampGen get ramp => const $AssetsImagesAndroidRampGen(); + $AssetsImagesAndroidSpaceshipGen get spaceship => + const $AssetsImagesAndroidSpaceshipGen(); } class $AssetsImagesBackboardGen { @@ -188,6 +190,16 @@ class $AssetsImagesLaunchRampGen { const AssetGenImage('assets/images/launch_ramp/ramp.png'); } +class $AssetsImagesMultiplierGen { + const $AssetsImagesMultiplierGen(); + + $AssetsImagesMultiplierX2Gen get x2 => const $AssetsImagesMultiplierX2Gen(); + $AssetsImagesMultiplierX3Gen get x3 => const $AssetsImagesMultiplierX3Gen(); + $AssetsImagesMultiplierX4Gen get x4 => const $AssetsImagesMultiplierX4Gen(); + $AssetsImagesMultiplierX5Gen get x5 => const $AssetsImagesMultiplierX5Gen(); + $AssetsImagesMultiplierX6Gen get x6 => const $AssetsImagesMultiplierX6Gen(); +} + class $AssetsImagesPlungerGen { const $AssetsImagesPlungerGen(); @@ -232,23 +244,6 @@ class $AssetsImagesSlingshotGen { const AssetGenImage('assets/images/slingshot/upper.png'); } -class $AssetsImagesSpaceshipGen { - const $AssetsImagesSpaceshipGen(); - - /// File path: assets/images/spaceship/bridge.png - AssetGenImage get bridge => - const AssetGenImage('assets/images/spaceship/bridge.png'); - - $AssetsImagesSpaceshipRailGen get rail => - const $AssetsImagesSpaceshipRailGen(); - $AssetsImagesSpaceshipRampGen get ramp => - const $AssetsImagesSpaceshipRampGen(); - - /// File path: assets/images/spaceship/saucer.png - AssetGenImage get saucer => - const AssetGenImage('assets/images/spaceship/saucer.png'); -} - class $AssetsImagesSparkyGen { const $AssetsImagesSparkyGen(); @@ -262,28 +257,66 @@ class $AssetsImagesSparkyGen { const $AssetsImagesSparkyComputerGen(); } -class $AssetsImagesAndroidBumperAGen { - const $AssetsImagesAndroidBumperAGen(); +class $AssetsImagesAndroidBumperGen { + const $AssetsImagesAndroidBumperGen(); - /// File path: assets/images/android_bumper/a/dimmed.png - AssetGenImage get dimmed => - const AssetGenImage('assets/images/android_bumper/a/dimmed.png'); + $AssetsImagesAndroidBumperAGen get a => + const $AssetsImagesAndroidBumperAGen(); + $AssetsImagesAndroidBumperBGen get b => + const $AssetsImagesAndroidBumperBGen(); + $AssetsImagesAndroidBumperCowGen get cow => + const $AssetsImagesAndroidBumperCowGen(); +} - /// File path: assets/images/android_bumper/a/lit.png - AssetGenImage get lit => - const AssetGenImage('assets/images/android_bumper/a/lit.png'); +class $AssetsImagesAndroidRailGen { + const $AssetsImagesAndroidRailGen(); + + /// File path: assets/images/android/rail/exit.png + AssetGenImage get exit => + const AssetGenImage('assets/images/android/rail/exit.png'); + + /// File path: assets/images/android/rail/main.png + AssetGenImage get main => + const AssetGenImage('assets/images/android/rail/main.png'); } -class $AssetsImagesAndroidBumperBGen { - const $AssetsImagesAndroidBumperBGen(); +class $AssetsImagesAndroidRampGen { + const $AssetsImagesAndroidRampGen(); - /// File path: assets/images/android_bumper/b/dimmed.png - AssetGenImage get dimmed => - const AssetGenImage('assets/images/android_bumper/b/dimmed.png'); + $AssetsImagesAndroidRampArrowGen get arrow => + const $AssetsImagesAndroidRampArrowGen(); - /// File path: assets/images/android_bumper/b/lit.png - AssetGenImage get lit => - const AssetGenImage('assets/images/android_bumper/b/lit.png'); + /// File path: assets/images/android/ramp/board-opening.png + AssetGenImage get boardOpening => + const AssetGenImage('assets/images/android/ramp/board-opening.png'); + + /// File path: assets/images/android/ramp/main.png + AssetGenImage get main => + const AssetGenImage('assets/images/android/ramp/main.png'); + + /// File path: assets/images/android/ramp/railing-background.png + AssetGenImage get railingBackground => + const AssetGenImage('assets/images/android/ramp/railing-background.png'); + + /// File path: assets/images/android/ramp/railing-foreground.png + AssetGenImage get railingForeground => + const AssetGenImage('assets/images/android/ramp/railing-foreground.png'); +} + +class $AssetsImagesAndroidSpaceshipGen { + const $AssetsImagesAndroidSpaceshipGen(); + + /// File path: assets/images/android/spaceship/animatronic.png + AssetGenImage get animatronic => + const AssetGenImage('assets/images/android/spaceship/animatronic.png'); + + /// File path: assets/images/android/spaceship/light-beam.png + AssetGenImage get lightBeam => + const AssetGenImage('assets/images/android/spaceship/light-beam.png'); + + /// File path: assets/images/android/spaceship/saucer.png + AssetGenImage get saucer => + const AssetGenImage('assets/images/android/spaceship/saucer.png'); } class $AssetsImagesDashBumperGen { @@ -307,39 +340,64 @@ class $AssetsImagesDinoAnimatronicGen { const AssetGenImage('assets/images/dino/animatronic/mouth.png'); } -class $AssetsImagesSpaceshipRailGen { - const $AssetsImagesSpaceshipRailGen(); +class $AssetsImagesMultiplierX2Gen { + const $AssetsImagesMultiplierX2Gen(); - /// File path: assets/images/spaceship/rail/exit.png - AssetGenImage get exit => - const AssetGenImage('assets/images/spaceship/rail/exit.png'); + /// File path: assets/images/multiplier/x2/dimmed.png + AssetGenImage get dimmed => + const AssetGenImage('assets/images/multiplier/x2/dimmed.png'); - /// File path: assets/images/spaceship/rail/main.png - AssetGenImage get main => - const AssetGenImage('assets/images/spaceship/rail/main.png'); + /// File path: assets/images/multiplier/x2/lit.png + AssetGenImage get lit => + const AssetGenImage('assets/images/multiplier/x2/lit.png'); } -class $AssetsImagesSpaceshipRampGen { - const $AssetsImagesSpaceshipRampGen(); +class $AssetsImagesMultiplierX3Gen { + const $AssetsImagesMultiplierX3Gen(); - $AssetsImagesSpaceshipRampArrowGen get arrow => - const $AssetsImagesSpaceshipRampArrowGen(); + /// File path: assets/images/multiplier/x3/dimmed.png + AssetGenImage get dimmed => + const AssetGenImage('assets/images/multiplier/x3/dimmed.png'); - /// File path: assets/images/spaceship/ramp/board-opening.png - AssetGenImage get boardOpening => - const AssetGenImage('assets/images/spaceship/ramp/board-opening.png'); + /// File path: assets/images/multiplier/x3/lit.png + AssetGenImage get lit => + const AssetGenImage('assets/images/multiplier/x3/lit.png'); +} - /// File path: assets/images/spaceship/ramp/main.png - AssetGenImage get main => - const AssetGenImage('assets/images/spaceship/ramp/main.png'); +class $AssetsImagesMultiplierX4Gen { + const $AssetsImagesMultiplierX4Gen(); - /// File path: assets/images/spaceship/ramp/railing-background.png - AssetGenImage get railingBackground => const AssetGenImage( - 'assets/images/spaceship/ramp/railing-background.png'); + /// File path: assets/images/multiplier/x4/dimmed.png + AssetGenImage get dimmed => + const AssetGenImage('assets/images/multiplier/x4/dimmed.png'); - /// File path: assets/images/spaceship/ramp/railing-foreground.png - AssetGenImage get railingForeground => const AssetGenImage( - 'assets/images/spaceship/ramp/railing-foreground.png'); + /// File path: assets/images/multiplier/x4/lit.png + AssetGenImage get lit => + const AssetGenImage('assets/images/multiplier/x4/lit.png'); +} + +class $AssetsImagesMultiplierX5Gen { + const $AssetsImagesMultiplierX5Gen(); + + /// File path: assets/images/multiplier/x5/dimmed.png + AssetGenImage get dimmed => + const AssetGenImage('assets/images/multiplier/x5/dimmed.png'); + + /// File path: assets/images/multiplier/x5/lit.png + AssetGenImage get lit => + const AssetGenImage('assets/images/multiplier/x5/lit.png'); +} + +class $AssetsImagesMultiplierX6Gen { + const $AssetsImagesMultiplierX6Gen(); + + /// File path: assets/images/multiplier/x6/dimmed.png + AssetGenImage get dimmed => + const AssetGenImage('assets/images/multiplier/x6/dimmed.png'); + + /// File path: assets/images/multiplier/x6/lit.png + AssetGenImage get lit => + const AssetGenImage('assets/images/multiplier/x6/lit.png'); } class $AssetsImagesSparkyBumperGen { @@ -362,6 +420,70 @@ class $AssetsImagesSparkyComputerGen { const AssetGenImage('assets/images/sparky/computer/top.png'); } +class $AssetsImagesAndroidBumperAGen { + const $AssetsImagesAndroidBumperAGen(); + + /// File path: assets/images/android/bumper/a/dimmed.png + AssetGenImage get dimmed => + const AssetGenImage('assets/images/android/bumper/a/dimmed.png'); + + /// File path: assets/images/android/bumper/a/lit.png + AssetGenImage get lit => + const AssetGenImage('assets/images/android/bumper/a/lit.png'); +} + +class $AssetsImagesAndroidBumperBGen { + const $AssetsImagesAndroidBumperBGen(); + + /// File path: assets/images/android/bumper/b/dimmed.png + AssetGenImage get dimmed => + const AssetGenImage('assets/images/android/bumper/b/dimmed.png'); + + /// File path: assets/images/android/bumper/b/lit.png + AssetGenImage get lit => + const AssetGenImage('assets/images/android/bumper/b/lit.png'); +} + +class $AssetsImagesAndroidBumperCowGen { + const $AssetsImagesAndroidBumperCowGen(); + + /// File path: assets/images/android/bumper/cow/dimmed.png + AssetGenImage get dimmed => + const AssetGenImage('assets/images/android/bumper/cow/dimmed.png'); + + /// File path: assets/images/android/bumper/cow/lit.png + AssetGenImage get lit => + const AssetGenImage('assets/images/android/bumper/cow/lit.png'); +} + +class $AssetsImagesAndroidRampArrowGen { + const $AssetsImagesAndroidRampArrowGen(); + + /// File path: assets/images/android/ramp/arrow/active1.png + AssetGenImage get active1 => + const AssetGenImage('assets/images/android/ramp/arrow/active1.png'); + + /// File path: assets/images/android/ramp/arrow/active2.png + AssetGenImage get active2 => + const AssetGenImage('assets/images/android/ramp/arrow/active2.png'); + + /// File path: assets/images/android/ramp/arrow/active3.png + AssetGenImage get active3 => + const AssetGenImage('assets/images/android/ramp/arrow/active3.png'); + + /// File path: assets/images/android/ramp/arrow/active4.png + AssetGenImage get active4 => + const AssetGenImage('assets/images/android/ramp/arrow/active4.png'); + + /// File path: assets/images/android/ramp/arrow/active5.png + AssetGenImage get active5 => + const AssetGenImage('assets/images/android/ramp/arrow/active5.png'); + + /// File path: assets/images/android/ramp/arrow/inactive.png + AssetGenImage get inactive => + const AssetGenImage('assets/images/android/ramp/arrow/inactive.png'); +} + class $AssetsImagesDashBumperAGen { const $AssetsImagesDashBumperAGen(); @@ -398,34 +520,6 @@ class $AssetsImagesDashBumperMainGen { const AssetGenImage('assets/images/dash/bumper/main/inactive.png'); } -class $AssetsImagesSpaceshipRampArrowGen { - const $AssetsImagesSpaceshipRampArrowGen(); - - /// File path: assets/images/spaceship/ramp/arrow/active1.png - AssetGenImage get active1 => - const AssetGenImage('assets/images/spaceship/ramp/arrow/active1.png'); - - /// File path: assets/images/spaceship/ramp/arrow/active2.png - AssetGenImage get active2 => - const AssetGenImage('assets/images/spaceship/ramp/arrow/active2.png'); - - /// File path: assets/images/spaceship/ramp/arrow/active3.png - AssetGenImage get active3 => - const AssetGenImage('assets/images/spaceship/ramp/arrow/active3.png'); - - /// File path: assets/images/spaceship/ramp/arrow/active4.png - AssetGenImage get active4 => - const AssetGenImage('assets/images/spaceship/ramp/arrow/active4.png'); - - /// File path: assets/images/spaceship/ramp/arrow/active5.png - AssetGenImage get active5 => - const AssetGenImage('assets/images/spaceship/ramp/arrow/active5.png'); - - /// File path: assets/images/spaceship/ramp/arrow/inactive.png - AssetGenImage get inactive => - const AssetGenImage('assets/images/spaceship/ramp/arrow/inactive.png'); -} - class $AssetsImagesSparkyBumperAGen { const $AssetsImagesSparkyBumperAGen(); diff --git a/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart b/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart index ad954975..e1a3857e 100644 --- a/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart +++ b/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart @@ -10,7 +10,7 @@ import 'package:pinball_flame/pinball_flame.dart'; export 'cubit/android_bumper_cubit.dart'; /// {@template android_bumper} -/// Bumper for area under the [Spaceship]. +/// Bumper for area under the [AndroidSpaceship]. /// {@endtemplate} class AndroidBumper extends BodyComponent with InitialPosition { /// {@macro android_bumper} @@ -19,6 +19,7 @@ class AndroidBumper extends BodyComponent with InitialPosition { required double minorRadius, required String litAssetPath, required String dimmedAssetPath, + required Vector2 spritePosition, Iterable? children, required this.bloc, }) : _majorRadius = majorRadius, @@ -32,6 +33,7 @@ class AndroidBumper extends BodyComponent with InitialPosition { _AndroidBumperSpriteGroupComponent( dimmedAssetPath: dimmedAssetPath, litAssetPath: litAssetPath, + position: spritePosition, state: bloc.state, ), ...?children, @@ -44,8 +46,9 @@ class AndroidBumper extends BodyComponent with InitialPosition { }) : this._( majorRadius: 3.52, minorRadius: 2.97, - litAssetPath: Assets.images.androidBumper.a.lit.keyName, - dimmedAssetPath: Assets.images.androidBumper.a.dimmed.keyName, + litAssetPath: Assets.images.android.bumper.a.lit.keyName, + dimmedAssetPath: Assets.images.android.bumper.a.dimmed.keyName, + spritePosition: Vector2(0, -0.1), bloc: AndroidBumperCubit(), children: children, ); @@ -56,8 +59,22 @@ class AndroidBumper extends BodyComponent with InitialPosition { }) : this._( majorRadius: 3.19, minorRadius: 2.79, - litAssetPath: Assets.images.androidBumper.b.lit.keyName, - dimmedAssetPath: Assets.images.androidBumper.b.dimmed.keyName, + litAssetPath: Assets.images.android.bumper.b.lit.keyName, + dimmedAssetPath: Assets.images.android.bumper.b.dimmed.keyName, + spritePosition: Vector2(0, -0.1), + bloc: AndroidBumperCubit(), + children: children, + ); + + /// {@macro android_bumper} + AndroidBumper.cow({ + Iterable? children, + }) : this._( + majorRadius: 3.4, + minorRadius: 2.9, + litAssetPath: Assets.images.android.bumper.cow.lit.keyName, + dimmedAssetPath: Assets.images.android.bumper.cow.dimmed.keyName, + spritePosition: Vector2(0, -0.68), bloc: AndroidBumperCubit(), children: children, ); @@ -113,12 +130,13 @@ class _AndroidBumperSpriteGroupComponent _AndroidBumperSpriteGroupComponent({ required String litAssetPath, required String dimmedAssetPath, + required Vector2 position, required AndroidBumperState state, }) : _litAssetPath = litAssetPath, _dimmedAssetPath = dimmedAssetPath, super( anchor: Anchor.center, - position: Vector2(0, -0.1), + position: position, current: state, ); diff --git a/packages/pinball_components/lib/src/components/android_bumper/cubit/android_bumper_cubit.dart b/packages/pinball_components/lib/src/components/android_bumper/cubit/android_bumper_cubit.dart index 3d3fd4b1..3e75f890 100644 --- a/packages/pinball_components/lib/src/components/android_bumper/cubit/android_bumper_cubit.dart +++ b/packages/pinball_components/lib/src/components/android_bumper/cubit/android_bumper_cubit.dart @@ -5,7 +5,7 @@ import 'package:bloc/bloc.dart'; part 'android_bumper_state.dart'; class AndroidBumperCubit extends Cubit { - AndroidBumperCubit() : super(AndroidBumperState.dimmed); + AndroidBumperCubit() : super(AndroidBumperState.lit); void onBallContacted() { emit(AndroidBumperState.dimmed); diff --git a/packages/pinball_components/lib/src/components/android_spaceship.dart b/packages/pinball_components/lib/src/components/android_spaceship.dart new file mode 100644 index 00000000..1dcf6780 --- /dev/null +++ b/packages/pinball_components/lib/src/components/android_spaceship.dart @@ -0,0 +1,209 @@ +// ignore_for_file: public_member_api_docs + +import 'dart:async'; +import 'dart:math' as math; + +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; +import 'package:pinball_flame/pinball_flame.dart'; + +class AndroidSpaceship extends Blueprint { + AndroidSpaceship({required Vector2 position}) + : super( + components: [ + _SpaceshipSaucer()..initialPosition = position, + _SpaceshipSaucerSpriteAnimationComponent()..position = position, + _LightBeamSpriteComponent()..position = position + Vector2(2.5, 5), + _AndroidHead()..initialPosition = position + Vector2(0.5, 0.25), + _SpaceshipHole( + outsideLayer: Layer.spaceshipExitRail, + outsidePriority: RenderPriority.ballOnSpaceshipRail, + )..initialPosition = position - Vector2(5.3, -5.4), + _SpaceshipHole( + outsideLayer: Layer.board, + outsidePriority: RenderPriority.ballOnBoard, + )..initialPosition = position - Vector2(-7.5, -1.1), + ], + ); +} + +class _SpaceshipSaucer extends BodyComponent with InitialPosition, Layered { + _SpaceshipSaucer() : super(renderBody: false) { + layer = Layer.spaceship; + } + + @override + Body createBody() { + final shape = _SpaceshipSaucerShape(); + final bodyDef = BodyDef( + position: initialPosition, + userData: this, + angle: -1.7, + ); + + return world.createBody(bodyDef)..createFixtureFromShape(shape); + } +} + +class _SpaceshipSaucerShape extends ChainShape { + _SpaceshipSaucerShape() { + const minorRadius = 9.75; + const majorRadius = 11.9; + + createChain( + [ + for (var angle = 0.2618; angle <= 6.0214; angle += math.pi / 180) + Vector2( + minorRadius * math.cos(angle), + majorRadius * math.sin(angle), + ), + ], + ); + } +} + +class _SpaceshipSaucerSpriteAnimationComponent extends SpriteAnimationComponent + with HasGameRef { + _SpaceshipSaucerSpriteAnimationComponent() + : super( + anchor: Anchor.center, + priority: RenderPriority.spaceshipSaucer, + ); + + @override + Future onLoad() async { + await super.onLoad(); + + final spriteSheet = gameRef.images.fromCache( + Assets.images.android.spaceship.saucer.keyName, + ); + + const amountPerRow = 5; + const amountPerColumn = 3; + final textureSize = Vector2( + spriteSheet.width / amountPerRow, + spriteSheet.height / amountPerColumn, + ); + size = textureSize / 10; + + animation = SpriteAnimation.fromFrameData( + spriteSheet, + SpriteAnimationData.sequenced( + amount: amountPerRow * amountPerColumn, + amountPerRow: amountPerRow, + stepTime: 1 / 24, + textureSize: textureSize, + ), + ); + } +} + +// TODO(allisonryan0002): add pulsing behavior. +class _LightBeamSpriteComponent extends SpriteComponent with HasGameRef { + _LightBeamSpriteComponent() + : super( + anchor: Anchor.center, + priority: RenderPriority.spaceshipLightBeam, + ); + + @override + Future onLoad() async { + await super.onLoad(); + final sprite = Sprite( + gameRef.images.fromCache( + Assets.images.android.spaceship.lightBeam.keyName, + ), + ); + this.sprite = sprite; + size = sprite.originalSize / 10; + } +} + +class _AndroidHead extends BodyComponent with InitialPosition, Layered { + _AndroidHead() + : super( + priority: RenderPriority.androidHead, + children: [_AndroidHeadSpriteAnimationComponent()], + renderBody: false, + ) { + layer = Layer.spaceship; + } + + @override + Body createBody() { + final shape = EllipseShape( + center: Vector2.zero(), + majorRadius: 3.1, + minorRadius: 2, + )..rotate(1.4); + // TODO(allisonryan0002): use bumping behavior. + final fixtureDef = FixtureDef( + shape, + restitution: 0.1, + ); + final bodyDef = BodyDef(position: initialPosition); + + return world.createBody(bodyDef)..createFixture(fixtureDef); + } +} + +class _AndroidHeadSpriteAnimationComponent extends SpriteAnimationComponent + with HasGameRef { + _AndroidHeadSpriteAnimationComponent() + : super( + anchor: Anchor.center, + position: Vector2(-0.24, -2.6), + ); + + @override + Future onLoad() async { + await super.onLoad(); + + final spriteSheet = gameRef.images.fromCache( + Assets.images.android.spaceship.animatronic.keyName, + ); + + const amountPerRow = 18; + const amountPerColumn = 4; + final textureSize = Vector2( + spriteSheet.width / amountPerRow, + spriteSheet.height / amountPerColumn, + ); + size = textureSize / 10; + + animation = SpriteAnimation.fromFrameData( + spriteSheet, + SpriteAnimationData.sequenced( + amount: amountPerRow * amountPerColumn, + amountPerRow: amountPerRow, + stepTime: 1 / 24, + textureSize: textureSize, + ), + ); + } +} + +class _SpaceshipHole extends LayerSensor { + _SpaceshipHole({required Layer outsideLayer, required int outsidePriority}) + : super( + insideLayer: Layer.spaceship, + outsideLayer: outsideLayer, + orientation: LayerEntranceOrientation.down, + insidePriority: RenderPriority.ballOnSpaceship, + outsidePriority: outsidePriority, + ) { + layer = Layer.spaceship; + } + + @override + Shape get shape { + return ArcShape( + center: Vector2(0, -3.2), + arcRadius: 5, + angle: 1, + rotation: -2, + ); + } +} diff --git a/packages/pinball_components/lib/src/components/bumping_behavior.dart b/packages/pinball_components/lib/src/components/bumping_behavior.dart index 654f96b4..af0d07c3 100644 --- a/packages/pinball_components/lib/src/components/bumping_behavior.dart +++ b/packages/pinball_components/lib/src/components/bumping_behavior.dart @@ -1,4 +1,5 @@ import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; import 'package:pinball_flame/pinball_flame.dart'; /// {@template bumping_behavior} @@ -11,15 +12,22 @@ class BumpingBehavior extends ContactBehavior { /// Determines how strong the bump is. final double _strength; + /// This is used to recoginze the current state of a contact manifold in world + /// coordinates. + @visibleForTesting + final WorldManifold worldManifold = WorldManifold(); + @override void postSolve(Object other, Contact contact, ContactImpulse impulse) { super.postSolve(other, contact, impulse); if (other is! BodyComponent) return; + contact.getWorldManifold(worldManifold); other.body.applyLinearImpulse( - contact.manifold.localPoint - ..normalize() - ..multiply(Vector2.all(other.body.mass * _strength)), + worldManifold.normal + ..multiply( + Vector2.all(other.body.mass * _strength), + ), ); } } diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index c6c5c802..2f0e4031 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -1,4 +1,5 @@ export 'android_bumper/android_bumper.dart'; +export 'android_spaceship.dart'; export 'backboard/backboard.dart'; export 'ball.dart'; export 'baseboard.dart'; @@ -19,6 +20,7 @@ export 'kicker.dart'; export 'launch_ramp.dart'; export 'layer.dart'; export 'layer_sensor.dart'; +export 'multiplier/multiplier.dart'; export 'plunger.dart'; export 'render_priority.dart'; export 'rocket.dart'; @@ -26,7 +28,6 @@ export 'score_text.dart'; export 'shapes/shapes.dart'; export 'signpost.dart'; export 'slingshot.dart'; -export 'spaceship.dart'; export 'spaceship_rail.dart'; export 'spaceship_ramp.dart'; export 'sparky_animatronic.dart'; diff --git a/packages/pinball_components/lib/src/components/multiplier/cubit/multiplier_cubit.dart b/packages/pinball_components/lib/src/components/multiplier/cubit/multiplier_cubit.dart new file mode 100644 index 00000000..1d265b2e --- /dev/null +++ b/packages/pinball_components/lib/src/components/multiplier/cubit/multiplier_cubit.dart @@ -0,0 +1,25 @@ +// ignore_for_file: public_member_api_docs + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:pinball_components/pinball_components.dart'; + +part 'multiplier_state.dart'; + +class MultiplierCubit extends Cubit { + MultiplierCubit(MultiplierValue multiplierValue) + : super(MultiplierState.initial(multiplierValue)); + + /// Event added when the game's current multiplier changes. + void next(int multiplier) { + if (state.value.equals(multiplier)) { + if (state.spriteState == MultiplierSpriteState.dimmed) { + emit(state.copyWith(spriteState: MultiplierSpriteState.lit)); + } + } else { + if (state.spriteState == MultiplierSpriteState.lit) { + emit(state.copyWith(spriteState: MultiplierSpriteState.dimmed)); + } + } + } +} diff --git a/packages/pinball_components/lib/src/components/multiplier/cubit/multiplier_state.dart b/packages/pinball_components/lib/src/components/multiplier/cubit/multiplier_state.dart new file mode 100644 index 00000000..e3adde70 --- /dev/null +++ b/packages/pinball_components/lib/src/components/multiplier/cubit/multiplier_state.dart @@ -0,0 +1,56 @@ +// ignore_for_file: public_member_api_docs + +part of 'multiplier_cubit.dart'; + +enum MultiplierSpriteState { + lit, + dimmed, +} + +class MultiplierState extends Equatable { + const MultiplierState({ + required this.value, + required this.spriteState, + }); + + const MultiplierState.initial(MultiplierValue multiplierValue) + : this( + value: multiplierValue, + spriteState: MultiplierSpriteState.dimmed, + ); + + /// Current value for the [Multiplier] + final MultiplierValue value; + + /// The [MultiplierSpriteGroupComponent] current sprite state + final MultiplierSpriteState spriteState; + + MultiplierState copyWith({ + MultiplierSpriteState? spriteState, + }) { + return MultiplierState( + value: value, + spriteState: spriteState ?? this.spriteState, + ); + } + + @override + List get props => [value, spriteState]; +} + +extension MultiplierValueX on MultiplierValue { + bool equals(int value) { + switch (this) { + case MultiplierValue.x2: + return value == 2; + case MultiplierValue.x3: + return value == 3; + case MultiplierValue.x4: + return value == 4; + case MultiplierValue.x5: + return value == 5; + case MultiplierValue.x6: + return value == 6; + } + } +} diff --git a/packages/pinball_components/lib/src/components/multiplier/multiplier.dart b/packages/pinball_components/lib/src/components/multiplier/multiplier.dart new file mode 100644 index 00000000..54d02857 --- /dev/null +++ b/packages/pinball_components/lib/src/components/multiplier/multiplier.dart @@ -0,0 +1,204 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flame/components.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball_components/gen/assets.gen.dart'; +import 'package:pinball_components/src/components/multiplier/cubit/multiplier_cubit.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +export 'cubit/multiplier_cubit.dart'; + +/// {@template multiplier} +/// Backlit multiplier decal displayed on the board. +/// {@endtemplate} +class Multiplier extends Component { + /// {@macro multiplier} + Multiplier._({ + required MultiplierValue value, + required Vector2 position, + required double angle, + required this.bloc, + }) : _value = value, + _position = position, + _angle = angle, + super(); + + /// {@macro multiplier} + Multiplier.x2({ + required Vector2 position, + required double angle, + }) : this._( + value: MultiplierValue.x2, + position: position, + angle: angle, + bloc: MultiplierCubit(MultiplierValue.x2), + ); + + /// {@macro multiplier} + Multiplier.x3({ + required Vector2 position, + required double angle, + }) : this._( + value: MultiplierValue.x3, + position: position, + angle: angle, + bloc: MultiplierCubit(MultiplierValue.x3), + ); + + /// {@macro multiplier} + Multiplier.x4({ + required Vector2 position, + required double angle, + }) : this._( + value: MultiplierValue.x4, + position: position, + angle: angle, + bloc: MultiplierCubit(MultiplierValue.x4), + ); + + /// {@macro multiplier} + Multiplier.x5({ + required Vector2 position, + required double angle, + }) : this._( + value: MultiplierValue.x5, + position: position, + angle: angle, + bloc: MultiplierCubit(MultiplierValue.x5), + ); + + /// {@macro multiplier} + Multiplier.x6({ + required Vector2 position, + required double angle, + }) : this._( + value: MultiplierValue.x6, + position: position, + angle: angle, + bloc: MultiplierCubit(MultiplierValue.x6), + ); + + /// Creates a [Multiplier] without any children. + /// + /// This can be used for testing [Multiplier]'s behaviors in isolation. + // TODO(alestiago): Refactor injecting bloc once the following is merged: + // https://github.com/flame-engine/flame/pull/1538 + @visibleForTesting + Multiplier.test({ + required MultiplierValue value, + required this.bloc, + }) : _value = value, + _position = Vector2.zero(), + _angle = 0; + +// TODO(ruimiguel): Consider refactoring once the following is merged: + // https://github.com/flame-engine/flame/pull/1538 + final MultiplierCubit bloc; + + final MultiplierValue _value; + final Vector2 _position; + final double _angle; + late final MultiplierSpriteGroupComponent _sprite; + + @override + void onRemove() { + bloc.close(); + super.onRemove(); + } + + @override + Future onLoad() async { + await super.onLoad(); + _sprite = MultiplierSpriteGroupComponent( + position: _position, + litAssetPath: _value.litAssetPath, + dimmedAssetPath: _value.dimmedAssetPath, + angle: _angle, + current: bloc.state, + ); + await add(_sprite); + } +} + +/// Available multiplier values. +enum MultiplierValue { + x2, + x3, + x4, + x5, + x6, +} + +extension on MultiplierValue { + String get litAssetPath { + switch (this) { + case MultiplierValue.x2: + return Assets.images.multiplier.x2.lit.keyName; + case MultiplierValue.x3: + return Assets.images.multiplier.x3.lit.keyName; + case MultiplierValue.x4: + return Assets.images.multiplier.x4.lit.keyName; + case MultiplierValue.x5: + return Assets.images.multiplier.x5.lit.keyName; + case MultiplierValue.x6: + return Assets.images.multiplier.x6.lit.keyName; + } + } + + String get dimmedAssetPath { + switch (this) { + case MultiplierValue.x2: + return Assets.images.multiplier.x2.dimmed.keyName; + case MultiplierValue.x3: + return Assets.images.multiplier.x3.dimmed.keyName; + case MultiplierValue.x4: + return Assets.images.multiplier.x4.dimmed.keyName; + case MultiplierValue.x5: + return Assets.images.multiplier.x5.dimmed.keyName; + case MultiplierValue.x6: + return Assets.images.multiplier.x6.dimmed.keyName; + } + } +} + +/// {@template multiplier_sprite_group_component} +/// A [SpriteGroupComponent] for a [Multiplier] with lit and dimmed states. +/// {@endtemplate} +@visibleForTesting +class MultiplierSpriteGroupComponent + extends SpriteGroupComponent + with HasGameRef, ParentIsA { + /// {@macro multiplier_sprite_group_component} + MultiplierSpriteGroupComponent({ + required Vector2 position, + required String litAssetPath, + required String dimmedAssetPath, + required double angle, + required MultiplierState current, + }) : _litAssetPath = litAssetPath, + _dimmedAssetPath = dimmedAssetPath, + super( + anchor: Anchor.center, + position: position, + angle: angle, + current: current.spriteState, + ); + + final String _litAssetPath; + final String _dimmedAssetPath; + + @override + Future onLoad() async { + await super.onLoad(); + parent.bloc.stream.listen((state) => current = state.spriteState); + + final sprites = { + MultiplierSpriteState.lit: + Sprite(gameRef.images.fromCache(_litAssetPath)), + MultiplierSpriteState.dimmed: + Sprite(gameRef.images.fromCache(_dimmedAssetPath)), + }; + this.sprites = sprites; + size = sprites[current]!.originalSize / 10; + } +} diff --git a/packages/pinball_components/lib/src/components/render_priority.dart b/packages/pinball_components/lib/src/components/render_priority.dart index c63b2b31..3e7d5a29 100644 --- a/packages/pinball_components/lib/src/components/render_priority.dart +++ b/packages/pinball_components/lib/src/components/render_priority.dart @@ -20,7 +20,7 @@ abstract class RenderPriority { static const int ballOnSpaceshipRamp = _above + spaceshipRampBackgroundRailing; - /// Render priority for the [Ball] while it's on the [Spaceship]. + /// Render priority for the [Ball] while it's on the [AndroidSpaceship]. static const int ballOnSpaceship = _above + spaceshipSaucer; /// Render priority for the [Ball] while it's on the [SpaceshipRail]. @@ -91,7 +91,7 @@ abstract class RenderPriority { static const int spaceshipSaucer = _above + ballOnSpaceshipRail; - static const int spaceshipSaucerWall = _above + spaceshipSaucer; + static const int spaceshipLightBeam = _below + spaceshipSaucer; static const int androidHead = _above + spaceshipSaucer; diff --git a/packages/pinball_components/lib/src/components/spaceship.dart b/packages/pinball_components/lib/src/components/spaceship.dart deleted file mode 100644 index a52df81d..00000000 --- a/packages/pinball_components/lib/src/components/spaceship.dart +++ /dev/null @@ -1,246 +0,0 @@ -import 'dart:async'; -import 'dart:math'; - -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; -import 'package:pinball_flame/pinball_flame.dart'; - -/// {@template spaceship} -/// A [Blueprint] which creates the spaceship feature. -/// {@endtemplate} -class Spaceship extends Blueprint { - /// {@macro spaceship} - Spaceship({required Vector2 position}) - : super( - components: [ - SpaceshipSaucer()..initialPosition = position, - _SpaceshipEntrance()..initialPosition = position, - AndroidHead()..initialPosition = position, - _SpaceshipHole( - outsideLayer: Layer.spaceshipExitRail, - outsidePriority: RenderPriority.ballOnSpaceshipRail, - )..initialPosition = position - Vector2(5.2, -4.8), - _SpaceshipHole( - outsideLayer: Layer.board, - outsidePriority: RenderPriority.ballOnBoard, - )..initialPosition = position - Vector2(-7.2, -0.8), - SpaceshipWall()..initialPosition = position, - ], - ); - - /// Total size of the spaceship. - static final size = Vector2(25, 19); -} - -/// {@template spaceship_saucer} -/// A [BodyComponent] for the base, or the saucer of the spaceship -/// {@endtemplate} -class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered { - /// {@macro spaceship_saucer} - SpaceshipSaucer() - : super( - priority: RenderPriority.spaceshipSaucer, - renderBody: false, - children: [ - _SpaceshipSaucerSpriteComponent(), - ], - ) { - layer = Layer.spaceship; - } - - @override - Body createBody() { - final shape = CircleShape()..radius = 3; - final fixtureDef = FixtureDef( - shape, - isSensor: true, - ); - final bodyDef = BodyDef( - position: initialPosition, - userData: this, - ); - - return world.createBody(bodyDef)..createFixture(fixtureDef); - } -} - -class _SpaceshipSaucerSpriteComponent extends SpriteComponent with HasGameRef { - _SpaceshipSaucerSpriteComponent() - : super( - anchor: Anchor.center, - // TODO(alestiago): Refactor to use sprite orignial size instead. - size: Spaceship.size, - ); - - @override - Future onLoad() async { - await super.onLoad(); - - // TODO(alestiago): Use cached sprite. - sprite = await gameRef.loadSprite( - Assets.images.spaceship.saucer.keyName, - ); - } -} - -/// {@template spaceship_bridge} -/// A [BodyComponent] that provides both the collision and the rotation -/// animation for the bridge. -/// {@endtemplate} -class AndroidHead extends BodyComponent with InitialPosition, Layered { - /// {@macro spaceship_bridge} - AndroidHead() - : super( - priority: RenderPriority.androidHead, - children: [_AndroidHeadSpriteAnimation()], - renderBody: false, - ) { - layer = Layer.spaceship; - } - - @override - Body createBody() { - final circleShape = CircleShape()..radius = 2; - - final bodyDef = BodyDef( - position: initialPosition, - userData: this, - ); - - return world.createBody(bodyDef) - ..createFixture( - FixtureDef(circleShape)..restitution = 0.4, - ); - } -} - -class _AndroidHeadSpriteAnimation extends SpriteAnimationComponent - with HasGameRef { - @override - Future onLoad() async { - await super.onLoad(); - - final image = await gameRef.images.load( - Assets.images.spaceship.bridge.keyName, - ); - size = Vector2(8.2, 10); - position = Vector2(0, -2); - anchor = Anchor.center; - - final data = SpriteAnimationData.sequenced( - amount: 72, - amountPerRow: 24, - stepTime: 0.05, - textureSize: size * 10, - ); - animation = SpriteAnimation.fromFrameData(image, data); - } -} - -class _SpaceshipEntrance extends LayerSensor { - _SpaceshipEntrance() - : super( - insideLayer: Layer.spaceship, - orientation: LayerEntranceOrientation.up, - insidePriority: RenderPriority.ballOnSpaceship, - ) { - layer = Layer.spaceship; - } - - @override - Shape get shape { - final radius = Spaceship.size.y / 2; - return PolygonShape() - ..setAsEdge( - Vector2( - radius * cos(20 * pi / 180), - radius * sin(20 * pi / 180), - )..rotate(90 * pi / 180), - Vector2( - radius * cos(340 * pi / 180), - radius * sin(340 * pi / 180), - )..rotate(90 * pi / 180), - ); - } -} - -class _SpaceshipHole extends LayerSensor { - _SpaceshipHole({required Layer outsideLayer, required int outsidePriority}) - : super( - insideLayer: Layer.spaceship, - outsideLayer: outsideLayer, - orientation: LayerEntranceOrientation.down, - insidePriority: RenderPriority.ballOnSpaceship, - outsidePriority: outsidePriority, - ) { - layer = Layer.spaceship; - } - - @override - Shape get shape { - return ArcShape( - center: Vector2(0, -3.2), - arcRadius: 5, - angle: 1, - rotation: -2, - ); - } -} - -/// {@template spaceship_wall_shape} -/// The [ChainShape] that defines the shape of the [SpaceshipWall]. -/// {@endtemplate} -class _SpaceshipWallShape extends ChainShape { - /// {@macro spaceship_wall_shape} - _SpaceshipWallShape() { - final minorRadius = (Spaceship.size.y - 2) / 2; - final majorRadius = (Spaceship.size.x - 2) / 2; - - createChain( - [ - // TODO(alestiago): Try converting this logic to radian. - for (var angle = 20; angle <= 340; angle++) - Vector2( - minorRadius * cos(angle * pi / 180), - majorRadius * sin(angle * pi / 180), - ), - ], - ); - } -} - -/// {@template spaceship_wall} -/// A [BodyComponent] that provides the collision for the wall -/// surrounding the spaceship. -/// -/// It has a small opening to allow the [Ball] to get inside the spaceship -/// saucer. -/// -/// It also contains the [SpriteComponent] for the lower wall -/// {@endtemplate} -class SpaceshipWall extends BodyComponent with InitialPosition, Layered { - /// {@macro spaceship_wall} - SpaceshipWall() - : super( - priority: RenderPriority.spaceshipSaucerWall, - renderBody: false, - ) { - layer = Layer.spaceship; - } - - @override - Body createBody() { - final shape = _SpaceshipWallShape(); - final fixtureDef = FixtureDef(shape); - - final bodyDef = BodyDef( - position: initialPosition, - userData: this, - angle: -1.7, - ); - - return world.createBody(bodyDef)..createFixture(fixtureDef); - } -} diff --git a/packages/pinball_components/lib/src/components/spaceship_rail.dart b/packages/pinball_components/lib/src/components/spaceship_rail.dart index 91540c62..df9fc16c 100644 --- a/packages/pinball_components/lib/src/components/spaceship_rail.dart +++ b/packages/pinball_components/lib/src/components/spaceship_rail.dart @@ -6,7 +6,7 @@ import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; /// {@template spaceship_rail} -/// A [Blueprint] for the rail exiting the [Spaceship]. +/// A [Blueprint] for the rail exiting the [AndroidSpaceship]. /// {@endtemplate} class SpaceshipRail extends Blueprint { /// {@macro spaceship_rail} @@ -116,7 +116,7 @@ class _SpaceshipRailSpriteComponent extends SpriteComponent with HasGameRef { final sprite = Sprite( gameRef.images.fromCache( - Assets.images.spaceship.rail.main.keyName, + Assets.images.android.rail.main.keyName, ), ); this.sprite = sprite; @@ -139,7 +139,7 @@ class _SpaceshipRailExitSpriteComponent extends SpriteComponent final sprite = Sprite( gameRef.images.fromCache( - Assets.images.spaceship.rail.exit.keyName, + Assets.images.android.rail.exit.keyName, ), ); this.sprite = sprite; diff --git a/packages/pinball_components/lib/src/components/spaceship_ramp.dart b/packages/pinball_components/lib/src/components/spaceship_ramp.dart index c9a1d574..6a034daa 100644 --- a/packages/pinball_components/lib/src/components/spaceship_ramp.dart +++ b/packages/pinball_components/lib/src/components/spaceship_ramp.dart @@ -8,7 +8,7 @@ import 'package:pinball_components/pinball_components.dart' hide Assets; import 'package:pinball_flame/pinball_flame.dart'; /// {@template spaceship_ramp} -/// A [Blueprint] which creates the ramp leading into the [Spaceship]. +/// A [Blueprint] which creates the ramp leading into the [AndroidSpaceship]. /// {@endtemplate} class SpaceshipRamp extends Blueprint { /// {@macro spaceship_ramp} @@ -73,17 +73,17 @@ extension on SpaceshipRampArrowSpriteState { String get path { switch (this) { case SpaceshipRampArrowSpriteState.inactive: - return Assets.images.spaceship.ramp.arrow.inactive.keyName; + return Assets.images.android.ramp.arrow.inactive.keyName; case SpaceshipRampArrowSpriteState.active1: - return Assets.images.spaceship.ramp.arrow.active1.keyName; + return Assets.images.android.ramp.arrow.active1.keyName; case SpaceshipRampArrowSpriteState.active2: - return Assets.images.spaceship.ramp.arrow.active2.keyName; + return Assets.images.android.ramp.arrow.active2.keyName; case SpaceshipRampArrowSpriteState.active3: - return Assets.images.spaceship.ramp.arrow.active3.keyName; + return Assets.images.android.ramp.arrow.active3.keyName; case SpaceshipRampArrowSpriteState.active4: - return Assets.images.spaceship.ramp.arrow.active4.keyName; + return Assets.images.android.ramp.arrow.active4.keyName; case SpaceshipRampArrowSpriteState.active5: - return Assets.images.spaceship.ramp.arrow.active5.keyName; + return Assets.images.android.ramp.arrow.active5.keyName; } } @@ -161,7 +161,7 @@ class _SpaceshipRampBackgroundRailingSpriteComponent extends SpriteComponent await super.onLoad(); final sprite = Sprite( gameRef.images.fromCache( - Assets.images.spaceship.ramp.railingBackground.keyName, + Assets.images.android.ramp.railingBackground.keyName, ), ); this.sprite = sprite; @@ -182,7 +182,7 @@ class _SpaceshipRampBackgroundRampSpriteComponent extends SpriteComponent await super.onLoad(); final sprite = Sprite( gameRef.images.fromCache( - Assets.images.spaceship.ramp.main.keyName, + Assets.images.android.ramp.main.keyName, ), ); this.sprite = sprite; @@ -234,7 +234,7 @@ class _SpaceshipRampBoardOpeningSpriteComponent extends SpriteComponent await super.onLoad(); final sprite = Sprite( gameRef.images.fromCache( - Assets.images.spaceship.ramp.boardOpening.keyName, + Assets.images.android.ramp.boardOpening.keyName, ), ); this.sprite = sprite; @@ -304,7 +304,7 @@ class _SpaceshipRampForegroundRailingSpriteComponent extends SpriteComponent await super.onLoad(); final sprite = Sprite( gameRef.images.fromCache( - Assets.images.spaceship.ramp.railingForeground.keyName, + Assets.images.android.ramp.railingForeground.keyName, ), ); this.sprite = sprite; diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index c260b626..0769f484 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -45,7 +45,6 @@ flutter: - asset: fonts/PixeloidMono-1G8ae.ttf assets: - - assets/images/ - assets/images/ball/ - assets/images/baseboard/ - assets/images/boundary/ @@ -57,15 +56,16 @@ flutter: - assets/images/dash/bumper/a/ - assets/images/dash/bumper/b/ - assets/images/dash/bumper/main/ - - assets/images/spaceship/ - - assets/images/spaceship/rail/ - - assets/images/spaceship/ramp/ - - assets/images/spaceship/ramp/arrow/ + - assets/images/android/spaceship/ + - assets/images/android/rail/ + - assets/images/android/ramp/ + - assets/images/android/ramp/arrow/ + - assets/images/android/bumper/a/ + - assets/images/android/bumper/b/ + - assets/images/android/bumper/cow/ - assets/images/kicker/ - assets/images/plunger/ - assets/images/slingshot/ - - assets/images/android_bumper/a/ - - assets/images/android_bumper/b/ - assets/images/sparky/ - assets/images/sparky/computer/ - assets/images/sparky/bumper/a/ @@ -74,6 +74,11 @@ flutter: - assets/images/backboard/ - assets/images/google_word/ - assets/images/signpost/ + - assets/images/multiplier/x2/ + - assets/images/multiplier/x3/ + - assets/images/multiplier/x4/ + - assets/images/multiplier/x5/ + - assets/images/multiplier/x6/ flutter_gen: line_length: 80 diff --git a/packages/pinball_components/sandbox/lib/main.dart b/packages/pinball_components/sandbox/lib/main.dart index 96083717..1f0077fb 100644 --- a/packages/pinball_components/sandbox/lib/main.dart +++ b/packages/pinball_components/sandbox/lib/main.dart @@ -27,6 +27,7 @@ void main() { addScoreTextStories(dashbook); addBackboardStories(dashbook); addDinoWallStories(dashbook); + addMultipliersStories(dashbook); runApp(dashbook); } diff --git a/packages/pinball_components/sandbox/lib/stories/android_acres/android_bumper_a_game.dart b/packages/pinball_components/sandbox/lib/stories/android_acres/android_bumper_a_game.dart index 4dcd1cb8..32638c2d 100644 --- a/packages/pinball_components/sandbox/lib/stories/android_acres/android_bumper_a_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/android_acres/android_bumper_a_game.dart @@ -9,8 +9,8 @@ class AndroidBumperAGame extends BallGame { : super( color: const Color(0xFF0000FF), imagesFileNames: [ - Assets.images.androidBumper.a.lit.keyName, - Assets.images.androidBumper.a.dimmed.keyName, + Assets.images.android.bumper.a.lit.keyName, + Assets.images.android.bumper.a.dimmed.keyName, ], ); diff --git a/packages/pinball_components/sandbox/lib/stories/android_acres/android_bumper_b_game.dart b/packages/pinball_components/sandbox/lib/stories/android_acres/android_bumper_b_game.dart index e504fe1e..bfd4206c 100644 --- a/packages/pinball_components/sandbox/lib/stories/android_acres/android_bumper_b_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/android_acres/android_bumper_b_game.dart @@ -9,8 +9,8 @@ class AndroidBumperBGame extends BallGame { : super( color: const Color(0xFF0000FF), imagesFileNames: [ - Assets.images.androidBumper.b.lit.keyName, - Assets.images.androidBumper.b.dimmed.keyName, + Assets.images.android.bumper.b.lit.keyName, + Assets.images.android.bumper.b.dimmed.keyName, ], ); diff --git a/packages/pinball_components/sandbox/lib/stories/android_acres/android_bumper_cow_game.dart b/packages/pinball_components/sandbox/lib/stories/android_acres/android_bumper_cow_game.dart new file mode 100644 index 00000000..ac1bc6fe --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/android_acres/android_bumper_cow_game.dart @@ -0,0 +1,33 @@ +import 'dart:async'; + +import 'package:flame/extensions.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:sandbox/stories/ball/basic_ball_game.dart'; + +class AndroidBumperCowGame extends BallGame { + AndroidBumperCowGame() + : super( + imagesFileNames: [ + Assets.images.android.bumper.cow.lit.keyName, + Assets.images.android.bumper.cow.dimmed.keyName, + ], + ); + + static const description = ''' + Shows how a AndroidBumper.cow is rendered. + + - Activate the "trace" parameter to overlay the body. +'''; + + @override + Future onLoad() async { + await super.onLoad(); + + camera.followVector2(Vector2.zero()); + await add( + AndroidBumper.cow()..priority = 1, + ); + + await traceAllBodies(); + } +} diff --git a/packages/pinball_components/sandbox/lib/stories/android_acres/android_spaceship_game.dart b/packages/pinball_components/sandbox/lib/stories/android_acres/android_spaceship_game.dart new file mode 100644 index 00000000..076b2d2b --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/android_acres/android_spaceship_game.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +import 'package:flame/input.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:sandbox/stories/ball/basic_ball_game.dart'; + +class AndroidSpaceshipGame extends BallGame { + AndroidSpaceshipGame() + : super( + ballPriority: RenderPriority.ballOnSpaceship, + ballLayer: Layer.spaceship, + imagesFileNames: [ + Assets.images.android.spaceship.saucer.keyName, + Assets.images.android.spaceship.animatronic.keyName, + Assets.images.android.spaceship.lightBeam.keyName, + ], + ); + + static const description = ''' + Shows how the AndroidSpaceship is rendered. + + - Activate the "trace" parameter to overlay the body. + - Tap anywhere on the screen to spawn a Ball into the game. +'''; + + @override + Future onLoad() async { + await super.onLoad(); + + camera.followVector2(Vector2.zero()); + await addFromBlueprint( + AndroidSpaceship(position: Vector2.zero()), + ); + + await traceAllBodies(); + } +} diff --git a/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_game.dart b/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_game.dart deleted file mode 100644 index ad897dd4..00000000 --- a/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_game.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'dart:async'; - -import 'package:flame/input.dart'; -import 'package:flutter/material.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; -import 'package:sandbox/common/common.dart'; - -class SpaceshipGame extends AssetsGame with TapDetector { - static const description = ''' - Shows how a Spaceship works. - - - Tap anywhere on the screen to spawn a Ball into the game. -'''; - - @override - Future onLoad() async { - await super.onLoad(); - - camera.followVector2(Vector2.zero()); - await addFromBlueprint( - Spaceship(position: Vector2.zero()), - ); - await ready(); - } - - @override - void onTapUp(TapUpInfo info) { - add( - Ball(baseColor: Colors.blue) - ..initialPosition = info.eventPosition.game - ..layer = Layer.spaceshipEntranceRamp, - ); - } -} diff --git a/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_rail_game.dart b/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_rail_game.dart index 4bd067fa..87bac14d 100644 --- a/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_rail_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_rail_game.dart @@ -13,8 +13,8 @@ class SpaceshipRailGame extends BallGame { ballPriority: RenderPriority.ballOnSpaceshipRail, ballLayer: Layer.spaceshipExitRail, imagesFileNames: [ - Assets.images.spaceship.rail.main.keyName, - Assets.images.spaceship.rail.exit.keyName, + Assets.images.android.rail.main.keyName, + Assets.images.android.rail.exit.keyName, ], ); diff --git a/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_ramp_game.dart b/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_ramp_game.dart index 1817f40a..43944a37 100644 --- a/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_ramp_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/android_acres/spaceship_ramp_game.dart @@ -14,16 +14,16 @@ class SpaceshipRampGame extends BallGame with KeyboardEvents { ballPriority: RenderPriority.ballOnSpaceshipRamp, ballLayer: Layer.spaceshipEntranceRamp, imagesFileNames: [ - Assets.images.spaceship.ramp.railingBackground.keyName, - Assets.images.spaceship.ramp.main.keyName, - Assets.images.spaceship.ramp.boardOpening.keyName, - Assets.images.spaceship.ramp.railingForeground.keyName, - Assets.images.spaceship.ramp.arrow.inactive.keyName, - Assets.images.spaceship.ramp.arrow.active1.keyName, - Assets.images.spaceship.ramp.arrow.active2.keyName, - Assets.images.spaceship.ramp.arrow.active3.keyName, - Assets.images.spaceship.ramp.arrow.active4.keyName, - Assets.images.spaceship.ramp.arrow.active5.keyName, + Assets.images.android.ramp.railingBackground.keyName, + Assets.images.android.ramp.main.keyName, + Assets.images.android.ramp.boardOpening.keyName, + Assets.images.android.ramp.railingForeground.keyName, + Assets.images.android.ramp.arrow.inactive.keyName, + Assets.images.android.ramp.arrow.active1.keyName, + Assets.images.android.ramp.arrow.active2.keyName, + Assets.images.android.ramp.arrow.active3.keyName, + Assets.images.android.ramp.arrow.active4.keyName, + Assets.images.android.ramp.arrow.active5.keyName, ], ); diff --git a/packages/pinball_components/sandbox/lib/stories/android_acres/stories.dart b/packages/pinball_components/sandbox/lib/stories/android_acres/stories.dart index 92ddd5d5..ec4a783e 100644 --- a/packages/pinball_components/sandbox/lib/stories/android_acres/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/android_acres/stories.dart @@ -2,7 +2,8 @@ import 'package:dashbook/dashbook.dart'; import 'package:sandbox/common/common.dart'; import 'package:sandbox/stories/android_acres/android_bumper_a_game.dart'; import 'package:sandbox/stories/android_acres/android_bumper_b_game.dart'; -import 'package:sandbox/stories/android_acres/spaceship_game.dart'; +import 'package:sandbox/stories/android_acres/android_bumper_cow_game.dart'; +import 'package:sandbox/stories/android_acres/android_spaceship_game.dart'; import 'package:sandbox/stories/android_acres/spaceship_rail_game.dart'; import 'package:sandbox/stories/android_acres/spaceship_ramp_game.dart'; @@ -19,9 +20,14 @@ void addAndroidAcresStories(Dashbook dashbook) { gameBuilder: (_) => AndroidBumperBGame(), ) ..addGame( - title: 'Spaceship', - description: SpaceshipGame.description, - gameBuilder: (_) => SpaceshipGame(), + title: 'Android Bumper Cow', + description: AndroidBumperCowGame.description, + gameBuilder: (_) => AndroidBumperCowGame(), + ) + ..addGame( + title: 'Android Spaceship', + description: AndroidSpaceshipGame.description, + gameBuilder: (_) => AndroidSpaceshipGame(), ) ..addGame( title: 'Spaceship Rail', diff --git a/packages/pinball_components/sandbox/lib/stories/multipliers/multipliers_game.dart b/packages/pinball_components/sandbox/lib/stories/multipliers/multipliers_game.dart new file mode 100644 index 00000000..ae641623 --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/multipliers/multipliers_game.dart @@ -0,0 +1,97 @@ +import 'dart:math' as math; +import 'package:flame/input.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:sandbox/stories/ball/basic_ball_game.dart'; + +class MultipliersGame extends BallGame with KeyboardEvents { + MultipliersGame() + : super( + imagesFileNames: [ + Assets.images.multiplier.x2.lit.keyName, + Assets.images.multiplier.x2.dimmed.keyName, + Assets.images.multiplier.x3.lit.keyName, + Assets.images.multiplier.x3.dimmed.keyName, + Assets.images.multiplier.x4.lit.keyName, + Assets.images.multiplier.x4.dimmed.keyName, + Assets.images.multiplier.x5.lit.keyName, + Assets.images.multiplier.x5.dimmed.keyName, + Assets.images.multiplier.x6.lit.keyName, + Assets.images.multiplier.x6.dimmed.keyName, + ], + ); + + static const description = ''' + Shows how the Multipliers are rendered. + + - Tap anywhere on the screen to spawn a ball into the game. + - Press digits 2 to 6 for toggle state multipliers 2 to 6. +'''; + + final List multipliers = [ + Multiplier.x2( + position: Vector2(-20, 0), + angle: -15 * math.pi / 180, + ), + Multiplier.x3( + position: Vector2(20, -5), + angle: 15 * math.pi / 180, + ), + Multiplier.x4( + position: Vector2(0, -15), + angle: 0, + ), + Multiplier.x5( + position: Vector2(-10, -25), + angle: -3 * math.pi / 180, + ), + Multiplier.x6( + position: Vector2(10, -35), + angle: 8 * math.pi / 180, + ), + ]; + + @override + Future onLoad() async { + await super.onLoad(); + + camera.followVector2(Vector2.zero()); + + await addAll(multipliers); + await traceAllBodies(); + } + + @override + KeyEventResult onKeyEvent( + RawKeyEvent event, + Set keysPressed, + ) { + if (event is RawKeyDownEvent) { + var currentMultiplier = 1; + + if (event.logicalKey == LogicalKeyboardKey.digit2) { + currentMultiplier = 2; + } + if (event.logicalKey == LogicalKeyboardKey.digit3) { + currentMultiplier = 3; + } + if (event.logicalKey == LogicalKeyboardKey.digit4) { + currentMultiplier = 4; + } + if (event.logicalKey == LogicalKeyboardKey.digit5) { + currentMultiplier = 5; + } + if (event.logicalKey == LogicalKeyboardKey.digit6) { + currentMultiplier = 6; + } + + for (final multiplier in multipliers) { + multiplier.bloc.next(currentMultiplier); + } + + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + } +} diff --git a/packages/pinball_components/sandbox/lib/stories/multipliers/stories.dart b/packages/pinball_components/sandbox/lib/stories/multipliers/stories.dart new file mode 100644 index 00000000..48b6da6d --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/multipliers/stories.dart @@ -0,0 +1,11 @@ +import 'package:dashbook/dashbook.dart'; +import 'package:sandbox/common/common.dart'; +import 'package:sandbox/stories/multipliers/multipliers_game.dart'; + +void addMultipliersStories(Dashbook dashbook) { + dashbook.storiesOf('Multipliers').addGame( + title: 'Multipliers', + description: MultipliersGame.description, + gameBuilder: (_) => MultipliersGame(), + ); +} diff --git a/packages/pinball_components/sandbox/lib/stories/stories.dart b/packages/pinball_components/sandbox/lib/stories/stories.dart index 9e1d44d8..d5e410b4 100644 --- a/packages/pinball_components/sandbox/lib/stories/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/stories.dart @@ -10,6 +10,7 @@ export 'flutter_forest/stories.dart'; export 'google_word/stories.dart'; export 'launch_ramp/stories.dart'; export 'layer/stories.dart'; +export 'multipliers/stories.dart'; export 'plunger/stories.dart'; export 'score_text/stories.dart'; export 'slingshot/stories.dart'; diff --git a/packages/pinball_components/sandbox/pubspec.lock b/packages/pinball_components/sandbox/pubspec.lock index 8d61da32..d2500fbe 100644 --- a/packages/pinball_components/sandbox/pubspec.lock +++ b/packages/pinball_components/sandbox/pubspec.lock @@ -15,6 +15,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.8.2" + bloc: + dependency: transitive + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "8.0.3" boolean_selector: dependency: transitive description: @@ -171,7 +178,7 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.4" + version: "0.6.3" json_annotation: dependency: transitive description: @@ -199,7 +206,7 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.3" meta: dependency: transitive description: @@ -220,7 +227,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.0" path_provider_linux: dependency: transitive description: @@ -351,7 +358,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.8.1" stack_trace: dependency: transitive description: @@ -386,7 +393,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.8" typed_data: dependency: transitive description: @@ -456,7 +463,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.1.1" very_good_analysis: dependency: "direct dev" description: diff --git a/packages/pinball_components/test/helpers/mocks.dart b/packages/pinball_components/test/helpers/mocks.dart index 2230becb..33c5670d 100644 --- a/packages/pinball_components/test/helpers/mocks.dart +++ b/packages/pinball_components/test/helpers/mocks.dart @@ -24,3 +24,5 @@ class MockGoogleLetterCubit extends Mock implements GoogleLetterCubit {} class MockSparkyBumperCubit extends Mock implements SparkyBumperCubit {} class MockDashNestBumperCubit extends Mock implements DashNestBumperCubit {} + +class MockMultiplierCubit extends Mock implements MultiplierCubit {} diff --git a/packages/pinball_components/test/src/components/android_bumper/android_bumper_test.dart b/packages/pinball_components/test/src/components/android_bumper/android_bumper_test.dart index abc51a28..a5256b79 100644 --- a/packages/pinball_components/test/src/components/android_bumper/android_bumper_test.dart +++ b/packages/pinball_components/test/src/components/android_bumper/android_bumper_test.dart @@ -13,10 +13,12 @@ import '../../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); final assets = [ - Assets.images.androidBumper.a.lit.keyName, - Assets.images.androidBumper.a.dimmed.keyName, - Assets.images.androidBumper.b.lit.keyName, - Assets.images.androidBumper.b.dimmed.keyName, + Assets.images.android.bumper.a.lit.keyName, + Assets.images.android.bumper.a.dimmed.keyName, + Assets.images.android.bumper.b.lit.keyName, + Assets.images.android.bumper.b.dimmed.keyName, + Assets.images.android.bumper.cow.lit.keyName, + Assets.images.android.bumper.cow.dimmed.keyName, ]; final flameTester = FlameTester(() => TestGame(assets)); @@ -33,6 +35,12 @@ void main() { expect(game.contains(androidBumper), isTrue); }); + flameTester.test('"cow" loads correctly', (game) async { + final androidBumper = AndroidBumper.cow(); + await game.ensureAdd(androidBumper); + expect(game.contains(androidBumper), isTrue); + }); + // TODO(alestiago): Consider refactoring once the following is merged: // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs diff --git a/packages/pinball_components/test/src/components/android_spaceship_test.dart b/packages/pinball_components/test/src/components/android_spaceship_test.dart new file mode 100644 index 00000000..92219a64 --- /dev/null +++ b/packages/pinball_components/test/src/components/android_spaceship_test.dart @@ -0,0 +1,66 @@ +// 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 'package:pinball_flame/pinball_flame.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + group('AndroidSpaceship', () { + group('Spaceship', () { + final assets = [ + Assets.images.android.spaceship.saucer.keyName, + Assets.images.android.spaceship.animatronic.keyName, + Assets.images.android.spaceship.lightBeam.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); + + flameTester.test('loads correctly', (game) async { + await game.addFromBlueprint(AndroidSpaceship(position: Vector2.zero())); + await game.ready(); + }); + + flameTester.testGameWidget( + 'renders correctly', + setUp: (game, tester) async { + await game.images.loadAll(assets); + await game + .addFromBlueprint(AndroidSpaceship(position: Vector2.zero())); + game.camera.followVector2(Vector2.zero()); + await game.ready(); + await tester.pump(); + }, + verify: (game, tester) async { + final animationDuration = game + .descendants() + .whereType() + .last + .animation! + .totalDuration(); + + await expectLater( + find.byGame(), + matchesGoldenFile('golden/android_spaceship/start.png'), + ); + + game.update(animationDuration * 0.5); + await tester.pump(); + await expectLater( + find.byGame(), + matchesGoldenFile('golden/android_spaceship/middle.png'), + ); + + game.update(animationDuration * 0.5); + await tester.pump(); + await expectLater( + find.byGame(), + matchesGoldenFile('golden/android_spaceship/end.png'), + ); + }, + ); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/bumping_behavior_test.dart b/packages/pinball_components/test/src/components/bumping_behavior_test.dart index d346a0ae..dd0493f7 100644 --- a/packages/pinball_components/test/src/components/bumping_behavior_test.dart +++ b/packages/pinball_components/test/src/components/bumping_behavior_test.dart @@ -7,22 +7,16 @@ import 'package:mocktail/mocktail.dart'; import 'package:pinball_components/src/components/bumping_behavior.dart'; import '../../helpers/helpers.dart'; -import 'layer_test.dart'; -class MockContactImpulse extends Mock implements ContactImpulse {} +class _MockContact extends Mock implements Contact {} -class MockManifold extends Mock implements Manifold {} +class _MockContactImpulse extends Mock implements ContactImpulse {} -class TestHeavyBodyComponent extends BodyComponent { +class _TestBodyComponent extends BodyComponent { @override - Body createBody() { - final shape = CircleShape(); - return world.createBody( - BodyDef( - type: BodyType.dynamic, - ), - )..createFixtureFromShape(shape, 20); - } + Body createBody() => world.createBody( + BodyDef(type: BodyType.dynamic), + )..createFixtureFromShape(CircleShape(), 1); } void main() { @@ -32,7 +26,7 @@ void main() { group('BumpingBehavior', () { flameTester.test('can be added', (game) async { final behavior = BumpingBehavior(strength: 0); - final component = TestBodyComponent(); + final component = _TestBodyComponent(); await component.add(behavior); await game.ensureAdd(component); }); @@ -40,16 +34,18 @@ void main() { flameTester.testGameWidget( 'the bump is greater when the strengh is greater', setUp: (game, tester) async { - final component1 = TestBodyComponent(); - final behavior1 = BumpingBehavior(strength: 1); + final component1 = _TestBodyComponent(); + final behavior1 = BumpingBehavior(strength: 1) + ..worldManifold.normal.setFrom(Vector2.all(1)); await component1.add(behavior1); - final component2 = TestBodyComponent(); - final behavior2 = BumpingBehavior(strength: 2); + final component2 = _TestBodyComponent(); + final behavior2 = BumpingBehavior(strength: 2) + ..worldManifold.normal.setFrom(Vector2.all(1)); await component2.add(behavior2); - final dummy1 = TestHeavyBodyComponent(); - final dummy2 = TestHeavyBodyComponent(); + final dummy1 = _TestBodyComponent(); + final dummy2 = _TestBodyComponent(); await game.ensureAddAll([ component1, @@ -58,14 +54,8 @@ void main() { dummy2, ]); - expect(dummy1.body.inverseMass, greaterThan(0)); - expect(dummy2.body.inverseMass, greaterThan(0)); - - final contact = MockContact(); - final manifold = MockManifold(); - final contactImpulse = MockContactImpulse(); - when(() => manifold.localPoint).thenReturn(Vector2.all(1)); - when(() => contact.manifold).thenReturn(manifold); + final contact = _MockContact(); + final contactImpulse = _MockContactImpulse(); behavior1.postSolve(dummy1, contact, contactImpulse); behavior2.postSolve(dummy2, contact, contactImpulse); diff --git a/packages/pinball_components/test/src/components/golden/android_spaceship/end.png b/packages/pinball_components/test/src/components/golden/android_spaceship/end.png new file mode 100644 index 00000000..c2a0631a Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/android_spaceship/end.png differ diff --git a/packages/pinball_components/test/src/components/golden/android_spaceship/middle.png b/packages/pinball_components/test/src/components/golden/android_spaceship/middle.png new file mode 100644 index 00000000..c6651abd Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/android_spaceship/middle.png differ diff --git a/packages/pinball_components/test/src/components/golden/android_spaceship/start.png b/packages/pinball_components/test/src/components/golden/android_spaceship/start.png new file mode 100644 index 00000000..25e8863a Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/android_spaceship/start.png differ diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x2-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x2-dimmed.png new file mode 100644 index 00000000..ca2d8bf1 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/multipliers/x2-dimmed.png differ diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x2-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x2-lit.png new file mode 100644 index 00000000..94001e27 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/multipliers/x2-lit.png differ diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x3-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x3-dimmed.png new file mode 100644 index 00000000..4727ea3e Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/multipliers/x3-dimmed.png differ diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x3-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x3-lit.png new file mode 100644 index 00000000..f2f84178 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/multipliers/x3-lit.png differ diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x4-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x4-dimmed.png new file mode 100644 index 00000000..76c84994 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/multipliers/x4-dimmed.png differ diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x4-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x4-lit.png new file mode 100644 index 00000000..b4918e62 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/multipliers/x4-lit.png differ diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x5-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x5-dimmed.png new file mode 100644 index 00000000..2bbbf1ef Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/multipliers/x5-dimmed.png differ diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x5-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x5-lit.png new file mode 100644 index 00000000..5e750af8 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/multipliers/x5-lit.png differ diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x6-dimmed.png b/packages/pinball_components/test/src/components/golden/multipliers/x6-dimmed.png new file mode 100644 index 00000000..aff09619 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/multipliers/x6-dimmed.png differ diff --git a/packages/pinball_components/test/src/components/golden/multipliers/x6-lit.png b/packages/pinball_components/test/src/components/golden/multipliers/x6-lit.png new file mode 100644 index 00000000..7e5edc10 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/multipliers/x6-lit.png differ diff --git a/packages/pinball_components/test/src/components/golden/spaceship.png b/packages/pinball_components/test/src/components/golden/spaceship.png deleted file mode 100644 index d43db8c7..00000000 Binary files a/packages/pinball_components/test/src/components/golden/spaceship.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/multiplier/cubit/multiplier_cubit_test.dart b/packages/pinball_components/test/src/components/multiplier/cubit/multiplier_cubit_test.dart new file mode 100644 index 00000000..35ed652e --- /dev/null +++ b/packages/pinball_components/test/src/components/multiplier/cubit/multiplier_cubit_test.dart @@ -0,0 +1,118 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +void main() { + group( + 'MultiplierCubit', + () { + blocTest( + "emits [lit] when 'next' on x2 dimmed with x2 multiplier value", + build: () => MultiplierCubit(MultiplierValue.x2), + act: (bloc) => bloc.next(2), + expect: () => [ + isA() + ..having( + (state) => state.spriteState, + 'spriteState', + MultiplierSpriteState.lit, + ), + ], + ); + + blocTest( + "emits [lit] when 'next' on x3 dimmed with x3 multiplier value", + build: () => MultiplierCubit(MultiplierValue.x3), + act: (bloc) => bloc.next(3), + expect: () => [ + isA() + ..having( + (state) => state.spriteState, + 'spriteState', + MultiplierSpriteState.lit, + ), + ], + ); + + blocTest( + "emits [lit] when 'next' on x4 dimmed with x4 multiplier value", + build: () => MultiplierCubit(MultiplierValue.x4), + act: (bloc) => bloc.next(4), + expect: () => [ + isA() + ..having( + (state) => state.spriteState, + 'spriteState', + MultiplierSpriteState.lit, + ), + ], + ); + + blocTest( + "emits [lit] when 'next' on x5 dimmed with x5 multiplier value", + build: () => MultiplierCubit(MultiplierValue.x5), + act: (bloc) => bloc.next(5), + expect: () => [ + isA() + ..having( + (state) => state.spriteState, + 'spriteState', + MultiplierSpriteState.lit, + ), + ], + ); + + blocTest( + "emits [lit] when 'next' on x6 dimmed with x6 multiplier value", + build: () => MultiplierCubit(MultiplierValue.x6), + act: (bloc) => bloc.next(6), + expect: () => [ + isA() + ..having( + (state) => state.spriteState, + 'spriteState', + MultiplierSpriteState.lit, + ), + ], + ); + + blocTest( + "emits [dimmed] when 'next' on lit with different multiplier value", + build: () => MultiplierCubit(MultiplierValue.x2), + seed: () => MultiplierState( + value: MultiplierValue.x2, + spriteState: MultiplierSpriteState.lit, + ), + act: (bloc) => bloc.next(3), + expect: () => [ + isA() + ..having( + (state) => state.spriteState, + 'spriteState', + MultiplierSpriteState.dimmed, + ), + ], + ); + + blocTest( + "emits nothing when 'next' on lit with same multiplier value", + build: () => MultiplierCubit(MultiplierValue.x2), + seed: () => MultiplierState( + value: MultiplierValue.x2, + spriteState: MultiplierSpriteState.lit, + ), + act: (bloc) => bloc.next(2), + expect: () => [], + ); + + blocTest( + "emits nothing when 'next' on dimmed with different multiplier value", + build: () => MultiplierCubit(MultiplierValue.x2), + act: (bloc) => bloc.next(3), + expect: () => [], + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/multiplier/cubit/multiplier_state_test.dart b/packages/pinball_components/test/src/components/multiplier/cubit/multiplier_state_test.dart new file mode 100644 index 00000000..9789d7c5 --- /dev/null +++ b/packages/pinball_components/test/src/components/multiplier/cubit/multiplier_state_test.dart @@ -0,0 +1,75 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/src/pinball_components.dart'; + +void main() { + group('MultiplierState', () { + test('supports value equality', () { + expect( + MultiplierState( + value: MultiplierValue.x2, + spriteState: MultiplierSpriteState.lit, + ), + equals( + MultiplierState( + value: MultiplierValue.x2, + spriteState: MultiplierSpriteState.lit, + ), + ), + ); + }); + + group('constructor', () { + test('can be instantiated', () { + expect( + MultiplierState( + value: MultiplierValue.x2, + spriteState: MultiplierSpriteState.lit, + ), + isNotNull, + ); + }); + }); + + group('copyWith', () { + test( + 'copies correctly ' + 'when no argument specified', + () { + const multiplierState = MultiplierState( + value: MultiplierValue.x2, + spriteState: MultiplierSpriteState.lit, + ); + expect( + multiplierState.copyWith(), + equals(multiplierState), + ); + }, + ); + + test( + 'copies correctly ' + 'when all arguments specified', + () { + const multiplierState = MultiplierState( + value: MultiplierValue.x2, + spriteState: MultiplierSpriteState.lit, + ); + final otherMultiplierState = MultiplierState( + value: MultiplierValue.x2, + spriteState: MultiplierSpriteState.dimmed, + ); + expect(multiplierState, isNot(equals(otherMultiplierState))); + + expect( + multiplierState.copyWith( + spriteState: MultiplierSpriteState.dimmed, + ), + equals(otherMultiplierState), + ); + }, + ); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart b/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart new file mode 100644 index 00000000..edc2735f --- /dev/null +++ b/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart @@ -0,0 +1,517 @@ +// ignore_for_file: cascade_invocations, prefer_const_constructors + +import 'package:bloc_test/bloc_test.dart'; +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'; + +void main() { + final bloc = MockMultiplierCubit(); + + group('Multiplier', () { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + Assets.images.multiplier.x2.lit.keyName, + Assets.images.multiplier.x2.dimmed.keyName, + Assets.images.multiplier.x3.lit.keyName, + Assets.images.multiplier.x3.dimmed.keyName, + Assets.images.multiplier.x4.lit.keyName, + Assets.images.multiplier.x4.dimmed.keyName, + Assets.images.multiplier.x5.lit.keyName, + Assets.images.multiplier.x5.dimmed.keyName, + Assets.images.multiplier.x6.lit.keyName, + Assets.images.multiplier.x6.dimmed.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); + + flameTester.test('"x2" loads correctly', (game) async { + final multiplier = Multiplier.x2( + position: Vector2.zero(), + angle: 0, + ); + await game.ensureAdd(multiplier); + expect(game.contains(multiplier), isTrue); + }); + + flameTester.test('"x3" loads correctly', (game) async { + final multiplier = Multiplier.x3( + position: Vector2.zero(), + angle: 0, + ); + await game.ensureAdd(multiplier); + expect(game.contains(multiplier), isTrue); + }); + + flameTester.test('"x4" loads correctly', (game) async { + final multiplier = Multiplier.x4( + position: Vector2.zero(), + angle: 0, + ); + await game.ensureAdd(multiplier); + expect(game.contains(multiplier), isTrue); + }); + + flameTester.test('"x5" loads correctly', (game) async { + final multiplier = Multiplier.x5( + position: Vector2.zero(), + angle: 0, + ); + await game.ensureAdd(multiplier); + expect(game.contains(multiplier), isTrue); + }); + + flameTester.test('"x6" loads correctly', (game) async { + final multiplier = Multiplier.x6( + position: Vector2.zero(), + angle: 0, + ); + await game.ensureAdd(multiplier); + expect(game.contains(multiplier), isTrue); + }); + + group('renders correctly', () { + group('x2', () { + const multiplierValue = MultiplierValue.x2; + + flameTester.testGameWidget( + 'lit when bloc state is lit', + setUp: (game, tester) async { + await game.images.loadAll(assets); + + whenListen( + bloc, + const Stream.empty(), + initialState: MultiplierState( + value: multiplierValue, + spriteState: MultiplierSpriteState.lit, + ), + ); + + final multiplier = Multiplier.test( + value: multiplierValue, + bloc: bloc, + ); + await game.ensureAdd(multiplier); + await tester.pump(); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + expect( + game + .descendants() + .whereType() + .first + .current, + MultiplierSpriteState.lit, + ); + + await expectLater( + find.byGame(), + matchesGoldenFile('../golden/multipliers/x2-lit.png'), + ); + }, + ); + + flameTester.testGameWidget( + 'dimmed when bloc state is dimmed', + setUp: (game, tester) async { + await game.images.loadAll(assets); + + whenListen( + bloc, + const Stream.empty(), + initialState: MultiplierState( + value: multiplierValue, + spriteState: MultiplierSpriteState.dimmed, + ), + ); + + final multiplier = Multiplier.test( + value: multiplierValue, + bloc: bloc, + ); + await game.ensureAdd(multiplier); + await tester.pump(); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + expect( + game + .descendants() + .whereType() + .first + .current, + MultiplierSpriteState.dimmed, + ); + + await expectLater( + find.byGame(), + matchesGoldenFile('../golden/multipliers/x2-dimmed.png'), + ); + }, + ); + }); + + group('x3', () { + const multiplierValue = MultiplierValue.x3; + + flameTester.testGameWidget( + 'lit when bloc state is lit', + setUp: (game, tester) async { + await game.images.loadAll(assets); + + whenListen( + bloc, + const Stream.empty(), + initialState: MultiplierState( + value: multiplierValue, + spriteState: MultiplierSpriteState.lit, + ), + ); + + final multiplier = Multiplier.test( + value: multiplierValue, + bloc: bloc, + ); + await game.ensureAdd(multiplier); + await tester.pump(); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + expect( + game + .descendants() + .whereType() + .first + .current, + MultiplierSpriteState.lit, + ); + + await expectLater( + find.byGame(), + matchesGoldenFile('../golden/multipliers/x3-lit.png'), + ); + }, + ); + + flameTester.testGameWidget( + 'dimmed when bloc state is dimmed', + setUp: (game, tester) async { + await game.images.loadAll(assets); + + whenListen( + bloc, + const Stream.empty(), + initialState: MultiplierState( + value: multiplierValue, + spriteState: MultiplierSpriteState.dimmed, + ), + ); + + final multiplier = Multiplier.test( + value: multiplierValue, + bloc: bloc, + ); + await game.ensureAdd(multiplier); + await tester.pump(); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + expect( + game + .descendants() + .whereType() + .first + .current, + MultiplierSpriteState.dimmed, + ); + + await expectLater( + find.byGame(), + matchesGoldenFile('../golden/multipliers/x3-dimmed.png'), + ); + }, + ); + }); + + group('x4', () { + const multiplierValue = MultiplierValue.x4; + + flameTester.testGameWidget( + 'lit when bloc state is lit', + setUp: (game, tester) async { + await game.images.loadAll(assets); + + whenListen( + bloc, + const Stream.empty(), + initialState: MultiplierState( + value: multiplierValue, + spriteState: MultiplierSpriteState.lit, + ), + ); + + final multiplier = Multiplier.test( + value: multiplierValue, + bloc: bloc, + ); + await game.ensureAdd(multiplier); + await tester.pump(); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + expect( + game + .descendants() + .whereType() + .first + .current, + MultiplierSpriteState.lit, + ); + + await expectLater( + find.byGame(), + matchesGoldenFile('../golden/multipliers/x4-lit.png'), + ); + }, + ); + + flameTester.testGameWidget( + 'dimmed when bloc state is dimmed', + setUp: (game, tester) async { + await game.images.loadAll(assets); + + whenListen( + bloc, + const Stream.empty(), + initialState: MultiplierState( + value: multiplierValue, + spriteState: MultiplierSpriteState.dimmed, + ), + ); + + final multiplier = Multiplier.test( + value: multiplierValue, + bloc: bloc, + ); + await game.ensureAdd(multiplier); + await tester.pump(); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + expect( + game + .descendants() + .whereType() + .first + .current, + MultiplierSpriteState.dimmed, + ); + + await expectLater( + find.byGame(), + matchesGoldenFile('../golden/multipliers/x4-dimmed.png'), + ); + }, + ); + }); + + group('x5', () { + const multiplierValue = MultiplierValue.x5; + + flameTester.testGameWidget( + 'lit when bloc state is lit', + setUp: (game, tester) async { + await game.images.loadAll(assets); + + whenListen( + bloc, + const Stream.empty(), + initialState: MultiplierState( + value: multiplierValue, + spriteState: MultiplierSpriteState.lit, + ), + ); + + final multiplier = Multiplier.test( + value: multiplierValue, + bloc: bloc, + ); + await game.ensureAdd(multiplier); + await tester.pump(); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + expect( + game + .descendants() + .whereType() + .first + .current, + MultiplierSpriteState.lit, + ); + + await expectLater( + find.byGame(), + matchesGoldenFile('../golden/multipliers/x5-lit.png'), + ); + }, + ); + + flameTester.testGameWidget( + 'dimmed when bloc state is dimmed', + setUp: (game, tester) async { + await game.images.loadAll(assets); + + whenListen( + bloc, + const Stream.empty(), + initialState: MultiplierState( + value: multiplierValue, + spriteState: MultiplierSpriteState.dimmed, + ), + ); + + final multiplier = Multiplier.test( + value: multiplierValue, + bloc: bloc, + ); + await game.ensureAdd(multiplier); + await tester.pump(); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + expect( + game + .descendants() + .whereType() + .first + .current, + MultiplierSpriteState.dimmed, + ); + + await expectLater( + find.byGame(), + matchesGoldenFile('../golden/multipliers/x5-dimmed.png'), + ); + }, + ); + }); + + group('x6', () { + const multiplierValue = MultiplierValue.x6; + + flameTester.testGameWidget( + 'lit when bloc state is lit', + setUp: (game, tester) async { + await game.images.loadAll(assets); + + whenListen( + bloc, + const Stream.empty(), + initialState: MultiplierState( + value: multiplierValue, + spriteState: MultiplierSpriteState.lit, + ), + ); + + final multiplier = Multiplier.test( + value: multiplierValue, + bloc: bloc, + ); + await game.ensureAdd(multiplier); + await tester.pump(); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + expect( + game + .descendants() + .whereType() + .first + .current, + MultiplierSpriteState.lit, + ); + + await expectLater( + find.byGame(), + matchesGoldenFile('../golden/multipliers/x6-lit.png'), + ); + }, + ); + + flameTester.testGameWidget( + 'dimmed when bloc state is dimmed', + setUp: (game, tester) async { + await game.images.loadAll(assets); + + whenListen( + bloc, + const Stream.empty(), + initialState: MultiplierState( + value: multiplierValue, + spriteState: MultiplierSpriteState.dimmed, + ), + ); + + final multiplier = Multiplier.test( + value: multiplierValue, + bloc: bloc, + ); + await game.ensureAdd(multiplier); + await tester.pump(); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + expect( + game + .descendants() + .whereType() + .first + .current, + MultiplierSpriteState.dimmed, + ); + + await expectLater( + find.byGame(), + matchesGoldenFile('../golden/multipliers/x6-dimmed.png'), + ); + }, + ); + }); + }); + + flameTester.test('closes bloc when removed', (game) async { + whenListen( + bloc, + const Stream.empty(), + initialState: MultiplierState( + value: MultiplierValue.x2, + spriteState: MultiplierSpriteState.dimmed, + ), + ); + when(bloc.close).thenAnswer((_) async {}); + final multiplier = Multiplier.test(value: MultiplierValue.x2, bloc: bloc); + + await game.ensureAdd(multiplier); + game.remove(multiplier); + await game.ready(); + + verify(bloc.close).called(1); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/spaceship_rail_test.dart b/packages/pinball_components/test/src/components/spaceship_rail_test.dart index bc5a7f75..a24b0a17 100644 --- a/packages/pinball_components/test/src/components/spaceship_rail_test.dart +++ b/packages/pinball_components/test/src/components/spaceship_rail_test.dart @@ -12,8 +12,8 @@ void main() { group('SpaceshipRail', () { TestWidgetsFlutterBinding.ensureInitialized(); final assets = [ - Assets.images.spaceship.rail.main.keyName, - Assets.images.spaceship.rail.exit.keyName, + Assets.images.android.rail.main.keyName, + Assets.images.android.rail.exit.keyName, ]; final flameTester = FlameTester(() => TestGame(assets)); diff --git a/packages/pinball_components/test/src/components/spaceship_ramp_test.dart b/packages/pinball_components/test/src/components/spaceship_ramp_test.dart index a65ba18b..1f5a231a 100644 --- a/packages/pinball_components/test/src/components/spaceship_ramp_test.dart +++ b/packages/pinball_components/test/src/components/spaceship_ramp_test.dart @@ -11,16 +11,16 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); final assets = [ - Assets.images.spaceship.ramp.boardOpening.keyName, - Assets.images.spaceship.ramp.railingForeground.keyName, - Assets.images.spaceship.ramp.railingBackground.keyName, - Assets.images.spaceship.ramp.main.keyName, - Assets.images.spaceship.ramp.arrow.inactive.keyName, - Assets.images.spaceship.ramp.arrow.active1.keyName, - Assets.images.spaceship.ramp.arrow.active2.keyName, - Assets.images.spaceship.ramp.arrow.active3.keyName, - Assets.images.spaceship.ramp.arrow.active4.keyName, - Assets.images.spaceship.ramp.arrow.active5.keyName, + Assets.images.android.ramp.boardOpening.keyName, + Assets.images.android.ramp.railingForeground.keyName, + Assets.images.android.ramp.railingBackground.keyName, + Assets.images.android.ramp.main.keyName, + Assets.images.android.ramp.arrow.inactive.keyName, + Assets.images.android.ramp.arrow.active1.keyName, + Assets.images.android.ramp.arrow.active2.keyName, + Assets.images.android.ramp.arrow.active3.keyName, + Assets.images.android.ramp.arrow.active4.keyName, + Assets.images.android.ramp.arrow.active5.keyName, ]; final flameTester = FlameTester(() => TestGame(assets)); diff --git a/packages/pinball_components/test/src/components/spaceship_test.dart b/packages/pinball_components/test/src/components/spaceship_test.dart deleted file mode 100644 index c9a90746..00000000 --- a/packages/pinball_components/test/src/components/spaceship_test.dart +++ /dev/null @@ -1,56 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; - -import '../../helpers/helpers.dart'; - -void main() { - group('Spaceship', () { - late Filter filterData; - late Fixture fixture; - late Body body; - late Ball ball; - late Forge2DGame game; - - setUp(() { - filterData = MockFilter(); - - fixture = MockFixture(); - when(() => fixture.filterData).thenReturn(filterData); - - body = MockBody(); - when(() => body.fixtures).thenReturn([fixture]); - - game = MockGame(); - - ball = MockBall(); - when(() => ball.gameRef).thenReturn(game); - when(() => ball.body).thenReturn(body); - }); - - group('Spaceship', () { - final tester = FlameTester(TestGame.new); - - tester.testGameWidget( - 'renders correctly', - setUp: (game, tester) async { - final position = Vector2(30, -30); - await game.addFromBlueprint(Spaceship(position: position)); - game.camera.followVector2(position); - await game.ready(); - }, - verify: (game, tester) async { - await expectLater( - find.byGame(), - matchesGoldenFile('golden/spaceship.png'), - ); - }, - ); - }); - }); -} diff --git a/test/game/components/android_acres_test.dart b/test/game/components/android_acres_test.dart index 419524c6..aef6a812 100644 --- a/test/game/components/android_acres_test.dart +++ b/test/game/components/android_acres_test.dart @@ -11,22 +11,27 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); final assets = [ - Assets.images.spaceship.ramp.boardOpening.keyName, - Assets.images.spaceship.ramp.railingForeground.keyName, - Assets.images.spaceship.ramp.railingBackground.keyName, - Assets.images.spaceship.ramp.main.keyName, - Assets.images.spaceship.ramp.arrow.inactive.keyName, - Assets.images.spaceship.ramp.arrow.active1.keyName, - Assets.images.spaceship.ramp.arrow.active2.keyName, - Assets.images.spaceship.ramp.arrow.active3.keyName, - Assets.images.spaceship.ramp.arrow.active4.keyName, - Assets.images.spaceship.ramp.arrow.active5.keyName, - Assets.images.spaceship.rail.main.keyName, - Assets.images.spaceship.rail.exit.keyName, - Assets.images.androidBumper.a.lit.keyName, - Assets.images.androidBumper.a.dimmed.keyName, - Assets.images.androidBumper.b.lit.keyName, - Assets.images.androidBumper.b.dimmed.keyName, + Assets.images.android.spaceship.saucer.keyName, + Assets.images.android.spaceship.animatronic.keyName, + Assets.images.android.spaceship.lightBeam.keyName, + Assets.images.android.ramp.boardOpening.keyName, + Assets.images.android.ramp.railingForeground.keyName, + Assets.images.android.ramp.railingBackground.keyName, + Assets.images.android.ramp.main.keyName, + Assets.images.android.ramp.arrow.inactive.keyName, + Assets.images.android.ramp.arrow.active1.keyName, + Assets.images.android.ramp.arrow.active2.keyName, + Assets.images.android.ramp.arrow.active3.keyName, + Assets.images.android.ramp.arrow.active4.keyName, + Assets.images.android.ramp.arrow.active5.keyName, + Assets.images.android.rail.main.keyName, + Assets.images.android.rail.exit.keyName, + Assets.images.android.bumper.a.lit.keyName, + Assets.images.android.bumper.a.dimmed.keyName, + Assets.images.android.bumper.b.lit.keyName, + Assets.images.android.bumper.b.dimmed.keyName, + Assets.images.android.bumper.cow.lit.keyName, + Assets.images.android.bumper.cow.dimmed.keyName, ]; final flameTester = FlameTester( () => EmptyPinballTestGame(assets: assets), @@ -46,7 +51,7 @@ void main() { 'a Spaceship', (game) async { expect( - AndroidAcres().blueprints.whereType().single, + AndroidAcres().blueprints.whereType().single, isNotNull, ); }, @@ -73,7 +78,7 @@ void main() { ); flameTester.test( - 'two AndroidBumper', + 'three AndroidBumper', (game) async { final androidZone = AndroidAcres(); await game.addFromBlueprint(androidZone); @@ -81,7 +86,7 @@ void main() { expect( game.descendants().whereType().length, - equals(2), + equals(3), ); }, ); diff --git a/test/game/components/multipliers/behaviors/multipliers_behavior_test.dart b/test/game/components/multipliers/behaviors/multipliers_behavior_test.dart new file mode 100644 index 00000000..a4f3502c --- /dev/null +++ b/test/game/components/multipliers/behaviors/multipliers_behavior_test.dart @@ -0,0 +1,133 @@ +// ignore_for_file: cascade_invocations, prefer_const_constructors + +import 'dart:async'; + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockingjay/mockingjay.dart'; +import 'package:pinball/game/components/multipliers/behaviors/behaviors.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + Assets.images.multiplier.x2.lit.keyName, + Assets.images.multiplier.x2.dimmed.keyName, + Assets.images.multiplier.x3.lit.keyName, + Assets.images.multiplier.x3.dimmed.keyName, + Assets.images.multiplier.x4.lit.keyName, + Assets.images.multiplier.x4.dimmed.keyName, + Assets.images.multiplier.x5.lit.keyName, + Assets.images.multiplier.x5.dimmed.keyName, + Assets.images.multiplier.x6.lit.keyName, + Assets.images.multiplier.x6.dimmed.keyName, + ]; + + group('MultipliersBehavior', () { + late GameBloc gameBloc; + + setUp(() { + registerFallbackValue(MockComponent()); + gameBloc = MockGameBloc(); + whenListen( + gameBloc, + const Stream.empty(), + initialState: const GameState.initial(), + ); + }); + + final flameBlocTester = FlameBlocTester( + gameBuilder: EmptyPinballTestGame.new, + blocBuilder: () => gameBloc, + assets: assets, + ); + + group('listenWhen', () { + test('is true when the multiplier has changed', () { + final state = GameState( + score: 10, + multiplier: 2, + rounds: 0, + bonusHistory: const [], + ); + + final previous = GameState.initial(); + expect( + MultipliersBehavior().listenWhen(previous, state), + isTrue, + ); + }); + + test('is false when the multiplier state is the same', () { + final state = GameState( + score: 10, + multiplier: 1, + rounds: 0, + bonusHistory: const [], + ); + + final previous = GameState.initial(); + expect( + MultipliersBehavior().listenWhen(previous, state), + isFalse, + ); + }); + }); + + group('onNewState', () { + flameBlocTester.testGameWidget( + "calls 'next' once per each multiplier when GameBloc emit state", + setUp: (game, tester) async { + final behavior = MultipliersBehavior(); + final parent = Multipliers.test(); + final multiplierX2Cubit = MockMultiplierCubit(); + final multiplierX3Cubit = MockMultiplierCubit(); + final multipliers = [ + Multiplier.test( + value: MultiplierValue.x2, + bloc: multiplierX2Cubit, + ), + Multiplier.test( + value: MultiplierValue.x3, + bloc: multiplierX3Cubit, + ), + ]; + + whenListen( + multiplierX2Cubit, + const Stream.empty(), + initialState: MultiplierState.initial(MultiplierValue.x2), + ); + when(() => multiplierX2Cubit.next(any())).thenAnswer((_) async {}); + + whenListen( + multiplierX3Cubit, + const Stream.empty(), + initialState: MultiplierState.initial(MultiplierValue.x2), + ); + when(() => multiplierX3Cubit.next(any())).thenAnswer((_) async {}); + + await parent.addAll(multipliers); + await game.ensureAdd(parent); + await parent.ensureAdd(behavior); + + await tester.pump(); + + behavior.onNewState( + GameState.initial().copyWith(multiplier: 2), + ); + + for (final multiplier in multipliers) { + verify( + () => multiplier.bloc.next(any()), + ).called(1); + } + }, + ); + }); + }); +} diff --git a/test/game/components/multipliers/multipliers_test.dart b/test/game/components/multipliers/multipliers_test.dart new file mode 100644 index 00000000..6b2d95a6 --- /dev/null +++ b/test/game/components/multipliers/multipliers_test.dart @@ -0,0 +1,63 @@ +// ignore_for_file: cascade_invocations + +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() { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + Assets.images.multiplier.x2.lit.keyName, + Assets.images.multiplier.x2.dimmed.keyName, + Assets.images.multiplier.x3.lit.keyName, + Assets.images.multiplier.x3.dimmed.keyName, + Assets.images.multiplier.x4.lit.keyName, + Assets.images.multiplier.x4.dimmed.keyName, + Assets.images.multiplier.x5.lit.keyName, + Assets.images.multiplier.x5.dimmed.keyName, + Assets.images.multiplier.x6.lit.keyName, + Assets.images.multiplier.x6.dimmed.keyName, + ]; + + late GameBloc gameBloc; + + setUp(() { + gameBloc = GameBloc(); + }); + + final flameBlocTester = FlameBlocTester( + gameBuilder: EmptyPinballTestGame.new, + blocBuilder: () => gameBloc, + assets: assets, + ); + + group('Multipliers', () { + flameBlocTester.testGameWidget( + 'loads correctly', + setUp: (game, tester) async { + final multipliersGroup = Multipliers(); + await game.ensureAdd(multipliersGroup); + + expect(game.contains(multipliersGroup), isTrue); + }, + ); + + group('loads', () { + flameBlocTester.testGameWidget( + 'five Multiplier', + setUp: (game, tester) async { + final multipliersGroup = Multipliers(); + await game.ensureAdd(multipliersGroup); + + expect( + multipliersGroup.descendants().whereType().length, + equals(5), + ); + }, + ); + }); + }); +} diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index ef831b5c..3a531cac 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -1,5 +1,6 @@ // ignore_for_file: cascade_invocations +import 'package:bloc_test/bloc_test.dart'; import 'package:flame/components.dart'; import 'package:flame/game.dart'; import 'package:flame_test/flame_test.dart'; @@ -14,10 +15,12 @@ import '../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); final assets = [ - Assets.images.androidBumper.a.lit.keyName, - Assets.images.androidBumper.a.dimmed.keyName, - Assets.images.androidBumper.b.lit.keyName, - Assets.images.androidBumper.b.dimmed.keyName, + Assets.images.android.bumper.a.lit.keyName, + Assets.images.android.bumper.a.dimmed.keyName, + Assets.images.android.bumper.b.lit.keyName, + Assets.images.android.bumper.b.dimmed.keyName, + Assets.images.android.bumper.cow.lit.keyName, + Assets.images.android.bumper.cow.dimmed.keyName, Assets.images.backboard.backboardScores.keyName, Assets.images.backboard.backboardGameOver.keyName, Assets.images.backboard.display.keyName, @@ -52,6 +55,16 @@ void main() { Assets.images.launchRamp.ramp.keyName, Assets.images.launchRamp.foregroundRailing.keyName, Assets.images.launchRamp.backgroundRailing.keyName, + Assets.images.multiplier.x2.lit.keyName, + Assets.images.multiplier.x2.dimmed.keyName, + Assets.images.multiplier.x3.lit.keyName, + Assets.images.multiplier.x3.dimmed.keyName, + Assets.images.multiplier.x4.lit.keyName, + Assets.images.multiplier.x4.dimmed.keyName, + Assets.images.multiplier.x5.lit.keyName, + Assets.images.multiplier.x5.dimmed.keyName, + Assets.images.multiplier.x6.lit.keyName, + Assets.images.multiplier.x6.dimmed.keyName, Assets.images.plunger.plunger.keyName, Assets.images.plunger.rocket.keyName, Assets.images.signpost.inactive.keyName, @@ -60,20 +73,21 @@ void main() { Assets.images.signpost.active3.keyName, Assets.images.slingshot.upper.keyName, Assets.images.slingshot.lower.keyName, - Assets.images.spaceship.saucer.keyName, - Assets.images.spaceship.bridge.keyName, - Assets.images.spaceship.ramp.boardOpening.keyName, - Assets.images.spaceship.ramp.railingForeground.keyName, - Assets.images.spaceship.ramp.railingBackground.keyName, - Assets.images.spaceship.ramp.main.keyName, - Assets.images.spaceship.ramp.arrow.inactive.keyName, - Assets.images.spaceship.ramp.arrow.active1.keyName, - Assets.images.spaceship.ramp.arrow.active2.keyName, - Assets.images.spaceship.ramp.arrow.active3.keyName, - Assets.images.spaceship.ramp.arrow.active4.keyName, - Assets.images.spaceship.ramp.arrow.active5.keyName, - Assets.images.spaceship.rail.main.keyName, - Assets.images.spaceship.rail.exit.keyName, + Assets.images.android.spaceship.saucer.keyName, + Assets.images.android.spaceship.animatronic.keyName, + Assets.images.android.spaceship.lightBeam.keyName, + Assets.images.android.ramp.boardOpening.keyName, + Assets.images.android.ramp.railingForeground.keyName, + Assets.images.android.ramp.railingBackground.keyName, + Assets.images.android.ramp.main.keyName, + Assets.images.android.ramp.arrow.inactive.keyName, + Assets.images.android.ramp.arrow.active1.keyName, + Assets.images.android.ramp.arrow.active2.keyName, + Assets.images.android.ramp.arrow.active3.keyName, + Assets.images.android.ramp.arrow.active4.keyName, + Assets.images.android.ramp.arrow.active5.keyName, + Assets.images.android.rail.main.keyName, + Assets.images.android.rail.exit.keyName, Assets.images.sparky.bumper.a.active.keyName, Assets.images.sparky.bumper.a.inactive.keyName, Assets.images.sparky.bumper.b.active.keyName, @@ -92,6 +106,17 @@ void main() { Assets.images.sparky.bumper.c.inactive.keyName, ]; + late GameBloc gameBloc; + + setUp(() { + gameBloc = MockGameBloc(); + whenListen( + gameBloc, + const Stream.empty(), + initialState: const GameState.initial(), + ); + }); + final flameTester = FlameTester( () => PinballTestGame(assets: assets), ); @@ -99,11 +124,16 @@ void main() { () => DebugPinballTestGame(assets: assets), ); + final flameBlocTester = FlameBlocTester( + gameBuilder: () => PinballTestGame(assets: assets), + blocBuilder: () => gameBloc, + ); + group('PinballGame', () { group('components', () { // TODO(alestiago): tests that Blueprints get added once the Blueprint // class is removed. - flameTester.test( + flameBlocTester.test( 'has only one Drain', (game) async { await game.ready(); @@ -114,11 +144,10 @@ void main() { }, ); - flameTester.test( + flameBlocTester.test( 'has only one BottomGroup', (game) async { await game.ready(); - expect( game.children.whereType().length, equals(1), @@ -126,7 +155,7 @@ void main() { }, ); - flameTester.test( + flameBlocTester.test( 'has only one Plunger', (game) async { await game.ready(); @@ -137,7 +166,7 @@ void main() { }, ); - flameTester.test('has one FlutterForest', (game) async { + flameBlocTester.test('has one FlutterForest', (game) async { await game.ready(); expect( game.children.whereType().length, @@ -145,7 +174,7 @@ void main() { ); }); - flameTester.test( + flameBlocTester.test( 'one GoogleWord', (game) async { await game.ready(); diff --git a/test/helpers/mocks.dart b/test/helpers/mocks.dart index 14a286e2..1578c3e0 100644 --- a/test/helpers/mocks.dart +++ b/test/helpers/mocks.dart @@ -64,6 +64,8 @@ class MockFilter extends Mock implements Filter {} class MockFixture extends Mock implements Fixture {} +class MockComponent extends Mock implements Component {} + class MockComponentSet extends Mock implements ComponentSet {} class MockDashNestBumper extends Mock implements DashNestBumper {} @@ -86,3 +88,9 @@ class MockGameFlowController extends Mock implements GameFlowController {} class MockAndroidBumper extends Mock implements AndroidBumper {} class MockSparkyBumper extends Mock implements SparkyBumper {} + +class MockMultiplier extends Mock implements Multiplier {} + +class MockMultipliersGroup extends Mock implements Multipliers {} + +class MockMultiplierCubit extends Mock implements MultiplierCubit {}