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/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/controlled_ball.dart b/lib/game/components/controlled_ball.dart index 659dd994..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; } } @@ -66,9 +66,8 @@ class BallController extends ComponentController // given animations. component.stop(); await Future.delayed(const Duration(seconds: 1)); - component - ..resume() - ..boost(Vector2(200, 500)); + component.resume(); + await component.boost(Vector2(40, 110)); } @override diff --git a/lib/game/components/flutter_forest.dart b/lib/game/components/flutter_forest.dart index 3c5f5a1f..5149ad81 100644 --- a/lib/game/components/flutter_forest.dart +++ b/lib/game/components/flutter_forest.dart @@ -25,7 +25,7 @@ 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() ..initialPosition = Vector2(18.55, -59.35); @@ -36,7 +36,7 @@ class FlutterForest extends Component final dashAnimatronic = DashAnimatronic()..position = Vector2(20, -66); await addAll([ - signPost, + signpost, smallLeftNest, smallRightNest, bigNest, 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/game_assets.dart b/lib/game/game_assets.dart index e7c7a343..1e966a42 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -7,8 +7,12 @@ extension PinballGameAssetsX on PinballGame { /// Returns a list of assets to be loaded List preLoadAssets() { return [ - images.load(components.Assets.images.ball.keyName), - images.load(components.Assets.images.flutterSignPost.keyName), + images.load(components.Assets.images.ball.ball.keyName), + images.load(components.Assets.images.ball.flameEffect.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), @@ -21,6 +25,9 @@ extension PinballGameAssetsX on PinballGame { images.load( components.Assets.images.launchRamp.foregroundRailing.keyName, ), + images.load( + components.Assets.images.launchRamp.backgroundRailing.keyName, + ), images.load(components.Assets.images.dino.dinoLandTop.keyName), images.load(components.Assets.images.dino.dinoLandBottom.keyName), images.load(components.Assets.images.dash.animatronic.keyName), diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index b1ff2c6c..f28a0bf6 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; @@ -164,7 +168,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/ball.png b/packages/pinball_components/assets/images/ball/ball.png similarity index 100% rename from packages/pinball_components/assets/images/ball.png rename to packages/pinball_components/assets/images/ball/ball.png diff --git a/packages/pinball_components/assets/images/ball/flame_effect.png b/packages/pinball_components/assets/images/ball/flame_effect.png new file mode 100644 index 00000000..03a6fca6 Binary files /dev/null and b/packages/pinball_components/assets/images/ball/flame_effect.png differ diff --git a/packages/pinball_components/assets/images/boundary/bottom.png b/packages/pinball_components/assets/images/boundary/bottom.png index 2effb7ac..90bfa493 100644 Binary files a/packages/pinball_components/assets/images/boundary/bottom.png and b/packages/pinball_components/assets/images/boundary/bottom.png differ diff --git a/packages/pinball_components/assets/images/dino/dino-land-bottom.png b/packages/pinball_components/assets/images/dino/dino-land-bottom.png index 1839dda3..9aa42e12 100644 Binary files a/packages/pinball_components/assets/images/dino/dino-land-bottom.png and b/packages/pinball_components/assets/images/dino/dino-land-bottom.png differ diff --git a/packages/pinball_components/assets/images/dino/dino-land-top.png b/packages/pinball_components/assets/images/dino/dino-land-top.png index 85c2619a..18b92541 100644 Binary files a/packages/pinball_components/assets/images/dino/dino-land-top.png and b/packages/pinball_components/assets/images/dino/dino-land-top.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/launch_ramp/background-railing.png b/packages/pinball_components/assets/images/launch_ramp/background-railing.png new file mode 100644 index 00000000..aa7d5774 Binary files /dev/null and b/packages/pinball_components/assets/images/launch_ramp/background-railing.png differ diff --git a/packages/pinball_components/assets/images/launch_ramp/foreground-railing.png b/packages/pinball_components/assets/images/launch_ramp/foreground-railing.png index 7dd0de96..f953fdf5 100644 Binary files a/packages/pinball_components/assets/images/launch_ramp/foreground-railing.png and b/packages/pinball_components/assets/images/launch_ramp/foreground-railing.png differ diff --git a/packages/pinball_components/assets/images/launch_ramp/ramp.png b/packages/pinball_components/assets/images/launch_ramp/ramp.png index c811dd83..b024860a 100644 Binary files a/packages/pinball_components/assets/images/launch_ramp/ramp.png and b/packages/pinball_components/assets/images/launch_ramp/ramp.png 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/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index f45543b0..ca4c3f3c 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -13,10 +13,7 @@ class $AssetsImagesGen { $AssetsImagesAlienBumperGen get alienBumper => const $AssetsImagesAlienBumperGen(); $AssetsImagesBackboardGen get backboard => const $AssetsImagesBackboardGen(); - - /// File path: assets/images/ball.png - AssetGenImage get ball => const AssetGenImage('assets/images/ball.png'); - + $AssetsImagesBallGen get ball => const $AssetsImagesBallGen(); $AssetsImagesBaseboardGen get baseboard => const $AssetsImagesBaseboardGen(); $AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen(); $AssetsImagesChromeDinoGen get chromeDino => @@ -24,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(); @@ -57,10 +50,23 @@ class $AssetsImagesBackboardGen { /// File path: assets/images/backboard/backboard_scores.png AssetGenImage get backboardScores => const AssetGenImage('assets/images/backboard/backboard_scores.png'); + + /// File path: assets/images/backboard/display.png AssetGenImage get display => const AssetGenImage('assets/images/backboard/display.png'); } +class $AssetsImagesBallGen { + const $AssetsImagesBallGen(); + + /// File path: assets/images/ball/ball.png + AssetGenImage get ball => const AssetGenImage('assets/images/ball/ball.png'); + + /// File path: assets/images/ball/flame_effect.png + AssetGenImage get flameEffect => + const AssetGenImage('assets/images/ball/flame_effect.png'); +} + class $AssetsImagesBaseboardGen { const $AssetsImagesBaseboardGen(); @@ -174,6 +180,10 @@ class $AssetsImagesKickerGen { class $AssetsImagesLaunchRampGen { const $AssetsImagesLaunchRampGen(); + /// File path: assets/images/launch_ramp/background-railing.png + AssetGenImage get backgroundRailing => + const AssetGenImage('assets/images/launch_ramp/background-railing.png'); + /// File path: assets/images/launch_ramp/foreground-railing.png AssetGenImage get foregroundRailing => const AssetGenImage('assets/images/launch_ramp/foreground-railing.png'); @@ -195,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(); @@ -309,8 +339,11 @@ class $AssetsImagesSparkyBumperGen { class $AssetsImagesSparkyComputerGen { const $AssetsImagesSparkyComputerGen(); + /// File path: assets/images/sparky/computer/base.png AssetGenImage get base => const AssetGenImage('assets/images/sparky/computer/base.png'); + + /// File path: assets/images/sparky/computer/top.png AssetGenImage get top => const AssetGenImage('assets/images/sparky/computer/top.png'); } @@ -354,8 +387,11 @@ class $AssetsImagesDashBumperMainGen { class $AssetsImagesSparkyBumperAGen { const $AssetsImagesSparkyBumperAGen(); + /// File path: assets/images/sparky/bumper/a/active.png AssetGenImage get active => const AssetGenImage('assets/images/sparky/bumper/a/active.png'); + + /// File path: assets/images/sparky/bumper/a/inactive.png AssetGenImage get inactive => const AssetGenImage('assets/images/sparky/bumper/a/inactive.png'); } @@ -363,8 +399,11 @@ class $AssetsImagesSparkyBumperAGen { class $AssetsImagesSparkyBumperBGen { const $AssetsImagesSparkyBumperBGen(); + /// File path: assets/images/sparky/bumper/b/active.png AssetGenImage get active => const AssetGenImage('assets/images/sparky/bumper/b/active.png'); + + /// File path: assets/images/sparky/bumper/b/inactive.png AssetGenImage get inactive => const AssetGenImage('assets/images/sparky/bumper/b/inactive.png'); } @@ -372,8 +411,11 @@ class $AssetsImagesSparkyBumperBGen { class $AssetsImagesSparkyBumperCGen { const $AssetsImagesSparkyBumperCGen(); + /// File path: assets/images/sparky/bumper/c/active.png AssetGenImage get active => const AssetGenImage('assets/images/sparky/bumper/c/active.png'); + + /// File path: assets/images/sparky/bumper/c/inactive.png AssetGenImage get inactive => const AssetGenImage('assets/images/sparky/bumper/c/inactive.png'); } 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 b85e70fa..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.pixeloidSans); + 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..b78c6adc 100644 --- a/packages/pinball_components/lib/src/components/alien_bumper.dart +++ b/packages/pinball_components/lib/src/components/alien_bumper.dart @@ -18,7 +18,8 @@ class AlienBumper extends BodyComponent with InitialPosition { _minorRadius = minorRadius, _activeAssetPath = activeAssetPath, _inactiveAssetPath = inactiveAssetPath, - _spriteComponent = spriteComponent; + _spriteComponent = spriteComponent, + super(priority: RenderPriority.alienBumper); /// {@macro alien_bumper} AlienBumper.a() diff --git a/packages/pinball_components/lib/src/components/backboard/backboard.dart b/packages/pinball_components/lib/src/components/backboard/backboard.dart index 1f8a0843..fe5fd37c 100644 --- a/packages/pinball_components/lib/src/components/backboard/backboard.dart +++ b/packages/pinball_components/lib/src/components/backboard/backboard.dart @@ -48,7 +48,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 36059cfd..b1e2703b 100644 --- a/packages/pinball_components/lib/src/components/ball.dart +++ b/packages/pinball_components/lib/src/components/ball.dart @@ -4,6 +4,7 @@ import 'dart:ui'; import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/widgets.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template ball} @@ -28,30 +29,12 @@ 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); /// The base [Color] used to tint this [Ball]. final Color baseColor; - double _boostTimer = 0; - static const _boostDuration = 2.0; - @override Body createBody() { final shape = CircleShape()..radius = size.x / 2; @@ -87,32 +70,20 @@ class Ball extends BodyComponent body.gravityScale = Vector2(0, 1); } + /// Applies a boost and [_TurboChargeSpriteAnimationComponent] on this [Ball]. + Future boost(Vector2 impulse) async { + body.linearVelocity = impulse; + await add(_TurboChargeSpriteAnimationComponent()); + } + @override void update(double dt) { super.update(dt); - if (_boostTimer > 0) { - _boostTimer -= dt; - final direction = body.linearVelocity.normalized(); - final effect = FireEffect( - burstPower: _boostTimer, - direction: direction, - position: Vector2(body.position.x, body.position.y), - priority: priority - 1, - ); - - unawaited(gameRef.add(effect)); - } _rescaleSize(); _setPositionalGravity(); } - /// Applies a boost on this [Ball]. - void boost(Vector2 impulse) { - body.linearVelocity = impulse; - _boostTimer = _boostDuration; - } - void _rescaleSize() { final boardHeight = BoardDimensions.bounds.height; const maxShrinkValue = BoardDimensions.perspectiveShrinkFactor; @@ -153,10 +124,61 @@ class _BallSpriteComponent extends SpriteComponent with HasGameRef { Future onLoad() async { await super.onLoad(); final sprite = await gameRef.loadSprite( - Assets.images.ball.keyName, + Assets.images.ball.ball.keyName, ); this.sprite = sprite; size = sprite.originalSize / 10; anchor = Anchor.center; } } + +class _TurboChargeSpriteAnimationComponent extends SpriteAnimationComponent + with HasGameRef { + _TurboChargeSpriteAnimationComponent() + : super( + anchor: const Anchor(0.53, 0.72), + priority: RenderPriority.turboChargeFlame, + removeOnFinish: true, + ); + + late final Vector2 _textureSize; + + @override + Future onLoad() async { + await super.onLoad(); + + final spriteSheet = await gameRef.images.load( + Assets.images.ball.flameEffect.keyName, + ); + + const amountPerRow = 8; + const amountPerColumn = 4; + _textureSize = Vector2( + spriteSheet.width / amountPerRow, + spriteSheet.height / amountPerColumn, + ); + + animation = SpriteAnimation.fromFrameData( + spriteSheet, + SpriteAnimationData.sequenced( + amount: amountPerRow * amountPerColumn, + amountPerRow: amountPerRow, + stepTime: 1 / 24, + textureSize: _textureSize, + loop: false, + ), + ); + } + + @override + void update(double dt) { + super.update(dt); + + if (parent != null) { + final body = (parent! as BodyComponent).body; + final direction = -body.linearVelocity.normalized(); + angle = math.atan2(direction.x, -direction.y); + size = (_textureSize / 45) * body.fixtures.first.shape.radius; + } + } +} diff --git a/packages/pinball_components/lib/src/components/boundaries.dart b/packages/pinball_components/lib/src/components/boundaries.dart index 983b4839..8c59d598 100644 --- a/packages/pinball_components/lib/src/components/boundaries.dart +++ b/packages/pinball_components/lib/src/components/boundaries.dart @@ -26,7 +26,7 @@ class _BottomBoundary extends BodyComponent with InitialPosition { /// {@macro bottom_boundary} _BottomBoundary() : super( - priority: 1, + priority: RenderPriority.bottomBoundary, children: [_BottomBoundarySpriteComponent()], ) { renderBody = false; @@ -78,7 +78,7 @@ class _BottomBoundarySpriteComponent extends SpriteComponent with HasGameRef { this.sprite = sprite; size = sprite.originalSize / 10; anchor = Anchor.center; - position = Vector2(-5.4, 55.8); + position = Vector2(-5.4, 55.6); } } @@ -90,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 e450b661..7846f140 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..bc66b184 100644 --- a/packages/pinball_components/lib/src/components/dash_animatronic.dart +++ b/packages/pinball_components/lib/src/components/dash_animatronic.dart @@ -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..b796b9d1 100644 --- a/packages/pinball_components/lib/src/components/dash_nest_bumper.dart +++ b/packages/pinball_components/lib/src/components/dash_nest_bumper.dart @@ -15,7 +15,8 @@ abstract class DashNestBumper extends BodyComponent with InitialPosition { required SpriteComponent spriteComponent, }) : _activeAssetPath = activeAssetPath, _inactiveAssetPath = inactiveAssetPath, - _spriteComponent = spriteComponent; + _spriteComponent = spriteComponent, + super(priority: RenderPriority.dashBumper); final String _activeAssetPath; late final Sprite _activeSprite; diff --git a/packages/pinball_components/lib/src/components/dino_walls.dart b/packages/pinball_components/lib/src/components/dino_walls.dart index 9bc0ba09..139e391b 100644 --- a/packages/pinball_components/lib/src/components/dino_walls.dart +++ b/packages/pinball_components/lib/src/components/dino_walls.dart @@ -31,7 +31,7 @@ class _DinoTopWall extends BodyComponent with InitialPosition { ///{@macro dino_top_wall} _DinoTopWall() : super( - priority: 1, + priority: RenderPriority.dinoTopWall, children: [_DinoTopWallSpriteComponent()], ) { renderBody = false; @@ -42,7 +42,7 @@ class _DinoTopWall extends BodyComponent with InitialPosition { final topStraightShape = EdgeShape() ..set( - Vector2(28.4, -35.1), + Vector2(28.65, -35.1), Vector2(29.5, -35.1), ); final topStraightFixtureDef = FixtureDef(topStraightShape); @@ -69,8 +69,8 @@ class _DinoTopWall extends BodyComponent with InitialPosition { final bottomCurveShape = BezierCurveShape( controlPoints: [ middleCurveShape.vertices.last, - Vector2(21.15, -16), - Vector2(25.6, -15.2), + Vector2(21.5, -15.8), + Vector2(25.8, -14.8), ], ); fixturesDef.add(FixtureDef(bottomCurveShape)); @@ -126,6 +126,7 @@ class _DinoBottomWall extends BodyComponent with InitialPosition { ///{@macro dino_top_wall} _DinoBottomWall() : super( + priority: RenderPriority.dinoBottomWall, children: [_DinoBottomWallSpriteComponent()], ) { renderBody = false; @@ -136,7 +137,7 @@ class _DinoBottomWall extends BodyComponent with InitialPosition { const restitution = 1.0; final topStraightControlPoints = [ - Vector2(32.4, -8.3), + Vector2(32.4, -8.8), Vector2(25, -7.7), ]; final topStraightShape = EdgeShape() @@ -220,6 +221,6 @@ class _DinoBottomWallSpriteComponent extends SpriteComponent with HasGameRef { ); this.sprite = sprite; size = sprite.originalSize / 10; - position = Vector2(23.8, -9.5); + position = Vector2(23.6, -9.5); } } 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 c3d7624d..2ac9ee91 100644 --- a/packages/pinball_components/lib/src/components/launch_ramp.dart +++ b/packages/pinball_components/lib/src/components/launch_ramp.dart @@ -42,10 +42,21 @@ class LaunchRamp extends Forge2DBlueprint { /// {@endtemplate} class _LaunchRampBase extends BodyComponent with InitialPosition, Layered { /// {@macro launch_ramp_base} - _LaunchRampBase() : super(priority: Ball.launchRampPriority - 1) { + _LaunchRampBase() + : super( + priority: RenderPriority.launchRamp, + children: [ + _LaunchRampBackgroundRailingSpriteComponent(), + _LaunchRampBaseSpriteComponent(), + ], + ) { layer = Layer.launcher; + renderBody = false; } + // TODO(ruimiguel): final asset differs slightly from the current shape. We + // need to fix shape with correct vertices, but right now merge them to have + // final assets at game and not be blocked. List _createFixtureDefs() { final fixturesDef = []; @@ -114,28 +125,36 @@ class _LaunchRampBase extends BodyComponent with InitialPosition, Layered { return body; } +} +class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef { @override Future onLoad() async { await super.onLoad(); - renderBody = false; - await add(_LaunchRampBaseSpriteComponent()); + final sprite = await gameRef.loadSprite( + Assets.images.launchRamp.ramp.keyName, + ); + this.sprite = sprite; + size = sprite.originalSize / 10; + anchor = Anchor.center; + position = Vector2(25.65, 0.7); } } -class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef { +class _LaunchRampBackgroundRailingSpriteComponent extends SpriteComponent + with HasGameRef { @override Future onLoad() async { await super.onLoad(); final sprite = await gameRef.loadSprite( - Assets.images.launchRamp.ramp.keyName, + Assets.images.launchRamp.backgroundRailing.keyName, ); this.sprite = sprite; size = sprite.originalSize / 10; anchor = Anchor.center; - position = Vector2(25.65, 0); + position = Vector2(25.6, -1.3); } } @@ -145,7 +164,13 @@ class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef { /// {@endtemplate} class _LaunchRampForegroundRailing extends BodyComponent with InitialPosition { /// {@macro launch_ramp_foreground_railing} - _LaunchRampForegroundRailing() : super(priority: Ball.launchRampPriority + 1); + _LaunchRampForegroundRailing() + : super( + priority: RenderPriority.launchRampForegroundRailing, + children: [_LaunchRampForegroundRailingSpriteComponent()], + ) { + renderBody = false; + } List _createFixtureDefs() { final fixturesDef = []; @@ -153,7 +178,7 @@ class _LaunchRampForegroundRailing extends BodyComponent with InitialPosition { final rightStraightShape = EdgeShape() ..set( Vector2(27.6, -57.9), - Vector2(30, -35.1), + Vector2(38.1, 42.6), ); final rightStraightFixtureDef = FixtureDef(rightStraightShape); fixturesDef.add(rightStraightFixtureDef); @@ -189,14 +214,6 @@ class _LaunchRampForegroundRailing extends BodyComponent with InitialPosition { return body; } - - @override - Future onLoad() async { - await super.onLoad(); - renderBody = false; - - await add(_LaunchRampForegroundRailingSpriteComponent()); - } } class _LaunchRampForegroundRailingSpriteComponent extends SpriteComponent @@ -211,7 +228,7 @@ class _LaunchRampForegroundRailingSpriteComponent extends SpriteComponent this.sprite = sprite; size = sprite.originalSize / 10; anchor = Anchor.center; - position = Vector2(22.8, 0); + position = Vector2(22.8, 0.5); } } @@ -248,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..1850369c --- /dev/null +++ b/packages/pinball_components/lib/src/components/render_priority.dart @@ -0,0 +1,114 @@ +// 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 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..665c2cbb --- /dev/null +++ b/packages/pinball_components/lib/src/components/signpost.dart @@ -0,0 +1,102 @@ +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) { + // TODO(allisonryan0002): Support caching + // https://github.com/VGVentures/pinball/pull/204 + // sprites[spriteState] = Sprite( + // gameRef.images.fromCache(spriteState.path), + // ); + sprites[spriteState] = await gameRef.loadSprite(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 4beb0367..b3d674dc 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()], ) { renderBody = false; @@ -145,7 +148,7 @@ class _SpaceshipEntrance extends LayerSensor { : super( insideLayer: Layer.spaceship, orientation: LayerEntranceOrientation.up, - insidePriority: Ball.spaceshipPriority, + insidePriority: RenderPriority.ballOnSpaceship, ) { layer = Layer.spaceship; } @@ -169,12 +172,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; @@ -225,7 +228,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..f2d8801c 100644 --- a/packages/pinball_components/lib/src/components/spaceship_ramp.dart +++ b/packages/pinball_components/lib/src/components/spaceship_ramp.dart @@ -22,15 +22,14 @@ class SpaceshipRamp extends Forge2DBlueprint { ]); final rightOpening = _SpaceshipRampOpening( - // TODO(ruimiguel): set Board priority when defined. - outsidePriority: 1, + outsidePriority: RenderPriority.ballOnBoard, rotation: math.pi, ) ..initialPosition = Vector2(1.7, -19.8) ..layer = Layer.opening; final leftOpening = _SpaceshipRampOpening( outsideLayer: Layer.spaceship, - outsidePriority: Ball.spaceshipPriority, + outsidePriority: RenderPriority.ballOnSpaceship, rotation: math.pi, ) ..initialPosition = Vector2(-13.7, -18.6) @@ -51,6 +50,7 @@ class SpaceshipRamp extends Forge2DBlueprint { rightOpening, leftOpening, baseRight, + _SpaceshipRampBackgroundRailingSpriteComponent(), spaceshipRamp, spaceshipRampForegroundRailing, ]); @@ -59,7 +59,7 @@ class SpaceshipRamp extends Forge2DBlueprint { class _SpaceshipRampBackground extends BodyComponent with InitialPosition, Layered { - _SpaceshipRampBackground() : super(priority: Ball.spaceshipRampPriority - 1) { + _SpaceshipRampBackground() : super(priority: RenderPriority.spaceshipRamp) { layer = Layer.spaceshipEntranceRamp; } @@ -119,12 +119,13 @@ class _SpaceshipRampBackground extends BodyComponent renderBody = false; await add(_SpaceshipRampBackgroundRampSpriteComponent()); - await add(_SpaceshipRampBackgroundRailingSpriteComponent()); } } class _SpaceshipRampBackgroundRailingSpriteComponent extends SpriteComponent with HasGameRef { + _SpaceshipRampBackgroundRailingSpriteComponent() + : super(priority: RenderPriority.spaceshipRampBackgroundRailing); @override Future onLoad() async { await super.onLoad(); @@ -171,7 +172,7 @@ class _SpaceshipRampForegroundRailing extends BodyComponent with InitialPosition, Layered { _SpaceshipRampForegroundRailing() : super( - priority: Ball.spaceshipRampPriority + 1, + priority: RenderPriority.spaceshipRampForegroundRailing, children: [_SpaceshipRampForegroundRailingSpriteComponent()], ) { layer = Layer.spaceshipEntranceRamp; @@ -287,7 +288,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..e4d9ebef 100644 --- a/packages/pinball_components/lib/src/components/sparky_bumper.dart +++ b/packages/pinball_components/lib/src/components/sparky_bumper.dart @@ -20,7 +20,8 @@ class SparkyBumper extends BodyComponent with InitialPosition { _minorRadius = minorRadius, _activeAssetPath = activeAssetPath, _inactiveAssetPath = inactiveAssetPath, - _spriteComponent = spriteComponent; + _spriteComponent = spriteComponent, + super(priority: RenderPriority.sparkyBumper); /// {@macro sparky_bumper} SparkyBumper.a() 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 35b82024..53783920 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -41,6 +41,7 @@ flutter: assets: - assets/images/ + - assets/images/ball/ - assets/images/baseboard/ - assets/images/boundary/ - assets/images/dino/ @@ -65,6 +66,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/flutter_forest/flutter_sign_post_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/signpost_game.dart similarity index 57% rename from packages/pinball_components/sandbox/lib/stories/flutter_forest/flutter_sign_post_game.dart rename to packages/pinball_components/sandbox/lib/stories/flutter_forest/signpost_game.dart index 3efb83fe..b7c11cf2 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/flutter_sign_post_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/signpost_game.dart @@ -1,22 +1,30 @@ import 'dart:async'; -import 'package:flame_forge2d/flame_forge2d.dart'; +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 FlutterSignPostGame extends BasicBallGame with Traceable { +class SignpostGame extends BasicBallGame with Traceable, TapDetector { static const info = ''' - Shows how a FlutterSignPost is rendered. + 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(); + camera.followVector2(Vector2.zero()); - await add(FlutterSignPost()..priority = 1); + await add(Signpost()..priority = 1); await traceAllBodies(); } + + @override + void onTap() { + super.onTap(); + firstChild()!.progress(); + } } 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..a11f6d7d 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 @@ -10,7 +10,7 @@ class SpaceshipRampGame extends BasicBallGame { SpaceshipRampGame() : super( color: Colors.blue, - ballPriority: Ball.spaceshipRampPriority, + ballPriority: RenderPriority.ballOnSpaceshipRamp, ballLayer: Layer.spaceshipEntranceRamp, ); 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/src/components/ball_test.dart b/packages/pinball_components/test/src/components/ball_test.dart index a0a73e2b..26a03886 100644 --- a/packages/pinball_components/test/src/components/ball_test.dart +++ b/packages/pinball_components/test/src/components/ball_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/material.dart'; @@ -169,20 +170,41 @@ void main() { expect(ball.body.linearVelocity, equals(Vector2.zero())); - ball.boost(Vector2.all(10)); + await ball.boost(Vector2.all(10)); expect(ball.body.linearVelocity.x, greaterThan(0)); expect(ball.body.linearVelocity.y, greaterThan(0)); }); - flameTester.test('adds fire effect components to the game', (game) async { + flameTester.test('adds TurboChargeSpriteAnimation', (game) async { final ball = Ball(baseColor: Colors.blue); await game.ensureAdd(ball); - ball.boost(Vector2.all(10)); + await ball.boost(Vector2.all(10)); game.update(0); - await game.ready(); - expect(game.children.whereType().length, greaterThan(0)); + expect( + ball.children.whereType().single, + isNotNull, + ); + }); + + flameTester.test('removes TurboChargeSpriteAnimation after it finishes', + (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ensureAdd(ball); + + await ball.boost(Vector2.all(10)); + game.update(0); + + final turboChargeSpriteAnimation = + ball.children.whereType().single; + + expect(ball.contains(turboChargeSpriteAnimation), isTrue); + + game.update(turboChargeSpriteAnimation.animation!.totalDuration()); + game.update(0.1); + + expect(ball.contains(turboChargeSpriteAnimation), isFalse); }); }); }); 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/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/boundaries.png b/packages/pinball_components/test/src/components/golden/boundaries.png index f184e3eb..4ac8f5eb 100644 Binary files a/packages/pinball_components/test/src/components/golden/boundaries.png and b/packages/pinball_components/test/src/components/golden/boundaries.png differ 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/dino-bottom-wall.png b/packages/pinball_components/test/src/components/golden/dino-bottom-wall.png deleted file mode 100644 index 14ae9c0d..00000000 Binary files a/packages/pinball_components/test/src/components/golden/dino-bottom-wall.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/golden/dino-top-wall.png b/packages/pinball_components/test/src/components/golden/dino-top-wall.png deleted file mode 100644 index 0d434d69..00000000 Binary files a/packages/pinball_components/test/src/components/golden/dino-top-wall.png and /dev/null differ diff --git a/packages/pinball_components/test/src/components/golden/dino-walls.png b/packages/pinball_components/test/src/components/golden/dino-walls.png index 80b07157..8c2ee569 100644 Binary files a/packages/pinball_components/test/src/components/golden/dino-walls.png and b/packages/pinball_components/test/src/components/golden/dino-walls.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/launch-ramp.png b/packages/pinball_components/test/src/components/golden/launch-ramp.png index e872c533..52ab2510 100644 Binary files a/packages/pinball_components/test/src/components/golden/launch-ramp.png and b/packages/pinball_components/test/src/components/golden/launch-ramp.png 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/launch_ramp_test.dart b/packages/pinball_components/test/src/components/launch_ramp_test.dart index 1f5d6f26..2defc168 100644 --- a/packages/pinball_components/test/src/components/launch_ramp_test.dart +++ b/packages/pinball_components/test/src/components/launch_ramp_test.dart @@ -16,6 +16,7 @@ void main() { 'renders correctly', setUp: (game, tester) async { await game.addFromBlueprint(LaunchRamp()); + await game.ready(); game.camera.followVector2(Vector2.zero()); game.camera.zoom = 4.1; }, 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..33844994 --- /dev/null +++ b/packages/pinball_components/test/src/components/signpost_test.dart @@ -0,0 +1,141 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(TestGame.new); + + group('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 { + final signpost = Signpost(); + await game.ensureAdd(signpost); + + 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 { + final signpost = Signpost(); + await game.ensureAdd(signpost); + signpost.progress(); + + 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 { + final signpost = Signpost(); + await game.ensureAdd(signpost); + signpost + ..progress() + ..progress(); + + 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 { + final signpost = Signpost(); + await game.ensureAdd(signpost); + signpost + ..progress() + ..progress() + ..progress(); + + 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/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/controlled_ball_test.dart b/test/game/components/controlled_ball_test.dart index 96c67dd4..e615d508 100644 --- a/test/game/components/controlled_ball_test.dart +++ b/test/game/components/controlled_ball_test.dart @@ -94,6 +94,7 @@ void main() { final controller = WrappedBallController(ball, gameRef); when(() => gameRef.read()).thenReturn(gameBloc); when(() => ball.controller).thenReturn(controller); + when(() => ball.boost(any())).thenAnswer((_) async {}); await controller.turboCharge(); @@ -109,6 +110,7 @@ void main() { final controller = WrappedBallController(ball, gameRef); when(() => gameRef.read()).thenReturn(gameBloc); when(() => ball.controller).thenReturn(controller); + when(() => ball.boost(any())).thenAnswer((_) async {}); await controller.turboCharge(); @@ -124,6 +126,7 @@ void main() { final controller = WrappedBallController(ball, gameRef); when(() => gameRef.read()).thenReturn(gameBloc); when(() => ball.controller).thenReturn(controller); + when(() => ball.boost(any())).thenAnswer((_) async {}); await controller.turboCharge(); diff --git a/test/game/components/flutter_forest_test.dart b/test/game/components/flutter_forest_test.dart index 2089b7b7..a318d342 100644 --- a/test/game/components/flutter_forest_test.dart +++ b/test/game/components/flutter_forest_test.dart @@ -27,13 +27,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), ); }, diff --git a/test/game/view/pinball_game_page_test.dart b/test/game/view/pinball_game_page_test.dart index 85f9cfc3..59a0ef88 100644 --- a/test/game/view/pinball_game_page_test.dart +++ b/test/game/view/pinball_game_page_test.dart @@ -5,40 +5,46 @@ 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(() { + 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 +57,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 +116,6 @@ void main() { onPressed: () { Navigator.of(context).push( PinballGamePage.route( - theme: theme, isDebugMode: isDebugMode, ), ); @@ -95,6 +125,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/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); }); });