diff --git a/lib/assets_manager/cubit/assets_manager_cubit.dart b/lib/assets_manager/cubit/assets_manager_cubit.dart index 65b780c2..eb0f7e31 100644 --- a/lib/assets_manager/cubit/assets_manager_cubit.dart +++ b/lib/assets_manager/cubit/assets_manager_cubit.dart @@ -18,7 +18,7 @@ class AssetsManagerCubit extends Cubit { /// delay here, which is a bit random in duration but enough to let the UI /// do its job without adding too much delay for the user, we are letting /// the UI paint first, and then we start loading the assets. - await Future.delayed(const Duration(milliseconds: 300)); + await Future.delayed(const Duration(seconds: 1)); emit( state.copyWith( loadables: [ diff --git a/lib/game/behaviors/behaviors.dart b/lib/game/behaviors/behaviors.dart index 190d014e..bb196cec 100644 --- a/lib/game/behaviors/behaviors.dart +++ b/lib/game/behaviors/behaviors.dart @@ -5,4 +5,5 @@ export 'bumper_noise_behavior.dart'; export 'camera_focusing_behavior.dart'; export 'character_selection_behavior.dart'; export 'cow_bumper_noise_behavior.dart'; +export 'kicker_noise_behavior.dart'; export 'scoring_behavior.dart'; diff --git a/lib/game/behaviors/kicker_noise_behavior.dart b/lib/game/behaviors/kicker_noise_behavior.dart new file mode 100644 index 00000000..a04ffeff --- /dev/null +++ b/lib/game/behaviors/kicker_noise_behavior.dart @@ -0,0 +1,11 @@ +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class KickerNoiseBehavior extends ContactBehavior { + @override + void beginContact(Object other, Contact contact) { + super.beginContact(other, contact); + readProvider().play(PinballAudio.kicker); + } +} diff --git a/lib/game/components/bottom_group.dart b/lib/game/components/bottom_group.dart index bc644f96..cfa7e434 100644 --- a/lib/game/components/bottom_group.dart +++ b/lib/game/components/bottom_group.dart @@ -52,6 +52,7 @@ class _BottomGroupSide extends Component { children: [ ScoringContactBehavior(points: Points.fiveThousand) ..applyTo(['bouncy_edge']), + KickerNoiseBehavior()..applyTo(['bouncy_edge']), ], )..initialPosition = Vector2( (22.44 * direction) + centerXAdjustment, diff --git a/packages/pinball_audio/assets/sfx/kicker_a.mp3 b/packages/pinball_audio/assets/sfx/kicker_a.mp3 new file mode 100644 index 00000000..475cbc13 Binary files /dev/null and b/packages/pinball_audio/assets/sfx/kicker_a.mp3 differ diff --git a/packages/pinball_audio/assets/sfx/kicker_b.mp3 b/packages/pinball_audio/assets/sfx/kicker_b.mp3 new file mode 100644 index 00000000..2b1bdfbc Binary files /dev/null and b/packages/pinball_audio/assets/sfx/kicker_b.mp3 differ diff --git a/packages/pinball_audio/lib/gen/assets.gen.dart b/packages/pinball_audio/lib/gen/assets.gen.dart index bdb1527a..c8b66234 100644 --- a/packages/pinball_audio/lib/gen/assets.gen.dart +++ b/packages/pinball_audio/lib/gen/assets.gen.dart @@ -23,6 +23,8 @@ class $AssetsSfxGen { 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 kickerA => 'assets/sfx/kicker_a.mp3'; + String get kickerB => 'assets/sfx/kicker_b.mp3'; String get launcher => 'assets/sfx/launcher.mp3'; String get sparky => 'assets/sfx/sparky.mp3'; } diff --git a/packages/pinball_audio/lib/src/pinball_audio.dart b/packages/pinball_audio/lib/src/pinball_audio.dart index e8b9c8ed..98074fc5 100644 --- a/packages/pinball_audio/lib/src/pinball_audio.dart +++ b/packages/pinball_audio/lib/src/pinball_audio.dart @@ -30,6 +30,9 @@ enum PinballAudio { /// Launcher. launcher, + /// Kicker. + kicker, + /// Sparky. sparky, @@ -113,39 +116,45 @@ class _LoopAudio extends _Audio { } } -class _BumperAudio extends _Audio { - _BumperAudio({ +class _RandomABAudio extends _Audio { + _RandomABAudio({ required this.createAudioPool, required this.seed, + required this.audioAssetA, + required this.audioAssetB, + this.volume, }); final CreateAudioPool createAudioPool; final Random seed; + final String audioAssetA; + final String audioAssetB; + final double? volume; - late AudioPool bumperA; - late AudioPool bumperB; + late AudioPool audioA; + late AudioPool audioB; @override Future load() async { await Future.wait( [ createAudioPool( - prefixFile(Assets.sfx.bumperA), + prefixFile(audioAssetA), maxPlayers: 4, prefix: '', - ).then((pool) => bumperA = pool), + ).then((pool) => audioA = pool), createAudioPool( - prefixFile(Assets.sfx.bumperB), + prefixFile(audioAssetB), maxPlayers: 4, prefix: '', - ).then((pool) => bumperB = pool), + ).then((pool) => audioB = pool), ], ); } @override void play() { - (seed.nextBool() ? bumperA : bumperB).start(volume: 0.6); + (seed.nextBool() ? audioA : audioB).start(volume: volume ?? 1); } } @@ -241,9 +250,19 @@ class PinballAudioPlayer { playSingleAudio: _playSingleAudio, path: Assets.sfx.gameOverVoiceOver, ), - PinballAudio.bumper: _BumperAudio( + PinballAudio.bumper: _RandomABAudio( + createAudioPool: _createAudioPool, + seed: _seed, + audioAssetA: Assets.sfx.bumperA, + audioAssetB: Assets.sfx.bumperB, + volume: 0.6, + ), + PinballAudio.kicker: _RandomABAudio( createAudioPool: _createAudioPool, seed: _seed, + audioAssetA: Assets.sfx.kickerA, + audioAssetB: Assets.sfx.kickerB, + volume: 0.6, ), PinballAudio.cowMoo: _ThrottledAudio( preCacheSingleAudio: _preCacheSingleAudio, diff --git a/packages/pinball_audio/test/src/pinball_audio_test.dart b/packages/pinball_audio/test/src/pinball_audio_test.dart index d1ff6f06..df21b1ad 100644 --- a/packages/pinball_audio/test/src/pinball_audio_test.dart +++ b/packages/pinball_audio/test/src/pinball_audio_test.dart @@ -119,6 +119,26 @@ void main() { ).called(1); }); + test('creates the kicker pools', () async { + await Future.wait(audioPlayer.load()); + + verify( + () => createAudioPool.onCall( + 'packages/pinball_audio/${Assets.sfx.kickerA}', + maxPlayers: 4, + prefix: '', + ), + ).called(1); + + verify( + () => createAudioPool.onCall( + 'packages/pinball_audio/${Assets.sfx.kickerB}', + maxPlayers: 4, + prefix: '', + ), + ).called(1); + }); + test('configures the audio cache instance', () async { await Future.wait(audioPlayer.load()); @@ -234,6 +254,55 @@ void main() { }); }); + group('kicker', () { + late AudioPool kickerAPool; + late AudioPool kickerBPool; + + setUp(() { + kickerAPool = _MockAudioPool(); + when(() => kickerAPool.start(volume: any(named: 'volume'))) + .thenAnswer((_) async => () {}); + when( + () => createAudioPool.onCall( + 'packages/pinball_audio/${Assets.sfx.kickerA}', + maxPlayers: any(named: 'maxPlayers'), + prefix: any(named: 'prefix'), + ), + ).thenAnswer((_) async => kickerAPool); + + kickerBPool = _MockAudioPool(); + when(() => kickerBPool.start(volume: any(named: 'volume'))) + .thenAnswer((_) async => () {}); + when( + () => createAudioPool.onCall( + 'packages/pinball_audio/${Assets.sfx.kickerB}', + maxPlayers: any(named: 'maxPlayers'), + prefix: any(named: 'prefix'), + ), + ).thenAnswer((_) async => kickerBPool); + }); + + group('when seed is true', () { + test('plays the kicker A sound pool', () async { + when(seed.nextBool).thenReturn(true); + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.kicker); + + verify(() => kickerAPool.start(volume: 0.6)).called(1); + }); + }); + + group('when seed is false', () { + test('plays the kicker B sound pool', () async { + when(seed.nextBool).thenReturn(false); + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.kicker); + + verify(() => kickerBPool.start(volume: 0.6)).called(1); + }); + }); + }); + group('cow moo', () { test('plays the correct file', () async { await Future.wait(audioPlayer.load()); diff --git a/packages/pinball_theme/assets/images/android/animation.png b/packages/pinball_theme/assets/images/android/animation.png index fc7465be..47f5f74c 100644 Binary files a/packages/pinball_theme/assets/images/android/animation.png and b/packages/pinball_theme/assets/images/android/animation.png differ diff --git a/packages/pinball_theme/assets/images/android/background.jpg b/packages/pinball_theme/assets/images/android/background.jpg index f446e140..bb1a6992 100644 Binary files a/packages/pinball_theme/assets/images/android/background.jpg and b/packages/pinball_theme/assets/images/android/background.jpg differ diff --git a/packages/pinball_theme/assets/images/android/ball.png b/packages/pinball_theme/assets/images/android/ball.png index ca2eebe3..8310c098 100644 Binary files a/packages/pinball_theme/assets/images/android/ball.png and b/packages/pinball_theme/assets/images/android/ball.png differ diff --git a/packages/pinball_theme/assets/images/android/icon.png b/packages/pinball_theme/assets/images/android/icon.png index ff365ffe..03857b0a 100644 Binary files a/packages/pinball_theme/assets/images/android/icon.png and b/packages/pinball_theme/assets/images/android/icon.png differ diff --git a/packages/pinball_theme/assets/images/android/leaderboard_icon.png b/packages/pinball_theme/assets/images/android/leaderboard_icon.png index 238e29ef..ae97c412 100644 Binary files a/packages/pinball_theme/assets/images/android/leaderboard_icon.png and b/packages/pinball_theme/assets/images/android/leaderboard_icon.png differ diff --git a/packages/pinball_theme/assets/images/dash/animation.png b/packages/pinball_theme/assets/images/dash/animation.png index e812415f..97d4fb07 100644 Binary files a/packages/pinball_theme/assets/images/dash/animation.png and b/packages/pinball_theme/assets/images/dash/animation.png differ diff --git a/packages/pinball_theme/assets/images/dash/background.jpg b/packages/pinball_theme/assets/images/dash/background.jpg index dc698060..667454f4 100644 Binary files a/packages/pinball_theme/assets/images/dash/background.jpg and b/packages/pinball_theme/assets/images/dash/background.jpg differ diff --git a/packages/pinball_theme/assets/images/dash/ball.png b/packages/pinball_theme/assets/images/dash/ball.png index 69e8dffa..f93023f6 100644 Binary files a/packages/pinball_theme/assets/images/dash/ball.png and b/packages/pinball_theme/assets/images/dash/ball.png differ diff --git a/packages/pinball_theme/assets/images/dash/icon.png b/packages/pinball_theme/assets/images/dash/icon.png index 45bba327..0df550ea 100644 Binary files a/packages/pinball_theme/assets/images/dash/icon.png and b/packages/pinball_theme/assets/images/dash/icon.png differ diff --git a/packages/pinball_theme/assets/images/dash/leaderboard_icon.png b/packages/pinball_theme/assets/images/dash/leaderboard_icon.png index 5c172d47..69b2919c 100644 Binary files a/packages/pinball_theme/assets/images/dash/leaderboard_icon.png and b/packages/pinball_theme/assets/images/dash/leaderboard_icon.png differ diff --git a/packages/pinball_theme/assets/images/dino/animation.png b/packages/pinball_theme/assets/images/dino/animation.png index c75b16f9..de7e1b60 100644 Binary files a/packages/pinball_theme/assets/images/dino/animation.png and b/packages/pinball_theme/assets/images/dino/animation.png differ diff --git a/packages/pinball_theme/assets/images/dino/background.jpg b/packages/pinball_theme/assets/images/dino/background.jpg index 45272247..6cf0626e 100644 Binary files a/packages/pinball_theme/assets/images/dino/background.jpg and b/packages/pinball_theme/assets/images/dino/background.jpg differ diff --git a/packages/pinball_theme/assets/images/dino/ball.png b/packages/pinball_theme/assets/images/dino/ball.png index 1a2102d8..7c1885a2 100644 Binary files a/packages/pinball_theme/assets/images/dino/ball.png and b/packages/pinball_theme/assets/images/dino/ball.png differ diff --git a/packages/pinball_theme/assets/images/dino/icon.png b/packages/pinball_theme/assets/images/dino/icon.png index 0114060e..28682c73 100644 Binary files a/packages/pinball_theme/assets/images/dino/icon.png and b/packages/pinball_theme/assets/images/dino/icon.png differ diff --git a/packages/pinball_theme/assets/images/dino/leaderboard_icon.png b/packages/pinball_theme/assets/images/dino/leaderboard_icon.png index b1033371..4a230b39 100644 Binary files a/packages/pinball_theme/assets/images/dino/leaderboard_icon.png and b/packages/pinball_theme/assets/images/dino/leaderboard_icon.png differ diff --git a/packages/pinball_theme/assets/images/pinball_button.png b/packages/pinball_theme/assets/images/pinball_button.png index 62373b85..8d10ec7c 100644 Binary files a/packages/pinball_theme/assets/images/pinball_button.png and b/packages/pinball_theme/assets/images/pinball_button.png differ diff --git a/packages/pinball_theme/assets/images/select_character_background.png b/packages/pinball_theme/assets/images/select_character_background.png index 69120148..7ba7f17f 100644 Binary files a/packages/pinball_theme/assets/images/select_character_background.png and b/packages/pinball_theme/assets/images/select_character_background.png differ diff --git a/packages/pinball_theme/assets/images/sparky/animation.png b/packages/pinball_theme/assets/images/sparky/animation.png index 1aff4772..ce51eb0d 100644 Binary files a/packages/pinball_theme/assets/images/sparky/animation.png and b/packages/pinball_theme/assets/images/sparky/animation.png differ diff --git a/packages/pinball_theme/assets/images/sparky/background.jpg b/packages/pinball_theme/assets/images/sparky/background.jpg index ad19a47a..08ad2dba 100644 Binary files a/packages/pinball_theme/assets/images/sparky/background.jpg and b/packages/pinball_theme/assets/images/sparky/background.jpg differ diff --git a/packages/pinball_theme/assets/images/sparky/ball.png b/packages/pinball_theme/assets/images/sparky/ball.png index fe3f4e82..7fd20368 100644 Binary files a/packages/pinball_theme/assets/images/sparky/ball.png and b/packages/pinball_theme/assets/images/sparky/ball.png differ diff --git a/packages/pinball_theme/assets/images/sparky/icon.png b/packages/pinball_theme/assets/images/sparky/icon.png index 4e484438..46b8dfa3 100644 Binary files a/packages/pinball_theme/assets/images/sparky/icon.png and b/packages/pinball_theme/assets/images/sparky/icon.png differ diff --git a/packages/pinball_theme/assets/images/sparky/leaderboard_icon.png b/packages/pinball_theme/assets/images/sparky/leaderboard_icon.png index 76001516..bd5e56e1 100644 Binary files a/packages/pinball_theme/assets/images/sparky/leaderboard_icon.png and b/packages/pinball_theme/assets/images/sparky/leaderboard_icon.png differ diff --git a/test/app/view/app_test.dart b/test/app/view/app_test.dart index 54814f36..d247a562 100644 --- a/test/app/view/app_test.dart +++ b/test/app/view/app_test.dart @@ -41,7 +41,7 @@ void main() { pinballAudioPlayer: pinballAudioPlayer, ), ); - await tester.pump(const Duration(milliseconds: 400)); + await tester.pump(const Duration(milliseconds: 1100)); expect(find.byType(PinballGamePage), findsOneWidget); }); }); diff --git a/test/game/behaviors/kicker_noise_behavior_test.dart b/test/game/behaviors/kicker_noise_behavior_test.dart new file mode 100644 index 00000000..4db18ab4 --- /dev/null +++ b/test/game/behaviors/kicker_noise_behavior_test.dart @@ -0,0 +1,59 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class _TestGame extends Forge2DGame { + Future pump( + _TestBodyComponent child, { + required PinballAudioPlayer audioPlayer, + }) { + return ensureAdd( + FlameProvider.value( + audioPlayer, + children: [child], + ), + ); + } +} + +class _TestBodyComponent extends BodyComponent { + @override + Body createBody() => world.createBody(BodyDef()); +} + +class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {} + +class _MockContact extends Mock implements Contact {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('KickerNoiseBehavior', () { + late PinballAudioPlayer audioPlayer; + final flameTester = FlameTester(_TestGame.new); + + setUp(() { + audioPlayer = _MockPinballAudioPlayer(); + }); + + flameTester.testGameWidget( + 'plays kicker sound', + setUp: (game, _) async { + final behavior = KickerNoiseBehavior(); + final parent = _TestBodyComponent(); + await game.pump(parent, audioPlayer: audioPlayer); + await parent.ensureAdd(behavior); + behavior.beginContact(Object(), _MockContact()); + }, + verify: (_, __) async { + verify(() => audioPlayer.play(PinballAudio.kicker)).called(1); + }, + ); + }); +}