diff --git a/lib/game/behaviors/behaviors.dart b/lib/game/behaviors/behaviors.dart index bb196cec..bca8be14 100644 --- a/lib/game/behaviors/behaviors.dart +++ b/lib/game/behaviors/behaviors.dart @@ -6,4 +6,5 @@ export 'camera_focusing_behavior.dart'; export 'character_selection_behavior.dart'; export 'cow_bumper_noise_behavior.dart'; export 'kicker_noise_behavior.dart'; +export 'rollover_noise_behavior.dart'; export 'scoring_behavior.dart'; diff --git a/lib/game/behaviors/rollover_noise_behavior.dart b/lib/game/behaviors/rollover_noise_behavior.dart new file mode 100644 index 00000000..06b2f77a --- /dev/null +++ b/lib/game/behaviors/rollover_noise_behavior.dart @@ -0,0 +1,13 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class RolloverNoiseBehavior extends ContactBehavior { + @override + void beginContact(Object other, Contact contact) { + super.beginContact(other, contact); + readProvider().play(PinballAudio.rollover); + } +} diff --git a/lib/game/components/google_gallery/google_gallery.dart b/lib/game/components/google_gallery/google_gallery.dart index 0b3d4b10..ec3f9e36 100644 --- a/lib/game/components/google_gallery/google_gallery.dart +++ b/lib/game/components/google_gallery/google_gallery.dart @@ -22,12 +22,14 @@ class GoogleGallery extends Component with ZIndex { side: BoardSide.right, children: [ ScoringContactBehavior(points: Points.fiveThousand), + RolloverNoiseBehavior(), ], ), GoogleRollover( side: BoardSide.left, children: [ ScoringContactBehavior(points: Points.fiveThousand), + RolloverNoiseBehavior(), ], ), GoogleWord(position: Vector2(-4.45, 1.8)), diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index dca26b84..b1f3c98a 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -125,6 +125,7 @@ class PinballGame extends PinballForge2DGame SkillShot( children: [ ScoringContactBehavior(points: Points.oneMillion), + RolloverNoiseBehavior(), ], ), AndroidAcres(), diff --git a/packages/pinball_audio/assets/sfx/rollover.mp3 b/packages/pinball_audio/assets/sfx/rollover.mp3 new file mode 100644 index 00000000..543a3560 Binary files /dev/null and b/packages/pinball_audio/assets/sfx/rollover.mp3 differ diff --git a/packages/pinball_audio/lib/gen/assets.gen.dart b/packages/pinball_audio/lib/gen/assets.gen.dart index c8b66234..0b8fb20b 100644 --- a/packages/pinball_audio/lib/gen/assets.gen.dart +++ b/packages/pinball_audio/lib/gen/assets.gen.dart @@ -26,6 +26,7 @@ class $AssetsSfxGen { 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 rollover => 'assets/sfx/rollover.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 9682b520..e0e69988 100644 --- a/packages/pinball_audio/lib/src/pinball_audio.dart +++ b/packages/pinball_audio/lib/src/pinball_audio.dart @@ -33,6 +33,9 @@ enum PinballAudio { /// Kicker. kicker, + /// Rollover. + rollover, + /// Sparky. sparky, @@ -56,7 +59,7 @@ typedef CreateAudioPool = Future Function( }); /// Defines the contract for playing a single audio. -typedef PlaySingleAudio = Future Function(String); +typedef PlaySingleAudio = Future Function(String, {double volume}); /// Defines the contract for looping a single audio. typedef LoopSingleAudio = Future Function(String, {double volume}); @@ -81,18 +84,20 @@ class _SimplePlayAudio extends _Audio { required this.preCacheSingleAudio, required this.playSingleAudio, required this.path, + this.volume, }); final PreCacheSingleAudio preCacheSingleAudio; final PlaySingleAudio playSingleAudio; final String path; + final double? volume; @override Future load() => preCacheSingleAudio(prefixFile(path)); @override void play() { - playSingleAudio(prefixFile(path)); + playSingleAudio(prefixFile(path), volume: volume ?? 1); } } @@ -266,6 +271,12 @@ class PinballAudioPlayer { playSingleAudio: _playSingleAudio, path: Assets.sfx.launcher, ), + PinballAudio.rollover: _SimplePlayAudio( + preCacheSingleAudio: _preCacheSingleAudio, + playSingleAudio: _playSingleAudio, + path: Assets.sfx.rollover, + volume: 0.3, + ), PinballAudio.ioPinballVoiceOver: _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 3e147329..769a880d 100644 --- a/packages/pinball_audio/test/src/pinball_audio_test.dart +++ b/packages/pinball_audio/test/src/pinball_audio_test.dart @@ -29,15 +29,15 @@ class _MockConfigureAudioCache extends Mock { } class _MockPlaySingleAudio extends Mock { - Future onCall(String url); + Future onCall(String path, {double volume}); } class _MockLoopSingleAudio extends Mock { - Future onCall(String url, {double volume}); + Future onCall(String path, {double volume}); } abstract class _PreCacheSingleAudio { - Future onCall(String url); + Future onCall(String path); } class _MockPreCacheSingleAudio extends Mock implements _PreCacheSingleAudio {} @@ -74,7 +74,8 @@ void main() { when(() => configureAudioCache.onCall(any())).thenAnswer((_) {}); playSingleAudio = _MockPlaySingleAudio(); - when(() => playSingleAudio.onCall(any())).thenAnswer((_) async {}); + when(() => playSingleAudio.onCall(any(), volume: any(named: 'volume'))) + .thenAnswer((_) async {}); loopSingleAudio = _MockLoopSingleAudio(); when(() => loopSingleAudio.onCall(any(), volume: any(named: 'volume'))) @@ -195,6 +196,10 @@ void main() { () => preCacheSingleAudio .onCall('packages/pinball_audio/assets/sfx/launcher.mp3'), ).called(1); + verify( + () => preCacheSingleAudio + .onCall('packages/pinball_audio/assets/sfx/rollover.mp3'), + ).called(1); verify( () => preCacheSingleAudio .onCall('packages/pinball_audio/assets/sfx/cow_moo.mp3'), @@ -346,8 +351,10 @@ void main() { audioPlayer.play(PinballAudio.google); verify( - () => playSingleAudio - .onCall('packages/pinball_audio/${Assets.sfx.google}'), + () => playSingleAudio.onCall( + 'packages/pinball_audio/${Assets.sfx.google}', + volume: any(named: 'volume'), + ), ).called(1); }); }); @@ -358,8 +365,10 @@ void main() { audioPlayer.play(PinballAudio.sparky); verify( - () => playSingleAudio - .onCall('packages/pinball_audio/${Assets.sfx.sparky}'), + () => playSingleAudio.onCall( + 'packages/pinball_audio/${Assets.sfx.sparky}', + volume: any(named: 'volume'), + ), ).called(1); }); }); @@ -370,8 +379,10 @@ void main() { audioPlayer.play(PinballAudio.dino); verify( - () => playSingleAudio - .onCall('packages/pinball_audio/${Assets.sfx.dino}'), + () => playSingleAudio.onCall( + 'packages/pinball_audio/${Assets.sfx.dino}', + volume: any(named: 'volume'), + ), ).called(1); }); }); @@ -382,8 +393,10 @@ void main() { audioPlayer.play(PinballAudio.android); verify( - () => playSingleAudio - .onCall('packages/pinball_audio/${Assets.sfx.android}'), + () => playSingleAudio.onCall( + 'packages/pinball_audio/${Assets.sfx.android}', + volume: any(named: 'volume'), + ), ).called(1); }); }); @@ -394,8 +407,10 @@ void main() { audioPlayer.play(PinballAudio.dash); verify( - () => playSingleAudio - .onCall('packages/pinball_audio/${Assets.sfx.dash}'), + () => playSingleAudio.onCall( + 'packages/pinball_audio/${Assets.sfx.dash}', + volume: any(named: 'volume'), + ), ).called(1); }); }); @@ -406,8 +421,24 @@ void main() { audioPlayer.play(PinballAudio.launcher); verify( - () => playSingleAudio - .onCall('packages/pinball_audio/${Assets.sfx.launcher}'), + () => playSingleAudio.onCall( + 'packages/pinball_audio/${Assets.sfx.launcher}', + volume: any(named: 'volume'), + ), + ).called(1); + }); + }); + + group('rollover', () { + test('plays the correct file', () async { + await Future.wait(audioPlayer.load()); + audioPlayer.play(PinballAudio.rollover); + + verify( + () => playSingleAudio.onCall( + 'packages/pinball_audio/${Assets.sfx.rollover}', + volume: .3, + ), ).called(1); }); }); @@ -420,6 +451,7 @@ void main() { verify( () => playSingleAudio.onCall( 'packages/pinball_audio/${Assets.sfx.ioPinballVoiceOver}', + volume: any(named: 'volume'), ), ).called(1); }); @@ -433,6 +465,7 @@ void main() { verify( () => playSingleAudio.onCall( 'packages/pinball_audio/${Assets.sfx.gameOverVoiceOver}', + volume: any(named: 'volume'), ), ).called(1); }); diff --git a/test/game/behaviors/rollover_noise_behavior_test.dart b/test/game/behaviors/rollover_noise_behavior_test.dart new file mode 100644 index 00000000..a196c8b6 --- /dev/null +++ b/test/game/behaviors/rollover_noise_behavior_test.dart @@ -0,0 +1,58 @@ +// 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('RolloverNoiseBehavior', () { + late PinballAudioPlayer audioPlayer; + final flameTester = FlameTester(_TestGame.new); + + setUp(() { + audioPlayer = _MockPinballAudioPlayer(); + }); + flameTester.testGameWidget( + 'plays rollover sound on contact', + setUp: (game, _) async { + final behavior = RolloverNoiseBehavior(); + final parent = _TestBodyComponent(); + await game.pump(parent, audioPlayer: audioPlayer); + await parent.ensureAdd(behavior); + behavior.beginContact(Object(), _MockContact()); + }, + verify: (_, __) async { + verify(() => audioPlayer.play(PinballAudio.rollover)).called(1); + }, + ); + }); +} diff --git a/test/game/components/google_gallery/google_gallery_test.dart b/test/game/components/google_gallery/google_gallery_test.dart index 9551285f..719be2dc 100644 --- a/test/game/components/google_gallery/google_gallery_test.dart +++ b/test/game/components/google_gallery/google_gallery_test.dart @@ -97,6 +97,20 @@ void main() { }, ); + flameTester.test( + 'RolloverNoiseBehavior to GoogleRollovers', + (game) async { + await game.pump(GoogleGallery()); + + game.descendants().whereType().forEach( + (rollover) => expect( + rollover.firstChild(), + isNotNull, + ), + ); + }, + ); + flameTester.test('a GoogleWordBonusBehavior', (game) async { final component = GoogleGallery(); await game.pump(component);