feat: adding sound effects

pull/143/head
Erick Zanardo 4 years ago
parent b72ff3178f
commit 631518395b

@ -8,7 +8,6 @@ import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template bonus_word}
@ -31,7 +30,7 @@ class BonusWord extends Component
@override
void onNewState(GameState state) {
if (state.bonusHistory.last == GameBonus.word) {
gameRef.read<PinballAudio>().googleBonus();
gameRef.audio.googleBonus();
final letters = children.whereType<BonusLetter>().toList();

@ -39,6 +39,6 @@ class BallScorePointsCallback extends ContactCallback<Ball, ScorePoints> {
Scored(points: scorePoints.points),
);
_gameRef.read<PinballAudio>().score();
_gameRef.audio.score();
}
}

@ -8,17 +8,20 @@ import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/gen/assets.gen.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_theme/pinball_theme.dart' hide Assets;
class PinballGame extends Forge2DGame
with FlameBloc, HasKeyboardHandlerComponents {
PinballGame({required this.theme}) {
PinballGame({required this.theme, required this.audio}) {
images.prefix = '';
}
final PinballTheme theme;
final PinballAudio audio;
late final Plunger plunger;
@override
@ -110,7 +113,13 @@ class PinballGame extends Forge2DGame
}
class DebugPinballGame extends PinballGame with TapDetector {
DebugPinballGame({required PinballTheme theme}) : super(theme: theme);
DebugPinballGame({
required PinballTheme theme,
required PinballAudio audio,
}) : super(
theme: theme,
audio: audio,
);
@override
Future<void> onLoad() async {

@ -53,13 +53,15 @@ class _PinballGameViewState extends State<PinballGameView> {
void initState() {
super.initState();
final audio = context.read<PinballAudio>();
_game = widget._isDebugMode
? DebugPinballGame(theme: widget.theme, audio: audio)
: PinballGame(theme: widget.theme, audio: audio);
// TODO(erickzanardo): Revisit this when we start to have more assets
// this could expose a Stream (maybe even a cubit?) so we could show the
// the loading progress with some fancy widgets.
_game = widget._isDebugMode
? DebugPinballGame(theme: widget.theme)
: PinballGame(theme: widget.theme);
_fetchAssets();
}

@ -1,20 +1,54 @@
import 'package:audioplayers/audioplayers.dart';
import 'package:flame_audio/audio_pool.dart';
import 'package:flame_audio/flame_audio.dart';
import 'package:pinball_audio/gen/assets.gen.dart';
/// Function that defines the contract of the creation
/// of an [AudioPool]
typedef CreateAudioPool = Future<AudioPool> Function(
String sound, {
bool? repeating,
int? maxPlayers,
int? minPlayers,
String? prefix,
});
/// Function that defines the contract for playing a single
/// audio
typedef PlaySingleAudio = Future<void> Function(String);
/// Function that defines the contract for configuring
/// an [AudioCache] instance
typedef ConfigureAudioCache = void Function(AudioCache);
/// {@template pinball_audio}
/// Sound manager for the pinball game
/// {@endtemplate}
class PinballAudio {
/// {@macro pinball_audio}
PinballAudio();
PinballAudio({
CreateAudioPool? createAudioPool,
PlaySingleAudio? playSingleAudio,
ConfigureAudioCache? configureAudioCache,
}) : _createAudioPool = createAudioPool ?? AudioPool.create,
_playSingleAudio = playSingleAudio ?? FlameAudio.audioCache.play,
_configureAudioCache = configureAudioCache ??
((AudioCache a) {
a.prefix = '';
});
final CreateAudioPool _createAudioPool;
final PlaySingleAudio _playSingleAudio;
final ConfigureAudioCache _configureAudioCache;
late AudioPool _scorePool;
/// Loads the sounds effects into the memory
Future<void> load() async {
FlameAudio.audioCache.prefix = '';
_scorePool = await AudioPool.create(
_configureAudioCache(FlameAudio.audioCache);
_scorePool = await _createAudioPool(
_prefixFile(Assets.sfx.plim),
maxPlayers: 4,
prefix: '',
@ -28,7 +62,7 @@ class PinballAudio {
/// Plays the google word bonus
void googleBonus() {
FlameAudio.audioCache.play(_prefixFile(Assets.sfx.google));
_playSingleAudio(_prefixFile(Assets.sfx.google));
}
String _prefixFile(String file) {

@ -7,6 +7,7 @@ environment:
sdk: ">=2.16.0 <3.0.0"
dependencies:
audioplayers: ^0.20.1
flame_audio: ^1.0.1
flutter:
sdk: flutter
@ -14,6 +15,7 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
mocktail: ^0.3.0
very_good_analysis: ^2.4.0
flutter_gen:

@ -0,0 +1,34 @@
// ignore_for_file: one_member_abstracts
import 'package:audioplayers/audioplayers.dart';
import 'package:flame_audio/audio_pool.dart';
import 'package:mocktail/mocktail.dart';
abstract class _CreateAudioPoolStub {
Future<AudioPool> onCall(
String sound, {
bool? repeating,
int? maxPlayers,
int? minPlayers,
String? prefix,
});
}
class CreateAudioPoolStub extends Mock implements _CreateAudioPoolStub {}
abstract class _ConfigureAudioCacheStub {
void onCall(AudioCache cache);
}
class ConfigureAudioCacheStub extends Mock implements _ConfigureAudioCacheStub {
}
abstract class _PlaySingleAudioStub {
Future<void> onCall(String url);
}
class PlaySingleAudioStub extends Mock implements _PlaySingleAudioStub {}
class MockAudioPool extends Mock implements AudioPool {}
class MockAudioCache extends Mock implements AudioCache {}

@ -1,11 +1,110 @@
// ignore_for_file: prefer_const_constructors
import 'package:flame_audio/flame_audio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball_audio/gen/assets.gen.dart';
import 'package:pinball_audio/pinball_audio.dart';
import '../helpers/helpers.dart';
void main() {
group('PinballAudio', () {
test('can be instantiated', () {
expect(PinballAudio(), isNotNull);
});
late CreateAudioPoolStub createAudioPool;
late ConfigureAudioCacheStub configureAudioCache;
late PlaySingleAudioStub playSingleAudio;
late PinballAudio audio;
setUpAll(() {
registerFallbackValue(MockAudioCache());
});
setUp(() {
createAudioPool = CreateAudioPoolStub();
when(
() => createAudioPool.onCall(
any(),
maxPlayers: any(named: 'maxPlayers'),
prefix: any(named: 'prefix'),
),
).thenAnswer((_) async => MockAudioPool());
configureAudioCache = ConfigureAudioCacheStub();
when(() => configureAudioCache.onCall(any())).thenAnswer((_) {});
playSingleAudio = PlaySingleAudioStub();
when(() => playSingleAudio.onCall(any())).thenAnswer((_) async {});
audio = PinballAudio(
configureAudioCache: configureAudioCache.onCall,
createAudioPool: createAudioPool.onCall,
playSingleAudio: playSingleAudio.onCall,
);
});
group('load', () {
test('creates the score pool', () async {
await audio.load();
verify(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.plim}',
maxPlayers: 4,
prefix: '',
),
).called(1);
});
test('configures the audio cache instance', () async {
await audio.load();
verify(() => configureAudioCache.onCall(FlameAudio.audioCache))
.called(1);
});
test('sets the correct prefix', () async {
audio = PinballAudio(
createAudioPool: createAudioPool.onCall,
playSingleAudio: playSingleAudio.onCall,
);
await audio.load();
expect(FlameAudio.audioCache.prefix, equals(''));
});
});
group('score', () {
test('plays the score sound pool', () async {
final audioPool = MockAudioPool();
when(audioPool.start).thenAnswer((_) async => () {});
when(
() => createAudioPool.onCall(
any(),
maxPlayers: any(named: 'maxPlayers'),
prefix: any(named: 'prefix'),
),
).thenAnswer((_) async => audioPool);
await audio.load();
audio.score();
verify(audioPool.start).called(1);
});
});
group('googleBonus', () {
test('plays the correct file', () async {
await audio.load();
audio.googleBonus();
verify(
() => playSingleAudio
.onCall('packages/pinball_audio/${Assets.sfx.google}'),
).called(1);
});
});
});
}

@ -9,20 +9,26 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/app/app.dart';
import 'package:pinball/landing/landing.dart';
import 'package:pinball_audio/pinball_audio.dart';
import '../../helpers/mocks.dart';
void main() {
group('App', () {
late LeaderboardRepository leaderboardRepository;
late PinballAudio pinballAudio;
setUp(() {
leaderboardRepository = MockLeaderboardRepository();
pinballAudio = MockPinballAudio();
});
testWidgets('renders LandingPage', (tester) async {
await tester.pumpWidget(
App(leaderboardRepository: leaderboardRepository),
App(
leaderboardRepository: leaderboardRepository,
pinballAudio: pinballAudio,
),
);
expect(find.byType(LandingPage), findsOneWidget);
});

@ -4,9 +4,11 @@ import 'package:bloc_test/bloc_test.dart';
import 'package:flame/effects.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_bloc/flutter_bloc.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 '../../helpers/helpers.dart';
@ -89,6 +91,21 @@ void main() {
},
);
flameTester.test(
'plays the google bonus sound',
(game) async {
when(() => state.bonusHistory).thenReturn([GameBonus.word]);
final bonusWord = BonusWord(position: Vector2.zero());
await game.ensureAdd(bonusWord);
await game.ready();
bonusWord.onNewState(state);
verify(bonusWord.gameRef.audio.googleBonus).called(1);
},
);
flameTester.test(
'adds a color effect to reset the color when the sequence is finished',
(game) async {
@ -195,11 +212,15 @@ void main() {
group('bonus letter activation', () {
late GameBloc gameBloc;
late PinballAudio pinballAudio;
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
// TODO(alestiago): Use TestGame once BonusLetter has controller.
gameBuilder: PinballGameTest.create,
blocBuilder: () => gameBloc,
repositories: () => [
RepositoryProvider<PinballAudio>.value(value: pinballAudio),
],
);
setUp(() {
@ -209,6 +230,9 @@ void main() {
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
pinballAudio = MockPinballAudio();
when(pinballAudio.googleBonus).thenAnswer((_) {});
});
flameBlocTester.testGameWidget(

@ -2,6 +2,7 @@ import 'package:flame_forge2d/flame_forge2d.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 '../../helpers/helpers.dart';
@ -20,6 +21,7 @@ void main() {
group('BallScorePointsCallback', () {
late PinballGame game;
late GameBloc bloc;
late PinballAudio audio;
late Ball ball;
late FakeScorePoints fakeScorePoints;
@ -27,6 +29,7 @@ void main() {
game = MockPinballGame();
bloc = MockGameBloc();
ball = MockBall();
audio = MockPinballAudio();
fakeScorePoints = FakeScorePoints();
});
@ -38,7 +41,8 @@ void main() {
test(
'emits Scored event with points',
() {
when<GameBloc>(game.read).thenReturn(bloc);
when(game.read<GameBloc>).thenReturn(bloc);
when(() => game.audio).thenReturn(audio);
BallScorePointsCallback(game).begin(
ball,
@ -53,6 +57,22 @@ void main() {
).called(1);
},
);
test(
'plays a Score sound',
() {
when(game.read<GameBloc>).thenReturn(bloc);
when(() => game.audio).thenReturn(audio);
BallScorePointsCallback(game).begin(
ball,
fakeScorePoints,
FakeContact(),
);
verify(audio.score).called(1);
},
);
});
});
}

@ -7,13 +7,17 @@ class FlameBlocTester<T extends FlameGame, B extends Bloc<dynamic, dynamic>>
FlameBlocTester({
required GameCreateFunction<T> gameBuilder,
required B Function() blocBuilder,
List<RepositoryProvider> Function()? repositories,
}) : super(
gameBuilder,
pumpWidget: (gameWidget, tester) async {
await tester.pumpWidget(
BlocProvider.value(
value: blocBuilder(),
child: gameWidget,
child: MultiRepositoryProvider(
providers: repositories?.call() ?? [],
child: gameWidget,
),
),
);
},

@ -1,6 +1,8 @@
import 'package:pinball/game/game.dart';
import 'package:pinball_theme/pinball_theme.dart';
import 'helpers.dart';
/// [PinballGame] extension to reduce boilerplate in tests.
extension PinballGameTest on PinballGame {
/// Create [PinballGame] with default [PinballTheme].
@ -8,6 +10,7 @@ extension PinballGameTest on PinballGame {
theme: const PinballTheme(
characterTheme: DashTheme(),
),
audio: MockPinballAudio(),
)..images.prefix = '';
}
@ -18,5 +21,6 @@ extension DebugPinballGameTest on DebugPinballGame {
theme: const PinballTheme(
characterTheme: DashTheme(),
),
audio: MockPinballAudio(),
);
}

@ -8,6 +8,7 @@ import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/leaderboard/leaderboard.dart';
import 'package:pinball/theme/theme.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
class MockPinballGame extends Mock implements PinballGame {}
@ -71,3 +72,5 @@ class MockSpaceshipExitRailEnd extends Mock implements SpaceshipExitRailEnd {}
class MockComponentSet extends Mock implements ComponentSet {}
class MockDashNestBumper extends Mock implements DashNestBumper {}
class MockPinballAudio extends Mock implements PinballAudio {}

Loading…
Cancel
Save