diff --git a/.gitignore b/.gitignore index a7531405..2d9c4dbe 100644 --- a/.gitignore +++ b/.gitignore @@ -131,6 +131,3 @@ app.*.map.json test/.test_runner.dart web/__/firebase/init.js - -# Application exceptions -!/packages/pinball_components/assets/images/flutter_sign_post.png diff --git a/assets/images/components/background.png b/assets/images/components/background.png index 023814e2..77a8542c 100644 Binary files a/assets/images/components/background.png and b/assets/images/components/background.png differ diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index 521d575e..2780b608 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -11,8 +11,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart'; +import 'package:pinball/game/game.dart'; import 'package:pinball/l10n/l10n.dart'; -import 'package:pinball/landing/landing.dart'; +import 'package:pinball/theme/theme.dart'; import 'package:pinball_audio/pinball_audio.dart'; class App extends StatelessWidget { @@ -34,20 +35,17 @@ class App extends StatelessWidget { RepositoryProvider.value(value: _leaderboardRepository), RepositoryProvider.value(value: _pinballAudio), ], - child: MaterialApp( - title: 'I/O Pinball', - theme: ThemeData( - appBarTheme: const AppBarTheme(color: Color(0xFF13B9FF)), - colorScheme: ColorScheme.fromSwatch( - accentColor: const Color(0xFF13B9FF), - ), + child: BlocProvider( + create: (context) => ThemeCubit(), + child: const MaterialApp( + title: 'I/O Pinball', + localizationsDelegates: [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, + home: PinballGamePage(), ), - localizationsDelegates: const [ - AppLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - ], - supportedLocales: AppLocalizations.supportedLocales, - home: const LandingPage(), ), ); } diff --git a/lib/game/components/alien_zone.dart b/lib/game/components/alien_zone.dart index 30d7bbd5..720c1180 100644 --- a/lib/game/components/alien_zone.dart +++ b/lib/game/components/alien_zone.dart @@ -5,14 +5,12 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; /// {@template alien_zone} /// Area positioned below [Spaceship] where the [Ball] /// can bounce off [AlienBumper]s. /// -/// When a [Ball] hits [AlienBumper]s, they toggle between activated and -/// deactivated states. +/// When a [Ball] hits an [AlienBumper], the bumper animates. /// {@endtemplate} class AlienZone extends Component with HasGameRef { /// {@macro alien_zone} @@ -22,12 +20,12 @@ class AlienZone extends Component with HasGameRef { Future onLoad() async { await super.onLoad(); - gameRef.addContactCallback(_ControlledAlienBumperBallContactCallback()); + gameRef.addContactCallback(AlienBumperBallContactCallback()); - final lowerBumper = ControlledAlienBumper.a() - ..initialPosition = Vector2(-32.52, -9.34); - final upperBumper = ControlledAlienBumper.b() - ..initialPosition = Vector2(-22.89, -17.43); + final lowerBumper = _AlienBumper.a() + ..initialPosition = Vector2(-32.52, -9.1); + final upperBumper = _AlienBumper.b() + ..initialPosition = Vector2(-22.89, -17.35); await addAll([ lowerBumper, @@ -36,60 +34,27 @@ class AlienZone extends Component with HasGameRef { } } -/// {@template controlled_alien_bumper} -/// [AlienBumper] with [_AlienBumperController] attached. -/// {@endtemplate} -@visibleForTesting -class ControlledAlienBumper extends AlienBumper - with Controls<_AlienBumperController>, ScorePoints { - /// {@macro controlled_alien_bumper} - ControlledAlienBumper.a() : super.a() { - controller = _AlienBumperController(this); - } +// TODO(alestiago): Revisit ScorePoints logic once the FlameForge2D +// ContactCallback process is enhanced. +class _AlienBumper extends AlienBumper with ScorePoints { + _AlienBumper.a() : super.a(); - /// {@macro controlled_alien_bumper} - ControlledAlienBumper.b() : super.b() { - controller = _AlienBumperController(this); - } + _AlienBumper.b() : super.b(); @override - // TODO(ruimiguel): change points when get final points map. int get points => 20; } -/// {@template alien_bumper_controller} -/// Controls a [AlienBumper]. -/// {@endtemplate} -class _AlienBumperController extends ComponentController - with HasGameRef { - /// {@macro alien_bumper_controller} - _AlienBumperController(AlienBumper alienBumper) : super(alienBumper); - - /// Flag for activated state of the [AlienBumper]. - /// - /// Used to toggle [AlienBumper]s' state between activated and deactivated. - bool isActivated = false; - - /// Registers when a [AlienBumper] is hit by a [Ball]. - void hit() { - if (isActivated) { - component.deactivate(); - } else { - component.activate(); - } - isActivated = !isActivated; - } -} - -/// Listens when a [Ball] bounces bounces against a [AlienBumper]. -class _ControlledAlienBumperBallContactCallback - extends ContactCallback, Ball> { +/// Listens when a [Ball] bounces against an [AlienBumper]. +@visibleForTesting +class AlienBumperBallContactCallback + extends ContactCallback { @override void begin( - Controls<_AlienBumperController> controlledAlienBumper, + AlienBumper alienBumper, Ball _, Contact __, ) { - controlledAlienBumper.controller.hit(); + alienBumper.animate(); } } diff --git a/lib/game/components/board.dart b/lib/game/components/board.dart index a9a9451f..8ee4128f 100644 --- a/lib/game/components/board.dart +++ b/lib/game/components/board.dart @@ -42,7 +42,7 @@ class Board extends Component { // TODO(alestiago): Consider renaming once entire Board is defined. class _BottomGroup extends Component { /// {@macro bottom_group} - _BottomGroup(); + _BottomGroup() : super(priority: RenderPriority.bottomGroup); @override Future onLoad() async { 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/controlled_ball.dart b/lib/game/components/controlled_ball.dart index 27a339c8..6b983cea 100644 --- a/lib/game/components/controlled_ball.dart +++ b/lib/game/components/controlled_ball.dart @@ -18,7 +18,7 @@ class ControlledBall extends Ball with Controls { required PinballTheme theme, }) : super(baseColor: theme.characterTheme.ballColor) { controller = BallController(this); - priority = Ball.launchRampPriority; + priority = RenderPriority.ballOnLaunchRamp; layer = Layer.launcher; } @@ -31,13 +31,13 @@ class ControlledBall extends Ball with Controls { required PinballTheme theme, }) : super(baseColor: theme.characterTheme.ballColor) { controller = BallController(this); - priority = Ball.boardPriority; + priority = RenderPriority.ballOnBoard; } /// [Ball] used in [DebugPinballGame]. ControlledBall.debug() : super(baseColor: const Color(0xFFFF0000)) { controller = DebugBallController(this); - priority = Ball.boardPriority; + priority = RenderPriority.ballOnBoard; } } diff --git a/lib/game/components/flutter_forest.dart b/lib/game/components/flutter_forest.dart index 3c5f5a1f..9c8ab309 100644 --- a/lib/game/components/flutter_forest.dart +++ b/lib/game/components/flutter_forest.dart @@ -11,7 +11,7 @@ import 'package:pinball_flame/pinball_flame.dart'; /// can bounce off [DashNestBumper]s. /// /// When all [DashNestBumper]s are hit at least once, the [GameBonus.dashNest] -/// is awarded, and the [BigDashNestBumper] releases a new [Ball]. +/// is awarded, and the [DashNestBumper.main] releases a new [Ball]. /// {@endtemplate} class FlutterForest extends Component with Controls<_FlutterForestController>, HasGameRef { @@ -25,18 +25,18 @@ class FlutterForest extends Component await super.onLoad(); gameRef.addContactCallback(_DashNestBumperBallContactCallback()); - final signPost = FlutterSignPost()..initialPosition = Vector2(8.35, -58.3); + final signpost = Signpost()..initialPosition = Vector2(8.35, -58.3); - final bigNest = _BigDashNestBumper() + final bigNest = _DashNestBumper.main() ..initialPosition = Vector2(18.55, -59.35); - final smallLeftNest = _SmallDashNestBumper.a() + final smallLeftNest = _DashNestBumper.a() ..initialPosition = Vector2(8.95, -51.95); - final smallRightNest = _SmallDashNestBumper.b() + final smallRightNest = _DashNestBumper.b() ..initialPosition = Vector2(23.3, -46.75); final dashAnimatronic = DashAnimatronic()..position = Vector2(20, -66); await addAll([ - signPost, + signpost, smallLeftNest, smallRightNest, bigNest, @@ -70,8 +70,6 @@ class _FlutterForestController extends ComponentController } Future _addBonusBall() async { - // TODO(alestiago): Remove hardcoded duration. - await Future.delayed(const Duration(milliseconds: 700)); await gameRef.add( ControlledBall.bonus(theme: gameRef.theme) ..initialPosition = Vector2(17.2, -52.7), @@ -81,15 +79,12 @@ class _FlutterForestController extends ComponentController // TODO(alestiago): Revisit ScorePoints logic once the FlameForge2D // ContactCallback process is enhanced. -class _BigDashNestBumper extends BigDashNestBumper with ScorePoints { - @override - int get points => 20; -} +class _DashNestBumper extends DashNestBumper with ScorePoints { + _DashNestBumper.main() : super.main(); -class _SmallDashNestBumper extends SmallDashNestBumper with ScorePoints { - _SmallDashNestBumper.a() : super.a(); + _DashNestBumper.a() : super.a(); - _SmallDashNestBumper.b() : super.b(); + _DashNestBumper.b() : super.b(); @override int get points => 20; diff --git a/lib/game/components/launcher.dart b/lib/game/components/launcher.dart index f3238152..d2e69174 100644 --- a/lib/game/components/launcher.dart +++ b/lib/game/components/launcher.dart @@ -16,8 +16,8 @@ class Launcher extends Forge2DBlueprint { @override void build(Forge2DGame gameRef) { - plunger = ControlledPlunger(compressionDistance: 12.3) - ..initialPosition = Vector2(40.1, 38); + plunger = ControlledPlunger(compressionDistance: 14) + ..initialPosition = Vector2(40.7, 38); final _rocket = RocketSpriteComponent()..position = Vector2(43, 62); 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/components/sparky_fire_zone.dart b/lib/game/components/sparky_fire_zone.dart index 0a5abe88..6d78d32b 100644 --- a/lib/game/components/sparky_fire_zone.dart +++ b/lib/game/components/sparky_fire_zone.dart @@ -5,14 +5,12 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; /// {@template sparky_fire_zone} /// Area positioned at the top left of the [Board] where the [Ball] /// can bounce off [SparkyBumper]s. /// -/// When a [Ball] hits [SparkyBumper]s, they toggle between activated and -/// deactivated states. +/// When a [Ball] hits [SparkyBumper]s, the bumper animates. /// {@endtemplate} class SparkyFireZone extends Component with HasGameRef { /// {@macro sparky_fire_zone} @@ -22,14 +20,14 @@ class SparkyFireZone extends Component with HasGameRef { Future onLoad() async { await super.onLoad(); - gameRef.addContactCallback(_ControlledSparkyBumperBallContactCallback()); + gameRef.addContactCallback(SparkyBumperBallContactCallback()); - final lowerLeftBumper = ControlledSparkyBumper.a() - ..initialPosition = Vector2(-23.15, -41.65); - final upperLeftBumper = ControlledSparkyBumper.b() - ..initialPosition = Vector2(-21.25, -58.15); - final rightBumper = ControlledSparkyBumper.c() - ..initialPosition = Vector2(-3.56, -53.051); + final lowerLeftBumper = _SparkyBumper.a() + ..initialPosition = Vector2(-22.9, -41.65); + final upperLeftBumper = _SparkyBumper.b() + ..initialPosition = Vector2(-21.25, -57.9); + final rightBumper = _SparkyBumper.c() + ..initialPosition = Vector2(-3.3, -52.55); await addAll([ lowerLeftBumper, @@ -39,65 +37,29 @@ class SparkyFireZone extends Component with HasGameRef { } } -/// {@template controlled_sparky_bumper} -/// [SparkyBumper] with [_SparkyBumperController] attached. -/// {@endtemplate} -@visibleForTesting -class ControlledSparkyBumper extends SparkyBumper - with Controls<_SparkyBumperController>, ScorePoints { - ///{@macro controlled_sparky_bumper} - ControlledSparkyBumper.a() : super.a() { - controller = _SparkyBumperController(this); - } +// TODO(alestiago): Revisit ScorePoints logic once the FlameForge2D +// ContactCallback process is enhanced. +class _SparkyBumper extends SparkyBumper with ScorePoints { + _SparkyBumper.a() : super.a(); - ///{@macro controlled_sparky_bumper} - ControlledSparkyBumper.b() : super.b() { - controller = _SparkyBumperController(this); - } + _SparkyBumper.b() : super.b(); - ///{@macro controlled_sparky_bumper} - ControlledSparkyBumper.c() : super.c() { - controller = _SparkyBumperController(this); - } + _SparkyBumper.c() : super.c(); @override int get points => 20; } -/// {@template sparky_bumper_controller} -/// Controls a [SparkyBumper]. -/// {@endtemplate} -class _SparkyBumperController extends ComponentController - with HasGameRef { - /// {@macro sparky_bumper_controller} - _SparkyBumperController(ControlledSparkyBumper controlledSparkyBumper) - : super(controlledSparkyBumper); - - /// Flag for activated state of the [SparkyBumper]. - /// - /// Used to toggle [SparkyBumper]s' state between activated and deactivated. - bool isActivated = false; - - /// Registers when a [SparkyBumper] is hit by a [Ball]. - void hit() { - if (isActivated) { - component.deactivate(); - } else { - component.activate(); - } - isActivated = !isActivated; - } -} - /// Listens when a [Ball] bounces bounces against a [SparkyBumper]. -class _ControlledSparkyBumperBallContactCallback - extends ContactCallback, Ball> { +@visibleForTesting +class SparkyBumperBallContactCallback + extends ContactCallback { @override void begin( - Controls<_SparkyBumperController> controlledSparkyBumper, + SparkyBumper sparkyBumper, Ball _, Contact __, ) { - controlledSparkyBumper.controller.hit(); + sparkyBumper.animate(); } } diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index 533c7bd1..a1988eda 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -9,7 +9,10 @@ extension PinballGameAssetsX on PinballGame { return [ images.load(components.Assets.images.ball.ball.keyName), images.load(components.Assets.images.ball.flameEffect.keyName), - images.load(components.Assets.images.flutterSignPost.keyName), + images.load(components.Assets.images.signpost.inactive.keyName), + images.load(components.Assets.images.signpost.active1.keyName), + images.load(components.Assets.images.signpost.active2.keyName), + images.load(components.Assets.images.signpost.active3.keyName), images.load(components.Assets.images.flipper.left.keyName), images.load(components.Assets.images.flipper.right.keyName), images.load(components.Assets.images.baseboard.left.keyName), @@ -38,16 +41,37 @@ 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), + images.load(components.Assets.images.alienBumper.a.active.keyName), + images.load(components.Assets.images.alienBumper.a.inactive.keyName), + images.load(components.Assets.images.alienBumper.b.active.keyName), + images.load(components.Assets.images.alienBumper.b.inactive.keyName), images.load(components.Assets.images.chromeDino.mouth.keyName), images.load(components.Assets.images.chromeDino.head.keyName), images.load(components.Assets.images.plunger.plunger.keyName), diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index b1ff2c6c..7034cfe5 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -5,6 +5,7 @@ import 'package:flame/components.dart'; import 'package:flame/input.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball/gen/assets.gen.dart'; import 'package:pinball_audio/pinball_audio.dart'; @@ -28,6 +29,9 @@ class PinballGame extends Forge2DGame /// Identifier of the play button overlay static const playButtonOverlay = 'play_button'; + @override + Color backgroundColor() => Colors.transparent; + final PinballTheme theme; final PinballAudio audio; @@ -38,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)))); @@ -164,7 +167,7 @@ class DebugPinballGame extends PinballGame with TapDetector { anchor: Anchor.center, ) ..position = Vector2(0, -7.8) - ..priority = -4; + ..priority = RenderPriority.background; await add(spriteComponent); } diff --git a/lib/game/view/pinball_game_page.dart b/lib/game/view/pinball_game_page.dart index f6b7ee81..38ae0144 100644 --- a/lib/game/view/pinball_game_page.dart +++ b/lib/game/view/pinball_game_page.dart @@ -5,45 +5,25 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/start_game/start_game.dart'; +import 'package:pinball/theme/theme.dart'; import 'package:pinball_audio/pinball_audio.dart'; -import 'package:pinball_theme/pinball_theme.dart'; class PinballGamePage extends StatelessWidget { const PinballGamePage({ Key? key, - required this.theme, - required this.game, + this.isDebugMode = kDebugMode, }) : super(key: key); - final PinballTheme theme; - final PinballGame game; + final bool isDebugMode; static Route route({ - required PinballTheme theme, bool isDebugMode = kDebugMode, }) { return MaterialPageRoute( builder: (context) { - final audio = context.read(); - - final game = isDebugMode - ? DebugPinballGame(theme: theme, audio: audio) - : PinballGame(theme: theme, audio: audio); - - final pinballAudio = context.read(); - final loadables = [ - ...game.preLoadAssets(), - pinballAudio.load(), - ]; - - return MultiBlocProvider( - providers: [ - BlocProvider(create: (_) => GameBloc()), - BlocProvider( - create: (_) => AssetsManagerCubit(loadables)..load(), - ), - ], - child: PinballGamePage(theme: theme, game: game), + return PinballGamePage( + isDebugMode: isDebugMode, ); }, ); @@ -51,7 +31,29 @@ class PinballGamePage extends StatelessWidget { @override Widget build(BuildContext context) { - return PinballGameView(game: game); + final theme = context.read().state.theme; + final audio = context.read(); + final pinballAudio = context.read(); + + final game = isDebugMode + ? DebugPinballGame(theme: theme, audio: audio) + : PinballGame(theme: theme, audio: audio); + + final loadables = [ + ...game.preLoadAssets(), + pinballAudio.load(), + ]; + + return MultiBlocProvider( + providers: [ + BlocProvider(create: (_) => StartGameBloc(game: game)), + BlocProvider(create: (_) => GameBloc()), + BlocProvider( + create: (_) => AssetsManagerCubit(loadables)..load(), + ), + ], + child: PinballGameView(game: game), + ); } } @@ -65,18 +67,51 @@ class PinballGameView extends StatelessWidget { @override Widget build(BuildContext context) { - final loadingProgress = context.watch().state.progress; + final isLoading = context.select( + (AssetsManagerCubit bloc) => bloc.state.progress != 1, + ); - if (loadingProgress != 1) { - return Scaffold( - body: Center( - child: Text( - loadingProgress.toString(), - ), + return Scaffold( + backgroundColor: Colors.blue, + body: isLoading + ? const _PinballGameLoadingView() + : PinballGameLoadedView(game: game), + ); + } +} + +class _PinballGameLoadingView extends StatelessWidget { + const _PinballGameLoadingView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final loadingProgress = context.select( + (AssetsManagerCubit bloc) => bloc.state.progress, + ); + + return Padding( + padding: const EdgeInsets.all(24), + child: Center( + child: LinearProgressIndicator( + color: Colors.white, + value: loadingProgress, ), - ); - } + ), + ); + } +} +@visibleForTesting +class PinballGameLoadedView extends StatelessWidget { + const PinballGameLoadedView({ + Key? key, + required this.game, + }) : super(key: key); + + final PinballGame game; + + @override + Widget build(BuildContext context) { return Stack( children: [ Positioned.fill( diff --git a/lib/game/view/widgets/play_button_overlay.dart b/lib/game/view/widgets/play_button_overlay.dart index 6f039124..ce5dce4b 100644 --- a/lib/game/view/widgets/play_button_overlay.dart +++ b/lib/game/view/widgets/play_button_overlay.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:pinball/game/pinball_game.dart'; import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball/theme/theme.dart'; /// {@template play_button_overlay} /// [Widget] that renders the button responsible to starting the game @@ -18,9 +19,27 @@ class PlayButtonOverlay extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = context.l10n; + return Center( child: ElevatedButton( - onPressed: _game.gameFlowController.start, + onPressed: () { + _game.gameFlowController.start(); + showDialog( + context: context, + barrierDismissible: false, + builder: (_) { + final height = MediaQuery.of(context).size.height * 0.5; + + return Center( + child: SizedBox( + height: height, + width: height * 1.4, + child: const CharacterSelectionDialog(), + ), + ); + }, + ); + }, child: Text(l10n.play), ), ); diff --git a/lib/gen/gen.dart b/lib/gen/gen.dart new file mode 100644 index 00000000..e7ad4c54 --- /dev/null +++ b/lib/gen/gen.dart @@ -0,0 +1 @@ +export 'assets.gen.dart'; diff --git a/lib/landing/landing.dart b/lib/landing/landing.dart deleted file mode 100644 index b7da30c3..00000000 --- a/lib/landing/landing.dart +++ /dev/null @@ -1 +0,0 @@ -export 'view/landing_page.dart'; diff --git a/lib/leaderboard/view/leaderboard_page.dart b/lib/leaderboard/view/leaderboard_page.dart index 54b364e9..61e63d75 100644 --- a/lib/leaderboard/view/leaderboard_page.dart +++ b/lib/leaderboard/view/leaderboard_page.dart @@ -69,7 +69,7 @@ class LeaderboardView extends StatelessWidget { const SizedBox(height: 20), TextButton( onPressed: () => Navigator.of(context).push( - CharacterSelectionPage.route(), + CharacterSelectionDialog.route(), ), child: Text(l10n.retry), ), diff --git a/lib/start_game/start_game.dart b/lib/start_game/start_game.dart index 7171c66d..1556b533 100644 --- a/lib/start_game/start_game.dart +++ b/lib/start_game/start_game.dart @@ -1 +1,2 @@ export 'bloc/start_game_bloc.dart'; +export 'widgets/widgets.dart'; diff --git a/lib/landing/view/landing_page.dart b/lib/start_game/widgets/how_to_play_dialog.dart similarity index 81% rename from lib/landing/view/landing_page.dart rename to lib/start_game/widgets/how_to_play_dialog.dart index 5b0474b6..aed7a3e3 100644 --- a/lib/landing/view/landing_page.dart +++ b/lib/start_game/widgets/how_to_play_dialog.dart @@ -2,42 +2,9 @@ import 'package:flutter/material.dart'; import 'package:pinball/l10n/l10n.dart'; -import 'package:pinball/theme/theme.dart'; -class LandingPage extends StatelessWidget { - const LandingPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - - return Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextButton( - onPressed: () => Navigator.of(context).push( - CharacterSelectionPage.route(), - ), - child: Text(l10n.play), - ), - TextButton( - onPressed: () => showDialog( - context: context, - builder: (_) => const _HowToPlayDialog(), - ), - child: Text(l10n.howToPlay), - ), - ], - ), - ), - ); - } -} - -class _HowToPlayDialog extends StatelessWidget { - const _HowToPlayDialog({Key? key}) : super(key: key); +class HowToPlayDialog extends StatelessWidget { + const HowToPlayDialog({Key? key}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/start_game/widgets/widgets.dart b/lib/start_game/widgets/widgets.dart new file mode 100644 index 00000000..bad2c6b5 --- /dev/null +++ b/lib/start_game/widgets/widgets.dart @@ -0,0 +1 @@ +export 'how_to_play_dialog.dart'; diff --git a/lib/theme/app_colors.dart b/lib/theme/app_colors.dart new file mode 100644 index 00000000..2d3899a6 --- /dev/null +++ b/lib/theme/app_colors.dart @@ -0,0 +1,15 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flutter/material.dart'; + +abstract class AppColors { + static const Color white = Color(0xFFFFFFFF); + + static const Color darkBlue = Color(0xFF0C32A4); + + static const Color orange = Color(0xFFFFEE02); + + static const Color blue = Color(0xFF4B94F6); + + static const Color transparent = Color(0x00000000); +} diff --git a/lib/theme/app_text_style.dart b/lib/theme/app_text_style.dart new file mode 100644 index 00000000..068f1eb9 --- /dev/null +++ b/lib/theme/app_text_style.dart @@ -0,0 +1,35 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flutter/widgets.dart'; +import 'package:pinball/theme/theme.dart'; +import 'package:pinball_components/pinball_components.dart'; + +const _fontPackage = 'pinball_components'; +const _primaryFontFamily = PinballFonts.pixeloidSans; + +abstract class AppTextStyle { + static const headline1 = TextStyle( + fontSize: 28, + package: _fontPackage, + fontFamily: _primaryFontFamily, + ); + + static const headline2 = TextStyle( + fontSize: 24, + package: _fontPackage, + fontFamily: _primaryFontFamily, + ); + + static const headline3 = TextStyle( + color: AppColors.white, + fontSize: 20, + package: _fontPackage, + fontFamily: _primaryFontFamily, + ); + + static const subtitle1 = TextStyle( + fontSize: 10, + fontFamily: _primaryFontFamily, + package: _fontPackage, + ); +} diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index f6318400..5e4fefe9 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -1,2 +1,4 @@ +export 'app_colors.dart'; +export 'app_text_style.dart'; export 'cubit/theme_cubit.dart'; export 'view/view.dart'; diff --git a/lib/theme/view/character_selection_page.dart b/lib/theme/view/character_selection_page.dart index 119a897e..22aaee22 100644 --- a/lib/theme/view/character_selection_page.dart +++ b/lib/theme/view/character_selection_page.dart @@ -2,17 +2,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:pinball/game/game.dart'; import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball/start_game/start_game.dart'; import 'package:pinball/theme/theme.dart'; import 'package:pinball_theme/pinball_theme.dart'; -class CharacterSelectionPage extends StatelessWidget { - const CharacterSelectionPage({Key? key}) : super(key: key); +class CharacterSelectionDialog extends StatelessWidget { + const CharacterSelectionDialog({Key? key}) : super(key: key); static Route route() { return MaterialPageRoute( - builder: (_) => const CharacterSelectionPage(), + builder: (_) => const CharacterSelectionDialog(), ); } @@ -46,11 +46,13 @@ class CharacterSelectionView extends StatelessWidget { const _CharacterSelectionGridView(), const SizedBox(height: 20), TextButton( - onPressed: () => Navigator.of(context).push( - PinballGamePage.route( - theme: context.read().state.theme, - ), - ), + onPressed: () { + Navigator.of(context).pop(); + showDialog( + context: context, + builder: (_) => const HowToPlayDialog(), + ); + }, child: Text(l10n.start), ), ], diff --git a/packages/pinball_components/assets/images/alien_bumper/a/active.png b/packages/pinball_components/assets/images/alien_bumper/a/active.png index 92943dfc..4bb38a74 100644 Binary files a/packages/pinball_components/assets/images/alien_bumper/a/active.png and b/packages/pinball_components/assets/images/alien_bumper/a/active.png differ diff --git a/packages/pinball_components/assets/images/alien_bumper/a/inactive.png b/packages/pinball_components/assets/images/alien_bumper/a/inactive.png index 99745fcf..d693f1c1 100644 Binary files a/packages/pinball_components/assets/images/alien_bumper/a/inactive.png and b/packages/pinball_components/assets/images/alien_bumper/a/inactive.png differ diff --git a/packages/pinball_components/assets/images/alien_bumper/b/active.png b/packages/pinball_components/assets/images/alien_bumper/b/active.png index ec7dc0cb..fbfd36e1 100644 Binary files a/packages/pinball_components/assets/images/alien_bumper/b/active.png and b/packages/pinball_components/assets/images/alien_bumper/b/active.png differ diff --git a/packages/pinball_components/assets/images/alien_bumper/b/inactive.png b/packages/pinball_components/assets/images/alien_bumper/b/inactive.png index 23f7f4b7..fd23693d 100644 Binary files a/packages/pinball_components/assets/images/alien_bumper/b/inactive.png and b/packages/pinball_components/assets/images/alien_bumper/b/inactive.png differ diff --git a/packages/pinball_components/assets/images/dash/bumper/a/active.png b/packages/pinball_components/assets/images/dash/bumper/a/active.png index feeee11f..57330eb4 100644 Binary files a/packages/pinball_components/assets/images/dash/bumper/a/active.png and b/packages/pinball_components/assets/images/dash/bumper/a/active.png differ diff --git a/packages/pinball_components/assets/images/dash/bumper/a/inactive.png b/packages/pinball_components/assets/images/dash/bumper/a/inactive.png index 58ab8c56..aead95ec 100644 Binary files a/packages/pinball_components/assets/images/dash/bumper/a/inactive.png and b/packages/pinball_components/assets/images/dash/bumper/a/inactive.png differ diff --git a/packages/pinball_components/assets/images/dash/bumper/b/active.png b/packages/pinball_components/assets/images/dash/bumper/b/active.png index 4bc2897f..fe871847 100644 Binary files a/packages/pinball_components/assets/images/dash/bumper/b/active.png and b/packages/pinball_components/assets/images/dash/bumper/b/active.png differ diff --git a/packages/pinball_components/assets/images/dash/bumper/b/inactive.png b/packages/pinball_components/assets/images/dash/bumper/b/inactive.png index eddc7693..3d53b743 100644 Binary files a/packages/pinball_components/assets/images/dash/bumper/b/inactive.png and b/packages/pinball_components/assets/images/dash/bumper/b/inactive.png differ diff --git a/packages/pinball_components/assets/images/dash/bumper/main/active.png b/packages/pinball_components/assets/images/dash/bumper/main/active.png index bef56684..9508b56c 100644 Binary files a/packages/pinball_components/assets/images/dash/bumper/main/active.png and b/packages/pinball_components/assets/images/dash/bumper/main/active.png differ diff --git a/packages/pinball_components/assets/images/dash/bumper/main/inactive.png b/packages/pinball_components/assets/images/dash/bumper/main/inactive.png index e6f15b38..b1d0ae7d 100644 Binary files a/packages/pinball_components/assets/images/dash/bumper/main/inactive.png and b/packages/pinball_components/assets/images/dash/bumper/main/inactive.png differ diff --git a/packages/pinball_components/assets/images/flutter_sign_post.png b/packages/pinball_components/assets/images/flutter_sign_post.png deleted file mode 100644 index 28a3facb..00000000 Binary files a/packages/pinball_components/assets/images/flutter_sign_post.png and /dev/null differ diff --git a/packages/pinball_components/assets/images/plunger/plunger.png b/packages/pinball_components/assets/images/plunger/plunger.png index f3cbdf0f..2ec6e001 100644 Binary files a/packages/pinball_components/assets/images/plunger/plunger.png and b/packages/pinball_components/assets/images/plunger/plunger.png differ diff --git a/packages/pinball_components/assets/images/signpost/active1.png b/packages/pinball_components/assets/images/signpost/active1.png new file mode 100644 index 00000000..1addb228 Binary files /dev/null and b/packages/pinball_components/assets/images/signpost/active1.png differ diff --git a/packages/pinball_components/assets/images/signpost/active2.png b/packages/pinball_components/assets/images/signpost/active2.png new file mode 100644 index 00000000..081a936c Binary files /dev/null and b/packages/pinball_components/assets/images/signpost/active2.png differ diff --git a/packages/pinball_components/assets/images/signpost/active3.png b/packages/pinball_components/assets/images/signpost/active3.png new file mode 100644 index 00000000..8d781dfb Binary files /dev/null and b/packages/pinball_components/assets/images/signpost/active3.png differ diff --git a/packages/pinball_components/assets/images/signpost/inactive.png b/packages/pinball_components/assets/images/signpost/inactive.png new file mode 100644 index 00000000..6043454b Binary files /dev/null and b/packages/pinball_components/assets/images/signpost/inactive.png differ 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/assets/images/sparky/bumper/a/active.png b/packages/pinball_components/assets/images/sparky/bumper/a/active.png index 6e84d8ef..81a91a52 100644 Binary files a/packages/pinball_components/assets/images/sparky/bumper/a/active.png and b/packages/pinball_components/assets/images/sparky/bumper/a/active.png differ diff --git a/packages/pinball_components/assets/images/sparky/bumper/a/inactive.png b/packages/pinball_components/assets/images/sparky/bumper/a/inactive.png index 9157a73f..a81c7915 100644 Binary files a/packages/pinball_components/assets/images/sparky/bumper/a/inactive.png and b/packages/pinball_components/assets/images/sparky/bumper/a/inactive.png differ diff --git a/packages/pinball_components/assets/images/sparky/bumper/b/active.png b/packages/pinball_components/assets/images/sparky/bumper/b/active.png index 02371ce1..a00f3f33 100644 Binary files a/packages/pinball_components/assets/images/sparky/bumper/b/active.png and b/packages/pinball_components/assets/images/sparky/bumper/b/active.png differ diff --git a/packages/pinball_components/assets/images/sparky/bumper/b/inactive.png b/packages/pinball_components/assets/images/sparky/bumper/b/inactive.png index 20b4f092..0eac905c 100644 Binary files a/packages/pinball_components/assets/images/sparky/bumper/b/inactive.png and b/packages/pinball_components/assets/images/sparky/bumper/b/inactive.png differ diff --git a/packages/pinball_components/assets/images/sparky/bumper/c/active.png b/packages/pinball_components/assets/images/sparky/bumper/c/active.png index 85748375..265f35aa 100644 Binary files a/packages/pinball_components/assets/images/sparky/bumper/c/active.png and b/packages/pinball_components/assets/images/sparky/bumper/c/active.png differ diff --git a/packages/pinball_components/assets/images/sparky/bumper/c/inactive.png b/packages/pinball_components/assets/images/sparky/bumper/c/inactive.png index b5b3584d..50a69d54 100644 Binary files a/packages/pinball_components/assets/images/sparky/bumper/c/inactive.png and b/packages/pinball_components/assets/images/sparky/bumper/c/inactive.png differ diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index 836f9495..0d67f870 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -21,17 +21,13 @@ class $AssetsImagesGen { $AssetsImagesDashGen get dash => const $AssetsImagesDashGen(); $AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen(); $AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen(); - - /// File path: assets/images/flutter_sign_post.png - AssetGenImage get flutterSignPost => - const AssetGenImage('assets/images/flutter_sign_post.png'); - $AssetsImagesGoogleWordGen get googleWord => const $AssetsImagesGoogleWordGen(); $AssetsImagesKickerGen get kicker => const $AssetsImagesKickerGen(); $AssetsImagesLaunchRampGen get launchRamp => const $AssetsImagesLaunchRampGen(); $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(); @@ -209,6 +205,26 @@ class $AssetsImagesPlungerGen { const AssetGenImage('assets/images/plunger/rocket.png'); } +class $AssetsImagesSignpostGen { + const $AssetsImagesSignpostGen(); + + /// File path: assets/images/signpost/active1.png + AssetGenImage get active1 => + const AssetGenImage('assets/images/signpost/active1.png'); + + /// File path: assets/images/signpost/active2.png + AssetGenImage get active2 => + const AssetGenImage('assets/images/signpost/active2.png'); + + /// File path: assets/images/signpost/active3.png + AssetGenImage get active3 => + const AssetGenImage('assets/images/signpost/active3.png'); + + /// File path: assets/images/signpost/inactive.png + AssetGenImage get inactive => + const AssetGenImage('assets/images/signpost/inactive.png'); +} + class $AssetsImagesSlingshotGen { const $AssetsImagesSlingshotGen(); @@ -295,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'); @@ -368,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/gen/gen.dart b/packages/pinball_components/lib/gen/gen.dart new file mode 100644 index 00000000..0171b231 --- /dev/null +++ b/packages/pinball_components/lib/gen/gen.dart @@ -0,0 +1,2 @@ +export 'assets.gen.dart'; +export 'pinball_fonts.dart'; diff --git a/packages/pinball_components/lib/gen/pinball_fonts.dart b/packages/pinball_components/lib/gen/pinball_fonts.dart index c1b3c6fa..59dcaa6e 100644 --- a/packages/pinball_components/lib/gen/pinball_fonts.dart +++ b/packages/pinball_components/lib/gen/pinball_fonts.dart @@ -1,16 +1,14 @@ import 'package:pinball_components/gen/fonts.gen.dart'; -String _prefixFont(String font) { - return 'packages/pinball_components/$font'; -} +const String _fontPath = 'packages/pinball_components/'; /// Class with the fonts available on the pinball game class PinballFonts { PinballFonts._(); /// Mono variation of the Pixeloid font - static final String pixeloidMono = _prefixFont(FontFamily.pixeloidMono); + static const String pixeloidMono = '$_fontPath/${FontFamily.pixeloidMono}'; /// Sans variation of the Pixeloid font - static final String pixeloidSans = _prefixFont(FontFamily.pixeloidMono); + static const String pixeloidSans = '$_fontPath/${FontFamily.pixeloidSans}'; } diff --git a/packages/pinball_components/lib/pinball_components.dart b/packages/pinball_components/lib/pinball_components.dart index 2551b54e..cf394b0e 100644 --- a/packages/pinball_components/lib/pinball_components.dart +++ b/packages/pinball_components/lib/pinball_components.dart @@ -1,5 +1,4 @@ library pinball_components; -export 'gen/assets.gen.dart'; -export 'gen/pinball_fonts.dart'; +export 'gen/gen.dart'; export 'src/pinball_components.dart'; diff --git a/packages/pinball_components/lib/src/components/alien_bumper.dart b/packages/pinball_components/lib/src/components/alien_bumper.dart index 9165cc15..1f96d214 100644 --- a/packages/pinball_components/lib/src/components/alien_bumper.dart +++ b/packages/pinball_components/lib/src/components/alien_bumper.dart @@ -1,36 +1,41 @@ +import 'dart:async'; + import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template alien_bumper} -/// Bumper for Alien area. +/// Bumper for area under the [Spaceship]. /// {@endtemplate} -// TODO(ruimiguel): refactor later to unify with DashBumpers. class AlienBumper extends BodyComponent with InitialPosition { /// {@macro alien_bumper} AlienBumper._({ required double majorRadius, required double minorRadius, - required String activeAssetPath, - required String inactiveAssetPath, - required SpriteComponent spriteComponent, + required String onAssetPath, + required String offAssetPath, }) : _majorRadius = majorRadius, _minorRadius = minorRadius, - _activeAssetPath = activeAssetPath, - _inactiveAssetPath = inactiveAssetPath, - _spriteComponent = spriteComponent; + super( + priority: RenderPriority.alienBumper, + children: [ + _AlienBumperSpriteGroupComponent( + onAssetPath: onAssetPath, + offAssetPath: offAssetPath, + ), + ], + ) { + renderBody = false; + } /// {@macro alien_bumper} AlienBumper.a() : this._( majorRadius: 3.52, minorRadius: 2.97, - activeAssetPath: Assets.images.alienBumper.a.active.keyName, - inactiveAssetPath: Assets.images.alienBumper.a.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0, -0.1), - ), + onAssetPath: Assets.images.alienBumper.a.active.keyName, + offAssetPath: Assets.images.alienBumper.a.inactive.keyName, ); /// {@macro alien_bumper} @@ -38,32 +43,12 @@ class AlienBumper extends BodyComponent with InitialPosition { : this._( majorRadius: 3.19, minorRadius: 2.79, - activeAssetPath: Assets.images.alienBumper.b.active.keyName, - inactiveAssetPath: Assets.images.alienBumper.b.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0, -0.1), - ), + onAssetPath: Assets.images.alienBumper.b.active.keyName, + offAssetPath: Assets.images.alienBumper.b.inactive.keyName, ); final double _majorRadius; final double _minorRadius; - final String _activeAssetPath; - late final Sprite _activeSprite; - final String _inactiveAssetPath; - late final Sprite _inactiveSprite; - final SpriteComponent _spriteComponent; - - @override - Future onLoad() async { - await super.onLoad(); - renderBody = false; - - await _loadSprites(); - - deactivate(); - await add(_spriteComponent); - } @override Body createBody() { @@ -84,25 +69,53 @@ class AlienBumper extends BodyComponent with InitialPosition { return world.createBody(bodyDef)..createFixture(fixtureDef); } - Future _loadSprites() async { - // TODO(alestiago): I think ideally we would like to do: - // Sprite(path).load so we don't require to store the activeAssetPath and - // the inactive assetPath. - _inactiveSprite = await gameRef.loadSprite(_inactiveAssetPath); - _activeSprite = await gameRef.loadSprite(_activeAssetPath); + /// Animates the [AlienBumper]. + Future animate() async { + final spriteGroupComponent = firstChild<_AlienBumperSpriteGroupComponent>() + ?..current = AlienBumperSpriteState.inactive; + await Future.delayed(const Duration(milliseconds: 50)); + spriteGroupComponent?.current = AlienBumperSpriteState.active; } +} - /// Activates the [AlienBumper]. - void activate() { - _spriteComponent - ..sprite = _activeSprite - ..size = _activeSprite.originalSize / 10; - } +/// Indicates the [AlienBumper]'s current sprite state. +@visibleForTesting +enum AlienBumperSpriteState { + /// A lit up bumper. + active, + + /// A dimmed bumper. + inactive, +} + +class _AlienBumperSpriteGroupComponent + extends SpriteGroupComponent with HasGameRef { + _AlienBumperSpriteGroupComponent({ + required String onAssetPath, + required String offAssetPath, + }) : _onAssetPath = onAssetPath, + _offAssetPath = offAssetPath, + super( + anchor: Anchor.center, + position: Vector2(0, -0.1), + ); + + final String _onAssetPath; + final String _offAssetPath; + + @override + Future onLoad() async { + await super.onLoad(); + + final sprites = { + AlienBumperSpriteState.active: + Sprite(gameRef.images.fromCache(_onAssetPath)), + AlienBumperSpriteState.inactive: + Sprite(gameRef.images.fromCache(_offAssetPath)), + }; + this.sprites = sprites; - /// Deactivates the [AlienBumper]. - void deactivate() { - _spriteComponent - ..sprite = _inactiveSprite - ..size = _inactiveSprite.originalSize / 10; + current = AlienBumperSpriteState.active; + size = sprites[current]!.originalSize / 10; } } diff --git a/packages/pinball_components/lib/src/components/backboard/backboard.dart b/packages/pinball_components/lib/src/components/backboard/backboard.dart index c5c4ac17..dc3cacb3 100644 --- a/packages/pinball_components/lib/src/components/backboard/backboard.dart +++ b/packages/pinball_components/lib/src/components/backboard/backboard.dart @@ -46,7 +46,7 @@ class Backboard extends PositionComponent with HasGameRef { /// [TextPaint] used on the [Backboard] static final textPaint = TextPaint( - style: TextStyle( + style: const TextStyle( fontSize: 6, color: Colors.white, fontFamily: PinballFonts.pixeloidSans, diff --git a/packages/pinball_components/lib/src/components/ball.dart b/packages/pinball_components/lib/src/components/ball.dart index abbfefc8..b1e2703b 100644 --- a/packages/pinball_components/lib/src/components/ball.dart +++ b/packages/pinball_components/lib/src/components/ball.dart @@ -29,21 +29,6 @@ class Ball extends BodyComponent renderBody = false; } - /// Render priority for the [Ball] while it's on the board. - static const int boardPriority = 0; - - /// Render priority for the [Ball] while it's on the [SpaceshipRamp]. - static const int spaceshipRampPriority = 4; - - /// Render priority for the [Ball] while it's on the [Spaceship]. - static const int spaceshipPriority = 4; - - /// Render priority for the [Ball] while it's on the [SpaceshipRail]. - static const int spaceshipRailPriority = 2; - - /// Render priority for the [Ball] while it's on the [LaunchRamp]. - static const int launchRampPriority = -2; - /// The size of the [Ball]. static final Vector2 size = Vector2.all(4.13); @@ -152,7 +137,7 @@ class _TurboChargeSpriteAnimationComponent extends SpriteAnimationComponent _TurboChargeSpriteAnimationComponent() : super( anchor: const Anchor(0.53, 0.72), - priority: Ball.boardPriority + 1, + priority: RenderPriority.turboChargeFlame, removeOnFinish: true, ); diff --git a/packages/pinball_components/lib/src/components/boundaries.dart b/packages/pinball_components/lib/src/components/boundaries.dart index 2ac8fe76..8c59d598 100644 --- a/packages/pinball_components/lib/src/components/boundaries.dart +++ b/packages/pinball_components/lib/src/components/boundaries.dart @@ -26,8 +26,7 @@ class _BottomBoundary extends BodyComponent with InitialPosition { /// {@macro bottom_boundary} _BottomBoundary() : super( - // TODO(ruimiguel): set final priority when RenderPriority PR merged. - priority: Ball.boardPriority + 2, + priority: RenderPriority.bottomBoundary, children: [_BottomBoundarySpriteComponent()], ) { renderBody = false; @@ -91,7 +90,7 @@ class _OuterBoundary extends BodyComponent with InitialPosition { /// {@macro outer_boundary} _OuterBoundary() : super( - priority: Ball.launchRampPriority - 1, + priority: RenderPriority.outerBoudary, children: [_OuterBoundarySpriteComponent()], ) { renderBody = false; diff --git a/packages/pinball_components/lib/src/components/chrome_dino.dart b/packages/pinball_components/lib/src/components/chrome_dino.dart index 348cabbb..6cc2ddf6 100644 --- a/packages/pinball_components/lib/src/components/chrome_dino.dart +++ b/packages/pinball_components/lib/src/components/chrome_dino.dart @@ -12,10 +12,12 @@ import 'package:pinball_components/pinball_components.dart'; /// {@endtemplate} class ChromeDino extends BodyComponent with InitialPosition { /// {@macro chrome_dino} - ChromeDino() { - // TODO(alestiago): Remove once sprites are defined. - paint = Paint()..color = Colors.blue; - } + ChromeDino() + : super( + // TODO(alestiago): Remove once sprites are defined. + paint: Paint()..color = Colors.blue, + priority: RenderPriority.dino, + ); /// The size of the dinosaur mouth. static final size = Vector2(5, 2.5); diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index ffada30b..9ea39d69 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -12,7 +12,6 @@ export 'dash_nest_bumper.dart'; export 'dino_walls.dart'; export 'fire_effect.dart'; export 'flipper.dart'; -export 'flutter_sign_post.dart'; export 'google_letter.dart'; export 'initial_position.dart'; export 'joint_anchor.dart'; @@ -21,9 +20,11 @@ export 'launch_ramp.dart'; export 'layer.dart'; export 'layer_sensor.dart'; export 'plunger.dart'; +export 'render_priority.dart'; export 'rocket.dart'; export 'score_text.dart'; export 'shapes/shapes.dart'; +export 'signpost.dart'; export 'slingshot.dart'; export 'spaceship.dart'; export 'spaceship_rail.dart'; diff --git a/packages/pinball_components/lib/src/components/dash_animatronic.dart b/packages/pinball_components/lib/src/components/dash_animatronic.dart index 1ab7e76e..634c5574 100644 --- a/packages/pinball_components/lib/src/components/dash_animatronic.dart +++ b/packages/pinball_components/lib/src/components/dash_animatronic.dart @@ -2,7 +2,7 @@ import 'package:flame/components.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template dash_animatronic} -/// Animated Dash that sits on top of the [BigDashNestBumper]. +/// Animated Dash that sits on top of the [DashNestBumper.main]. /// {@endtemplate} class DashAnimatronic extends SpriteAnimationComponent with HasGameRef { /// {@macro dash_animatronic} @@ -10,6 +10,7 @@ class DashAnimatronic extends SpriteAnimationComponent with HasGameRef { : super( anchor: Anchor.center, playing: false, + priority: RenderPriority.dashAnimatronic, ); @override diff --git a/packages/pinball_components/lib/src/components/dash_nest_bumper.dart b/packages/pinball_components/lib/src/components/dash_nest_bumper.dart index a16fa376..46f96b37 100644 --- a/packages/pinball_components/lib/src/components/dash_nest_bumper.dart +++ b/packages/pinball_components/lib/src/components/dash_nest_bumper.dart @@ -2,138 +2,76 @@ 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/pinball_components.dart'; /// {@template dash_nest_bumper} /// Bumper with a nest appearance. /// {@endtemplate} -abstract class DashNestBumper extends BodyComponent with InitialPosition { +class DashNestBumper extends BodyComponent with InitialPosition { /// {@macro dash_nest_bumper} DashNestBumper._({ + required double majorRadius, + required double minorRadius, required String activeAssetPath, required String inactiveAssetPath, - required SpriteComponent spriteComponent, - }) : _activeAssetPath = activeAssetPath, - _inactiveAssetPath = inactiveAssetPath, - _spriteComponent = spriteComponent; - - final String _activeAssetPath; - late final Sprite _activeSprite; - final String _inactiveAssetPath; - late final Sprite _inactiveSprite; - final SpriteComponent _spriteComponent; - - Future _loadSprites() async { - // TODO(alestiago): I think ideally we would like to do: - // Sprite(path).load so we don't require to store the activeAssetPath and - // the inactive assetPath. - _inactiveSprite = await gameRef.loadSprite(_inactiveAssetPath); - _activeSprite = await gameRef.loadSprite(_activeAssetPath); - } - - /// Activates the [DashNestBumper]. - void activate() { - _spriteComponent - ..sprite = _activeSprite - ..size = _activeSprite.originalSize / 10; + required Vector2 spritePosition, + }) : _majorRadius = majorRadius, + _minorRadius = minorRadius, + super( + priority: RenderPriority.dashBumper, + children: [ + _DashNestBumperSpriteGroupComponent( + activeAssetPath: activeAssetPath, + inactiveAssetPath: inactiveAssetPath, + position: spritePosition, + ), + ], + ) { + renderBody = false; } - /// Deactivates the [DashNestBumper]. - void deactivate() { - _spriteComponent - ..sprite = _inactiveSprite - ..size = _inactiveSprite.originalSize / 10; - } - - @override - Future onLoad() async { - await super.onLoad(); - await _loadSprites(); - - // TODO(erickzanardo): Look into using onNewState instead. - // Currently doing: onNewState(gameRef.read()) will throw an - // `Exception: build context is not available yet` - deactivate(); - await add(_spriteComponent); - } -} - -/// {@macro dash_nest_bumper} -class BigDashNestBumper extends DashNestBumper { /// {@macro dash_nest_bumper} - BigDashNestBumper() - : super._( + DashNestBumper.main() + : this._( + majorRadius: 5.1, + minorRadius: 3.75, activeAssetPath: Assets.images.dash.bumper.main.active.keyName, inactiveAssetPath: Assets.images.dash.bumper.main.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0, -0.3), - ), + spritePosition: Vector2(0, -0.3), ); - @override - Body createBody() { - final shape = EllipseShape( - center: Vector2.zero(), - majorRadius: 5.1, - minorRadius: 3.75, - )..rotate(math.pi / 1.9); - final fixtureDef = FixtureDef(shape); - final bodyDef = BodyDef( - position: initialPosition, - userData: this, - ); - - return world.createBody(bodyDef)..createFixture(fixtureDef); - } -} - -/// {@macro dash_nest_bumper} -class SmallDashNestBumper extends DashNestBumper { /// {@macro dash_nest_bumper} - SmallDashNestBumper._({ - required String activeAssetPath, - required String inactiveAssetPath, - required SpriteComponent spriteComponent, - }) : super._( - activeAssetPath: activeAssetPath, - inactiveAssetPath: inactiveAssetPath, - spriteComponent: spriteComponent, - ); - - /// {@macro dash_nest_bumper} - SmallDashNestBumper.a() + DashNestBumper.a() : this._( + majorRadius: 3, + minorRadius: 2.5, activeAssetPath: Assets.images.dash.bumper.a.active.keyName, inactiveAssetPath: Assets.images.dash.bumper.a.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0.35, -1.2), - ), + spritePosition: Vector2(0.35, -1.2), ); /// {@macro dash_nest_bumper} - SmallDashNestBumper.b() + DashNestBumper.b() : this._( + majorRadius: 3, + minorRadius: 2.5, activeAssetPath: Assets.images.dash.bumper.b.active.keyName, inactiveAssetPath: Assets.images.dash.bumper.b.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0.35, -1.2), - ), + spritePosition: Vector2(0.35, -1.2), ); + final double _majorRadius; + final double _minorRadius; + @override Body createBody() { final shape = EllipseShape( center: Vector2.zero(), - majorRadius: 3, - minorRadius: 2.25, - )..rotate(math.pi / 2); - final fixtureDef = FixtureDef( - shape, - restitution: 4, - ); + majorRadius: _majorRadius, + minorRadius: _minorRadius, + )..rotate(math.pi / 1.9); + final fixtureDef = FixtureDef(shape, restitution: 4); final bodyDef = BodyDef( position: initialPosition, userData: this, @@ -141,4 +79,58 @@ class SmallDashNestBumper extends DashNestBumper { return world.createBody(bodyDef)..createFixture(fixtureDef); } + + /// Activates the [DashNestBumper]. + void activate() { + firstChild<_DashNestBumperSpriteGroupComponent>()?.current = + DashNestBumperSpriteState.active; + } + + /// Deactivates the [DashNestBumper]. + void deactivate() { + firstChild<_DashNestBumperSpriteGroupComponent>()?.current = + DashNestBumperSpriteState.inactive; + } +} + +/// Indicates the [DashNestBumper]'s current sprite state. +@visibleForTesting +enum DashNestBumperSpriteState { + /// A lit up bumper. + active, + + /// A dimmed bumper. + inactive, +} + +class _DashNestBumperSpriteGroupComponent + extends SpriteGroupComponent with HasGameRef { + _DashNestBumperSpriteGroupComponent({ + required String activeAssetPath, + required String inactiveAssetPath, + required Vector2 position, + }) : _activeAssetPath = activeAssetPath, + _inactiveAssetPath = inactiveAssetPath, + super( + anchor: Anchor.center, + position: position, + ); + + final String _activeAssetPath; + final String _inactiveAssetPath; + + @override + Future onLoad() async { + await super.onLoad(); + final sprites = { + DashNestBumperSpriteState.active: + Sprite(gameRef.images.fromCache(_activeAssetPath)), + DashNestBumperSpriteState.inactive: + Sprite(gameRef.images.fromCache(_inactiveAssetPath)), + }; + this.sprites = sprites; + + current = DashNestBumperSpriteState.inactive; + size = sprites[current]!.originalSize / 10; + } } diff --git a/packages/pinball_components/lib/src/components/dino_walls.dart b/packages/pinball_components/lib/src/components/dino_walls.dart index 4e0dbee2..139e391b 100644 --- a/packages/pinball_components/lib/src/components/dino_walls.dart +++ b/packages/pinball_components/lib/src/components/dino_walls.dart @@ -31,8 +31,7 @@ class _DinoTopWall extends BodyComponent with InitialPosition { ///{@macro dino_top_wall} _DinoTopWall() : super( - // TODO(ruimiguel): set final priority when RenderPriority PR merged. - priority: Ball.boardPriority + 1, + priority: RenderPriority.dinoTopWall, children: [_DinoTopWallSpriteComponent()], ) { renderBody = false; @@ -127,8 +126,7 @@ class _DinoBottomWall extends BodyComponent with InitialPosition { ///{@macro dino_top_wall} _DinoBottomWall() : super( - // TODO(ruimiguel): set final priority when RenderPriority PR merged. - priority: Ball.boardPriority + 1, + priority: RenderPriority.dinoBottomWall, children: [_DinoBottomWallSpriteComponent()], ) { renderBody = false; diff --git a/packages/pinball_components/lib/src/components/flutter_sign_post.dart b/packages/pinball_components/lib/src/components/flutter_sign_post.dart deleted file mode 100644 index d7999183..00000000 --- a/packages/pinball_components/lib/src/components/flutter_sign_post.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flame/components.dart'; -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:pinball_components/pinball_components.dart'; - -/// {@template flutter_sign_post} -/// A sign, found in the Flutter Forest. -/// {@endtemplate} -class FlutterSignPost extends BodyComponent with InitialPosition { - /// {@macro flutter_sign_post} - FlutterSignPost() - : super( - children: [_FlutterSignPostSpriteComponent()], - ) { - renderBody = false; - } - - @override - Body createBody() { - final shape = CircleShape()..radius = 0.25; - final fixtureDef = FixtureDef(shape); - final bodyDef = BodyDef( - position: initialPosition, - ); - - return world.createBody(bodyDef)..createFixture(fixtureDef); - } -} - -class _FlutterSignPostSpriteComponent extends SpriteComponent with HasGameRef { - @override - Future onLoad() async { - await super.onLoad(); - - final sprite = await gameRef.loadSprite( - Assets.images.flutterSignPost.keyName, - ); - this.sprite = sprite; - size = sprite.originalSize / 10; - anchor = Anchor.bottomCenter; - position = Vector2(0.65, 0.45); - } -} diff --git a/packages/pinball_components/lib/src/components/launch_ramp.dart b/packages/pinball_components/lib/src/components/launch_ramp.dart index c144f38d..2ac9ee91 100644 --- a/packages/pinball_components/lib/src/components/launch_ramp.dart +++ b/packages/pinball_components/lib/src/components/launch_ramp.dart @@ -44,7 +44,7 @@ class _LaunchRampBase extends BodyComponent with InitialPosition, Layered { /// {@macro launch_ramp_base} _LaunchRampBase() : super( - priority: Ball.launchRampPriority - 1, + priority: RenderPriority.launchRamp, children: [ _LaunchRampBackgroundRailingSpriteComponent(), _LaunchRampBaseSpriteComponent(), @@ -166,7 +166,7 @@ class _LaunchRampForegroundRailing extends BodyComponent with InitialPosition { /// {@macro launch_ramp_foreground_railing} _LaunchRampForegroundRailing() : super( - priority: Ball.launchRampPriority + 1, + priority: RenderPriority.launchRampForegroundRailing, children: [_LaunchRampForegroundRailingSpriteComponent()], ) { renderBody = false; @@ -265,8 +265,8 @@ class _LaunchRampExit extends LayerSensor { insideLayer: Layer.launcher, outsideLayer: Layer.board, orientation: LayerEntranceOrientation.down, - insidePriority: Ball.launchRampPriority, - outsidePriority: 0, + insidePriority: RenderPriority.ballOnLaunchRamp, + outsidePriority: RenderPriority.ballOnBoard, ) { layer = Layer.launcher; renderBody = false; diff --git a/packages/pinball_components/lib/src/components/layer_sensor.dart b/packages/pinball_components/lib/src/components/layer_sensor.dart index 05f9a98d..85cc8506 100644 --- a/packages/pinball_components/lib/src/components/layer_sensor.dart +++ b/packages/pinball_components/lib/src/components/layer_sensor.dart @@ -34,7 +34,7 @@ abstract class LayerSensor extends BodyComponent with InitialPosition, Layered { }) : _insideLayer = insideLayer, _outsideLayer = outsideLayer ?? Layer.board, _insidePriority = insidePriority, - _outsidePriority = outsidePriority ?? Ball.boardPriority { + _outsidePriority = outsidePriority ?? RenderPriority.ballOnBoard { layer = Layer.opening; } final Layer _insideLayer; diff --git a/packages/pinball_components/lib/src/components/plunger.dart b/packages/pinball_components/lib/src/components/plunger.dart index f01c007c..efa8817a 100644 --- a/packages/pinball_components/lib/src/components/plunger.dart +++ b/packages/pinball_components/lib/src/components/plunger.dart @@ -14,25 +14,49 @@ class Plunger extends BodyComponent with InitialPosition, Layered { required this.compressionDistance, // TODO(ruimiguel): set to priority +1 over LaunchRamp once all priorities // are fixed. - }) : super(priority: 0) { + }) : super(priority: RenderPriority.plunger) { layer = Layer.launcher; + renderBody = false; } /// Distance the plunger can lower. final double compressionDistance; + late final _PlungerSpriteAnimationGroupComponent _spriteComponent; + + List _createFixtureDefs() { + final fixturesDef = []; + + final leftShapeVertices = [ + Vector2(0, 0), + Vector2(-1.8, 0), + Vector2(-1.8, -2.2), + Vector2(0, -0.3), + ]..map((vector) => vector.rotate(BoardDimensions.perspectiveAngle)) + .toList(); + final leftTriangleShape = PolygonShape()..set(leftShapeVertices); + + final leftTriangleFixtureDef = FixtureDef(leftTriangleShape)..density = 80; + fixturesDef.add(leftTriangleFixtureDef); + + final rightShapeVertices = [ + Vector2(0, 0), + Vector2(1.8, 0), + Vector2(1.8, -2.2), + Vector2(0, -0.3), + ]..map((vector) => vector.rotate(BoardDimensions.perspectiveAngle)) + .toList(); + final rightTriangleShape = PolygonShape()..set(rightShapeVertices); + + final rightTriangleFixtureDef = FixtureDef(rightTriangleShape) + ..density = 80; + fixturesDef.add(rightTriangleFixtureDef); + + return fixturesDef; + } + @override Body createBody() { - final shape = PolygonShape() - ..setAsBox( - 1.35, - 0.5, - Vector2.zero(), - BoardDimensions.perspectiveAngle, - ); - - final fixtureDef = FixtureDef(shape)..density = 80; - final bodyDef = BodyDef( position: initialPosition, userData: this, @@ -40,12 +64,15 @@ class Plunger extends BodyComponent with InitialPosition, Layered { gravityScale: Vector2.zero(), ); - return world.createBody(bodyDef)..createFixture(fixtureDef); + final body = world.createBody(bodyDef); + _createFixtureDefs().forEach(body.createFixture); + return body; } /// Set a constant downward velocity on the [Plunger]. void pull() { body.linearVelocity = Vector2(0, 7); + _spriteComponent.pull(); } /// Set an upward velocity on the [Plunger]. @@ -55,6 +82,7 @@ class Plunger extends BodyComponent with InitialPosition, Layered { void release() { final velocity = (initialPosition.y - body.position.y) * 5; body.linearVelocity = Vector2(0, velocity); + _spriteComponent.release(); } /// Anchors the [Plunger] to the [PrismaticJoint] that controls its vertical @@ -77,24 +105,84 @@ class Plunger extends BodyComponent with InitialPosition, Layered { Future onLoad() async { await super.onLoad(); await _anchorToJoint(); - renderBody = false; - await add(_PlungerSpriteComponent()); + + _spriteComponent = _PlungerSpriteAnimationGroupComponent(); + await add(_spriteComponent); } } -class _PlungerSpriteComponent extends SpriteComponent with HasGameRef { +/// Animation states associated with a [Plunger]. +enum _PlungerAnimationState { + /// Pull state. + pull, + + /// Release state. + release, +} + +/// Animations for pulling and releasing [Plunger]. +class _PlungerSpriteAnimationGroupComponent + extends SpriteAnimationGroupComponent<_PlungerAnimationState> + with HasGameRef { + _PlungerSpriteAnimationGroupComponent() + : super( + anchor: Anchor.center, + position: Vector2(1.87, 14.9), + ); + + void pull() { + if (current != _PlungerAnimationState.pull) { + animation?.reset(); + } + current = _PlungerAnimationState.pull; + } + + void release() { + if (current != _PlungerAnimationState.release) { + animation?.reset(); + } + current = _PlungerAnimationState.release; + } + @override Future onLoad() async { await super.onLoad(); - final sprite = await gameRef.loadSprite( + + final spriteSheet = await gameRef.images.load( Assets.images.plunger.plunger.keyName, ); - this.sprite = sprite; - size = sprite.originalSize / 10; - anchor = Anchor.center; - position = Vector2(1.5, 13.4); - angle = -0.008; + const amountPerRow = 20; + const amountPerColumn = 1; + + final textureSize = Vector2( + spriteSheet.width / amountPerRow, + spriteSheet.height / amountPerColumn, + ); + size = textureSize / 10; + + // TODO(ruimiguel): we only need plunger pull animation, and release is just + // to reverse it, so we need to divide by 2 while we don't have only half of + // the animation (but amountPerRow and amountPerColumn needs to be correct + // in order of calculate textureSize correctly). + + final pullAnimation = SpriteAnimation.fromFrameData( + spriteSheet, + SpriteAnimationData.sequenced( + amount: amountPerRow * amountPerColumn ~/ 2, + amountPerRow: amountPerRow ~/ 2, + stepTime: 1 / 24, + textureSize: textureSize, + texturePosition: Vector2.zero(), + loop: false, + ), + ); + + animations = { + _PlungerAnimationState.release: pullAnimation.reversed(), + _PlungerAnimationState.pull: pullAnimation, + }; + current = _PlungerAnimationState.release; } } diff --git a/packages/pinball_components/lib/src/components/render_priority.dart b/packages/pinball_components/lib/src/components/render_priority.dart new file mode 100644 index 00000000..1b02326f --- /dev/null +++ b/packages/pinball_components/lib/src/components/render_priority.dart @@ -0,0 +1,116 @@ +// ignore_for_file: public_member_api_docs + +import 'package:pinball_components/pinball_components.dart'; + +/// {@template render_priority} +/// Priorities for the component rendering order in the pinball game. +/// {@endtemplate} +// TODO(allisonryan0002): find alternative to section comments. +abstract class RenderPriority { + static const _base = 0; + static const _above = 1; + static const _below = -1; + + // Ball + + /// Render priority for the [Ball] while it's on the board. + static const int ballOnBoard = _base; + + /// Render priority for the [Ball] while it's on the [SpaceshipRamp]. + static const int ballOnSpaceshipRamp = + _above + spaceshipRampBackgroundRailing; + + /// Render priority for the [Ball] while it's on the [Spaceship]. + static const int ballOnSpaceship = _above + spaceshipSaucer; + + /// Render priority for the [Ball] while it's on the [SpaceshipRail]. + static const int ballOnSpaceshipRail = _below + spaceshipSaucer; + + /// Render priority for the [Ball] while it's on the [LaunchRamp]. + static const int ballOnLaunchRamp = _above + launchRamp; + + // Background + + // TODO(allisonryan0002): fix this magic priority. Could bump all priorities + // so there are no negatives. + static const int background = 3 * _below + _base; + + // Boundaries + + static const int bottomBoundary = _above + dinoBottomWall; + + static const int outerBoudary = _above + background; + + // Bottom Group + + static const int bottomGroup = _above + ballOnBoard; + + // Launcher + + static const int launchRamp = _above + outerBoudary; + + static const int launchRampForegroundRailing = _above + ballOnLaunchRamp; + + static const int plunger = _above + launchRamp; + + static const int rocket = _above + bottomBoundary; + + // Dino Land + + static const int dinoTopWall = _above + ballOnBoard; + + static const int dino = _above + dinoTopWall; + + static const int dinoBottomWall = _above + dino; + + static const int slingshot = _above + ballOnBoard; + + // Flutter Forest + + static const int signpost = _above + launchRampForegroundRailing; + + static const int dashBumper = _above + ballOnBoard; + + static const int dashAnimatronic = _above + launchRampForegroundRailing; + + // Sparky Fire Zone + + static const int computerBase = _below + ballOnBoard; + + static const int computerTop = _above + ballOnBoard; + + static const int sparkyAnimatronic = _above + spaceshipRampForegroundRailing; + + static const int sparkyBumper = _above + ballOnBoard; + + static const int turboChargeFlame = _above + ballOnBoard; + + // Android Spaceship + + static const int spaceshipRail = _above + bottomGroup; + + static const int spaceshipRailForeground = _above + spaceshipRail; + + static const int spaceshipSaucer = _above + spaceshipRail; + + static const int spaceshipSaucerWall = _above + spaceshipSaucer; + + static const int androidHead = _above + spaceshipSaucer; + + static const int spaceshipRamp = _above + ballOnBoard; + + static const int spaceshipRampBackgroundRailing = _above + spaceshipRamp; + + static const int spaceshipRampArrow = _above + spaceshipRamp; + + static const int spaceshipRampForegroundRailing = + _above + ballOnSpaceshipRamp; + + static const int spaceshipRampBoardOpening = _below + ballOnBoard; + + static const int alienBumper = _above + ballOnBoard; + + // Score Text + + static const int scoreText = _above + spaceshipRampForegroundRailing; +} diff --git a/packages/pinball_components/lib/src/components/rocket.dart b/packages/pinball_components/lib/src/components/rocket.dart index ee22efca..3f9161ca 100644 --- a/packages/pinball_components/lib/src/components/rocket.dart +++ b/packages/pinball_components/lib/src/components/rocket.dart @@ -9,7 +9,7 @@ class RocketSpriteComponent extends SpriteComponent with HasGameRef { // TODO(ruimiguel): change this priority to be over launcher ramp and bottom // wall. /// {@macro rocket_sprite_component} - RocketSpriteComponent() : super(priority: 5); + RocketSpriteComponent() : super(priority: RenderPriority.rocket); @override Future onLoad() async { diff --git a/packages/pinball_components/lib/src/components/score_text.dart b/packages/pinball_components/lib/src/components/score_text.dart index 6fe5f6a3..a81b4a6f 100644 --- a/packages/pinball_components/lib/src/components/score_text.dart +++ b/packages/pinball_components/lib/src/components/score_text.dart @@ -18,7 +18,7 @@ class ScoreText extends TextComponent { text: text, position: position, anchor: Anchor.center, - priority: Ball.spaceshipRampPriority + 1, + priority: RenderPriority.scoreText, ); late final Effect _effect; diff --git a/packages/pinball_components/lib/src/components/signpost.dart b/packages/pinball_components/lib/src/components/signpost.dart new file mode 100644 index 00000000..175c3382 --- /dev/null +++ b/packages/pinball_components/lib/src/components/signpost.dart @@ -0,0 +1,99 @@ +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball_components/pinball_components.dart'; + +/// Represents the [Signpost]'s current [Sprite] state. +@visibleForTesting +enum SignpostSpriteState { + /// Signpost with no active dashes. + inactive, + + /// Signpost with a single sign of active dashes. + active1, + + /// Signpost with two signs of active dashes. + active2, + + /// Signpost with all signs of active dashes. + active3, +} + +extension on SignpostSpriteState { + String get path { + switch (this) { + case SignpostSpriteState.inactive: + return Assets.images.signpost.inactive.keyName; + case SignpostSpriteState.active1: + return Assets.images.signpost.active1.keyName; + case SignpostSpriteState.active2: + return Assets.images.signpost.active2.keyName; + case SignpostSpriteState.active3: + return Assets.images.signpost.active3.keyName; + } + } + + SignpostSpriteState get next { + return SignpostSpriteState + .values[(index + 1) % SignpostSpriteState.values.length]; + } +} + +/// {@template signpost} +/// A sign, found in the Flutter Forest. +/// +/// Lights up a new sign whenever all three [DashNestBumper]s are hit. +/// {@endtemplate} +class Signpost extends BodyComponent with InitialPosition { + /// {@macro signpost} + Signpost() + : super( + priority: RenderPriority.signpost, + children: [_SignpostSpriteComponent()], + ) { + renderBody = false; + } + + /// Forwards the sprite to the next [SignpostSpriteState]. + /// + /// If the current state is the last one it cycles back to the initial state. + void progress() => firstChild<_SignpostSpriteComponent>()!.progress(); + + @override + Body createBody() { + final shape = CircleShape()..radius = 0.25; + final fixtureDef = FixtureDef(shape); + final bodyDef = BodyDef( + position: initialPosition, + ); + + return world.createBody(bodyDef)..createFixture(fixtureDef); + } +} + +class _SignpostSpriteComponent extends SpriteGroupComponent + with HasGameRef { + _SignpostSpriteComponent() + : super( + anchor: Anchor.bottomCenter, + position: Vector2(0.65, 0.45), + ); + + void progress() => current = current?.next; + + @override + Future onLoad() async { + await super.onLoad(); + + final sprites = {}; + this.sprites = sprites; + for (final spriteState in SignpostSpriteState.values) { + sprites[spriteState] = Sprite( + gameRef.images.fromCache(spriteState.path), + ); + } + + current = SignpostSpriteState.inactive; + size = sprites[current]!.originalSize / 10; + } +} diff --git a/packages/pinball_components/lib/src/components/slingshot.dart b/packages/pinball_components/lib/src/components/slingshot.dart index b460565b..52cdb698 100644 --- a/packages/pinball_components/lib/src/components/slingshot.dart +++ b/packages/pinball_components/lib/src/components/slingshot.dart @@ -43,7 +43,7 @@ class Slingshot extends BodyComponent with InitialPosition { }) : _length = length, _angle = angle, super( - priority: 1, + priority: RenderPriority.slingshot, children: [_SlinghsotSpriteComponent(spritePath, angle: angle)], ) { renderBody = false; diff --git a/packages/pinball_components/lib/src/components/spaceship.dart b/packages/pinball_components/lib/src/components/spaceship.dart index 0a0b39db..b95a7a65 100644 --- a/packages/pinball_components/lib/src/components/spaceship.dart +++ b/packages/pinball_components/lib/src/components/spaceship.dart @@ -35,9 +35,12 @@ class Spaceship extends Forge2DBlueprint { AndroidHead()..initialPosition = position, _SpaceshipHole( outsideLayer: Layer.spaceshipExitRail, - outsidePriority: Ball.spaceshipRailPriority, + outsidePriority: RenderPriority.ballOnSpaceshipRail, )..initialPosition = position - Vector2(5.2, -4.8), - _SpaceshipHole()..initialPosition = position - Vector2(-7.2, -0.8), + _SpaceshipHole( + outsideLayer: Layer.board, + outsidePriority: RenderPriority.ballOnBoard, + )..initialPosition = position - Vector2(-7.2, -0.8), SpaceshipWall()..initialPosition = position, ]); } @@ -48,7 +51,7 @@ class Spaceship extends Forge2DBlueprint { /// {@endtemplate} class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered { /// {@macro spaceship_saucer} - SpaceshipSaucer() : super(priority: Ball.spaceshipPriority - 1) { + SpaceshipSaucer() : super(priority: RenderPriority.spaceshipSaucer) { layer = Layer.spaceship; } @@ -94,7 +97,7 @@ class AndroidHead extends BodyComponent with InitialPosition, Layered { /// {@macro spaceship_bridge} AndroidHead() : super( - priority: Ball.spaceshipPriority + 1, + priority: RenderPriority.androidHead, children: [_AndroidHeadSpriteAnimation()], ) { layer = Layer.spaceship; @@ -151,7 +154,7 @@ class _SpaceshipEntrance extends LayerSensor { : super( insideLayer: Layer.spaceship, orientation: LayerEntranceOrientation.up, - insidePriority: Ball.spaceshipPriority, + insidePriority: RenderPriority.ballOnSpaceship, ) { layer = Layer.spaceship; } @@ -175,12 +178,12 @@ class _SpaceshipEntrance extends LayerSensor { } class _SpaceshipHole extends LayerSensor { - _SpaceshipHole({Layer? outsideLayer, int? outsidePriority = 1}) + _SpaceshipHole({required Layer outsideLayer, required int outsidePriority}) : super( insideLayer: Layer.spaceship, outsideLayer: outsideLayer, orientation: LayerEntranceOrientation.down, - insidePriority: 4, + insidePriority: RenderPriority.ballOnSpaceship, outsidePriority: outsidePriority, ) { renderBody = false; @@ -231,7 +234,7 @@ class _SpaceshipWallShape extends ChainShape { /// {@endtemplate} class SpaceshipWall extends BodyComponent with InitialPosition, Layered { /// {@macro spaceship_wall} - SpaceshipWall() : super(priority: Ball.spaceshipPriority + 1) { + SpaceshipWall() : super(priority: RenderPriority.spaceshipSaucerWall) { layer = Layer.spaceship; } diff --git a/packages/pinball_components/lib/src/components/spaceship_rail.dart b/packages/pinball_components/lib/src/components/spaceship_rail.dart index cc308c77..49fdd475 100644 --- a/packages/pinball_components/lib/src/components/spaceship_rail.dart +++ b/packages/pinball_components/lib/src/components/spaceship_rail.dart @@ -41,10 +41,7 @@ class SpaceshipRail extends Forge2DBlueprint { /// Represents the spaceship drop rail from the [Spaceship]. class _SpaceshipRailRamp extends BodyComponent with InitialPosition, Layered { - _SpaceshipRailRamp() - : super( - priority: Ball.spaceshipRailPriority - 1, - ) { + _SpaceshipRailRamp() : super(priority: RenderPriority.spaceshipRail) { layer = Layer.spaceshipExitRail; } @@ -160,7 +157,8 @@ class _SpaceshipRailRampSpriteComponent extends SpriteComponent } class _SpaceshipRailForeground extends SpriteComponent with HasGameRef { - _SpaceshipRailForeground() : super(priority: Ball.spaceshipRailPriority + 1); + _SpaceshipRailForeground() + : super(priority: RenderPriority.spaceshipRailForeground); @override Future onLoad() async { @@ -177,13 +175,9 @@ class _SpaceshipRailForeground extends SpriteComponent with HasGameRef { } /// Represents the ground bases of the [_SpaceshipRailRamp]. -class _SpaceshipRailBase extends BodyComponent with InitialPosition, Layered { - _SpaceshipRailBase({required this.radius}) - : super( - priority: Ball.spaceshipRailPriority + 1, - ) { +class _SpaceshipRailBase extends BodyComponent with InitialPosition { + _SpaceshipRailBase({required this.radius}) { renderBody = false; - layer = Layer.board; } final double radius; @@ -206,7 +200,7 @@ class _SpaceshipRailExit extends LayerSensor { : super( orientation: LayerEntranceOrientation.down, insideLayer: Layer.spaceshipExitRail, - insidePriority: Ball.spaceshipRailPriority, + insidePriority: RenderPriority.ballOnSpaceshipRail, ) { renderBody = false; layer = Layer.spaceshipExitRail; diff --git a/packages/pinball_components/lib/src/components/spaceship_ramp.dart b/packages/pinball_components/lib/src/components/spaceship_ramp.dart index b0adb8cb..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([ @@ -22,22 +32,23 @@ class SpaceshipRamp extends Forge2DBlueprint { ]); final rightOpening = _SpaceshipRampOpening( - // TODO(ruimiguel): set Board priority when defined. - outsidePriority: 1, - rotation: math.pi, + outsidePriority: RenderPriority.ballOnBoard, + 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: Ball.spaceshipPriority, - rotation: math.pi, + outsidePriority: RenderPriority.ballOnSpaceship, + 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); @@ -51,16 +62,71 @@ class SpaceshipRamp extends Forge2DBlueprint { rightOpening, leftOpening, 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: Ball.spaceshipRampPriority - 1) { + _SpaceshipRampBackground() + : super( + priority: RenderPriority.spaceshipRamp, + children: [ + _SpaceshipRampBackgroundRampSpriteComponent(), + ], + ) { layer = Layer.spaceshipEntranceRamp; + renderBody = false; } /// Width between walls of the ramp. @@ -112,29 +178,26 @@ class _SpaceshipRampBackground extends BodyComponent return body; } - - @override - Future onLoad() async { - await super.onLoad(); - renderBody = false; - - await add(_SpaceshipRampBackgroundRampSpriteComponent()); - await add(_SpaceshipRampBackgroundRailingSpriteComponent()); - } } class _SpaceshipRampBackgroundRailingSpriteComponent extends SpriteComponent with HasGameRef { + _SpaceshipRampBackgroundRailingSpriteComponent() + : 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); } } @@ -143,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; } } @@ -158,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; @@ -171,7 +273,7 @@ class _SpaceshipRampForegroundRailing extends BodyComponent with InitialPosition, Layered { _SpaceshipRampForegroundRailing() : super( - priority: Ball.spaceshipRampPriority + 1, + priority: RenderPriority.spaceshipRampForegroundRailing, children: [_SpaceshipRampForegroundRailingSpriteComponent()], ) { layer = Layer.spaceshipEntranceRamp; @@ -230,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); } } @@ -287,7 +395,7 @@ class _SpaceshipRampOpening extends LayerSensor { insideLayer: Layer.spaceshipEntranceRamp, outsideLayer: outsideLayer, orientation: LayerEntranceOrientation.down, - insidePriority: Ball.spaceshipRampPriority, + insidePriority: RenderPriority.ballOnSpaceshipRamp, outsidePriority: outsidePriority, ) { renderBody = false; diff --git a/packages/pinball_components/lib/src/components/sparky_bumper.dart b/packages/pinball_components/lib/src/components/sparky_bumper.dart index 3022ed38..becac26b 100644 --- a/packages/pinball_components/lib/src/components/sparky_bumper.dart +++ b/packages/pinball_components/lib/src/components/sparky_bumper.dart @@ -2,37 +2,43 @@ 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/pinball_components.dart'; /// {@template sparky_bumper} /// Bumper for Sparky area. /// {@endtemplate} -// TODO(ruimiguel): refactor later to unify with DashBumpers. class SparkyBumper extends BodyComponent with InitialPosition { /// {@macro sparky_bumper} SparkyBumper._({ required double majorRadius, required double minorRadius, - required String activeAssetPath, - required String inactiveAssetPath, - required SpriteComponent spriteComponent, + required String onAssetPath, + required String offAssetPath, + required Vector2 spritePosition, }) : _majorRadius = majorRadius, _minorRadius = minorRadius, - _activeAssetPath = activeAssetPath, - _inactiveAssetPath = inactiveAssetPath, - _spriteComponent = spriteComponent; + super( + priority: RenderPriority.sparkyBumper, + children: [ + _SparkyBumperSpriteGroupComponent( + onAssetPath: onAssetPath, + offAssetPath: offAssetPath, + position: spritePosition, + ), + ], + ) { + renderBody = false; + } /// {@macro sparky_bumper} SparkyBumper.a() : this._( majorRadius: 2.9, minorRadius: 2.1, - activeAssetPath: Assets.images.sparky.bumper.a.active.keyName, - inactiveAssetPath: Assets.images.sparky.bumper.a.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0, -0.25), - ), + onAssetPath: Assets.images.sparky.bumper.a.active.keyName, + offAssetPath: Assets.images.sparky.bumper.a.inactive.keyName, + spritePosition: Vector2(0, -0.25), ); /// {@macro sparky_bumper} @@ -40,12 +46,9 @@ class SparkyBumper extends BodyComponent with InitialPosition { : this._( majorRadius: 2.85, minorRadius: 2, - activeAssetPath: Assets.images.sparky.bumper.b.active.keyName, - inactiveAssetPath: Assets.images.sparky.bumper.b.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0, -0.35), - ), + onAssetPath: Assets.images.sparky.bumper.b.active.keyName, + offAssetPath: Assets.images.sparky.bumper.b.inactive.keyName, + spritePosition: Vector2(0, -0.35), ); /// {@macro sparky_bumper} @@ -53,33 +56,13 @@ class SparkyBumper extends BodyComponent with InitialPosition { : this._( majorRadius: 3, minorRadius: 2.2, - activeAssetPath: Assets.images.sparky.bumper.c.active.keyName, - inactiveAssetPath: Assets.images.sparky.bumper.c.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0, -0.4), - ), + onAssetPath: Assets.images.sparky.bumper.c.active.keyName, + offAssetPath: Assets.images.sparky.bumper.c.inactive.keyName, + spritePosition: Vector2(0, -0.4), ); final double _majorRadius; final double _minorRadius; - final String _activeAssetPath; - late final Sprite _activeSprite; - final String _inactiveAssetPath; - late final Sprite _inactiveSprite; - final SpriteComponent _spriteComponent; - - @override - Future onLoad() async { - await super.onLoad(); - await _loadSprites(); - - // TODO(erickzanardo): Look into using onNewState instead. - // Currently doing: onNewState(gameRef.read()) will throw an - // `Exception: build context is not available yet` - deactivate(); - await add(_spriteComponent); - } @override Body createBody() { @@ -101,25 +84,53 @@ class SparkyBumper extends BodyComponent with InitialPosition { return world.createBody(bodyDef)..createFixture(fixtureDef); } - Future _loadSprites() async { - // TODO(alestiago): I think ideally we would like to do: - // Sprite(path).load so we don't require to store the activeAssetPath and - // the inactive assetPath. - _inactiveSprite = await gameRef.loadSprite(_inactiveAssetPath); - _activeSprite = await gameRef.loadSprite(_activeAssetPath); + /// Animates the [DashNestBumper]. + Future animate() async { + final spriteGroupComponent = firstChild<_SparkyBumperSpriteGroupComponent>() + ?..current = SparkyBumperSpriteState.inactive; + await Future.delayed(const Duration(milliseconds: 50)); + spriteGroupComponent?.current = SparkyBumperSpriteState.active; } +} - /// Activates the [DashNestBumper]. - void activate() { - _spriteComponent - ..sprite = _activeSprite - ..size = _activeSprite.originalSize / 10; - } +/// Indicates the [SparkyBumper]'s current sprite state. +@visibleForTesting +enum SparkyBumperSpriteState { + /// A lit up bumper. + active, + + /// A dimmed bumper. + inactive, +} + +class _SparkyBumperSpriteGroupComponent + extends SpriteGroupComponent with HasGameRef { + _SparkyBumperSpriteGroupComponent({ + required String onAssetPath, + required String offAssetPath, + required Vector2 position, + }) : _onAssetPath = onAssetPath, + _offAssetPath = offAssetPath, + super( + anchor: Anchor.center, + position: position, + ); + + final String _onAssetPath; + final String _offAssetPath; + + @override + Future onLoad() async { + await super.onLoad(); + final sprites = { + SparkyBumperSpriteState.active: + Sprite(gameRef.images.fromCache(_onAssetPath)), + SparkyBumperSpriteState.inactive: + Sprite(gameRef.images.fromCache(_offAssetPath)), + }; + this.sprites = sprites; - /// Deactivates the [DashNestBumper]. - void deactivate() { - _spriteComponent - ..sprite = _inactiveSprite - ..size = _inactiveSprite.originalSize / 10; + current = SparkyBumperSpriteState.active; + size = sprites[current]!.originalSize / 10; } } diff --git a/packages/pinball_components/lib/src/components/sparky_computer.dart b/packages/pinball_components/lib/src/components/sparky_computer.dart index 04ab315f..427847ae 100644 --- a/packages/pinball_components/lib/src/components/sparky_computer.dart +++ b/packages/pinball_components/lib/src/components/sparky_computer.dart @@ -23,7 +23,7 @@ class SparkyComputer extends Forge2DBlueprint { } class _ComputerBase extends BodyComponent with InitialPosition { - _ComputerBase(); + _ComputerBase() : super(priority: RenderPriority.computerBase); List _createFixtureDefs() { final fixturesDef = []; @@ -101,7 +101,7 @@ class _ComputerTopSpriteComponent extends SpriteComponent with HasGameRef { : super( anchor: Anchor.center, position: Vector2(-12.45, -49.75), - priority: 1, + priority: RenderPriority.computerTop, ); @override diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index da4446c1..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/ @@ -64,6 +65,7 @@ flutter: - assets/images/sparky/bumper/c/ - assets/images/backboard/ - assets/images/google_word/ + - assets/images/signpost/ flutter_gen: line_length: 80 diff --git a/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_a_game.dart b/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_a_game.dart index 007d67e1..fec10fe0 100644 --- a/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_a_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_a_game.dart @@ -17,6 +17,11 @@ class AlienBumperAGame extends BasicBallGame { Future onLoad() async { await super.onLoad(); + await images.loadAll([ + Assets.images.alienBumper.a.active.keyName, + Assets.images.alienBumper.a.inactive.keyName, + ]); + final center = screenToWorld(camera.viewport.canvasSize! / 2); final alienBumperA = AlienBumper.a() ..initialPosition = Vector2(center.x - 20, center.y - 20) diff --git a/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_b_game.dart b/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_b_game.dart index fada1dd9..0218127b 100644 --- a/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_b_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_b_game.dart @@ -17,6 +17,11 @@ class AlienBumperBGame extends BasicBallGame { Future onLoad() async { await super.onLoad(); + await images.loadAll([ + Assets.images.alienBumper.b.active.keyName, + Assets.images.alienBumper.b.inactive.keyName, + ]); + final center = screenToWorld(camera.viewport.canvasSize! / 2); final alienBumperB = AlienBumper.b() ..initialPosition = Vector2(center.x - 10, center.y + 10) diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/big_dash_nest_bumper_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/big_dash_nest_bumper_game.dart index e74abe35..1ccd7e8e 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/big_dash_nest_bumper_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/big_dash_nest_bumper_game.dart @@ -15,9 +15,14 @@ class BigDashNestBumperGame extends BasicBallGame with Traceable { @override Future onLoad() async { await super.onLoad(); + + await images.loadAll([ + Assets.images.dash.bumper.main.active.keyName, + Assets.images.dash.bumper.main.inactive.keyName, + ]); + camera.followVector2(Vector2.zero()); - await add(BigDashNestBumper()..priority = 1); - await traceAllBodies(); + await add(DashNestBumper.main()..priority = 1); await traceAllBodies(); } } diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/flutter_sign_post_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/flutter_sign_post_game.dart deleted file mode 100644 index 3efb83fe..00000000 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/flutter_sign_post_game.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'dart:async'; - -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:sandbox/common/common.dart'; -import 'package:sandbox/stories/ball/basic_ball_game.dart'; - -class FlutterSignPostGame extends BasicBallGame with Traceable { - static const info = ''' - Shows how a FlutterSignPost is rendered. - - - Activate the "trace" parameter to overlay the body. -'''; - - @override - Future onLoad() async { - await super.onLoad(); - camera.followVector2(Vector2.zero()); - await add(FlutterSignPost()..priority = 1); - await traceAllBodies(); - } -} diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/signpost_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/signpost_game.dart new file mode 100644 index 00000000..fd7ce93c --- /dev/null +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/signpost_game.dart @@ -0,0 +1,37 @@ +import 'dart:async'; + +import 'package:flame/input.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:sandbox/common/common.dart'; +import 'package:sandbox/stories/ball/basic_ball_game.dart'; + +class SignpostGame extends BasicBallGame with Traceable, TapDetector { + static const info = ''' + Shows how a Signpost is rendered. + + - Activate the "trace" parameter to overlay the body. + - Tap to progress the sprite. +'''; + + @override + Future onLoad() async { + await super.onLoad(); + + await images.loadAll([ + Assets.images.signpost.inactive.keyName, + Assets.images.signpost.active1.keyName, + Assets.images.signpost.active2.keyName, + Assets.images.signpost.active3.keyName, + ]); + + camera.followVector2(Vector2.zero()); + await add(Signpost()); + await traceAllBodies(); + } + + @override + void onTap() { + super.onTap(); + firstChild()!.progress(); + } +} diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_a_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_a_game.dart index 4ef99b21..b0a621fa 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_a_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_a_game.dart @@ -15,9 +15,14 @@ class SmallDashNestBumperAGame extends BasicBallGame with Traceable { @override Future onLoad() async { await super.onLoad(); + + await images.loadAll([ + Assets.images.dash.bumper.a.active.keyName, + Assets.images.dash.bumper.a.inactive.keyName, + ]); + camera.followVector2(Vector2.zero()); - await add(SmallDashNestBumper.a()..priority = 1); - await traceAllBodies(); + await add(DashNestBumper.a()..priority = 1); await traceAllBodies(); } } diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_b_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_b_game.dart index 625b8e5c..5512d4c9 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_b_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_b_game.dart @@ -15,9 +15,14 @@ class SmallDashNestBumperBGame extends BasicBallGame with Traceable { @override Future onLoad() async { await super.onLoad(); + + await images.loadAll([ + Assets.images.dash.bumper.b.active.keyName, + Assets.images.dash.bumper.b.inactive.keyName, + ]); + camera.followVector2(Vector2.zero()); - await add(SmallDashNestBumper.b()..priority = 1); - await traceAllBodies(); + await add(DashNestBumper.b()..priority = 1); await traceAllBodies(); } } diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/stories.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/stories.dart index a625d174..a563a09a 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/stories.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/stories.dart @@ -2,20 +2,19 @@ import 'package:dashbook/dashbook.dart'; import 'package:flame/game.dart'; import 'package:sandbox/common/common.dart'; import 'package:sandbox/stories/flutter_forest/big_dash_nest_bumper_game.dart'; -import 'package:sandbox/stories/flutter_forest/flutter_sign_post_game.dart'; +import 'package:sandbox/stories/flutter_forest/signpost_game.dart'; import 'package:sandbox/stories/flutter_forest/small_dash_nest_bumper_a_game.dart'; import 'package:sandbox/stories/flutter_forest/small_dash_nest_bumper_b_game.dart'; void addDashNestBumperStories(Dashbook dashbook) { dashbook.storiesOf('Flutter Forest') ..add( - 'Flutter Sign Post', + 'Signpost', (context) => GameWidget( - game: FlutterSignPostGame() - ..trace = context.boolProperty('Trace', true), + game: SignpostGame()..trace = context.boolProperty('Trace', true), ), - codeLink: buildSourceLink('flutter_forest/flutter_sign_post.dart'), - info: FlutterSignPostGame.info, + codeLink: buildSourceLink('flutter_forest/signpost.dart'), + info: SignpostGame.info, ) ..add( 'Big Dash Nest Bumper', diff --git a/packages/pinball_components/sandbox/lib/stories/launch_ramp/launch_ramp_game.dart b/packages/pinball_components/sandbox/lib/stories/launch_ramp/launch_ramp_game.dart index 67fa9431..2d636e30 100644 --- a/packages/pinball_components/sandbox/lib/stories/launch_ramp/launch_ramp_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/launch_ramp/launch_ramp_game.dart @@ -10,7 +10,7 @@ class LaunchRampGame extends BasicBallGame { LaunchRampGame() : super( color: Colors.blue, - ballPriority: Ball.launchRampPriority, + ballPriority: RenderPriority.ballOnLaunchRamp, ballLayer: Layer.launcher, ); diff --git a/packages/pinball_components/sandbox/lib/stories/spaceship_rail/spaceship_rail_game.dart b/packages/pinball_components/sandbox/lib/stories/spaceship_rail/spaceship_rail_game.dart index c738d56a..471333c3 100644 --- a/packages/pinball_components/sandbox/lib/stories/spaceship_rail/spaceship_rail_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/spaceship_rail/spaceship_rail_game.dart @@ -10,7 +10,7 @@ class SpaceshipRailGame extends BasicBallGame { SpaceshipRailGame() : super( color: Colors.blue, - ballPriority: Ball.spaceshipRailPriority, + ballPriority: RenderPriority.ballOnSpaceshipRail, ballLayer: Layer.spaceshipExitRail, ); 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 aa2ed5cd..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,15 +2,16 @@ 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, - ballPriority: Ball.spaceshipRampPriority, + ballPriority: RenderPriority.ballOnSpaceshipRamp, ballLayer: Layer.spaceshipEntranceRamp, ); @@ -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/sandbox/lib/stories/sparky_bumper/sparky_bumper_game.dart b/packages/pinball_components/sandbox/lib/stories/sparky_bumper/sparky_bumper_game.dart index 37537952..265b6246 100644 --- a/packages/pinball_components/sandbox/lib/stories/sparky_bumper/sparky_bumper_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/sparky_bumper/sparky_bumper_game.dart @@ -16,12 +16,21 @@ class SparkyBumperGame extends BasicBallGame with Traceable { Future onLoad() async { await super.onLoad(); + await images.loadAll([ + Assets.images.sparky.bumper.a.active.keyName, + Assets.images.sparky.bumper.a.inactive.keyName, + Assets.images.sparky.bumper.b.active.keyName, + Assets.images.sparky.bumper.b.inactive.keyName, + Assets.images.sparky.bumper.c.active.keyName, + Assets.images.sparky.bumper.c.inactive.keyName, + ]); + final center = screenToWorld(camera.viewport.canvasSize! / 2); final sparkyBumperA = SparkyBumper.a() - ..initialPosition = Vector2(center.x - 20, center.y - 20) + ..initialPosition = Vector2(center.x - 20, center.y + 20) ..priority = 1; final sparkyBumperB = SparkyBumper.b() - ..initialPosition = Vector2(center.x - 10, center.y + 10) + ..initialPosition = Vector2(center.x - 10, center.y - 10) ..priority = 1; final sparkyBumperC = SparkyBumper.c() ..initialPosition = Vector2(center.x + 20, center.y) diff --git a/packages/pinball_components/sandbox/lib/stories/zoom/basic_zoom_game.dart b/packages/pinball_components/sandbox/lib/stories/zoom/basic_zoom_game.dart index 7e6d035f..f1c17fe9 100644 --- a/packages/pinball_components/sandbox/lib/stories/zoom/basic_zoom_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/zoom/basic_zoom_game.dart @@ -14,7 +14,7 @@ class BasicCameraZoomGame extends BasicGame with TapDetector { @override Future onLoad() async { - final sprite = await loadSprite(Assets.images.flutterSignPost.keyName); + final sprite = await loadSprite(Assets.images.signpost.inactive.keyName); await add( SpriteComponent( diff --git a/packages/pinball_components/test/helpers/test_game.dart b/packages/pinball_components/test/helpers/test_game.dart index 5bd4b30d..1faf75e8 100644 --- a/packages/pinball_components/test/helpers/test_game.dart +++ b/packages/pinball_components/test/helpers/test_game.dart @@ -2,9 +2,19 @@ import 'package:flame/input.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; class TestGame extends Forge2DGame { - TestGame() { + TestGame([List? assets]) : _assets = assets { images.prefix = ''; } + + final List? _assets; + + @override + Future onLoad() async { + if (_assets != null) { + await images.loadAll(_assets!); + } + await super.onLoad(); + } } class KeyboardTestGame extends TestGame with HasKeyboardHandlerComponents {} diff --git a/packages/pinball_components/test/src/components/alien_bumper_test.dart b/packages/pinball_components/test/src/components/alien_bumper_test.dart index cd55b62e..c6384759 100644 --- a/packages/pinball_components/test/src/components/alien_bumper_test.dart +++ b/packages/pinball_components/test/src/components/alien_bumper_test.dart @@ -9,7 +9,13 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(TestGame.new); + final assets = [ + Assets.images.alienBumper.a.active.keyName, + Assets.images.alienBumper.a.inactive.keyName, + Assets.images.alienBumper.b.active.keyName, + Assets.images.alienBumper.b.inactive.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); group('AlienBumper', () { flameTester.test('"a" loads correctly', (game) async { @@ -25,43 +31,30 @@ void main() { expect(game.contains(bumper), isTrue); }); - flameTester.test('activate returns normally', (game) async { + flameTester.test('animate switches between on and off sprites', + (game) async { final bumper = AlienBumper.a(); await game.ensureAdd(bumper); - expect(bumper.activate, returnsNormally); - }); - - flameTester.test('deactivate returns normally', (game) async { - final bumper = AlienBumper.a(); - await game.ensureAdd(bumper); - - expect(bumper.deactivate, returnsNormally); - }); + final spriteGroupComponent = bumper.firstChild()!; - flameTester.test('changes sprite', (game) async { - final bumper = AlienBumper.a(); - await game.ensureAdd(bumper); - - final spriteComponent = bumper.firstChild()!; - - final deactivatedSprite = spriteComponent.sprite; - bumper.activate(); expect( - spriteComponent.sprite, - isNot(equals(deactivatedSprite)), + spriteGroupComponent.current, + equals(AlienBumperSpriteState.active), ); - final activatedSprite = spriteComponent.sprite; - bumper.deactivate(); + final future = bumper.animate(); + expect( - spriteComponent.sprite, - isNot(equals(activatedSprite)), + spriteGroupComponent.current, + equals(AlienBumperSpriteState.inactive), ); + await future; + expect( - activatedSprite, - isNot(equals(deactivatedSprite)), + spriteGroupComponent.current, + equals(AlienBumperSpriteState.active), ); }); }); diff --git a/packages/pinball_components/test/src/components/camera_zoom_test.dart b/packages/pinball_components/test/src/components/camera_zoom_test.dart index 00f43847..a7f64eca 100644 --- a/packages/pinball_components/test/src/components/camera_zoom_test.dart +++ b/packages/pinball_components/test/src/components/camera_zoom_test.dart @@ -17,7 +17,7 @@ void main() { game.camera.followVector2(Vector2.zero()); game.camera.zoom = 10; final sprite = await game.loadSprite( - Assets.images.flutterSignPost.keyName, + Assets.images.signpost.inactive.keyName, ); await game.add( diff --git a/packages/pinball_components/test/src/components/dash_nest_bumper_test.dart b/packages/pinball_components/test/src/components/dash_nest_bumper_test.dart index 2c6bb00c..ac036ef4 100644 --- a/packages/pinball_components/test/src/components/dash_nest_bumper_test.dart +++ b/packages/pinball_components/test/src/components/dash_nest_bumper_test.dart @@ -9,107 +9,68 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(TestGame.new); - group('BigDashNestBumper', () { - flameTester.test('loads correctly', (game) async { - final bumper = BigDashNestBumper(); + group('DashNestBumper', () { + final assets = [ + Assets.images.dash.bumper.main.active.keyName, + Assets.images.dash.bumper.main.inactive.keyName, + Assets.images.dash.bumper.a.active.keyName, + Assets.images.dash.bumper.a.inactive.keyName, + Assets.images.dash.bumper.b.active.keyName, + Assets.images.dash.bumper.b.inactive.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); + + flameTester.test('"main" loads correctly', (game) async { + final bumper = DashNestBumper.main(); await game.ensureAdd(bumper); expect(game.contains(bumper), isTrue); }); - flameTester.test('activate returns normally', (game) async { - final bumper = BigDashNestBumper(); + flameTester.test('"a" loads correctly', (game) async { + final bumper = DashNestBumper.a(); await game.ensureAdd(bumper); - expect(bumper.activate, returnsNormally); + expect(game.contains(bumper), isTrue); }); - flameTester.test('deactivate returns normally', (game) async { - final bumper = BigDashNestBumper(); + flameTester.test('"b" loads correctly', (game) async { + final bumper = DashNestBumper.b(); await game.ensureAdd(bumper); - - expect(bumper.deactivate, returnsNormally); + expect(game.contains(bumper), isTrue); }); - flameTester.test('changes sprite', (game) async { - final bumper = BigDashNestBumper(); + flameTester.test('activate switches to active sprite', (game) async { + final bumper = DashNestBumper.main(); await game.ensureAdd(bumper); - final spriteComponent = bumper.firstChild()!; + final spriteGroupComponent = bumper.firstChild()!; - final deactivatedSprite = spriteComponent.sprite; - bumper.activate(); expect( - spriteComponent.sprite, - isNot(equals(deactivatedSprite)), + spriteGroupComponent.current, + equals(DashNestBumperSpriteState.inactive), ); - final activatedSprite = spriteComponent.sprite; - bumper.deactivate(); - expect( - spriteComponent.sprite, - isNot(equals(activatedSprite)), - ); + bumper.activate(); expect( - activatedSprite, - isNot(equals(deactivatedSprite)), + spriteGroupComponent.current, + equals(DashNestBumperSpriteState.active), ); }); - }); - - group('SmallDashNestBumper', () { - flameTester.test('"a" loads correctly', (game) async { - final bumper = SmallDashNestBumper.a(); - await game.ensureAdd(bumper); - - expect(game.contains(bumper), isTrue); - }); - - flameTester.test('"b" loads correctly', (game) async { - final bumper = SmallDashNestBumper.b(); - await game.ensureAdd(bumper); - expect(game.contains(bumper), isTrue); - }); - - flameTester.test('activate returns normally', (game) async { - final bumper = SmallDashNestBumper.a(); - await game.ensureAdd(bumper); - - expect(bumper.activate, returnsNormally); - }); - - flameTester.test('deactivate returns normally', (game) async { - final bumper = SmallDashNestBumper.a(); - await game.ensureAdd(bumper); - - expect(bumper.deactivate, returnsNormally); - }); - flameTester.test('changes sprite', (game) async { - final bumper = SmallDashNestBumper.a(); + flameTester.test('deactivate switches to inactive sprite', (game) async { + final bumper = DashNestBumper.main(); await game.ensureAdd(bumper); - final spriteComponent = bumper.firstChild()!; + final spriteGroupComponent = bumper.firstChild()! + ..current = DashNestBumperSpriteState.active; - final deactivatedSprite = spriteComponent.sprite; - bumper.activate(); - expect( - spriteComponent.sprite, - isNot(equals(deactivatedSprite)), - ); - - final activatedSprite = spriteComponent.sprite; bumper.deactivate(); - expect( - spriteComponent.sprite, - isNot(equals(activatedSprite)), - ); expect( - activatedSprite, - isNot(equals(deactivatedSprite)), + spriteGroupComponent.current, + equals(DashNestBumperSpriteState.inactive), ); }); }); diff --git a/packages/pinball_components/test/src/components/flutter_sign_post_test.dart b/packages/pinball_components/test/src/components/flutter_sign_post_test.dart deleted file mode 100644 index 0dee4482..00000000 --- a/packages/pinball_components/test/src/components/flutter_sign_post_test.dart +++ /dev/null @@ -1,40 +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:pinball_components/pinball_components.dart'; - -import '../../helpers/helpers.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(TestGame.new); - - group('FlutterSignPost', () { - flameTester.testGameWidget( - 'renders correctly', - setUp: (game, tester) async { - await game.ensureAdd(FlutterSignPost()); - game.camera.followVector2(Vector2.zero()); - }, - verify: (game, tester) async { - await expectLater( - find.byGame(), - matchesGoldenFile('golden/flutter-sign-post.png'), - ); - }, - ); - - flameTester.test( - 'loads correctly', - (game) async { - final flutterSignPost = FlutterSignPost(); - await game.ready(); - await game.ensureAdd(flutterSignPost); - - expect(game.contains(flutterSignPost), isTrue); - }, - ); - }); -} diff --git a/packages/pinball_components/test/src/components/golden/camera_zoom/finished.png b/packages/pinball_components/test/src/components/golden/camera_zoom/finished.png index be784ada..1d3daa81 100644 Binary files a/packages/pinball_components/test/src/components/golden/camera_zoom/finished.png and b/packages/pinball_components/test/src/components/golden/camera_zoom/finished.png differ diff --git a/packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png b/packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png index 3809f0d0..f0312ae5 100644 Binary files a/packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png and b/packages/pinball_components/test/src/components/golden/camera_zoom/in_between.png differ diff --git a/packages/pinball_components/test/src/components/golden/camera_zoom/no_zoom.png b/packages/pinball_components/test/src/components/golden/camera_zoom/no_zoom.png index a6215d65..5fd65077 100644 Binary files a/packages/pinball_components/test/src/components/golden/camera_zoom/no_zoom.png and b/packages/pinball_components/test/src/components/golden/camera_zoom/no_zoom.png differ diff --git a/packages/pinball_components/test/src/components/golden/flutter-sign-post.png b/packages/pinball_components/test/src/components/golden/flutter-sign-post.png deleted file mode 100644 index 68388670..00000000 Binary files a/packages/pinball_components/test/src/components/golden/flutter-sign-post.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/golden/plunger.png b/packages/pinball_components/test/src/components/golden/plunger.png deleted file mode 100644 index a33405f1..00000000 Binary files a/packages/pinball_components/test/src/components/golden/plunger.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/golden/plunger/pull.png b/packages/pinball_components/test/src/components/golden/plunger/pull.png new file mode 100644 index 00000000..0ec27a4e Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/plunger/pull.png differ diff --git a/packages/pinball_components/test/src/components/golden/plunger/release.png b/packages/pinball_components/test/src/components/golden/plunger/release.png new file mode 100644 index 00000000..61f7a4d9 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/plunger/release.png differ diff --git a/packages/pinball_components/test/src/components/golden/signpost/active1.png b/packages/pinball_components/test/src/components/golden/signpost/active1.png new file mode 100644 index 00000000..f11af5a8 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/signpost/active1.png differ diff --git a/packages/pinball_components/test/src/components/golden/signpost/active2.png b/packages/pinball_components/test/src/components/golden/signpost/active2.png new file mode 100644 index 00000000..6ddf8786 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/signpost/active2.png differ diff --git a/packages/pinball_components/test/src/components/golden/signpost/active3.png b/packages/pinball_components/test/src/components/golden/signpost/active3.png new file mode 100644 index 00000000..5e9b0005 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/signpost/active3.png differ diff --git a/packages/pinball_components/test/src/components/golden/signpost/inactive.png b/packages/pinball_components/test/src/components/golden/signpost/inactive.png new file mode 100644 index 00000000..7ed00fba Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/signpost/inactive.png differ 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/layer_sensor_test.dart b/packages/pinball_components/test/src/components/layer_sensor_test.dart index c87f02b3..f91a6bcb 100644 --- a/packages/pinball_components/test/src/components/layer_sensor_test.dart +++ b/packages/pinball_components/test/src/components/layer_sensor_test.dart @@ -148,7 +148,7 @@ void main() { callback.begin(ball, sensor, MockContact()); verify(() => ball.layer = Layer.board); - verify(() => ball.priority = Ball.boardPriority).called(1); + verify(() => ball.priority = RenderPriority.ballOnBoard).called(1); verify(ball.reorderChildren).called(1); }); @@ -174,7 +174,7 @@ void main() { callback.begin(ball, sensor, MockContact()); verify(() => ball.layer = Layer.board); - verify(() => ball.priority = Ball.boardPriority).called(1); + verify(() => ball.priority = RenderPriority.ballOnBoard).called(1); verify(ball.reorderChildren).called(1); }); }); diff --git a/packages/pinball_components/test/src/components/plunger_test.dart b/packages/pinball_components/test/src/components/plunger_test.dart index 7922f060..eafc15d5 100644 --- a/packages/pinball_components/test/src/components/plunger_test.dart +++ b/packages/pinball_components/test/src/components/plunger_test.dart @@ -23,9 +23,21 @@ void main() { game.camera.zoom = 4.1; }, verify: (game, tester) async { + final plunger = game.descendants().whereType().first; + plunger.pull(); + game.update(1); + await tester.pump(); await expectLater( find.byGame(), - matchesGoldenFile('golden/plunger.png'), + matchesGoldenFile('golden/plunger/pull.png'), + ); + + plunger.release(); + game.update(1); + await tester.pump(); + await expectLater( + find.byGame(), + matchesGoldenFile('golden/plunger/release.png'), ); }, ); @@ -110,12 +122,17 @@ void main() { }); group('pull', () { + late Plunger plunger; + + setUp(() { + plunger = Plunger( + compressionDistance: compressionDistance, + ); + }); + flameTester.test( 'moves downwards when pull is called', (game) async { - final plunger = Plunger( - compressionDistance: compressionDistance, - ); await game.ensureAdd(plunger); plunger.pull(); @@ -123,6 +140,18 @@ void main() { expect(plunger.body.linearVelocity.x, isZero); }, ); + + flameTester.test( + 'moves downwards when pull is called ' + 'and plunger is below its starting position', (game) async { + await game.ensureAdd(plunger); + plunger.pull(); + plunger.release(); + plunger.pull(); + + expect(plunger.body.linearVelocity.y, isPositive); + expect(plunger.body.linearVelocity.x, isZero); + }); }); group('release', () { diff --git a/packages/pinball_components/test/src/components/signpost_test.dart b/packages/pinball_components/test/src/components/signpost_test.dart new file mode 100644 index 00000000..018c1bee --- /dev/null +++ b/packages/pinball_components/test/src/components/signpost_test.dart @@ -0,0 +1,155 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + Assets.images.signpost.inactive.keyName, + Assets.images.signpost.active1.keyName, + Assets.images.signpost.active2.keyName, + Assets.images.signpost.active3.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); + + group('Signpost', () { + flameTester.test( + 'loads correctly', + (game) async { + final signpost = Signpost(); + await game.ready(); + await game.ensureAdd(signpost); + + expect(game.contains(signpost), isTrue); + }, + ); + + group('renders correctly', () { + flameTester.testGameWidget( + 'inactive sprite', + setUp: (game, tester) async { + await game.images.loadAll(assets); + final signpost = Signpost(); + await game.ensureAdd(signpost); + await tester.pump(); + + expect( + signpost.firstChild()!.current, + SignpostSpriteState.inactive, + ); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/signpost/inactive.png'), + ); + }, + ); + + flameTester.testGameWidget( + 'active1 sprite', + setUp: (game, tester) async { + await game.images.loadAll(assets); + final signpost = Signpost(); + await game.ensureAdd(signpost); + signpost.progress(); + await tester.pump(); + + expect( + signpost.firstChild()!.current, + SignpostSpriteState.active1, + ); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/signpost/active1.png'), + ); + }, + ); + + flameTester.testGameWidget( + 'active2 sprite', + setUp: (game, tester) async { + await game.images.loadAll(assets); + final signpost = Signpost(); + await game.ensureAdd(signpost); + signpost + ..progress() + ..progress(); + await tester.pump(); + + expect( + signpost.firstChild()!.current, + SignpostSpriteState.active2, + ); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/signpost/active2.png'), + ); + }, + ); + + flameTester.testGameWidget( + 'active3 sprite', + setUp: (game, tester) async { + await game.images.loadAll(assets); + final signpost = Signpost(); + await game.ensureAdd(signpost); + signpost + ..progress() + ..progress() + ..progress(); + await tester.pump(); + + expect( + signpost.firstChild()!.current, + SignpostSpriteState.active3, + ); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('golden/signpost/active3.png'), + ); + }, + ); + }); + + flameTester.test( + 'progress correctly cycles through all sprites', + (game) async { + final signpost = Signpost(); + await game.ready(); + await game.ensureAdd(signpost); + + final spriteComponent = signpost.firstChild()!; + + expect(spriteComponent.current, SignpostSpriteState.inactive); + signpost.progress(); + expect(spriteComponent.current, SignpostSpriteState.active1); + signpost.progress(); + expect(spriteComponent.current, SignpostSpriteState.active2); + signpost.progress(); + expect(spriteComponent.current, SignpostSpriteState.active3); + signpost.progress(); + expect(spriteComponent.current, SignpostSpriteState.inactive); + }, + ); + }); +} 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/packages/pinball_components/test/src/components/sparky_bumper_test.dart b/packages/pinball_components/test/src/components/sparky_bumper_test.dart index 470c254b..a2fcc5ed 100644 --- a/packages/pinball_components/test/src/components/sparky_bumper_test.dart +++ b/packages/pinball_components/test/src/components/sparky_bumper_test.dart @@ -9,7 +9,15 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(TestGame.new); + final assets = [ + Assets.images.sparky.bumper.a.active.keyName, + Assets.images.sparky.bumper.a.inactive.keyName, + Assets.images.sparky.bumper.b.active.keyName, + Assets.images.sparky.bumper.b.inactive.keyName, + Assets.images.sparky.bumper.c.active.keyName, + Assets.images.sparky.bumper.c.inactive.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); group('SparkyBumper', () { flameTester.test('"a" loads correctly', (game) async { @@ -31,43 +39,30 @@ void main() { expect(game.contains(bumper), isTrue); }); - flameTester.test('activate returns normally', (game) async { + flameTester.test('animate switches between on and off sprites', + (game) async { final bumper = SparkyBumper.a(); await game.ensureAdd(bumper); - expect(bumper.activate, returnsNormally); - }); - - flameTester.test('deactivate returns normally', (game) async { - final bumper = SparkyBumper.a(); - await game.ensureAdd(bumper); - - expect(bumper.deactivate, returnsNormally); - }); + final spriteGroupComponent = bumper.firstChild()!; - flameTester.test('changes sprite', (game) async { - final bumper = SparkyBumper.a(); - await game.ensureAdd(bumper); - - final spriteComponent = bumper.firstChild()!; - - final deactivatedSprite = spriteComponent.sprite; - bumper.activate(); expect( - spriteComponent.sprite, - isNot(equals(deactivatedSprite)), + spriteGroupComponent.current, + equals(SparkyBumperSpriteState.active), ); - final activatedSprite = spriteComponent.sprite; - bumper.deactivate(); + final future = bumper.animate(); + expect( - spriteComponent.sprite, - isNot(equals(activatedSprite)), + spriteGroupComponent.current, + equals(SparkyBumperSpriteState.inactive), ); + await future; + expect( - activatedSprite, - isNot(equals(deactivatedSprite)), + spriteGroupComponent.current, + equals(SparkyBumperSpriteState.active), ); }); }); diff --git a/test/app/view/app_test.dart b/test/app/view/app_test.dart index 01b5fea6..9fc79b5d 100644 --- a/test/app/view/app_test.dart +++ b/test/app/view/app_test.dart @@ -7,8 +7,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:pinball/app/app.dart'; -import 'package:pinball/landing/landing.dart'; +import 'package:pinball/game/game.dart'; import 'package:pinball_audio/pinball_audio.dart'; import '../../helpers/mocks.dart'; @@ -21,16 +22,18 @@ void main() { setUp(() { leaderboardRepository = MockLeaderboardRepository(); pinballAudio = MockPinballAudio(); + + when(pinballAudio.load).thenAnswer((_) => Future.value()); }); - testWidgets('renders LandingPage', (tester) async { + testWidgets('renders PinballGamePage', (tester) async { await tester.pumpWidget( App( leaderboardRepository: leaderboardRepository, pinballAudio: pinballAudio, ), ); - expect(find.byType(LandingPage), findsOneWidget); + expect(find.byType(PinballGamePage), findsOneWidget); }); }); } diff --git a/test/game/components/alien_zone_test.dart b/test/game/components/alien_zone_test.dart index 863bef31..de4e58fc 100644 --- a/test/game/components/alien_zone_test.dart +++ b/test/game/components/alien_zone_test.dart @@ -13,13 +13,18 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(EmptyPinballTestGame.new); + final assets = [ + Assets.images.alienBumper.a.active.keyName, + Assets.images.alienBumper.a.inactive.keyName, + Assets.images.alienBumper.b.active.keyName, + Assets.images.alienBumper.b.inactive.keyName, + ]; + final flameTester = FlameTester(() => EmptyPinballTestGame(assets)); group('AlienZone', () { flameTester.test( 'loads correctly', (game) async { - await game.ready(); final alienZone = AlienZone(); await game.ensureAdd(alienZone); @@ -31,7 +36,6 @@ void main() { flameTester.test( 'two AlienBumper', (game) async { - await game.ready(); final alienZone = AlienZone(); await game.ensureAdd(alienZone); @@ -44,55 +48,40 @@ void main() { }); group('bumpers', () { - late ControlledAlienBumper controlledAlienBumper; late GameBloc gameBloc; setUp(() { gameBloc = MockGameBloc(); + whenListen( + gameBloc, + const Stream.empty(), + initialState: const GameState.initial(), + ); }); - final flameBlocTester = FlameBlocTester( + final flameBlocTester = FlameBlocTester( gameBuilder: EmptyPinballTestGame.new, blocBuilder: () => gameBloc, + assets: assets, ); - flameTester.testGameWidget( - 'activate when deactivated bumper is hit', - setUp: (game, tester) async { - controlledAlienBumper = ControlledAlienBumper.a(); - await game.ensureAdd(controlledAlienBumper); + flameTester.test('call animate on contact', (game) async { + final contactCallback = AlienBumperBallContactCallback(); + final bumper = MockAlienBumper(); + final ball = MockBall(); - controlledAlienBumper.controller.hit(); - }, - verify: (game, tester) async { - expect(controlledAlienBumper.controller.isActivated, isTrue); - }, - ); + when(bumper.animate).thenAnswer((_) async {}); - flameTester.testGameWidget( - 'deactivate when activated bumper is hit', - setUp: (game, tester) async { - controlledAlienBumper = ControlledAlienBumper.a(); - await game.ensureAdd(controlledAlienBumper); + contactCallback.begin(bumper, ball, MockContact()); - controlledAlienBumper.controller.hit(); - controlledAlienBumper.controller.hit(); - }, - verify: (game, tester) async { - expect(controlledAlienBumper.controller.isActivated, isFalse); - }, - ); + verify(bumper.animate).called(1); + }); flameBlocTester.testGameWidget( 'add Scored event', setUp: (game, tester) async { final ball = Ball(baseColor: const Color(0xFF00FFFF)); final alienZone = AlienZone(); - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial(), - ); await game.ensureAdd(alienZone); await game.ensureAdd(ball); diff --git a/test/game/components/board_test.dart b/test/game/components/board_test.dart index 0a1928ab..3ecbe6e7 100644 --- a/test/game/components/board_test.dart +++ b/test/game/components/board_test.dart @@ -9,7 +9,19 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(EmptyPinballTestGame.new); + final assets = [ + Assets.images.dash.bumper.main.active.keyName, + Assets.images.dash.bumper.main.inactive.keyName, + Assets.images.dash.bumper.a.active.keyName, + Assets.images.dash.bumper.a.inactive.keyName, + Assets.images.dash.bumper.b.active.keyName, + Assets.images.dash.bumper.b.inactive.keyName, + Assets.images.signpost.inactive.keyName, + Assets.images.signpost.active1.keyName, + Assets.images.signpost.active2.keyName, + Assets.images.signpost.active3.keyName, + ]; + final flameTester = FlameTester(() => EmptyPinballTestGame(assets)); group('Board', () { flameTester.test( diff --git a/test/game/components/flutter_forest_test.dart b/test/game/components/flutter_forest_test.dart index 2089b7b7..73259afd 100644 --- a/test/game/components/flutter_forest_test.dart +++ b/test/game/components/flutter_forest_test.dart @@ -12,7 +12,19 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(EmptyPinballTestGame.new); + final assets = [ + Assets.images.dash.bumper.main.active.keyName, + Assets.images.dash.bumper.main.inactive.keyName, + Assets.images.dash.bumper.a.active.keyName, + Assets.images.dash.bumper.a.inactive.keyName, + Assets.images.dash.bumper.b.active.keyName, + Assets.images.dash.bumper.b.inactive.keyName, + Assets.images.signpost.inactive.keyName, + Assets.images.signpost.active1.keyName, + Assets.images.signpost.active2.keyName, + Assets.images.signpost.active3.keyName, + ]; + final flameTester = FlameTester(() => EmptyPinballTestGame(assets)); group('FlutterForest', () { flameTester.test( @@ -27,13 +39,13 @@ void main() { group('loads', () { flameTester.test( - 'a FlutterSignPost', + 'a Signpost', (game) async { final flutterForest = FlutterForest(); await game.ensureAdd(flutterForest); expect( - flutterForest.descendants().whereType().length, + flutterForest.descendants().whereType().length, equals(1), ); }, @@ -53,27 +65,14 @@ void main() { ); flameTester.test( - 'a BigDashNestBumper', + 'three DashNestBumper', (game) async { final flutterForest = FlutterForest(); await game.ensureAdd(flutterForest); expect( - flutterForest.descendants().whereType().length, - equals(1), - ); - }, - ); - - flameTester.test( - 'two SmallDashNestBumper', - (game) async { - final flutterForest = FlutterForest(); - await game.ensureAdd(flutterForest); - - expect( - flutterForest.descendants().whereType().length, - equals(2), + flutterForest.descendants().whereType().length, + equals(3), ); }, ); @@ -88,13 +87,14 @@ void main() { }); final flameBlocTester = FlameBlocTester( - gameBuilder: EmptyPinballTestGame.new, + gameBuilder: () => EmptyPinballTestGame(assets), blocBuilder: () { gameBloc = MockGameBloc(); const state = GameState.initial(); whenListen(gameBloc, Stream.value(state), initialState: state); return gameBloc; }, + assets: assets, ); flameBlocTester.testGameWidget( 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 b5c76dc6..00000000 --- a/test/game/components/score_effect_controller_test.dart +++ /dev/null @@ -1,105 +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, - 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, - 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, - 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, - balls: 3, - bonusHistory: [], - ), - ); - - controller.onNewState( - const GameState( - score: 14, - 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/components/sparky_fire_zone_test.dart b/test/game/components/sparky_fire_zone_test.dart index 692af291..4c3fcdb9 100644 --- a/test/game/components/sparky_fire_zone_test.dart +++ b/test/game/components/sparky_fire_zone_test.dart @@ -13,13 +13,20 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(EmptyPinballTestGame.new); + final assets = [ + Assets.images.sparky.bumper.a.active.keyName, + Assets.images.sparky.bumper.a.inactive.keyName, + Assets.images.sparky.bumper.b.active.keyName, + Assets.images.sparky.bumper.b.inactive.keyName, + Assets.images.sparky.bumper.c.active.keyName, + Assets.images.sparky.bumper.c.inactive.keyName, + ]; + final flameTester = FlameTester(() => EmptyPinballTestGame(assets)); group('SparkyFireZone', () { flameTester.test( 'loads correctly', (game) async { - await game.ready(); final sparkyFireZone = SparkyFireZone(); await game.ensureAdd(sparkyFireZone); @@ -31,7 +38,6 @@ void main() { flameTester.test( 'three SparkyBumper', (game) async { - await game.ready(); final sparkyFireZone = SparkyFireZone(); await game.ensureAdd(sparkyFireZone); @@ -44,12 +50,9 @@ void main() { }); group('bumpers', () { - late ControlledSparkyBumper controlledSparkyBumper; - late Ball ball; late GameBloc gameBloc; setUp(() { - ball = Ball(baseColor: const Color(0xFF00FFFF)); gameBloc = MockGameBloc(); whenListen( gameBloc, @@ -58,41 +61,28 @@ void main() { ); }); - final flameBlocTester = FlameBlocTester( + final flameBlocTester = FlameBlocTester( gameBuilder: EmptyPinballTestGame.new, blocBuilder: () => gameBloc, + assets: assets, ); - flameTester.testGameWidget( - 'activate when deactivated bumper is hit', - setUp: (game, tester) async { - controlledSparkyBumper = ControlledSparkyBumper.a(); - await game.ensureAdd(controlledSparkyBumper); + flameTester.test('call animate on contact', (game) async { + final contactCallback = SparkyBumperBallContactCallback(); + final bumper = MockSparkyBumper(); + final ball = MockBall(); - controlledSparkyBumper.controller.hit(); - }, - verify: (game, tester) async { - expect(controlledSparkyBumper.controller.isActivated, isTrue); - }, - ); + when(bumper.animate).thenAnswer((_) async {}); - flameTester.testGameWidget( - 'deactivate when activated bumper is hit', - setUp: (game, tester) async { - controlledSparkyBumper = ControlledSparkyBumper.a(); - await game.ensureAdd(controlledSparkyBumper); + contactCallback.begin(bumper, ball, MockContact()); - controlledSparkyBumper.controller.hit(); - controlledSparkyBumper.controller.hit(); - }, - verify: (game, tester) async { - expect(controlledSparkyBumper.controller.isActivated, isFalse); - }, - ); + verify(bumper.animate).called(1); + }); flameBlocTester.testGameWidget( 'add Scored event', setUp: (game, tester) async { + final ball = Ball(baseColor: const Color(0xFF00FFFF)); final sparkyFireZone = SparkyFireZone(); await game.ensureAdd(sparkyFireZone); await game.ensureAdd(ball); diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index c29ee315..1775a42f 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -12,8 +12,40 @@ import '../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(PinballTestGame.new); - final debugModeFlameTester = FlameTester(DebugPinballTestGame.new); + final assets = [ + Assets.images.dash.bumper.main.active.keyName, + Assets.images.dash.bumper.main.inactive.keyName, + Assets.images.dash.bumper.a.active.keyName, + Assets.images.dash.bumper.a.inactive.keyName, + Assets.images.dash.bumper.b.active.keyName, + Assets.images.dash.bumper.b.inactive.keyName, + Assets.images.signpost.inactive.keyName, + Assets.images.signpost.active1.keyName, + Assets.images.signpost.active2.keyName, + Assets.images.signpost.active3.keyName, + Assets.images.alienBumper.a.active.keyName, + Assets.images.alienBumper.a.inactive.keyName, + Assets.images.alienBumper.b.active.keyName, + Assets.images.alienBumper.b.inactive.keyName, + Assets.images.sparky.bumper.a.active.keyName, + Assets.images.sparky.bumper.a.inactive.keyName, + Assets.images.sparky.bumper.b.active.keyName, + 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)); group('PinballGame', () { // TODO(alestiago): test if [PinballGame] registers @@ -90,6 +122,7 @@ void main() { final flameBlocTester = FlameBlocTester( gameBuilder: EmptyPinballTestGame.new, blocBuilder: () => gameBloc, + // assets: assets, ); flameBlocTester.testGameWidget( @@ -208,6 +241,7 @@ void main() { FlameBlocTester( gameBuilder: DebugPinballTestGame.new, blocBuilder: () => gameBloc, + assets: assets, ); debugModeFlameBlocTester.testGameWidget( diff --git a/test/game/view/pinball_game_page_test.dart b/test/game/view/pinball_game_page_test.dart index 85f9cfc3..3c54197a 100644 --- a/test/game/view/pinball_game_page_test.dart +++ b/test/game/view/pinball_game_page_test.dart @@ -5,40 +5,47 @@ import 'package:flame/game.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pinball/game/game.dart'; -import 'package:pinball_theme/pinball_theme.dart'; +import 'package:pinball/theme/theme.dart'; import '../../helpers/helpers.dart'; void main() { - const theme = PinballTheme(characterTheme: DashTheme()); final game = PinballTestGame(); group('PinballGamePage', () { - testWidgets('renders PinballGameView', (tester) async { - final gameBloc = MockGameBloc(); + late ThemeCubit themeCubit; + late GameBloc gameBloc; + + setUp(() async { + await Future.wait(game.preLoadAssets()); + themeCubit = MockThemeCubit(); + gameBloc = MockGameBloc(); + + whenListen( + themeCubit, + const Stream.empty(), + initialState: const ThemeState.initial(), + ); + whenListen( gameBloc, Stream.value(const GameState.initial()), initialState: const GameState.initial(), ); + }); + testWidgets('renders PinballGameView', (tester) async { await tester.pumpApp( - PinballGamePage(theme: theme, game: game), - gameBloc: gameBloc, + PinballGamePage(), + themeCubit: themeCubit, ); + expect(find.byType(PinballGameView), findsOneWidget); }); testWidgets( 'renders the loading indicator while the assets load', (tester) async { - final gameBloc = MockGameBloc(); - whenListen( - gameBloc, - Stream.value(const GameState.initial()), - initialState: const GameState.initial(), - ); - final assetsManagerCubit = MockAssetsManagerCubit(); final initialAssetsState = AssetsManagerState( loadables: [Future.value()], @@ -51,27 +58,52 @@ void main() { ); await tester.pumpApp( - PinballGamePage(theme: theme, game: game), - gameBloc: gameBloc, + PinballGameView( + game: game, + ), assetsManagerCubit: assetsManagerCubit, + themeCubit: themeCubit, ); - expect(find.text('0.0'), findsOneWidget); - final loadedAssetsState = AssetsManagerState( - loadables: [Future.value()], - loaded: [Future.value()], - ); - whenListen( - assetsManagerCubit, - Stream.value(loadedAssetsState), - initialState: loadedAssetsState, + expect( + find.byWidgetPredicate( + (widget) => + widget is LinearProgressIndicator && widget.value == 0.0, + ), + findsOneWidget, ); - - await tester.pump(); - expect(find.byType(PinballGameView), findsOneWidget); }, ); + testWidgets( + 'renders PinballGameLoadedView after resources have been loaded', + (tester) async { + final assetsManagerCubit = MockAssetsManagerCubit(); + + final loadedAssetsState = AssetsManagerState( + loadables: [Future.value()], + loaded: [Future.value()], + ); + whenListen( + assetsManagerCubit, + Stream.value(loadedAssetsState), + initialState: loadedAssetsState, + ); + + await tester.pumpApp( + PinballGameView( + game: game, + ), + assetsManagerCubit: assetsManagerCubit, + themeCubit: themeCubit, + gameBloc: gameBloc, + ); + + await tester.pump(); + + expect(find.byType(PinballGameLoadedView), findsOneWidget); + }); + group('route', () { Future pumpRoute({ required WidgetTester tester, @@ -85,7 +117,6 @@ void main() { onPressed: () { Navigator.of(context).push( PinballGamePage.route( - theme: theme, isDebugMode: isDebugMode, ), ); @@ -95,6 +126,7 @@ void main() { }, ), ), + themeCubit: themeCubit, ); await tester.tap(find.text('Tap me')); diff --git a/test/game/view/play_button_overlay_test.dart b/test/game/view/widgets/play_button_overlay_test.dart similarity index 71% rename from test/game/view/play_button_overlay_test.dart rename to test/game/view/widgets/play_button_overlay_test.dart index 020998d4..210cc347 100644 --- a/test/game/view/play_button_overlay_test.dart +++ b/test/game/view/widgets/play_button_overlay_test.dart @@ -1,8 +1,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/theme/theme.dart'; -import '../../helpers/helpers.dart'; +import '../../../helpers/helpers.dart'; void main() { group('PlayButtonOverlay', () { @@ -31,5 +32,15 @@ void main() { verify(gameFlowController.start).called(1); }); + + testWidgets('displays CharacterSelectionDialog when tapped', + (tester) async { + await tester.pumpApp(PlayButtonOverlay(game: game)); + + await tester.tap(find.text('Play')); + await tester.pump(); + + expect(find.byType(CharacterSelectionDialog), findsOneWidget); + }); }); } diff --git a/test/helpers/builders.dart b/test/helpers/builders.dart index d0eea644..2c23e3fe 100644 --- a/test/helpers/builders.dart +++ b/test/helpers/builders.dart @@ -1,16 +1,23 @@ -import 'package:flame/src/game/flame_game.dart'; +import 'package:flame/game.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; class FlameBlocTester> extends FlameTester { FlameBlocTester({ required GameCreateFunction gameBuilder, required B Function() blocBuilder, + // TODO(allisonryan0002): find alternative for testGameWidget. Loading + // assets in onLoad fails because the game loads after + List? assets, List Function()? repositories, }) : super( gameBuilder, pumpWidget: (gameWidget, tester) async { + if (assets != null) { + await Future.wait(assets.map(gameWidget.game.images.load)); + } await tester.pumpWidget( BlocProvider.value( value: blocBuilder(), diff --git a/test/helpers/mocks.dart b/test/helpers/mocks.dart index fd01a509..e9af4408 100644 --- a/test/helpers/mocks.dart +++ b/test/helpers/mocks.dart @@ -82,3 +82,7 @@ class MockActiveOverlaysNotifier extends Mock implements ActiveOverlaysNotifier {} class MockGameFlowController extends Mock implements GameFlowController {} + +class MockAlienBumper extends Mock implements AlienBumper {} + +class MockSparkyBumper extends Mock implements SparkyBumper {} diff --git a/test/helpers/test_games.dart b/test/helpers/test_games.dart index 3747a231..10caa768 100644 --- a/test/helpers/test_games.dart +++ b/test/helpers/test_games.dart @@ -1,5 +1,7 @@ // ignore_for_file: must_call_super +import 'dart:async'; + import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/game/game.dart'; @@ -14,26 +16,53 @@ class TestGame extends Forge2DGame with FlameBloc { } class PinballTestGame extends PinballGame { - PinballTestGame() - : super( + PinballTestGame([List? assets]) + : _assets = assets, + super( audio: MockPinballAudio(), theme: const PinballTheme( characterTheme: DashTheme(), ), ); + final List? _assets; + + @override + Future onLoad() async { + if (_assets != null) { + await images.loadAll(_assets!); + } + await super.onLoad(); + } } class DebugPinballTestGame extends DebugPinballGame { - DebugPinballTestGame() - : super( + DebugPinballTestGame([List? assets]) + : _assets = assets, + super( audio: MockPinballAudio(), theme: const PinballTheme( characterTheme: DashTheme(), ), ); + + final List? _assets; + + @override + Future onLoad() async { + if (_assets != null) { + await images.loadAll(_assets!); + } + await super.onLoad(); + } } class EmptyPinballTestGame extends PinballTestGame { + EmptyPinballTestGame([List? assets]) : super(assets); + @override - Future onLoad() async {} + Future onLoad() async { + if (_assets != null) { + await images.loadAll(_assets!); + } + } } diff --git a/test/landing/view/landing_page_test.dart b/test/landing/view/landing_page_test.dart deleted file mode 100644 index 369f8cab..00000000 --- a/test/landing/view/landing_page_test.dart +++ /dev/null @@ -1,71 +0,0 @@ -// ignore_for_file: prefer_const_constructors - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockingjay/mockingjay.dart'; -import 'package:pinball/l10n/l10n.dart'; -import 'package:pinball/landing/landing.dart'; - -import '../../helpers/helpers.dart'; - -void main() { - group('LandingPage', () { - testWidgets('renders correctly', (tester) async { - final l10n = await AppLocalizations.delegate.load(Locale('en')); - await tester.pumpApp(LandingPage()); - - expect(find.byType(TextButton), findsNWidgets(2)); - expect(find.text(l10n.play), findsOneWidget); - expect(find.text(l10n.howToPlay), findsOneWidget); - }); - - testWidgets('tapping on play button navigates to CharacterSelectionPage', - (tester) async { - final l10n = await AppLocalizations.delegate.load(Locale('en')); - final navigator = MockNavigator(); - when(() => navigator.push(any())).thenAnswer((_) async {}); - - await tester.pumpApp( - LandingPage(), - navigator: navigator, - ); - - await tester.tap(find.widgetWithText(TextButton, l10n.play)); - - verify(() => navigator.push(any())).called(1); - }); - - testWidgets('tapping on how to play button displays dialog with controls', - (tester) async { - final l10n = await AppLocalizations.delegate.load(Locale('en')); - await tester.pumpApp(LandingPage()); - - await tester.tap(find.widgetWithText(TextButton, l10n.howToPlay)); - await tester.pump(); - - expect(find.byType(Dialog), findsOneWidget); - }); - }); - - group('KeyIndicator', () { - testWidgets('fromKeyName renders correctly', (tester) async { - const keyName = 'A'; - - await tester.pumpApp( - KeyIndicator.fromKeyName(keyName: keyName), - ); - - expect(find.text(keyName), findsOneWidget); - }); - - testWidgets('fromIcon renders correctly', (tester) async { - const keyIcon = Icons.keyboard_arrow_down; - - await tester.pumpApp( - KeyIndicator.fromIcon(keyIcon: keyIcon), - ); - - expect(find.byIcon(keyIcon), findsOneWidget); - }); - }); -} diff --git a/test/start_game/widgets/how_to_play_dialog_test.dart b/test/start_game/widgets/how_to_play_dialog_test.dart new file mode 100644 index 00000000..082f102e --- /dev/null +++ b/test/start_game/widgets/how_to_play_dialog_test.dart @@ -0,0 +1,39 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball/start_game/start_game.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + group('HowToPlayDialog', () { + testWidgets('displays dialog', (tester) async { + await tester.pumpApp(HowToPlayDialog()); + + expect(find.byType(Dialog), findsOneWidget); + }); + }); + + group('KeyIndicator', () { + testWidgets('fromKeyName renders correctly', (tester) async { + const keyName = 'A'; + + await tester.pumpApp( + KeyIndicator.fromKeyName(keyName: keyName), + ); + + expect(find.text(keyName), findsOneWidget); + }); + + testWidgets('fromIcon renders correctly', (tester) async { + const keyIcon = Icons.keyboard_arrow_down; + + await tester.pumpApp( + KeyIndicator.fromIcon(keyIcon: keyIcon), + ); + + expect(find.byIcon(keyIcon), findsOneWidget); + }); + }); +} diff --git a/test/theme/view/character_selection_page_test.dart b/test/theme/view/character_selection_page_test.dart index eeac690f..dcf54a13 100644 --- a/test/theme/view/character_selection_page_test.dart +++ b/test/theme/view/character_selection_page_test.dart @@ -4,6 +4,7 @@ import 'package:bloc_test/bloc_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockingjay/mockingjay.dart'; +import 'package:pinball/start_game/start_game.dart'; import 'package:pinball/theme/theme.dart'; import 'package:pinball_theme/pinball_theme.dart'; @@ -24,7 +25,7 @@ void main() { group('CharacterSelectionPage', () { testWidgets('renders CharacterSelectionView', (tester) async { await tester.pumpApp( - CharacterSelectionPage(), + CharacterSelectionDialog(), themeCubit: themeCubit, ); expect(find.byType(CharacterSelectionView), findsOneWidget); @@ -38,7 +39,7 @@ void main() { return ElevatedButton( onPressed: () { Navigator.of(context) - .push(CharacterSelectionPage.route()); + .push(CharacterSelectionDialog.route()); }, child: Text('Tap me'), ); @@ -51,7 +52,7 @@ void main() { await tester.tap(find.text('Tap me')); await tester.pumpAndSettle(); - expect(find.byType(CharacterSelectionPage), findsOneWidget); + expect(find.byType(CharacterSelectionDialog), findsOneWidget); }); }); @@ -82,20 +83,17 @@ void main() { verify(() => themeCubit.characterSelected(SparkyTheme())).called(1); }); - testWidgets('navigates to PinballGamePage when start is tapped', + testWidgets('displays how to play dialog when start is tapped', (tester) async { - final navigator = MockNavigator(); - when(() => navigator.push(any())).thenAnswer((_) async {}); - await tester.pumpApp( CharacterSelectionView(), themeCubit: themeCubit, - navigator: navigator, ); await tester.ensureVisible(find.byType(TextButton)); await tester.tap(find.byType(TextButton)); + await tester.pumpAndSettle(); - verify(() => navigator.push(any())).called(1); + expect(find.byType(HowToPlayDialog), findsOneWidget); }); });