feat: add cow moo sound (#412)

pull/414/head
Allison Ryan 3 years ago committed by GitHub
parent 80da24b43c
commit 8b40a642ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,4 +4,5 @@ export 'bonus_noise_behavior.dart';
export 'bumper_noise_behavior.dart'; export 'bumper_noise_behavior.dart';
export 'camera_focusing_behavior.dart'; export 'camera_focusing_behavior.dart';
export 'character_selection_behavior.dart'; export 'character_selection_behavior.dart';
export 'cow_bumper_noise_behavior.dart';
export 'scoring_behavior.dart'; export 'scoring_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 CowBumperNoiseBehavior extends ContactBehavior {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
readProvider<PinballAudioPlayer>().play(PinballAudio.cowMoo);
}
}

@ -47,7 +47,7 @@ class AndroidAcres extends Component {
AndroidBumper.cow( AndroidBumper.cow(
children: [ children: [
ScoringContactBehavior(points: Points.twentyThousand), ScoringContactBehavior(points: Points.twentyThousand),
BumperNoiseBehavior(), CowBumperNoiseBehavior(),
], ],
)..initialPosition = Vector2(-20.7, -13), )..initialPosition = Vector2(-20.7, -13),
AndroidSpaceshipBonusBehavior(), AndroidSpaceshipBonusBehavior(),

@ -17,6 +17,7 @@ class $AssetsSfxGen {
String get android => 'assets/sfx/android.mp3'; String get android => 'assets/sfx/android.mp3';
String get bumperA => 'assets/sfx/bumper_a.mp3'; String get bumperA => 'assets/sfx/bumper_a.mp3';
String get bumperB => 'assets/sfx/bumper_b.mp3'; String get bumperB => 'assets/sfx/bumper_b.mp3';
String get cowMoo => 'assets/sfx/cow_moo.mp3';
String get dash => 'assets/sfx/dash.mp3'; String get dash => 'assets/sfx/dash.mp3';
String get dino => 'assets/sfx/dino.mp3'; String get dino => 'assets/sfx/dino.mp3';
String get gameOverVoiceOver => 'assets/sfx/game_over_voice_over.mp3'; String get gameOverVoiceOver => 'assets/sfx/game_over_voice_over.mp3';

@ -1,32 +1,36 @@
import 'dart:math'; import 'dart:math';
import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers/audioplayers.dart';
import 'package:clock/clock.dart';
import 'package:flame_audio/audio_pool.dart'; import 'package:flame_audio/audio_pool.dart';
import 'package:flame_audio/flame_audio.dart'; import 'package:flame_audio/flame_audio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball_audio/gen/assets.gen.dart'; import 'package:pinball_audio/gen/assets.gen.dart';
/// Sounds available for play /// Sounds available to play.
enum PinballAudio { enum PinballAudio {
/// Google /// Google.
google, google,
/// Bumper /// Bumper.
bumper, bumper,
/// Background music /// Cow moo.
cowMoo,
/// Background music.
backgroundMusic, backgroundMusic,
/// IO Pinball voice over /// IO Pinball voice over.
ioPinballVoiceOver, ioPinballVoiceOver,
/// Game over /// Game over.
gameOverVoiceOver, gameOverVoiceOver,
/// Launcher /// Launcher.
launcher, launcher,
/// Sparky /// Sparky.
sparky, sparky,
/// Android /// Android
@ -145,8 +149,37 @@ class _BumperAudio extends _Audio {
} }
} }
class _ThrottledAudio extends _Audio {
_ThrottledAudio({
required this.preCacheSingleAudio,
required this.playSingleAudio,
required this.path,
required this.duration,
});
final PreCacheSingleAudio preCacheSingleAudio;
final PlaySingleAudio playSingleAudio;
final String path;
final Duration duration;
DateTime? _lastPlayed;
@override
Future<void> load() => preCacheSingleAudio(prefixFile(path));
@override
void play() {
final now = clock.now();
if (_lastPlayed == null ||
(_lastPlayed != null && now.difference(_lastPlayed!) > duration)) {
_lastPlayed = now;
playSingleAudio(prefixFile(path));
}
}
}
/// {@template pinball_audio_player} /// {@template pinball_audio_player}
/// Sound manager for the pinball game /// Sound manager for the pinball game.
/// {@endtemplate} /// {@endtemplate}
class PinballAudioPlayer { class PinballAudioPlayer {
/// {@macro pinball_audio_player} /// {@macro pinball_audio_player}
@ -212,6 +245,12 @@ class PinballAudioPlayer {
createAudioPool: _createAudioPool, createAudioPool: _createAudioPool,
seed: _seed, seed: _seed,
), ),
PinballAudio.cowMoo: _ThrottledAudio(
preCacheSingleAudio: _preCacheSingleAudio,
playSingleAudio: _playSingleAudio,
path: Assets.sfx.cowMoo,
duration: const Duration(seconds: 2),
),
PinballAudio.backgroundMusic: _LoopAudio( PinballAudio.backgroundMusic: _LoopAudio(
preCacheSingleAudio: _preCacheSingleAudio, preCacheSingleAudio: _preCacheSingleAudio,
loopSingleAudio: _loopSingleAudio, loopSingleAudio: _loopSingleAudio,
@ -232,19 +271,19 @@ class PinballAudioPlayer {
final Random _seed; final Random _seed;
/// Registered audios on the Player /// Registered audios on the Player.
@visibleForTesting @visibleForTesting
// ignore: library_private_types_in_public_api // ignore: library_private_types_in_public_api
late final Map<PinballAudio, _Audio> audios; late final Map<PinballAudio, _Audio> audios;
/// Loads the sounds effects into the memory /// Loads the sounds effects into the memory.
List<Future<void>> load() { List<Future<void>> load() {
_configureAudioCache(FlameAudio.audioCache); _configureAudioCache(FlameAudio.audioCache);
return audios.values.map((a) => a.load()).toList(); return audios.values.map((a) => a.load()).toList();
} }
/// Plays the received audio /// Plays the received audio.
void play(PinballAudio audio) { void play(PinballAudio audio) {
assert( assert(
audios.containsKey(audio), audios.containsKey(audio),

@ -8,6 +8,7 @@ environment:
dependencies: dependencies:
audioplayers: ^0.20.1 audioplayers: ^0.20.1
clock: ^1.1.0
flame_audio: ^1.0.1 flame_audio: ^1.0.1
flutter: flutter:
sdk: flutter sdk: flutter

@ -2,6 +2,7 @@
import 'dart:math'; import 'dart:math';
import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers/audioplayers.dart';
import 'package:clock/clock.dart';
import 'package:flame_audio/audio_pool.dart'; import 'package:flame_audio/audio_pool.dart';
import 'package:flame_audio/flame_audio.dart'; import 'package:flame_audio/flame_audio.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -43,6 +44,8 @@ class _MockPreCacheSingleAudio extends Mock implements _PreCacheSingleAudio {}
class _MockRandom extends Mock implements Random {} class _MockRandom extends Mock implements Random {}
class _MockClock extends Mock implements Clock {}
void main() { void main() {
group('PinballAudio', () { group('PinballAudio', () {
late _MockCreateAudioPool createAudioPool; late _MockCreateAudioPool createAudioPool;
@ -171,6 +174,10 @@ void main() {
() => preCacheSingleAudio () => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/launcher.mp3'), .onCall('packages/pinball_audio/assets/sfx/launcher.mp3'),
).called(1); ).called(1);
verify(
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/cow_moo.mp3'),
).called(1);
verify( verify(
() => preCacheSingleAudio () => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/music/background.mp3'), .onCall('packages/pinball_audio/assets/music/background.mp3'),
@ -227,6 +234,42 @@ void main() {
}); });
}); });
group('cow moo', () {
test('plays the correct file', () async {
await Future.wait(audioPlayer.load());
audioPlayer.play(PinballAudio.cowMoo);
verify(
() => playSingleAudio
.onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'),
).called(1);
});
test('only plays the sound again after 2 seconds', () async {
final clock = _MockClock();
await withClock(clock, () async {
when(clock.now).thenReturn(DateTime(2022));
await Future.wait(audioPlayer.load());
audioPlayer
..play(PinballAudio.cowMoo)
..play(PinballAudio.cowMoo);
verify(
() => playSingleAudio
.onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'),
).called(1);
when(clock.now).thenReturn(DateTime(2022, 1, 1, 1, 2));
audioPlayer.play(PinballAudio.cowMoo);
verify(
() => playSingleAudio
.onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'),
).called(1);
});
});
});
group('google', () { group('google', () {
test('plays the correct file', () async { test('plays the correct file', () async {
await Future.wait(audioPlayer.load()); await Future.wait(audioPlayer.load());

@ -16,9 +16,7 @@ class _TestGame extends Forge2DGame {
return ensureAdd( return ensureAdd(
FlameProvider<PinballAudioPlayer>.value( FlameProvider<PinballAudioPlayer>.value(
audioPlayer, audioPlayer,
children: [ children: [child],
child,
],
), ),
); );
} }

@ -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<void> pump(
_TestBodyComponent child, {
required PinballAudioPlayer audioPlayer,
}) {
return ensureAdd(
FlameProvider<PinballAudioPlayer>.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('CowBumperNoiseBehavior', () {
late PinballAudioPlayer audioPlayer;
final flameTester = FlameTester(_TestGame.new);
setUp(() {
audioPlayer = _MockPinballAudioPlayer();
});
flameTester.testGameWidget(
'plays cow moo sound on contact',
setUp: (game, _) async {
final behavior = CowBumperNoiseBehavior();
final parent = _TestBodyComponent();
await game.pump(parent, audioPlayer: audioPlayer);
await parent.ensureAdd(behavior);
behavior.beginContact(Object(), _MockContact());
},
verify: (_, __) async {
verify(() => audioPlayer.play(PinballAudio.cowMoo)).called(1);
},
);
});
}

@ -4,7 +4,7 @@ import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/behaviors/bumper_noise_behavior.dart'; import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart'; import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
@ -117,16 +117,33 @@ void main() {
); );
flameTester.test( flameTester.test(
'three AndroidBumpers with BumperNoiseBehavior', 'two AndroidBumpers with BumperNoiseBehavior',
(game) async { (game) async {
await game.pump(AndroidAcres()); await game.pump(AndroidAcres());
final bumpers = game.descendants().whereType<AndroidBumper>(); final bumpers = game.descendants().whereType<AndroidBumper>();
var behaviorCount = 0;
for (final bumper in bumpers) { for (final bumper in bumpers) {
if (bumper.firstChild<BumperNoiseBehavior>() != null) {
behaviorCount++;
}
}
expect(behaviorCount, equals(2));
},
);
flameTester.test(
'one AndroidBumper with CowBumperNoiseBehavior',
(game) async {
await game.pump(AndroidAcres());
final bumpers = game.descendants().whereType<AndroidBumper>();
expect( expect(
bumper.firstChild<BumperNoiseBehavior>(), bumpers.singleWhere(
isNotNull, (bumper) => bumper.firstChild<CowBumperNoiseBehavior>() != null,
),
isA<AndroidBumper>(),
); );
}
}, },
); );
}); });

Loading…
Cancel
Save