diff --git a/lib/game/bloc/game_bloc.dart b/lib/game/bloc/game_bloc.dart index f30d22b4..dce20813 100644 --- a/lib/game/bloc/game_bloc.dart +++ b/lib/game/bloc/game_bloc.dart @@ -11,9 +11,9 @@ class GameBloc extends Bloc { GameBloc() : super(const GameState.initial()) { on(_onBallLost); on(_onScored); - on(_onIncreasedMultiplier); - on(_onAppliedMultiplier); - on(_onResetMultiplier); + on(_onIncreasedMultiplier); + on(_onAppliedMultiplier); + on(_onResetMultiplier); on(_onBonusActivated); on(_onSparkyTurboChargeActivated); } @@ -30,19 +30,19 @@ class GameBloc extends Bloc { } } - void _onIncreasedMultiplier(IncreasedMultiplier event, Emitter emit) { + void _onIncreasedMultiplier(MultiplierIncreased event, Emitter emit) { if (!state.isGameOver) { emit(state.copyWith(multiplier: state.multiplier + event.increase)); } } - void _onAppliedMultiplier(AppliedMultiplier event, Emitter emit) { + void _onAppliedMultiplier(MultiplierApplied event, Emitter emit) { if (!state.isGameOver) { emit(state.copyWith(score: state.score * state.multiplier)); } } - void _onResetMultiplier(ResetMultiplier event, Emitter emit) { + void _onResetMultiplier(MultiplierReset event, Emitter emit) { if (!state.isGameOver) { emit(state.copyWith(multiplier: 1)); } diff --git a/lib/game/bloc/game_event.dart b/lib/game/bloc/game_event.dart index 58000cd9..983012de 100644 --- a/lib/game/bloc/game_event.dart +++ b/lib/game/bloc/game_event.dart @@ -49,12 +49,12 @@ class SparkyTurboChargeActivated extends GameEvent { List get props => []; } -/// {@template increased_multiplier_game_event} +/// {@template multiplier_increased_game_event} /// Event added when multiplier is being increased. /// {@endtemplate} -class IncreasedMultiplier extends GameEvent { - /// {@macro increased_multiplier_game_event} - const IncreasedMultiplier({ +class MultiplierIncreased extends GameEvent { + /// {@macro multiplier_increased_game_event} + const MultiplierIncreased({ required this.increase, }) : assert(increase > 0, 'Increase must be greater than 0'); @@ -64,23 +64,23 @@ class IncreasedMultiplier extends GameEvent { List get props => [increase]; } -/// {@template applied_multiplier_game_event} +/// {@template multiplier_applied_game_event} /// Event added when multiplier is applied to score. /// {@endtemplate} -class AppliedMultiplier extends GameEvent { - /// {@macro applied_multiplier_game_event} - const AppliedMultiplier(); +class MultiplierApplied extends GameEvent { + /// {@macro multiplier_applied_game_event} + const MultiplierApplied(); @override List get props => []; } -/// {@template reset_multiplier_game_event} +/// {@template multiplier_reset_game_event} /// Event added when multiplier is reset. /// {@endtemplate} -class ResetMultiplier extends GameEvent { - /// {@macro reset_multiplier_game_event} - const ResetMultiplier(); +class MultiplierReset extends GameEvent { + /// {@macro multiplier_reset_game_event} + const MultiplierReset(); @override List get props => []; diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index e05f9f00..fc914ebf 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -9,7 +9,6 @@ export 'flutter_forest.dart'; export 'game_flow_controller.dart'; export 'google_word.dart'; export 'launcher.dart'; -export 'score_effect_controller.dart'; export 'score_points.dart'; export 'sparky_fire_zone.dart'; export 'wall.dart'; diff --git a/lib/game/components/score_effect_controller.dart b/lib/game/components/score_effect_controller.dart deleted file mode 100644 index f4a185e1..00000000 --- a/lib/game/components/score_effect_controller.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'dart:math'; - -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'; - -/// {@template score_effect_controller} -/// A [ComponentController] responsible for adding [ScoreText]s -/// on the game screen when the user earns points. -/// {@endtemplate} -class ScoreEffectController extends ComponentController - with BlocComponent { - /// {@macro score_effect_controller} - ScoreEffectController(PinballGame component) : super(component); - - int _lastScore = 0; - final _random = Random(); - - double _noise() { - return _random.nextDouble() * 5 * (_random.nextBool() ? -1 : 1); - } - - @override - bool listenWhen(GameState? previousState, GameState newState) { - return previousState?.score != newState.score; - } - - @override - void onNewState(GameState state) { - final newScore = state.score - _lastScore; - _lastScore = state.score; - - component.add( - ScoreText( - text: newScore.toString(), - position: Vector2( - _noise(), - _noise() + (BoardDimensions.bounds.topCenter.dy + 10), - ), - ), - ); - } -} diff --git a/lib/game/components/score_points.dart b/lib/game/components/score_points.dart index f0d6ec3a..8a76680d 100644 --- a/lib/game/components/score_points.dart +++ b/lib/game/components/score_points.dart @@ -30,11 +30,18 @@ class BallScorePointsCallback extends ContactCallback { @override void begin( - Ball _, + Ball ball, ScorePoints scorePoints, - Contact __, + Contact _, ) { _gameRef.read().add(Scored(points: scorePoints.points)); _gameRef.audio.score(); + + _gameRef.add( + ScoreText( + text: scorePoints.points.toString(), + position: ball.body.position, + ), + ); } } diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index 361bbeaf..a1988eda 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -41,13 +41,30 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.boundary.outer.keyName), images.load(components.Assets.images.spaceship.saucer.keyName), images.load(components.Assets.images.spaceship.bridge.keyName), - images.load(components.Assets.images.spaceship.ramp.main.keyName), images.load(components.Assets.images.spaceship.ramp.boardOpening.keyName), + images.load( + components.Assets.images.spaceship.ramp.railingForeground.keyName, + ), images.load( components.Assets.images.spaceship.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.spaceship.ramp.railingForeground.keyName, + components.Assets.images.spaceship.ramp.arrow.active1.keyName, + ), + images.load( + components.Assets.images.spaceship.ramp.arrow.active2.keyName, + ), + images.load( + components.Assets.images.spaceship.ramp.arrow.active3.keyName, + ), + images.load( + components.Assets.images.spaceship.ramp.arrow.active4.keyName, + ), + images.load( + components.Assets.images.spaceship.ramp.arrow.active5.keyName, ), images.load(components.Assets.images.spaceship.rail.main.keyName), images.load(components.Assets.images.spaceship.rail.foreground.keyName), diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index f28a0bf6..7034cfe5 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -42,7 +42,6 @@ class PinballGame extends Forge2DGame Future onLoad() async { _addContactCallbacks(); - unawaited(add(ScoreEffectController(this))); unawaited(add(gameFlowController = GameFlowController(this))); unawaited(add(CameraController(this))); unawaited(add(Backboard.waiting(position: Vector2(0, -88)))); diff --git a/packages/pinball_components/assets/images/spaceship/ramp/arrow/active1.png b/packages/pinball_components/assets/images/spaceship/ramp/arrow/active1.png new file mode 100644 index 00000000..c649d64a Binary files /dev/null and b/packages/pinball_components/assets/images/spaceship/ramp/arrow/active1.png differ diff --git a/packages/pinball_components/assets/images/spaceship/ramp/arrow/active2.png b/packages/pinball_components/assets/images/spaceship/ramp/arrow/active2.png new file mode 100644 index 00000000..8ee71e36 Binary files /dev/null and b/packages/pinball_components/assets/images/spaceship/ramp/arrow/active2.png differ diff --git a/packages/pinball_components/assets/images/spaceship/ramp/arrow/active3.png b/packages/pinball_components/assets/images/spaceship/ramp/arrow/active3.png new file mode 100644 index 00000000..023e86e9 Binary files /dev/null and b/packages/pinball_components/assets/images/spaceship/ramp/arrow/active3.png differ diff --git a/packages/pinball_components/assets/images/spaceship/ramp/arrow/active4.png b/packages/pinball_components/assets/images/spaceship/ramp/arrow/active4.png new file mode 100644 index 00000000..f5a413f5 Binary files /dev/null and b/packages/pinball_components/assets/images/spaceship/ramp/arrow/active4.png differ diff --git a/packages/pinball_components/assets/images/spaceship/ramp/arrow/active5.png b/packages/pinball_components/assets/images/spaceship/ramp/arrow/active5.png new file mode 100644 index 00000000..c0b76b4d Binary files /dev/null and b/packages/pinball_components/assets/images/spaceship/ramp/arrow/active5.png differ diff --git a/packages/pinball_components/assets/images/spaceship/ramp/arrow/inactive.png b/packages/pinball_components/assets/images/spaceship/ramp/arrow/inactive.png new file mode 100644 index 00000000..6ad09460 Binary files /dev/null and b/packages/pinball_components/assets/images/spaceship/ramp/arrow/inactive.png differ diff --git a/packages/pinball_components/assets/images/spaceship/ramp/board-opening.png b/packages/pinball_components/assets/images/spaceship/ramp/board-opening.png index 53144013..1c6b78f2 100644 Binary files a/packages/pinball_components/assets/images/spaceship/ramp/board-opening.png and b/packages/pinball_components/assets/images/spaceship/ramp/board-opening.png differ diff --git a/packages/pinball_components/assets/images/spaceship/ramp/main.png b/packages/pinball_components/assets/images/spaceship/ramp/main.png index 9c4fd0e5..b885753b 100644 Binary files a/packages/pinball_components/assets/images/spaceship/ramp/main.png and b/packages/pinball_components/assets/images/spaceship/ramp/main.png differ diff --git a/packages/pinball_components/assets/images/spaceship/ramp/railing-background.png b/packages/pinball_components/assets/images/spaceship/ramp/railing-background.png index 08470439..e5630446 100644 Binary files a/packages/pinball_components/assets/images/spaceship/ramp/railing-background.png and b/packages/pinball_components/assets/images/spaceship/ramp/railing-background.png differ diff --git a/packages/pinball_components/assets/images/spaceship/ramp/railing-foreground.png b/packages/pinball_components/assets/images/spaceship/ramp/railing-foreground.png index 0a2641e2..fa25d124 100644 Binary files a/packages/pinball_components/assets/images/spaceship/ramp/railing-foreground.png and b/packages/pinball_components/assets/images/spaceship/ramp/railing-foreground.png differ diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index ca4c3f3c..0d67f870 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -311,6 +311,9 @@ class $AssetsImagesSpaceshipRailGen { class $AssetsImagesSpaceshipRampGen { const $AssetsImagesSpaceshipRampGen(); + $AssetsImagesSpaceshipRampArrowGen get arrow => + const $AssetsImagesSpaceshipRampArrowGen(); + /// File path: assets/images/spaceship/ramp/board-opening.png AssetGenImage get boardOpening => const AssetGenImage('assets/images/spaceship/ramp/board-opening.png'); @@ -384,6 +387,34 @@ 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/render_priority.dart b/packages/pinball_components/lib/src/components/render_priority.dart index 1850369c..1b02326f 100644 --- a/packages/pinball_components/lib/src/components/render_priority.dart +++ b/packages/pinball_components/lib/src/components/render_priority.dart @@ -101,6 +101,8 @@ abstract class RenderPriority { static const int spaceshipRampBackgroundRailing = _above + spaceshipRamp; + static const int spaceshipRampArrow = _above + spaceshipRamp; + static const int spaceshipRampForegroundRailing = _above + ballOnSpaceshipRamp; diff --git a/packages/pinball_components/lib/src/components/spaceship_ramp.dart b/packages/pinball_components/lib/src/components/spaceship_ramp.dart index f2d8801c..e40c0e16 100644 --- a/packages/pinball_components/lib/src/components/spaceship_ramp.dart +++ b/packages/pinball_components/lib/src/components/spaceship_ramp.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.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'; @@ -15,6 +16,15 @@ class SpaceshipRamp extends Forge2DBlueprint { /// {@macro spaceship_ramp} SpaceshipRamp(); + /// [SpriteGroupComponent] representing the arrow that lights up. + @visibleForTesting + late final SpaceshipRampArrowSpriteComponent spaceshipRampArrow; + + /// Forwards the sprite to the next [SpaceshipRampArrowSpriteState]. + /// + /// If the current state is the last one it cycles back to the initial state. + void progress() => spaceshipRampArrow.progress(); + @override void build(_) { addAllContactCallback([ @@ -23,20 +33,22 @@ class SpaceshipRamp extends Forge2DBlueprint { final rightOpening = _SpaceshipRampOpening( outsidePriority: RenderPriority.ballOnBoard, - rotation: math.pi, + rotation: -5 * math.pi / 180, ) - ..initialPosition = Vector2(1.7, -19.8) + ..initialPosition = Vector2(1.7, -19.12) ..layer = Layer.opening; final leftOpening = _SpaceshipRampOpening( outsideLayer: Layer.spaceship, outsidePriority: RenderPriority.ballOnSpaceship, - rotation: math.pi, + rotation: -5 * math.pi / 180, ) - ..initialPosition = Vector2(-13.7, -18.6) + ..initialPosition = Vector2(-13.7, -19) ..layer = Layer.spaceshipEntranceRamp; final spaceshipRamp = _SpaceshipRampBackground(); + spaceshipRampArrow = SpaceshipRampArrowSpriteComponent(); + final spaceshipRampBoardOpeningSprite = _SpaceshipRampBoardOpeningSpriteComponent() ..position = Vector2(3.4, -39.5); @@ -52,15 +64,69 @@ class SpaceshipRamp extends Forge2DBlueprint { baseRight, _SpaceshipRampBackgroundRailingSpriteComponent(), spaceshipRamp, + spaceshipRampArrow, spaceshipRampForegroundRailing, ]); } } +/// Indicates the state of the arrow on the [SpaceshipRamp]. +@visibleForTesting +enum SpaceshipRampArrowSpriteState { + /// Arrow with no dashes lit up. + inactive, + + /// Arrow with 1 light lit up. + active1, + + /// Arrow with 2 lights lit up. + active2, + + /// Arrow with 3 lights lit up. + active3, + + /// Arrow with 4 lights lit up. + active4, + + /// Arrow with all 5 lights lit up. + active5, +} + +extension on SpaceshipRampArrowSpriteState { + String get path { + switch (this) { + case SpaceshipRampArrowSpriteState.inactive: + return Assets.images.spaceship.ramp.arrow.inactive.keyName; + case SpaceshipRampArrowSpriteState.active1: + return Assets.images.spaceship.ramp.arrow.active1.keyName; + case SpaceshipRampArrowSpriteState.active2: + return Assets.images.spaceship.ramp.arrow.active2.keyName; + case SpaceshipRampArrowSpriteState.active3: + return Assets.images.spaceship.ramp.arrow.active3.keyName; + case SpaceshipRampArrowSpriteState.active4: + return Assets.images.spaceship.ramp.arrow.active4.keyName; + case SpaceshipRampArrowSpriteState.active5: + return Assets.images.spaceship.ramp.arrow.active5.keyName; + } + } + + SpaceshipRampArrowSpriteState get next { + return SpaceshipRampArrowSpriteState + .values[(index + 1) % SpaceshipRampArrowSpriteState.values.length]; + } +} + class _SpaceshipRampBackground extends BodyComponent with InitialPosition, Layered { - _SpaceshipRampBackground() : super(priority: RenderPriority.spaceshipRamp) { + _SpaceshipRampBackground() + : super( + priority: RenderPriority.spaceshipRamp, + children: [ + _SpaceshipRampBackgroundRampSpriteComponent(), + ], + ) { layer = Layer.spaceshipEntranceRamp; + renderBody = false; } /// Width between walls of the ramp. @@ -112,30 +178,26 @@ class _SpaceshipRampBackground extends BodyComponent return body; } - - @override - Future onLoad() async { - await super.onLoad(); - renderBody = false; - - await add(_SpaceshipRampBackgroundRampSpriteComponent()); - } } class _SpaceshipRampBackgroundRailingSpriteComponent extends SpriteComponent with HasGameRef { _SpaceshipRampBackgroundRailingSpriteComponent() - : super(priority: RenderPriority.spaceshipRampBackgroundRailing); + : super( + anchor: Anchor.center, + position: Vector2(-11.7, -54.3), + priority: RenderPriority.spaceshipRampBackgroundRailing, + ); @override Future onLoad() async { await super.onLoad(); - final sprite = await gameRef.loadSprite( - Assets.images.spaceship.ramp.railingBackground.keyName, + final sprite = Sprite( + gameRef.images.fromCache( + Assets.images.spaceship.ramp.railingBackground.keyName, + ), ); this.sprite = sprite; size = sprite.originalSize / 10; - anchor = Anchor.center; - position = Vector2(-11.7, -54.3); } } @@ -144,13 +206,50 @@ class _SpaceshipRampBackgroundRampSpriteComponent extends SpriteComponent @override Future onLoad() async { await super.onLoad(); - final sprite = await gameRef.loadSprite( - Assets.images.spaceship.ramp.main.keyName, + final sprite = Sprite( + gameRef.images.fromCache( + Assets.images.spaceship.ramp.main.keyName, + ), ); this.sprite = sprite; size = sprite.originalSize / 10; anchor = Anchor.center; - position = Vector2(-11.7, -53.6); + position = Vector2(-10.7, -53.6); + } +} + +/// {@template spaceship_ramp_arrow_sprite_component} +/// An arrow inside [SpaceshipRamp]. +/// +/// Lights up a each dash whenever a [Ball] gets into [SpaceshipRamp]. +/// {@endtemplate} +class SpaceshipRampArrowSpriteComponent + extends SpriteGroupComponent + with HasGameRef { + /// {@macro spaceship_ramp_arrow_sprite_component} + SpaceshipRampArrowSpriteComponent() + : super( + anchor: Anchor.center, + position: Vector2(-3.9, -56.5), + priority: RenderPriority.spaceshipRampArrow, + ); + + /// Changes arrow image to the next [Sprite]. + void progress() => current = current?.next; + + @override + Future onLoad() async { + await super.onLoad(); + final sprites = {}; + this.sprites = sprites; + for (final spriteState in SpaceshipRampArrowSpriteState.values) { + sprites[spriteState] = Sprite( + gameRef.images.fromCache(spriteState.path), + ); + } + + current = SpaceshipRampArrowSpriteState.inactive; + size = sprites[current]!.originalSize / 10; } } @@ -159,8 +258,10 @@ class _SpaceshipRampBoardOpeningSpriteComponent extends SpriteComponent @override Future onLoad() async { await super.onLoad(); - final sprite = await gameRef.loadSprite( - Assets.images.spaceship.ramp.boardOpening.keyName, + final sprite = Sprite( + gameRef.images.fromCache( + Assets.images.spaceship.ramp.boardOpening.keyName, + ), ); this.sprite = sprite; size = sprite.originalSize / 10; @@ -231,16 +332,22 @@ class _SpaceshipRampForegroundRailing extends BodyComponent class _SpaceshipRampForegroundRailingSpriteComponent extends SpriteComponent with HasGameRef { + _SpaceshipRampForegroundRailingSpriteComponent() + : super( + anchor: Anchor.center, + position: Vector2(-12.3, -52.5), + ); + @override Future onLoad() async { await super.onLoad(); - final sprite = await gameRef.loadSprite( - Assets.images.spaceship.ramp.railingForeground.keyName, + final sprite = Sprite( + gameRef.images.fromCache( + Assets.images.spaceship.ramp.railingForeground.keyName, + ), ); this.sprite = sprite; size = sprite.originalSize / 10; - anchor = Anchor.center; - position = Vector2(-12.3, -52.5); } } diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index 26d919da..a14c5296 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -52,6 +52,7 @@ flutter: - assets/images/spaceship/ - assets/images/spaceship/rail/ - assets/images/spaceship/ramp/ + - assets/images/spaceship/ramp/arrow/ - assets/images/chrome_dino/ - assets/images/kicker/ - assets/images/plunger/ diff --git a/packages/pinball_components/sandbox/lib/stories/spaceship_ramp/spaceship_ramp_game.dart b/packages/pinball_components/sandbox/lib/stories/spaceship_ramp/spaceship_ramp_game.dart index a11f6d7d..f259b28b 100644 --- a/packages/pinball_components/sandbox/lib/stories/spaceship_ramp/spaceship_ramp_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/spaceship_ramp/spaceship_ramp_game.dart @@ -2,11 +2,12 @@ import 'dart:async'; import 'package:flame/input.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.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 SpaceshipRampGame extends BasicBallGame { +class SpaceshipRampGame extends BasicBallGame with KeyboardEvents { SpaceshipRampGame() : super( color: Colors.blue, @@ -19,13 +20,45 @@ class SpaceshipRampGame extends BasicBallGame { - Activate the "trace" parameter to overlay the body. - Tap anywhere on the screen to spawn a ball into the game. + - Press space to progress arrow sprites. '''; + late final SpaceshipRamp _spaceshipRamp; + @override Future onLoad() async { await super.onLoad(); - await addFromBlueprint(SpaceshipRamp()); + + await images.loadAll([ + 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, + ]); + + _spaceshipRamp = SpaceshipRamp(); + await addFromBlueprint(_spaceshipRamp); camera.followVector2(Vector2(-12, -50)); await traceAllBodies(); } + + @override + KeyEventResult onKeyEvent( + RawKeyEvent event, + Set keysPressed, + ) { + if (event is RawKeyDownEvent && + event.logicalKey == LogicalKeyboardKey.space) { + _spaceshipRamp.progress(); + return KeyEventResult.handled; + } + + return KeyEventResult.ignored; + } } diff --git a/packages/pinball_components/test/src/components/golden/spaceship-ramp.png b/packages/pinball_components/test/src/components/golden/spaceship-ramp.png deleted file mode 100644 index b88ae491..00000000 Binary files a/packages/pinball_components/test/src/components/golden/spaceship-ramp.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/golden/spaceship_ramp/active1.png b/packages/pinball_components/test/src/components/golden/spaceship_ramp/active1.png new file mode 100644 index 00000000..1342e4a9 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/spaceship_ramp/active1.png differ diff --git a/packages/pinball_components/test/src/components/golden/spaceship_ramp/active2.png b/packages/pinball_components/test/src/components/golden/spaceship_ramp/active2.png new file mode 100644 index 00000000..daf85a54 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/spaceship_ramp/active2.png differ diff --git a/packages/pinball_components/test/src/components/golden/spaceship_ramp/active3.png b/packages/pinball_components/test/src/components/golden/spaceship_ramp/active3.png new file mode 100644 index 00000000..3d9f1998 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/spaceship_ramp/active3.png differ diff --git a/packages/pinball_components/test/src/components/golden/spaceship_ramp/active4.png b/packages/pinball_components/test/src/components/golden/spaceship_ramp/active4.png new file mode 100644 index 00000000..aa7eac8f Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/spaceship_ramp/active4.png differ diff --git a/packages/pinball_components/test/src/components/golden/spaceship_ramp/active5.png b/packages/pinball_components/test/src/components/golden/spaceship_ramp/active5.png new file mode 100644 index 00000000..597b1b66 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/spaceship_ramp/active5.png differ diff --git a/packages/pinball_components/test/src/components/golden/spaceship_ramp/inactive.png b/packages/pinball_components/test/src/components/golden/spaceship_ramp/inactive.png new file mode 100644 index 00000000..edd38070 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/spaceship_ramp/inactive.png differ 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 8b623461..4e5d149b 100644 --- a/packages/pinball_components/test/src/components/spaceship_ramp_test.dart +++ b/packages/pinball_components/test/src/components/spaceship_ramp_test.dart @@ -1,5 +1,6 @@ // ignore_for_file: cascade_invocations +import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -9,22 +10,197 @@ import 'package:pinball_flame/pinball_flame.dart'; 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, + ]; + final flameTester = FlameTester(() => TestGame(assets)); + group('SpaceshipRamp', () { - final tester = FlameTester(TestGame.new); - - tester.testGameWidget( - 'renders correctly', - setUp: (game, tester) async { - await game.addFromBlueprint(SpaceshipRamp()); - game.camera.followVector2(Vector2(-13, -50)); - await game.ready(); - }, - verify: (game, tester) async { - await expectLater( - find.byGame(), - matchesGoldenFile('golden/spaceship-ramp.png'), - ); + flameTester.test( + 'loads correctly', + (game) async { + final spaceshipRamp = SpaceshipRamp(); + await game.ensureAdd(spaceshipRamp); + + expect(game.contains(spaceshipRamp), isTrue); }, ); + + group('renders correctly', () { + final centerForSpaceshipRamp = Vector2(-13, -55); + + flameTester.testGameWidget( + 'inactive sprite', + setUp: (game, tester) async { + await game.images.loadAll(assets); + final spaceshipRamp = SpaceshipRamp(); + await game.addFromBlueprint(spaceshipRamp); + await game.ready(); + await tester.pump(); + + expect( + spaceshipRamp.spaceshipRampArrow.current, + SpaceshipRampArrowSpriteState.inactive, + ); + + game.camera.followVector2(centerForSpaceshipRamp); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/spaceship_ramp/inactive.png'), + ); + }, + ); + + flameTester.testGameWidget( + 'active1 sprite', + setUp: (game, tester) async { + await game.images.loadAll(assets); + final spaceshipRamp = SpaceshipRamp(); + await game.addFromBlueprint(spaceshipRamp); + await game.ready(); + spaceshipRamp.progress(); + await tester.pump(); + + expect( + spaceshipRamp.spaceshipRampArrow.current, + SpaceshipRampArrowSpriteState.active1, + ); + + game.camera.followVector2(centerForSpaceshipRamp); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/spaceship_ramp/active1.png'), + ); + }, + ); + + flameTester.testGameWidget( + 'active2 sprite', + setUp: (game, tester) async { + await game.images.loadAll(assets); + final spaceshipRamp = SpaceshipRamp(); + await game.addFromBlueprint(spaceshipRamp); + await game.ready(); + spaceshipRamp + ..progress() + ..progress(); + await tester.pump(); + + expect( + spaceshipRamp.spaceshipRampArrow.current, + SpaceshipRampArrowSpriteState.active2, + ); + + game.camera.followVector2(centerForSpaceshipRamp); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/spaceship_ramp/active2.png'), + ); + }, + ); + + flameTester.testGameWidget( + 'active3 sprite', + setUp: (game, tester) async { + await game.images.loadAll(assets); + final spaceshipRamp = SpaceshipRamp(); + await game.addFromBlueprint(spaceshipRamp); + await game.ready(); + spaceshipRamp + ..progress() + ..progress() + ..progress(); + await tester.pump(); + + expect( + spaceshipRamp.spaceshipRampArrow.current, + SpaceshipRampArrowSpriteState.active3, + ); + + game.camera.followVector2(centerForSpaceshipRamp); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/spaceship_ramp/active3.png'), + ); + }, + ); + + flameTester.testGameWidget( + 'active4 sprite', + setUp: (game, tester) async { + await game.images.loadAll(assets); + final spaceshipRamp = SpaceshipRamp(); + await game.addFromBlueprint(spaceshipRamp); + await game.ready(); + spaceshipRamp + ..progress() + ..progress() + ..progress() + ..progress(); + await tester.pump(); + + expect( + spaceshipRamp.spaceshipRampArrow.current, + SpaceshipRampArrowSpriteState.active4, + ); + + game.camera.followVector2(centerForSpaceshipRamp); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/spaceship_ramp/active4.png'), + ); + }, + ); + + flameTester.testGameWidget( + 'active5 sprite', + setUp: (game, tester) async { + await game.images.loadAll(assets); + final spaceshipRamp = SpaceshipRamp(); + await game.addFromBlueprint(spaceshipRamp); + await game.ready(); + spaceshipRamp + ..progress() + ..progress() + ..progress() + ..progress() + ..progress(); + await tester.pump(); + + expect( + spaceshipRamp.spaceshipRampArrow.current, + SpaceshipRampArrowSpriteState.active5, + ); + + game.camera.followVector2(centerForSpaceshipRamp); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/spaceship_ramp/active5.png'), + ); + }, + ); + }); }); } diff --git a/test/game/bloc/game_bloc_test.dart b/test/game/bloc/game_bloc_test.dart index 0d94b03f..b31ea0d5 100644 --- a/test/game/bloc/game_bloc_test.dart +++ b/test/game/bloc/game_bloc_test.dart @@ -85,14 +85,14 @@ void main() { ); }); - group('IncreasedMultiplier', () { + group('MultiplierIncreased', () { blocTest( 'increases multiplier ' 'when game is not over', build: GameBloc.new, act: (bloc) => bloc - ..add(const IncreasedMultiplier(increase: 1)) - ..add(const IncreasedMultiplier(increase: 1)), + ..add(const MultiplierIncreased(increase: 1)) + ..add(const MultiplierIncreased(increase: 1)), expect: () => [ const GameState( score: 0, @@ -117,7 +117,7 @@ void main() { for (var i = 0; i < bloc.state.balls; i++) { bloc.add(const BallLost()); } - bloc.add(const IncreasedMultiplier(increase: 1)); + bloc.add(const MultiplierIncreased(increase: 1)); }, expect: () => [ const GameState( @@ -142,7 +142,7 @@ void main() { ); }); - group('AppliedMultiplier', () { + group('MultiplierApplied', () { blocTest( 'apply multiplier to score', build: GameBloc.new, @@ -153,7 +153,7 @@ void main() { bonusHistory: [], ), act: (bloc) { - bloc.add(const AppliedMultiplier()); + bloc.add(const MultiplierApplied()); }, expect: () => [ const GameState( @@ -166,7 +166,7 @@ void main() { ); }); - group('ResetMultiplier', () { + group('MultiplierReset', () { blocTest( 'resets multiplier', build: GameBloc.new, @@ -177,7 +177,7 @@ void main() { bonusHistory: [], ), act: (bloc) { - bloc.add(const ResetMultiplier()); + bloc.add(const MultiplierReset()); }, expect: () => [ const GameState( diff --git a/test/game/bloc/game_event_test.dart b/test/game/bloc/game_event_test.dart index 94d1c9fb..7ed4b6f4 100644 --- a/test/game/bloc/game_event_test.dart +++ b/test/game/bloc/game_event_test.dart @@ -41,51 +41,51 @@ void main() { }); }); - group('IncreasedMultiplier', () { + group('MultiplierIncreased', () { test('can be instantiated', () { - expect(const IncreasedMultiplier(increase: 1), isNotNull); + expect(const MultiplierIncreased(increase: 1), isNotNull); }); test('supports value equality', () { expect( - IncreasedMultiplier(increase: 1), - equals(const IncreasedMultiplier(increase: 1)), + MultiplierIncreased(increase: 1), + equals(const MultiplierIncreased(increase: 1)), ); expect( - const IncreasedMultiplier(increase: 1), - isNot(equals(const IncreasedMultiplier(increase: 2))), + const MultiplierIncreased(increase: 1), + isNot(equals(const MultiplierIncreased(increase: 2))), ); }); test( 'throws AssertionError ' 'when increase is smaller than 1', () { - expect(() => IncreasedMultiplier(increase: 0), throwsAssertionError); + expect(() => MultiplierIncreased(increase: 0), throwsAssertionError); }); }); - group('AppliedMultiplier', () { + group('MultiplierApplied', () { test('can be instantiated', () { - expect(const AppliedMultiplier(), isNotNull); + expect(const MultiplierApplied(), isNotNull); }); test('supports value equality', () { expect( - AppliedMultiplier(), - equals(const AppliedMultiplier()), + MultiplierApplied(), + equals(const MultiplierApplied()), ); }); }); - group('ResetMultiplier', () { + group('MultiplierReset', () { test('can be instantiated', () { - expect(const ResetMultiplier(), isNotNull); + expect(const MultiplierReset(), isNotNull); }); test('supports value equality', () { expect( - ResetMultiplier(), - equals(const ResetMultiplier()), + MultiplierReset(), + equals(const MultiplierReset()), ); }); }); diff --git a/test/game/components/score_effect_controller_test.dart b/test/game/components/score_effect_controller_test.dart deleted file mode 100644 index 14fd7b86..00000000 --- a/test/game/components/score_effect_controller_test.dart +++ /dev/null @@ -1,110 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'package:flame/components.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:pinball/game/game.dart'; -import 'package:pinball_components/pinball_components.dart'; - -import '../../helpers/helpers.dart'; - -void main() { - group('ScoreEffectController', () { - late ScoreEffectController controller; - late PinballGame game; - - setUpAll(() { - registerFallbackValue(Component()); - }); - - setUp(() { - game = MockPinballGame(); - when(() => game.add(any())).thenAnswer((_) async {}); - - controller = ScoreEffectController(game); - }); - - group('listenWhen', () { - test('returns true when the user has earned points', () { - const previous = GameState.initial(); - const current = GameState( - score: 10, - multiplier: 1, - balls: 3, - bonusHistory: [], - ); - expect(controller.listenWhen(previous, current), isTrue); - }); - - test( - 'returns true when the user has earned points and there was no ' - 'previous state', - () { - const current = GameState( - score: 10, - multiplier: 1, - balls: 3, - bonusHistory: [], - ); - expect(controller.listenWhen(null, current), isTrue); - }, - ); - - test( - 'returns false when no points were earned', - () { - const current = GameState.initial(); - const previous = GameState.initial(); - expect(controller.listenWhen(previous, current), isFalse); - }, - ); - }); - - group('onNewState', () { - test( - 'adds a ScoreText with the correct score for the ' - 'first time', - () { - const state = GameState( - score: 10, - multiplier: 1, - balls: 3, - bonusHistory: [], - ); - - controller.onNewState(state); - - final effect = - verify(() => game.add(captureAny())).captured.first as ScoreText; - - expect(effect.text, equals('10')); - }, - ); - - test('adds a ScoreTextEffect with the correct score', () { - controller.onNewState( - const GameState( - score: 10, - multiplier: 1, - balls: 3, - bonusHistory: [], - ), - ); - - controller.onNewState( - const GameState( - score: 14, - multiplier: 1, - balls: 3, - bonusHistory: [], - ), - ); - - final effect = - verify(() => game.add(captureAny())).captured.last as ScoreText; - - expect(effect.text, equals('4')); - }); - }); - }); -} diff --git a/test/game/components/score_points_test.dart b/test/game/components/score_points_test.dart index 8317f20c..dcd0ad82 100644 --- a/test/game/components/score_points_test.dart +++ b/test/game/components/score_points_test.dart @@ -28,9 +28,13 @@ void main() { setUp(() { game = MockPinballGame(); bloc = MockGameBloc(); - ball = MockBall(); audio = MockPinballAudio(); fakeScorePoints = FakeScorePoints(); + + ball = MockBall(); + final ballBody = MockBody(); + when(() => ball.body).thenReturn(ballBody); + when(() => ballBody.position).thenReturn(Vector2.all(4)); }); setUpAll(() { @@ -73,6 +77,29 @@ void main() { verify(audio.score).called(1); }, ); + + test( + "adds a ScoreText component at Ball's position", + () { + when(game.read).thenReturn(bloc); + when(() => game.audio).thenReturn(audio); + + BallScorePointsCallback(game).begin( + ball, + fakeScorePoints, + FakeContact(), + ); + + verify( + () => game.add( + ScoreText( + text: fakeScorePoints.points.toString(), + position: ball.body.position, + ), + ), + ).called(1); + }, + ); }); }); } diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index 2dbe3308..1775a42f 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -33,6 +33,16 @@ void main() { Assets.images.sparky.bumper.b.inactive.keyName, Assets.images.sparky.bumper.c.active.keyName, Assets.images.sparky.bumper.c.inactive.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, ]; final flameTester = FlameTester(() => PinballTestGame(assets)); final debugModeFlameTester = FlameTester(() => DebugPinballTestGame(assets));