diff --git a/lib/game/behaviors/behaviors.dart b/lib/game/behaviors/behaviors.dart index 44cce1df..c7ad6880 100644 --- a/lib/game/behaviors/behaviors.dart +++ b/lib/game/behaviors/behaviors.dart @@ -1,4 +1,5 @@ export 'ball_spawning_behavior.dart'; +export 'bonus_noise_behavior.dart'; export 'bumper_noise_behavior.dart'; export 'camera_focusing_behavior.dart'; export 'scoring_behavior.dart'; diff --git a/lib/game/behaviors/bonus_noise_behavior.dart b/lib/game/behaviors/bonus_noise_behavior.dart new file mode 100644 index 00000000..9d67e964 --- /dev/null +++ b/lib/game/behaviors/bonus_noise_behavior.dart @@ -0,0 +1,41 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// Behavior that handles playing a bonus sound effect +class BonusNoiseBehavior extends Component { + @override + Future onLoad() async { + await add( + FlameBlocListener( + listenWhen: (previous, current) { + return previous.bonusHistory.length != current.bonusHistory.length; + }, + onNewState: (state) { + final bonus = state.bonusHistory.last; + final audioPlayer = readProvider(); + + switch (bonus) { + case GameBonus.googleWord: + audioPlayer.play(PinballAudio.google); + break; + case GameBonus.sparkyTurboCharge: + audioPlayer.play(PinballAudio.sparky); + break; + case GameBonus.dinoChomp: + // TODO(erickzanardo): Add sound + break; + case GameBonus.androidSpaceship: + // TODO(erickzanardo): Add sound + break; + case GameBonus.dashNest: + // TODO(erickzanardo): Add sound + break; + } + }, + ), + ); + } +} diff --git a/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart b/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart index e49d4537..586b8547 100644 --- a/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart +++ b/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart @@ -1,7 +1,6 @@ import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:pinball/game/game.dart'; -import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; @@ -22,7 +21,6 @@ class GoogleWordBonusBehavior extends Component .every((letter) => letter.bloc.state == GoogleLetterState.lit); if (achievedBonus) { - readProvider().play(PinballAudio.google); bloc.add(const BonusActivated(GameBonus.googleWord)); for (final letter in googleLetters) { letter.bloc.onReset(); diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 503d6261..8f1628be 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -67,6 +67,7 @@ class PinballGame extends PinballForge2DGame FlameProvider.value(_l10n), ], children: [ + BonusNoiseBehavior(), GameBlocStatusListener(), BallSpawningBehavior(), CameraFocusingBehavior(), diff --git a/packages/pinball_audio/assets/sfx/sparky.mp3 b/packages/pinball_audio/assets/sfx/sparky.mp3 new file mode 100644 index 00000000..14bf0ec6 Binary files /dev/null and b/packages/pinball_audio/assets/sfx/sparky.mp3 differ diff --git a/packages/pinball_audio/lib/gen/assets.gen.dart b/packages/pinball_audio/lib/gen/assets.gen.dart index 916906c4..08e83d87 100644 --- a/packages/pinball_audio/lib/gen/assets.gen.dart +++ b/packages/pinball_audio/lib/gen/assets.gen.dart @@ -14,13 +14,13 @@ class $AssetsMusicGen { class $AssetsSfxGen { const $AssetsSfxGen(); - String get afterLaunch => 'assets/sfx/after_launch.mp3'; String get bumperA => 'assets/sfx/bumper_a.mp3'; String get bumperB => 'assets/sfx/bumper_b.mp3'; String get gameOverVoiceOver => 'assets/sfx/game_over_voice_over.mp3'; String get google => 'assets/sfx/google.mp3'; String get ioPinballVoiceOver => 'assets/sfx/io_pinball_voice_over.mp3'; String get launcher => 'assets/sfx/launcher.mp3'; + String get sparky => 'assets/sfx/sparky.mp3'; } class Assets { diff --git a/packages/pinball_audio/lib/src/pinball_audio.dart b/packages/pinball_audio/lib/src/pinball_audio.dart index 56289417..95c993c5 100644 --- a/packages/pinball_audio/lib/src/pinball_audio.dart +++ b/packages/pinball_audio/lib/src/pinball_audio.dart @@ -25,6 +25,9 @@ enum PinballAudio { /// Launcher launcher, + + /// Sparky + sparky, } /// Defines the contract of the creation of an [AudioPool]. @@ -161,6 +164,11 @@ class PinballPlayer { playSingleAudio: _playSingleAudio, path: Assets.sfx.google, ), + PinballAudio.sparky: _SimplePlayAudio( + preCacheSingleAudio: _preCacheSingleAudio, + playSingleAudio: _playSingleAudio, + path: Assets.sfx.sparky, + ), PinballAudio.launcher: _SimplePlayAudio( preCacheSingleAudio: _preCacheSingleAudio, playSingleAudio: _playSingleAudio, diff --git a/packages/pinball_audio/test/src/pinball_audio_test.dart b/packages/pinball_audio/test/src/pinball_audio_test.dart index fdcd661b..39060eb2 100644 --- a/packages/pinball_audio/test/src/pinball_audio_test.dart +++ b/packages/pinball_audio/test/src/pinball_audio_test.dart @@ -141,6 +141,10 @@ void main() { () => preCacheSingleAudio .onCall('packages/pinball_audio/assets/sfx/google.mp3'), ).called(1); + verify( + () => preCacheSingleAudio + .onCall('packages/pinball_audio/assets/sfx/sparky.mp3'), + ).called(1); verify( () => preCacheSingleAudio.onCall( 'packages/pinball_audio/assets/sfx/io_pinball_voice_over.mp3', @@ -211,7 +215,7 @@ void main() { }); }); - group('googleBonus', () { + group('google', () { test('plays the correct file', () async { await Future.wait(player.load()); player.play(PinballAudio.google); @@ -223,6 +227,18 @@ void main() { }); }); + group('sparky', () { + test('plays the correct file', () async { + await Future.wait(player.load()); + player.play(PinballAudio.sparky); + + verify( + () => playSingleAudio + .onCall('packages/pinball_audio/${Assets.sfx.sparky}'), + ).called(1); + }); + }); + group('launcher', () { test('plays the correct file', () async { await Future.wait(player.load()); diff --git a/test/game/behaviors/bonus_noise_behavior_test.dart b/test/game/behaviors/bonus_noise_behavior_test.dart new file mode 100644 index 00000000..5ec37bce --- /dev/null +++ b/test/game/behaviors/bonus_noise_behavior_test.dart @@ -0,0 +1,186 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class _TestGame extends Forge2DGame { + Future pump( + BonusNoiseBehavior child, { + required PinballPlayer player, + required GameBloc bloc, + }) { + return ensureAdd( + FlameBlocProvider.value( + value: bloc, + children: [ + FlameProvider.value( + player, + children: [ + child, + ], + ), + ], + ), + ); + } +} + +class _MockPinballPlayer extends Mock implements PinballPlayer {} + +class _MockGameBloc extends Mock implements GameBloc {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('BonusNoiseBehavior', () { + late PinballPlayer player; + late GameBloc bloc; + final flameTester = FlameTester(_TestGame.new); + + setUpAll(() { + registerFallbackValue(PinballAudio.google); + }); + + setUp(() { + player = _MockPinballPlayer(); + when(() => player.play(any())).thenAnswer((_) {}); + bloc = _MockGameBloc(); + }); + + flameTester.testGameWidget( + 'plays google sound', + setUp: (game, _) async { + const state = GameState( + totalScore: 0, + roundScore: 0, + multiplier: 1, + rounds: 0, + bonusHistory: [GameBonus.googleWord], + status: GameStatus.playing, + ); + const initialState = GameState.initial(); + whenListen( + bloc, + Stream.fromIterable([initialState, state]), + initialState: initialState, + ); + final behavior = BonusNoiseBehavior(); + await game.pump(behavior, player: player, bloc: bloc); + }, + verify: (_, __) async { + verify(() => player.play(PinballAudio.google)).called(1); + }, + ); + + flameTester.testGameWidget( + 'plays sparky sound', + setUp: (game, _) async { + const state = GameState( + totalScore: 0, + roundScore: 0, + multiplier: 1, + rounds: 0, + bonusHistory: [GameBonus.sparkyTurboCharge], + status: GameStatus.playing, + ); + const initialState = GameState.initial(); + whenListen( + bloc, + Stream.fromIterable([initialState, state]), + initialState: initialState, + ); + final behavior = BonusNoiseBehavior(); + await game.pump(behavior, player: player, bloc: bloc); + }, + verify: (_, __) async { + verify(() => player.play(PinballAudio.sparky)).called(1); + }, + ); + + flameTester.testGameWidget( + 'plays dino chomp sound', + setUp: (game, _) async { + const state = GameState( + totalScore: 0, + roundScore: 0, + multiplier: 1, + rounds: 0, + bonusHistory: [GameBonus.dinoChomp], + status: GameStatus.playing, + ); + const initialState = GameState.initial(); + whenListen( + bloc, + Stream.fromIterable([initialState, state]), + initialState: initialState, + ); + final behavior = BonusNoiseBehavior(); + await game.pump(behavior, player: player, bloc: bloc); + }, + verify: (_, __) async { + // TODO(erickzanardo): Change when the sound is implemented + verifyNever(() => player.play(any())); + }, + ); + + flameTester.testGameWidget( + 'plays android spaceship sound', + setUp: (game, _) async { + const state = GameState( + totalScore: 0, + roundScore: 0, + multiplier: 1, + rounds: 0, + bonusHistory: [GameBonus.androidSpaceship], + status: GameStatus.playing, + ); + const initialState = GameState.initial(); + whenListen( + bloc, + Stream.fromIterable([initialState, state]), + initialState: initialState, + ); + final behavior = BonusNoiseBehavior(); + await game.pump(behavior, player: player, bloc: bloc); + }, + verify: (_, __) async { + // TODO(erickzanardo): Change when the sound is implemented + verifyNever(() => player.play(any())); + }, + ); + + flameTester.testGameWidget( + 'plays dash nest sound', + setUp: (game, _) async { + const state = GameState( + totalScore: 0, + roundScore: 0, + multiplier: 1, + rounds: 0, + bonusHistory: [GameBonus.dashNest], + status: GameStatus.playing, + ); + const initialState = GameState.initial(); + whenListen( + bloc, + Stream.fromIterable([initialState, state]), + initialState: initialState, + ); + final behavior = BonusNoiseBehavior(); + await game.pump(behavior, player: player, bloc: bloc); + }, + verify: (_, __) async { + // TODO(erickzanardo): Change when the sound is implemented + verifyNever(() => player.play(any())); + }, + ); + }); +}