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..0e0ef85b 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, @@ -149,6 +152,40 @@ class _BumperAudio extends _Audio { } } +class _KickerAudio extends _Audio { + _KickerAudio({ + required this.createAudioPool, + required this.seed, + }); + + final CreateAudioPool createAudioPool; + final Random seed; + + late AudioPool kickerA; + late AudioPool kickerB; + + @override + Future load() async { + await Future.wait( + [ + createAudioPool( + prefixFile(Assets.sfx.kickerA), + prefix: '', + ).then((pool) => kickerA = pool), + createAudioPool( + prefixFile(Assets.sfx.kickerB), + prefix: '', + ).then((pool) => kickerB = pool), + ], + ); + } + + @override + void play() { + (seed.nextBool() ? kickerA : kickerB).start(volume: 0.6); + } +} + class _ThrottledAudio extends _Audio { _ThrottledAudio({ required this.preCacheSingleAudio, @@ -245,6 +282,10 @@ class PinballAudioPlayer { createAudioPool: _createAudioPool, seed: _seed, ), + PinballAudio.kicker: _KickerAudio( + createAudioPool: _createAudioPool, + seed: _seed, + ), PinballAudio.cowMoo: _ThrottledAudio( 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 d1ff6f06..aaec1198 100644 --- a/packages/pinball_audio/test/src/pinball_audio_test.dart +++ b/packages/pinball_audio/test/src/pinball_audio_test.dart @@ -119,6 +119,24 @@ void main() { ).called(1); }); + test('creates the kicker pools', () async { + await Future.wait(audioPlayer.load()); + + verify( + () => createAudioPool.onCall( + 'packages/pinball_audio/${Assets.sfx.kickerA}', + prefix: '', + ), + ).called(1); + + verify( + () => createAudioPool.onCall( + 'packages/pinball_audio/${Assets.sfx.kickerB}', + prefix: '', + ), + ).called(1); + }); + test('configures the audio cache instance', () async { await Future.wait(audioPlayer.load()); @@ -234,6 +252,53 @@ 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}', + 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}', + 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/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); + }, + ); + }); +}