diff --git a/lib/game/components/game_flow_controller.dart b/lib/game/components/game_flow_controller.dart index 48dd5518..edc65329 100644 --- a/lib/game/components/game_flow_controller.dart +++ b/lib/game/components/game_flow_controller.dart @@ -39,6 +39,7 @@ class GameFlowController extends ComponentController /// Puts the game on a playing state void start() { + component.audio.backgroundMusic(); component.firstChild()?.waitingMode(); component.firstChild()?.focusOnGame(); component.overlays.remove(PinballGame.playButtonOverlay); diff --git a/lib/game/view/widgets/play_button_overlay.dart b/lib/game/view/widgets/play_button_overlay.dart index c855f776..1d4a10fb 100644 --- a/lib/game/view/widgets/play_button_overlay.dart +++ b/lib/game/view/widgets/play_button_overlay.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:pinball/game/pinball_game.dart'; import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/select_character/select_character.dart'; +import 'package:pinball_ui/pinball_ui.dart'; /// {@template play_button_overlay} /// [Widget] that renders the button responsible to starting the game @@ -20,14 +21,12 @@ class PlayButtonOverlay extends StatelessWidget { Widget build(BuildContext context) { final l10n = context.l10n; - return Center( - child: ElevatedButton( - onPressed: () async { - _game.gameFlowController.start(); - await showCharacterSelectionDialog(context); - }, - child: Text(l10n.play), - ), + return PinballButton( + text: l10n.play, + onTap: () async { + _game.gameFlowController.start(); + await showCharacterSelectionDialog(context); + }, ); } } diff --git a/packages/pinball_audio/assets/music/background.ogg b/packages/pinball_audio/assets/music/background.ogg new file mode 100644 index 00000000..72482f6d Binary files /dev/null and b/packages/pinball_audio/assets/music/background.ogg differ diff --git a/packages/pinball_audio/lib/gen/assets.gen.dart b/packages/pinball_audio/lib/gen/assets.gen.dart index 3609b939..0e6e120e 100644 --- a/packages/pinball_audio/lib/gen/assets.gen.dart +++ b/packages/pinball_audio/lib/gen/assets.gen.dart @@ -5,6 +5,12 @@ import 'package:flutter/widgets.dart'; +class $AssetsMusicGen { + const $AssetsMusicGen(); + + String get background => 'assets/music/background.ogg'; +} + class $AssetsSfxGen { const $AssetsSfxGen(); @@ -15,6 +21,7 @@ class $AssetsSfxGen { class Assets { Assets._(); + static const $AssetsMusicGen music = $AssetsMusicGen(); static const $AssetsSfxGen sfx = $AssetsSfxGen(); } diff --git a/packages/pinball_audio/lib/src/pinball_audio.dart b/packages/pinball_audio/lib/src/pinball_audio.dart index b2875084..8bda14e5 100644 --- a/packages/pinball_audio/lib/src/pinball_audio.dart +++ b/packages/pinball_audio/lib/src/pinball_audio.dart @@ -17,6 +17,14 @@ typedef CreateAudioPool = Future Function( /// audio typedef PlaySingleAudio = Future Function(String); +/// Function that defines the contract for looping a single +/// audio +typedef LoopSingleAudio = Future Function(String); + +/// Function that defines the contract for pre fetching an +/// audio +typedef PreCacheSingleAudio = Future Function(String); + /// Function that defines the contract for configuring /// an [AudioCache] instance typedef ConfigureAudioCache = void Function(AudioCache); @@ -29,9 +37,14 @@ class PinballAudio { PinballAudio({ CreateAudioPool? createAudioPool, PlaySingleAudio? playSingleAudio, + LoopSingleAudio? loopSingleAudio, + PreCacheSingleAudio? preCacheSingleAudio, ConfigureAudioCache? configureAudioCache, }) : _createAudioPool = createAudioPool ?? AudioPool.create, _playSingleAudio = playSingleAudio ?? FlameAudio.audioCache.play, + _loopSingleAudio = loopSingleAudio ?? FlameAudio.audioCache.loop, + _preCacheSingleAudio = + preCacheSingleAudio ?? FlameAudio.audioCache.load, _configureAudioCache = configureAudioCache ?? ((AudioCache a) { a.prefix = ''; @@ -41,6 +54,10 @@ class PinballAudio { final PlaySingleAudio _playSingleAudio; + final LoopSingleAudio _loopSingleAudio; + + final PreCacheSingleAudio _preCacheSingleAudio; + final ConfigureAudioCache _configureAudioCache; late AudioPool _scorePool; @@ -48,11 +65,17 @@ class PinballAudio { /// Loads the sounds effects into the memory Future load() async { _configureAudioCache(FlameAudio.audioCache); + _scorePool = await _createAudioPool( _prefixFile(Assets.sfx.plim), maxPlayers: 4, prefix: '', ); + + await Future.wait([ + _preCacheSingleAudio(_prefixFile(Assets.sfx.google)), + _preCacheSingleAudio(_prefixFile(Assets.music.background)), + ]); } /// Plays the basic score sound @@ -65,6 +88,11 @@ class PinballAudio { _playSingleAudio(_prefixFile(Assets.sfx.google)); } + /// Plays the background music + void backgroundMusic() { + _loopSingleAudio(_prefixFile(Assets.music.background)); + } + String _prefixFile(String file) { return 'packages/pinball_audio/$file'; } diff --git a/packages/pinball_audio/pubspec.yaml b/packages/pinball_audio/pubspec.yaml index a34ba5b5..74713dfa 100644 --- a/packages/pinball_audio/pubspec.yaml +++ b/packages/pinball_audio/pubspec.yaml @@ -26,3 +26,4 @@ flutter_gen: flutter: assets: - assets/sfx/ + - assets/music/ diff --git a/packages/pinball_audio/test/helpers/mocks.dart b/packages/pinball_audio/test/helpers/mocks.dart index c80fe65b..62ea985b 100644 --- a/packages/pinball_audio/test/helpers/mocks.dart +++ b/packages/pinball_audio/test/helpers/mocks.dart @@ -29,6 +29,19 @@ abstract class _PlaySingleAudioStub { class PlaySingleAudioStub extends Mock implements _PlaySingleAudioStub {} +abstract class _LoopSingleAudioStub { + Future onCall(String url); +} + +class LoopSingleAudioStub extends Mock implements _LoopSingleAudioStub {} + +abstract class _PreCacheSingleAudioStub { + Future onCall(String url); +} + +class PreCacheSingleAudioStub extends Mock implements _PreCacheSingleAudioStub { +} + class MockAudioPool extends Mock implements AudioPool {} class MockAudioCache extends Mock implements AudioCache {} diff --git a/packages/pinball_audio/test/src/pinball_audio_test.dart b/packages/pinball_audio/test/src/pinball_audio_test.dart index 2efe9553..393934f0 100644 --- a/packages/pinball_audio/test/src/pinball_audio_test.dart +++ b/packages/pinball_audio/test/src/pinball_audio_test.dart @@ -16,6 +16,8 @@ void main() { late CreateAudioPoolStub createAudioPool; late ConfigureAudioCacheStub configureAudioCache; late PlaySingleAudioStub playSingleAudio; + late LoopSingleAudioStub loopSingleAudio; + late PreCacheSingleAudioStub preCacheSingleAudio; late PinballAudio audio; setUpAll(() { @@ -38,10 +40,18 @@ void main() { playSingleAudio = PlaySingleAudioStub(); when(() => playSingleAudio.onCall(any())).thenAnswer((_) async {}); + loopSingleAudio = LoopSingleAudioStub(); + when(() => loopSingleAudio.onCall(any())).thenAnswer((_) async {}); + + preCacheSingleAudio = PreCacheSingleAudioStub(); + when(() => preCacheSingleAudio.onCall(any())).thenAnswer((_) async {}); + audio = PinballAudio( configureAudioCache: configureAudioCache.onCall, createAudioPool: createAudioPool.onCall, playSingleAudio: playSingleAudio.onCall, + loopSingleAudio: loopSingleAudio.onCall, + preCacheSingleAudio: preCacheSingleAudio.onCall, ); }); @@ -69,11 +79,25 @@ void main() { audio = PinballAudio( createAudioPool: createAudioPool.onCall, playSingleAudio: playSingleAudio.onCall, + preCacheSingleAudio: preCacheSingleAudio.onCall, ); await audio.load(); expect(FlameAudio.audioCache.prefix, equals('')); }); + + test('pre cache the assets', () async { + await audio.load(); + + verify( + () => preCacheSingleAudio + .onCall('packages/pinball_audio/assets/sfx/google.ogg'), + ).called(1); + verify( + () => preCacheSingleAudio + .onCall('packages/pinball_audio/assets/music/background.ogg'), + ).called(1); + }); }); group('score', () { @@ -106,5 +130,17 @@ void main() { ).called(1); }); }); + + group('backgroundMusic', () { + test('plays the correct file', () async { + await audio.load(); + audio.backgroundMusic(); + + verify( + () => loopSingleAudio + .onCall('packages/pinball_audio/${Assets.music.background}'), + ).called(1); + }); + }); }); } diff --git a/packages/pinball_components/lib/src/components/kicker/kicker.dart b/packages/pinball_components/lib/src/components/kicker/kicker.dart index 570f2990..1a45ad60 100644 --- a/packages/pinball_components/lib/src/components/kicker/kicker.dart +++ b/packages/pinball_components/lib/src/components/kicker/kicker.dart @@ -36,7 +36,7 @@ class Kicker extends BodyComponent with InitialPosition { }) : _side = side, super( children: [ - BumpingBehavior(strength: 20)..applyTo(['bouncy_edge']), + BumpingBehavior(strength: 25)..applyTo(['bouncy_edge']), KickerBallContactBehavior()..applyTo(['bouncy_edge']), KickerBlinkingBehavior(), _KickerSpriteGroupComponent( diff --git a/packages/pinball_ui/lib/src/widgets/pinball_button.dart b/packages/pinball_ui/lib/src/widgets/pinball_button.dart index 585a8d54..dd4685c1 100644 --- a/packages/pinball_ui/lib/src/widgets/pinball_button.dart +++ b/packages/pinball_ui/lib/src/widgets/pinball_button.dart @@ -21,26 +21,29 @@ class PinballButton extends StatelessWidget { @override Widget build(BuildContext context) { - return DecoratedBox( - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage(Assets.images.button.pinballButton.keyName), + return Material( + color: PinballColors.transparent, + child: DecoratedBox( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(Assets.images.button.pinballButton.keyName), + ), ), - ), - child: Center( - child: InkWell( - onTap: onTap, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 32, - vertical: 16, - ), - child: Text( - text, - style: Theme.of(context) - .textTheme - .headline3! - .copyWith(color: PinballColors.white), + child: Center( + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: Text( + text, + style: Theme.of(context) + .textTheme + .headline3! + .copyWith(color: PinballColors.white), + ), ), ), ), diff --git a/test/game/components/game_flow_controller_test.dart b/test/game/components/game_flow_controller_test.dart index ef93892c..c3674146 100644 --- a/test/game/components/game_flow_controller_test.dart +++ b/test/game/components/game_flow_controller_test.dart @@ -4,6 +4,7 @@ import 'package:flame/game.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_theme/pinball_theme.dart'; @@ -33,6 +34,7 @@ void main() { late Backboard backboard; late CameraController cameraController; late GameFlowController gameFlowController; + late PinballAudio pinballAudio; late ActiveOverlaysNotifier overlays; setUp(() { @@ -41,6 +43,7 @@ void main() { cameraController = MockCameraController(); gameFlowController = GameFlowController(game); overlays = MockActiveOverlaysNotifier(); + pinballAudio = MockPinballAudio(); when( () => backboard.gameOverMode( @@ -59,6 +62,7 @@ void main() { when(game.firstChild).thenReturn(cameraController); when(() => game.overlays).thenReturn(overlays); when(() => game.characterTheme).thenReturn(DashTheme()); + when(() => game.audio).thenReturn(pinballAudio); }); test( @@ -95,6 +99,15 @@ void main() { .called(1); }, ); + + test( + 'plays the background music on start', + () { + gameFlowController.onNewState(GameState.initial()); + + verify(pinballAudio.backgroundMusic).called(1); + }, + ); }); }); } diff --git a/web/favicon.png b/web/favicon.png index 66a69cb1..8aaa46ac 100644 Binary files a/web/favicon.png and b/web/favicon.png differ