mirror of https://github.com/flutter/pinball.git
commit
659a872147
@ -0,0 +1,19 @@
|
|||||||
|
name: pinball_audio
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- "packages/pinball_audio/**"
|
||||||
|
- ".github/workflows/pinball_audio.yaml"
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "packages/pinball_audio/**"
|
||||||
|
- ".github/workflows/pinball_audio.yaml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
|
||||||
|
with:
|
||||||
|
working_directory: packages/pinball_audio
|
||||||
|
coverage_excludes: "lib/gen/*.dart"
|
@ -0,0 +1,39 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# VSCode related
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Web related
|
||||||
|
lib/generated_plugin_registrant.dart
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
@ -0,0 +1,11 @@
|
|||||||
|
# pinball_audio
|
||||||
|
|
||||||
|
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
|
||||||
|
[![License: MIT][license_badge]][license_link]
|
||||||
|
|
||||||
|
Package with the sound manager for the pinball game
|
||||||
|
|
||||||
|
[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||||
|
[license_link]: https://opensource.org/licenses/MIT
|
||||||
|
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
|
||||||
|
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
|
@ -0,0 +1,4 @@
|
|||||||
|
include: package:very_good_analysis/analysis_options.2.4.0.yaml
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
- lib/**/*.gen.dart
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,69 @@
|
|||||||
|
/// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
/// *****************************************************
|
||||||
|
/// FlutterGen
|
||||||
|
/// *****************************************************
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class $AssetsSfxGen {
|
||||||
|
const $AssetsSfxGen();
|
||||||
|
|
||||||
|
String get google => 'assets/sfx/google.ogg';
|
||||||
|
String get plim => 'assets/sfx/plim.ogg';
|
||||||
|
}
|
||||||
|
|
||||||
|
class Assets {
|
||||||
|
Assets._();
|
||||||
|
|
||||||
|
static const $AssetsSfxGen sfx = $AssetsSfxGen();
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssetGenImage extends AssetImage {
|
||||||
|
const AssetGenImage(String assetName)
|
||||||
|
: super(assetName, package: 'pinball_audio');
|
||||||
|
|
||||||
|
Image image({
|
||||||
|
Key? key,
|
||||||
|
ImageFrameBuilder? frameBuilder,
|
||||||
|
ImageLoadingBuilder? loadingBuilder,
|
||||||
|
ImageErrorWidgetBuilder? errorBuilder,
|
||||||
|
String? semanticLabel,
|
||||||
|
bool excludeFromSemantics = false,
|
||||||
|
double? width,
|
||||||
|
double? height,
|
||||||
|
Color? color,
|
||||||
|
BlendMode? colorBlendMode,
|
||||||
|
BoxFit? fit,
|
||||||
|
AlignmentGeometry alignment = Alignment.center,
|
||||||
|
ImageRepeat repeat = ImageRepeat.noRepeat,
|
||||||
|
Rect? centerSlice,
|
||||||
|
bool matchTextDirection = false,
|
||||||
|
bool gaplessPlayback = false,
|
||||||
|
bool isAntiAlias = false,
|
||||||
|
FilterQuality filterQuality = FilterQuality.low,
|
||||||
|
}) {
|
||||||
|
return Image(
|
||||||
|
key: key,
|
||||||
|
image: this,
|
||||||
|
frameBuilder: frameBuilder,
|
||||||
|
loadingBuilder: loadingBuilder,
|
||||||
|
errorBuilder: errorBuilder,
|
||||||
|
semanticLabel: semanticLabel,
|
||||||
|
excludeFromSemantics: excludeFromSemantics,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
color: color,
|
||||||
|
colorBlendMode: colorBlendMode,
|
||||||
|
fit: fit,
|
||||||
|
alignment: alignment,
|
||||||
|
repeat: repeat,
|
||||||
|
centerSlice: centerSlice,
|
||||||
|
matchTextDirection: matchTextDirection,
|
||||||
|
gaplessPlayback: gaplessPlayback,
|
||||||
|
isAntiAlias: isAntiAlias,
|
||||||
|
filterQuality: filterQuality,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get path => assetName;
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
library pinball_audio;
|
||||||
|
|
||||||
|
export 'src/pinball_audio.dart';
|
@ -0,0 +1,71 @@
|
|||||||
|
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({
|
||||||
|
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 {
|
||||||
|
_configureAudioCache(FlameAudio.audioCache);
|
||||||
|
_scorePool = await _createAudioPool(
|
||||||
|
_prefixFile(Assets.sfx.plim),
|
||||||
|
maxPlayers: 4,
|
||||||
|
prefix: '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plays the basic score sound
|
||||||
|
void score() {
|
||||||
|
_scorePool.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plays the google word bonus
|
||||||
|
void googleBonus() {
|
||||||
|
_playSingleAudio(_prefixFile(Assets.sfx.google));
|
||||||
|
}
|
||||||
|
|
||||||
|
String _prefixFile(String file) {
|
||||||
|
return 'packages/pinball_audio/$file';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
name: pinball_audio
|
||||||
|
description: Package with the sound manager for the pinball game
|
||||||
|
version: 1.0.0+1
|
||||||
|
publish_to: none
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.16.0 <3.0.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
audioplayers: ^0.20.1
|
||||||
|
flame_audio: ^1.0.1
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
mocktail: ^0.3.0
|
||||||
|
very_good_analysis: ^2.4.0
|
||||||
|
|
||||||
|
flutter_gen:
|
||||||
|
line_length: 80
|
||||||
|
assets:
|
||||||
|
package_parameter_enabled: true
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
assets:
|
||||||
|
- assets/sfx/
|
@ -0,0 +1 @@
|
|||||||
|
export 'mocks.dart';
|
@ -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 {}
|
@ -0,0 +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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,39 @@
|
|||||||
|
import 'package:flame/extensions.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
import 'package:sandbox/stories/ball/basic_ball_game.dart';
|
||||||
|
|
||||||
|
class KickerGame extends BasicBallGame {
|
||||||
|
KickerGame({
|
||||||
|
required this.trace,
|
||||||
|
}) : super(color: const Color(0xFFFF0000));
|
||||||
|
|
||||||
|
static const info = '''
|
||||||
|
Shows how Kickers are rendered.
|
||||||
|
|
||||||
|
- Activate the "trace" parameter to overlay the body.
|
||||||
|
- Tap anywhere on the screen to spawn a ball into the game.
|
||||||
|
''';
|
||||||
|
|
||||||
|
final bool trace;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
final center = screenToWorld(camera.viewport.canvasSize! / 2);
|
||||||
|
|
||||||
|
final leftKicker = Kicker(side: BoardSide.left)
|
||||||
|
..initialPosition = Vector2(center.x - (Kicker.size.x * 2), center.y);
|
||||||
|
await add(leftKicker);
|
||||||
|
|
||||||
|
final rightKicker = Kicker(side: BoardSide.right)
|
||||||
|
..initialPosition = Vector2(center.x + (Kicker.size.x * 2), center.y);
|
||||||
|
await add(rightKicker);
|
||||||
|
|
||||||
|
if (trace) {
|
||||||
|
leftKicker.trace();
|
||||||
|
rightKicker.trace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:dashbook/dashbook.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
import 'package:sandbox/stories/kicker/kicker_game.dart';
|
||||||
|
|
||||||
|
void addKickerStories(Dashbook dashbook) {
|
||||||
|
dashbook.storiesOf('Kickers').add(
|
||||||
|
'Basic',
|
||||||
|
(context) => GameWidget(
|
||||||
|
game: KickerGame(
|
||||||
|
trace: context.boolProperty('Trace', true),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
codeLink: buildSourceLink('kicker_game/basic.dart'),
|
||||||
|
info: KickerGame.info,
|
||||||
|
);
|
||||||
|
}
|
After Width: | Height: | Size: 59 KiB |
@ -1,22 +1,29 @@
|
|||||||
import 'package:pinball/game/game.dart';
|
import 'package:pinball/game/game.dart';
|
||||||
import 'package:pinball_theme/pinball_theme.dart';
|
import 'package:pinball_theme/pinball_theme.dart';
|
||||||
|
|
||||||
/// [PinballGame] extension to reduce boilerplate in tests.
|
import 'helpers.dart';
|
||||||
extension PinballGameTest on PinballGame {
|
|
||||||
/// Create [PinballGame] with default [PinballTheme].
|
class PinballGameTest extends PinballGame {
|
||||||
static PinballGame create() => PinballGame(
|
PinballGameTest()
|
||||||
|
: super(
|
||||||
|
audio: MockPinballAudio(),
|
||||||
theme: const PinballTheme(
|
theme: const PinballTheme(
|
||||||
characterTheme: DashTheme(),
|
characterTheme: DashTheme(),
|
||||||
),
|
),
|
||||||
)..images.prefix = '';
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [DebugPinballGame] extension to reduce boilerplate in tests.
|
class DebugPinballGameTest extends DebugPinballGame {
|
||||||
extension DebugPinballGameTest on DebugPinballGame {
|
DebugPinballGameTest()
|
||||||
/// Create [PinballGame] with default [PinballTheme].
|
: super(
|
||||||
static DebugPinballGame create() => DebugPinballGame(
|
audio: MockPinballAudio(),
|
||||||
theme: const PinballTheme(
|
theme: const PinballTheme(
|
||||||
characterTheme: DashTheme(),
|
characterTheme: DashTheme(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EmptyPinballGameTest extends PinballGameTest {
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {}
|
||||||
|
}
|
||||||
|
Loading…
Reference in new issue