Merge branch 'main' into fix/ramp-opening

fix/ramp-opening
Rui Miguel Alonso 3 years ago committed by GitHub
commit 690815c3a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -19,21 +19,31 @@ class AssetsManagerCubit extends Cubit<AssetsManagerState> {
/// do its job without adding too much delay for the user, we are letting /// do its job without adding too much delay for the user, we are letting
/// the UI paint first, and then we start loading the assets. /// the UI paint first, and then we start loading the assets.
await Future<void>.delayed(const Duration(seconds: 1)); await Future<void>.delayed(const Duration(seconds: 1));
emit( final loadables = <Future<void> Function()>[
state.copyWith( _game.preFetchLeaderboard,
loadables: [
_game.preFetchLeaderboard(),
..._game.preLoadAssets(), ..._game.preLoadAssets(),
..._audioPlayer.load(), ..._audioPlayer.load(),
...BonusAnimation.loadAssets(), ...BonusAnimation.loadAssets(),
...SelectedCharacter.loadAssets(), ...SelectedCharacter.loadAssets(),
], ];
emit(
state.copyWith(
assetsCount: loadables.length,
), ),
); );
final all = state.loadables.map((loadable) async {
await loadable; late void Function() _triggerLoad;
emit(state.copyWith(loaded: [...state.loaded, loadable])); _triggerLoad = () async {
}).toList(); if (loadables.isEmpty) return;
await Future.wait(all); final loadable = loadables.removeAt(0);
await loadable();
_triggerLoad();
emit(state.copyWith(loaded: state.loaded + 1));
};
const _throttleSize = 3;
for (var i = 0; i < _throttleSize; i++) {
_triggerLoad();
}
} }
} }

@ -1,44 +1,42 @@
part of 'assets_manager_cubit.dart'; part of 'assets_manager_cubit.dart';
/// {@template assets_manager_state} /// {@template assets_manager_state}
/// State used to load the game assets /// State used to load the game assets.
/// {@endtemplate} /// {@endtemplate}
class AssetsManagerState extends Equatable { class AssetsManagerState extends Equatable {
/// {@macro assets_manager_state} /// {@macro assets_manager_state}
const AssetsManagerState({ const AssetsManagerState({
required this.loadables, required this.assetsCount,
required this.loaded, required this.loaded,
}); });
/// {@macro assets_manager_state} /// {@macro assets_manager_state}
const AssetsManagerState.initial() const AssetsManagerState.initial() : this(assetsCount: 0, loaded: 0);
: this(loadables: const [], loaded: const []);
/// List of futures to load /// Number of assets to load.
final List<Future> loadables; final int assetsCount;
/// List of loaded futures /// Number of already loaded assets.
final List<Future> loaded; final int loaded;
/// Returns a value between 0 and 1 to indicate the loading progress /// Returns a value between 0 and 1 to indicate the loading progress.
double get progress => double get progress => loaded == 0 ? 0 : loaded / assetsCount;
loadables.isEmpty ? 0 : loaded.length / loadables.length;
/// Only returns false if all the assets have been loaded /// Only returns false if all the assets have been loaded.
bool get isLoading => progress != 1; bool get isLoading => progress != 1;
/// Returns a copy of this instance with the given parameters /// Returns a copy of this instance with the given parameters
/// updated /// updated.
AssetsManagerState copyWith({ AssetsManagerState copyWith({
List<Future>? loadables, int? assetsCount,
List<Future>? loaded, int? loaded,
}) { }) {
return AssetsManagerState( return AssetsManagerState(
loadables: loadables ?? this.loadables, assetsCount: assetsCount ?? this.assetsCount,
loaded: loaded ?? this.loaded, loaded: loaded ?? this.loaded,
); );
} }
@override @override
List<Object> get props => [loaded, loadables]; List<Object> get props => [loaded, assetsCount];
} }

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/game.dart'; import 'package:flame/game.dart';
import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_bloc/flame_bloc.dart';
@ -7,9 +9,9 @@ import 'package:pinball_components/pinball_components.dart';
/// {@template focus_data} /// {@template focus_data}
/// Defines a [Camera] focus point. /// Defines a [Camera] focus point.
/// {@endtemplate} /// {@endtemplate}
class FocusData { class _FocusData {
/// {@template focus_data} /// {@macro focus_data}
FocusData({ const _FocusData({
required this.zoom, required this.zoom,
required this.position, required this.position,
}); });
@ -24,7 +26,11 @@ class FocusData {
/// Changes the game focus when the [GameBloc] status changes. /// Changes the game focus when the [GameBloc] status changes.
class CameraFocusingBehavior extends Component class CameraFocusingBehavior extends Component
with FlameBlocListenable<GameBloc, GameState>, HasGameRef { with FlameBlocListenable<GameBloc, GameState>, HasGameRef {
late final Map<String, FocusData> _foci; final Map<GameStatus, _FocusData> _foci = {};
GameStatus? _activeFocus;
final _previousSize = Vector2.zero();
@override @override
bool listenWhen(GameState? previousState, GameState newState) { bool listenWhen(GameState? previousState, GameState newState) {
@ -32,51 +38,62 @@ class CameraFocusingBehavior extends Component
} }
@override @override
void onNewState(GameState state) { void onNewState(GameState state) => _zoomTo(state.status);
switch (state.status) {
case GameStatus.waiting:
break;
case GameStatus.playing:
_zoom(_foci['game']!);
break;
case GameStatus.gameOver:
_zoom(_foci['backbox']!);
break;
}
}
@override @override
Future<void> onLoad() async { void onGameResize(Vector2 size) {
await super.onLoad(); super.onGameResize(size);
_foci = { if (size == _previousSize) {
'game': FocusData( return;
zoom: gameRef.size.y / 16, }
position: Vector2(0, -7.8), _previousSize.setFrom(size);
),
'waiting': FocusData( final maxWidth = size.x / 90;
zoom: gameRef.size.y / 18, final maxHeight = size.y / 160;
final scale = min(maxHeight, maxWidth);
_foci.addAll({
GameStatus.waiting: _FocusData(
zoom: scale + (maxWidth > maxHeight ? 0.3 : -0.5),
position: Vector2(0, -112), position: Vector2(0, -112),
), ),
'backbox': FocusData( GameStatus.playing: _FocusData(
zoom: gameRef.size.y / 10, zoom: scale + (maxWidth > maxHeight ? 0.5 : -0.2),
position: Vector2(0, -7.8),
),
GameStatus.gameOver: _FocusData(
zoom: scale + (maxWidth > maxHeight ? 2.8 : 3.3),
position: Vector2(0, -111), position: Vector2(0, -111),
), ),
}; });
if (_activeFocus != null) {
_snap(_activeFocus!);
}
}
_snap(_foci['waiting']!); @override
Future<void> onLoad() async {
await super.onLoad();
_snap(GameStatus.waiting);
} }
void _snap(FocusData data) { void _snap(GameStatus focusKey) {
final focusData = _foci[_activeFocus = focusKey]!;
gameRef.camera gameRef.camera
..speed = 100 ..speed = 100
..followVector2(data.position) ..followVector2(focusData.position)
..zoom = data.zoom; ..zoom = focusData.zoom;
} }
void _zoom(FocusData data) { void _zoomTo(GameStatus focusKey) {
final zoom = CameraZoom(value: data.zoom); final focusData = _foci[_activeFocus = focusKey]!;
final zoom = CameraZoom(value: focusData.zoom);
zoom.completed.then((_) { zoom.completed.then((_) {
gameRef.camera.moveTo(data.position); gameRef.camera.moveTo(focusData.position);
}); });
add(zoom); add(zoom);
} }

@ -31,7 +31,6 @@ class GameBlocStatusListener extends Component
.descendants() .descendants()
.whereType<Plunger>() .whereType<Plunger>()
.forEach(_addPlungerBehaviors); .forEach(_addPlungerBehaviors);
gameRef.overlays.remove(PinballGame.playButtonOverlay); gameRef.overlays.remove(PinballGame.playButtonOverlay);
gameRef.overlays.remove(PinballGame.replayButtonOverlay); gameRef.overlays.remove(PinballGame.replayButtonOverlay);
break; break;
@ -43,7 +42,6 @@ class GameBlocStatusListener extends Component
.state .state
.characterTheme, .characterTheme,
); );
gameRef gameRef
.descendants() .descendants()
.whereType<Flipper>() .whereType<Flipper>()
@ -97,8 +95,8 @@ class GameBlocStatusListener extends Component
} }
void _addFlipperBehaviors(Flipper flipper) => flipper void _addFlipperBehaviors(Flipper flipper) => flipper
..add(FlipperKeyControllingBehavior()) .firstChild<FlameBlocProvider<FlipperCubit, FlipperState>>()!
..moveDown(); .add(FlipperKeyControllingBehavior());
void _removeFlipperBehaviors(Flipper flipper) => flipper void _removeFlipperBehaviors(Flipper flipper) => flipper
.descendants() .descendants()

@ -6,158 +6,196 @@ import 'package:pinball_theme/pinball_theme.dart' hide Assets;
/// Add methods to help loading and caching game assets. /// Add methods to help loading and caching game assets.
extension PinballGameAssetsX on PinballGame { extension PinballGameAssetsX on PinballGame {
/// Returns a list of assets to be loaded /// Returns a list of assets to be loaded
List<Future<Image>> preLoadAssets() { List<Future<Image> Function()> preLoadAssets() {
const dashTheme = DashTheme(); const dashTheme = DashTheme();
const sparkyTheme = SparkyTheme(); const sparkyTheme = SparkyTheme();
const androidTheme = AndroidTheme(); const androidTheme = AndroidTheme();
const dinoTheme = DinoTheme(); const dinoTheme = DinoTheme();
return [ return [
images.load(components.Assets.images.boardBackground.keyName), () => images.load(components.Assets.images.boardBackground.keyName),
images.load(components.Assets.images.ball.flameEffect.keyName), () => images.load(components.Assets.images.ball.flameEffect.keyName),
images.load(components.Assets.images.signpost.inactive.keyName), () => images.load(components.Assets.images.signpost.inactive.keyName),
images.load(components.Assets.images.signpost.active1.keyName), () => images.load(components.Assets.images.signpost.active1.keyName),
images.load(components.Assets.images.signpost.active2.keyName), () => images.load(components.Assets.images.signpost.active2.keyName),
images.load(components.Assets.images.signpost.active3.keyName), () => images.load(components.Assets.images.signpost.active3.keyName),
images.load(components.Assets.images.flipper.left.keyName), () => images.load(components.Assets.images.flipper.left.keyName),
images.load(components.Assets.images.flipper.right.keyName), () => images.load(components.Assets.images.flipper.right.keyName),
images.load(components.Assets.images.baseboard.left.keyName), () => images.load(components.Assets.images.baseboard.left.keyName),
images.load(components.Assets.images.baseboard.right.keyName), () => images.load(components.Assets.images.baseboard.right.keyName),
images.load(components.Assets.images.kicker.left.lit.keyName), () => images.load(components.Assets.images.kicker.left.lit.keyName),
images.load(components.Assets.images.kicker.left.dimmed.keyName), () => images.load(components.Assets.images.kicker.left.dimmed.keyName),
images.load(components.Assets.images.kicker.right.lit.keyName), () => images.load(components.Assets.images.kicker.right.lit.keyName),
images.load(components.Assets.images.kicker.right.dimmed.keyName), () => images.load(components.Assets.images.kicker.right.dimmed.keyName),
images.load(components.Assets.images.slingshot.upper.keyName), () => images.load(components.Assets.images.slingshot.upper.keyName),
images.load(components.Assets.images.slingshot.lower.keyName), () => images.load(components.Assets.images.slingshot.lower.keyName),
images.load(components.Assets.images.launchRamp.ramp.keyName), () => images.load(components.Assets.images.launchRamp.ramp.keyName),
images.load( () => images.load(
components.Assets.images.launchRamp.foregroundRailing.keyName, components.Assets.images.launchRamp.foregroundRailing.keyName,
), ),
images.load( () => images.load(
components.Assets.images.launchRamp.backgroundRailing.keyName, components.Assets.images.launchRamp.backgroundRailing.keyName,
), ),
images.load(components.Assets.images.dino.bottomWall.keyName), () => images.load(components.Assets.images.dino.bottomWall.keyName),
images.load(components.Assets.images.dino.topWall.keyName), () => images.load(components.Assets.images.dino.topWall.keyName),
images.load(components.Assets.images.dino.topWallTunnel.keyName), () => images.load(components.Assets.images.dino.topWallTunnel.keyName),
images.load(components.Assets.images.dino.animatronic.head.keyName), () => images.load(components.Assets.images.dino.animatronic.head.keyName),
() =>
images.load(components.Assets.images.dino.animatronic.mouth.keyName), images.load(components.Assets.images.dino.animatronic.mouth.keyName),
images.load(components.Assets.images.dash.animatronic.keyName), () => images.load(components.Assets.images.dash.animatronic.keyName),
images.load(components.Assets.images.dash.bumper.a.active.keyName), () => images.load(components.Assets.images.dash.bumper.a.active.keyName),
() =>
images.load(components.Assets.images.dash.bumper.a.inactive.keyName), images.load(components.Assets.images.dash.bumper.a.inactive.keyName),
images.load(components.Assets.images.dash.bumper.b.active.keyName), () => images.load(components.Assets.images.dash.bumper.b.active.keyName),
() =>
images.load(components.Assets.images.dash.bumper.b.inactive.keyName), images.load(components.Assets.images.dash.bumper.b.inactive.keyName),
() =>
images.load(components.Assets.images.dash.bumper.main.active.keyName), images.load(components.Assets.images.dash.bumper.main.active.keyName),
images.load(components.Assets.images.dash.bumper.main.inactive.keyName), () => images
images.load(components.Assets.images.plunger.plunger.keyName), .load(components.Assets.images.dash.bumper.main.inactive.keyName),
images.load(components.Assets.images.plunger.rocket.keyName), () => images.load(components.Assets.images.plunger.plunger.keyName),
images.load(components.Assets.images.boundary.bottom.keyName), () => images.load(components.Assets.images.plunger.rocket.keyName),
images.load(components.Assets.images.boundary.outer.keyName), () => images.load(components.Assets.images.boundary.bottom.keyName),
images.load(components.Assets.images.boundary.outerBottom.keyName), () => images.load(components.Assets.images.boundary.outer.keyName),
images.load(components.Assets.images.android.spaceship.saucer.keyName), () => images.load(components.Assets.images.boundary.outerBottom.keyName),
images () => images
.load(components.Assets.images.android.spaceship.saucer.keyName),
() => images
.load(components.Assets.images.android.spaceship.animatronic.keyName), .load(components.Assets.images.android.spaceship.animatronic.keyName),
images.load(components.Assets.images.android.spaceship.lightBeam.keyName), () => images
images.load(components.Assets.images.android.ramp.boardOpening.keyName), .load(components.Assets.images.android.spaceship.lightBeam.keyName),
images.load( () => images
.load(components.Assets.images.android.ramp.boardOpening.keyName),
() => images.load(
components.Assets.images.android.ramp.railingForeground.keyName, components.Assets.images.android.ramp.railingForeground.keyName,
), ),
images.load( () => images.load(
components.Assets.images.android.ramp.railingBackground.keyName, components.Assets.images.android.ramp.railingBackground.keyName,
), ),
images.load(components.Assets.images.android.ramp.main.keyName), () => images.load(components.Assets.images.android.ramp.main.keyName),
images.load(components.Assets.images.android.ramp.arrow.inactive.keyName), () => images
images.load( .load(components.Assets.images.android.ramp.arrow.inactive.keyName),
() => images.load(
components.Assets.images.android.ramp.arrow.active1.keyName, components.Assets.images.android.ramp.arrow.active1.keyName,
), ),
images.load( () => images.load(
components.Assets.images.android.ramp.arrow.active2.keyName, components.Assets.images.android.ramp.arrow.active2.keyName,
), ),
images.load( () => images.load(
components.Assets.images.android.ramp.arrow.active3.keyName, components.Assets.images.android.ramp.arrow.active3.keyName,
), ),
images.load( () => images.load(
components.Assets.images.android.ramp.arrow.active4.keyName, components.Assets.images.android.ramp.arrow.active4.keyName,
), ),
images.load( () => images.load(
components.Assets.images.android.ramp.arrow.active5.keyName, components.Assets.images.android.ramp.arrow.active5.keyName,
), ),
images.load(components.Assets.images.android.rail.main.keyName), () => images.load(components.Assets.images.android.rail.main.keyName),
images.load(components.Assets.images.android.rail.exit.keyName), () => images.load(components.Assets.images.android.rail.exit.keyName),
images.load(components.Assets.images.android.bumper.a.lit.keyName), () => images.load(components.Assets.images.android.bumper.a.lit.keyName),
() =>
images.load(components.Assets.images.android.bumper.a.dimmed.keyName), images.load(components.Assets.images.android.bumper.a.dimmed.keyName),
images.load(components.Assets.images.android.bumper.b.lit.keyName), () => images.load(components.Assets.images.android.bumper.b.lit.keyName),
() =>
images.load(components.Assets.images.android.bumper.b.dimmed.keyName), images.load(components.Assets.images.android.bumper.b.dimmed.keyName),
() =>
images.load(components.Assets.images.android.bumper.cow.lit.keyName), images.load(components.Assets.images.android.bumper.cow.lit.keyName),
images.load(components.Assets.images.android.bumper.cow.dimmed.keyName), () => images
images.load(components.Assets.images.sparky.computer.top.keyName), .load(components.Assets.images.android.bumper.cow.dimmed.keyName),
images.load(components.Assets.images.sparky.computer.base.keyName), () => images.load(components.Assets.images.sparky.computer.top.keyName),
images.load(components.Assets.images.sparky.computer.glow.keyName), () => images.load(components.Assets.images.sparky.computer.base.keyName),
images.load(components.Assets.images.sparky.animatronic.keyName), () => images.load(components.Assets.images.sparky.computer.glow.keyName),
images.load(components.Assets.images.sparky.bumper.a.lit.keyName), () => images.load(components.Assets.images.sparky.animatronic.keyName),
() => images.load(components.Assets.images.sparky.bumper.a.lit.keyName),
() =>
images.load(components.Assets.images.sparky.bumper.a.dimmed.keyName), images.load(components.Assets.images.sparky.bumper.a.dimmed.keyName),
images.load(components.Assets.images.sparky.bumper.b.lit.keyName), () => images.load(components.Assets.images.sparky.bumper.b.lit.keyName),
() =>
images.load(components.Assets.images.sparky.bumper.b.dimmed.keyName), images.load(components.Assets.images.sparky.bumper.b.dimmed.keyName),
images.load(components.Assets.images.sparky.bumper.c.lit.keyName), () => images.load(components.Assets.images.sparky.bumper.c.lit.keyName),
() =>
images.load(components.Assets.images.sparky.bumper.c.dimmed.keyName), images.load(components.Assets.images.sparky.bumper.c.dimmed.keyName),
images.load(components.Assets.images.backbox.marquee.keyName), () => images.load(components.Assets.images.backbox.marquee.keyName),
() =>
images.load(components.Assets.images.backbox.displayDivider.keyName), images.load(components.Assets.images.backbox.displayDivider.keyName),
() =>
images.load(components.Assets.images.backbox.button.facebook.keyName), images.load(components.Assets.images.backbox.button.facebook.keyName),
() =>
images.load(components.Assets.images.backbox.button.twitter.keyName), images.load(components.Assets.images.backbox.button.twitter.keyName),
images.load( () => images.load(
components.Assets.images.backbox.displayTitleDecoration.keyName, components.Assets.images.backbox.displayTitleDecoration.keyName,
), ),
() =>
images.load(components.Assets.images.googleWord.letter1.lit.keyName), images.load(components.Assets.images.googleWord.letter1.lit.keyName),
images.load(components.Assets.images.googleWord.letter1.dimmed.keyName), () => images
.load(components.Assets.images.googleWord.letter1.dimmed.keyName),
() =>
images.load(components.Assets.images.googleWord.letter2.lit.keyName), images.load(components.Assets.images.googleWord.letter2.lit.keyName),
images.load(components.Assets.images.googleWord.letter2.dimmed.keyName), () => images
.load(components.Assets.images.googleWord.letter2.dimmed.keyName),
() =>
images.load(components.Assets.images.googleWord.letter3.lit.keyName), images.load(components.Assets.images.googleWord.letter3.lit.keyName),
images.load(components.Assets.images.googleWord.letter3.dimmed.keyName), () => images
.load(components.Assets.images.googleWord.letter3.dimmed.keyName),
() =>
images.load(components.Assets.images.googleWord.letter4.lit.keyName), images.load(components.Assets.images.googleWord.letter4.lit.keyName),
images.load(components.Assets.images.googleWord.letter4.dimmed.keyName), () => images
.load(components.Assets.images.googleWord.letter4.dimmed.keyName),
() =>
images.load(components.Assets.images.googleWord.letter5.lit.keyName), images.load(components.Assets.images.googleWord.letter5.lit.keyName),
images.load(components.Assets.images.googleWord.letter5.dimmed.keyName), () => images
.load(components.Assets.images.googleWord.letter5.dimmed.keyName),
() =>
images.load(components.Assets.images.googleWord.letter6.lit.keyName), images.load(components.Assets.images.googleWord.letter6.lit.keyName),
images.load(components.Assets.images.googleWord.letter6.dimmed.keyName), () => images
images.load(components.Assets.images.googleRollover.left.decal.keyName), .load(components.Assets.images.googleWord.letter6.dimmed.keyName),
() => images
.load(components.Assets.images.googleRollover.left.decal.keyName),
() =>
images.load(components.Assets.images.googleRollover.left.pin.keyName), images.load(components.Assets.images.googleRollover.left.pin.keyName),
images.load(components.Assets.images.googleRollover.right.decal.keyName), () => images
images.load(components.Assets.images.googleRollover.right.pin.keyName), .load(components.Assets.images.googleRollover.right.decal.keyName),
images.load(components.Assets.images.multiball.lit.keyName), () => images
images.load(components.Assets.images.multiball.dimmed.keyName), .load(components.Assets.images.googleRollover.right.pin.keyName),
images.load(components.Assets.images.multiplier.x2.lit.keyName), () => images.load(components.Assets.images.multiball.lit.keyName),
images.load(components.Assets.images.multiplier.x2.dimmed.keyName), () => images.load(components.Assets.images.multiball.dimmed.keyName),
images.load(components.Assets.images.multiplier.x3.lit.keyName), () => images.load(components.Assets.images.multiplier.x2.lit.keyName),
images.load(components.Assets.images.multiplier.x3.dimmed.keyName), () => images.load(components.Assets.images.multiplier.x2.dimmed.keyName),
images.load(components.Assets.images.multiplier.x4.lit.keyName), () => images.load(components.Assets.images.multiplier.x3.lit.keyName),
images.load(components.Assets.images.multiplier.x4.dimmed.keyName), () => images.load(components.Assets.images.multiplier.x3.dimmed.keyName),
images.load(components.Assets.images.multiplier.x5.lit.keyName), () => images.load(components.Assets.images.multiplier.x4.lit.keyName),
images.load(components.Assets.images.multiplier.x5.dimmed.keyName), () => images.load(components.Assets.images.multiplier.x4.dimmed.keyName),
images.load(components.Assets.images.multiplier.x6.lit.keyName), () => images.load(components.Assets.images.multiplier.x5.lit.keyName),
images.load(components.Assets.images.multiplier.x6.dimmed.keyName), () => images.load(components.Assets.images.multiplier.x5.dimmed.keyName),
images.load(components.Assets.images.score.fiveThousand.keyName), () => images.load(components.Assets.images.multiplier.x6.lit.keyName),
images.load(components.Assets.images.score.twentyThousand.keyName), () => images.load(components.Assets.images.multiplier.x6.dimmed.keyName),
images.load(components.Assets.images.score.twoHundredThousand.keyName), () => images.load(components.Assets.images.score.fiveThousand.keyName),
images.load(components.Assets.images.score.oneMillion.keyName), () => images.load(components.Assets.images.score.twentyThousand.keyName),
images.load(components.Assets.images.flapper.backSupport.keyName), () => images
images.load(components.Assets.images.flapper.frontSupport.keyName), .load(components.Assets.images.score.twoHundredThousand.keyName),
images.load(components.Assets.images.flapper.flap.keyName), () => images.load(components.Assets.images.score.oneMillion.keyName),
images.load(components.Assets.images.skillShot.decal.keyName), () => images.load(components.Assets.images.flapper.backSupport.keyName),
images.load(components.Assets.images.skillShot.pin.keyName), () => images.load(components.Assets.images.flapper.frontSupport.keyName),
images.load(components.Assets.images.skillShot.lit.keyName), () => images.load(components.Assets.images.flapper.flap.keyName),
images.load(components.Assets.images.skillShot.dimmed.keyName), () => images.load(components.Assets.images.skillShot.decal.keyName),
() => images.load(components.Assets.images.skillShot.pin.keyName),
() => images.load(components.Assets.images.skillShot.lit.keyName),
() => images.load(components.Assets.images.skillShot.dimmed.keyName),
() =>
images.load(components.Assets.images.displayArrows.arrowLeft.keyName), images.load(components.Assets.images.displayArrows.arrowLeft.keyName),
images.load(components.Assets.images.displayArrows.arrowRight.keyName), () => images
images.load(androidTheme.leaderboardIcon.keyName), .load(components.Assets.images.displayArrows.arrowRight.keyName),
images.load(androidTheme.ball.keyName), () => images.load(androidTheme.leaderboardIcon.keyName),
images.load(dashTheme.leaderboardIcon.keyName), () => images.load(androidTheme.ball.keyName),
images.load(dashTheme.ball.keyName), () => images.load(dashTheme.leaderboardIcon.keyName),
images.load(dinoTheme.leaderboardIcon.keyName), () => images.load(dashTheme.ball.keyName),
images.load(dinoTheme.ball.keyName), () => images.load(dinoTheme.leaderboardIcon.keyName),
images.load(sparkyTheme.leaderboardIcon.keyName), () => images.load(dinoTheme.ball.keyName),
images.load(sparkyTheme.ball.keyName), () => images.load(sparkyTheme.leaderboardIcon.keyName),
images.load(androidTheme.background.keyName), () => images.load(sparkyTheme.ball.keyName),
images.load(dashTheme.background.keyName), () => images.load(androidTheme.background.keyName),
images.load(dinoTheme.background.keyName), () => images.load(dashTheme.background.keyName),
images.load(sparkyTheme.background.keyName), () => images.load(dinoTheme.background.keyName),
() => images.load(sparkyTheme.background.keyName),
]; ];
} }
} }

@ -169,13 +169,18 @@ class PinballGame extends PinballForge2DGame
.bloc .bloc
.pulled(); .pulled();
} else { } else {
final leftSide = info.eventPosition.widget.x < canvasSize.x / 2; final tappedLeftSide = info.eventPosition.widget.x < canvasSize.x / 2;
focusedBoardSide[pointerId] = focusedBoardSide[pointerId] =
leftSide ? BoardSide.left : BoardSide.right; tappedLeftSide ? BoardSide.left : BoardSide.right;
final flippers = descendants().whereType<Flipper>().where((flipper) { final flippers = descendants()
return flipper.side == focusedBoardSide[pointerId]; .whereType<Flipper>()
}); .where((flipper) => flipper.side == focusedBoardSide[pointerId]);
flippers.first.moveUp(); for (final flipper in flippers) {
flipper
.descendants()
.whereType<FlameBlocProvider<FlipperCubit, FlipperState>>()
.forEach((provider) => provider.bloc.moveUp());
}
} }
} }
@ -196,11 +201,15 @@ class PinballGame extends PinballForge2DGame
void _moveFlippersDown(int pointerId) { void _moveFlippersDown(int pointerId) {
if (focusedBoardSide[pointerId] != null) { if (focusedBoardSide[pointerId] != null) {
final flippers = descendants().whereType<Flipper>().where((flipper) { final flippers = descendants()
return flipper.side == focusedBoardSide[pointerId]; .whereType<Flipper>()
}); .where((flipper) => flipper.side == focusedBoardSide[pointerId]);
flippers.first.moveDown(); for (final flipper in flippers) {
focusedBoardSide.remove(pointerId); flipper
.descendants()
.whereType<FlameBlocProvider<FlipperCubit, FlipperState>>()
.forEach((provider) => provider.bloc.moveDown());
}
} }
} }
} }
@ -230,9 +239,7 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
await add(PreviewLine()); await addAll([PreviewLine(), _DebugInformation()]);
await add(_DebugInformation());
} }
@override @override
@ -247,14 +254,10 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
} }
@override @override
void onPanStart(DragStartInfo info) { void onPanStart(DragStartInfo info) => lineStart = info.eventPosition.game;
lineStart = info.eventPosition.game;
}
@override @override
void onPanUpdate(DragUpdateInfo info) { void onPanUpdate(DragUpdateInfo info) => lineEnd = info.eventPosition.game;
lineEnd = info.eventPosition.game;
}
@override @override
void onPanEnd(DragEndInfo info) { void onPanEnd(DragEndInfo info) {

@ -72,14 +72,16 @@ class BonusAnimation extends StatefulWidget {
final VoidCallback? _onCompleted; final VoidCallback? _onCompleted;
/// Returns a list of assets to be loaded for animations. /// Returns a list of assets to be loaded for animations.
static List<Future> loadAssets() { static List<Future Function()> loadAssets() {
Flame.images.prefix = ''; Flame.images.prefix = '';
return [ return [
Flame.images.load(Assets.images.bonusAnimation.dashNest.keyName), () => Flame.images.load(Assets.images.bonusAnimation.dashNest.keyName),
Flame.images.load(Assets.images.bonusAnimation.sparkyTurboCharge.keyName), () => Flame.images
Flame.images.load(Assets.images.bonusAnimation.dinoChomp.keyName), .load(Assets.images.bonusAnimation.sparkyTurboCharge.keyName),
Flame.images.load(Assets.images.bonusAnimation.androidSpaceship.keyName), () => Flame.images.load(Assets.images.bonusAnimation.dinoChomp.keyName),
Flame.images.load(Assets.images.bonusAnimation.googleWord.keyName), () => Flame.images
.load(Assets.images.bonusAnimation.androidSpaceship.keyName),
() => Flame.images.load(Assets.images.bonusAnimation.googleWord.keyName),
]; ];
} }

@ -22,12 +22,12 @@ class SelectedCharacter extends StatefulWidget {
State<SelectedCharacter> createState() => _SelectedCharacterState(); State<SelectedCharacter> createState() => _SelectedCharacterState();
/// Returns a list of assets to be loaded. /// Returns a list of assets to be loaded.
static List<Future> loadAssets() { static List<Future Function()> loadAssets() {
return [ return [
Flame.images.load(const DashTheme().animation.keyName), () => Flame.images.load(const DashTheme().animation.keyName),
Flame.images.load(const AndroidTheme().animation.keyName), () => Flame.images.load(const AndroidTheme().animation.keyName),
Flame.images.load(const DinoTheme().animation.keyName), () => Flame.images.load(const DinoTheme().animation.keyName),
Flame.images.load(const SparkyTheme().animation.keyName), () => Flame.images.load(const SparkyTheme().animation.keyName),
]; ];
} }
} }

@ -334,10 +334,10 @@ class PinballAudioPlayer {
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> Function()> 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.

@ -102,7 +102,9 @@ void main() {
group('load', () { group('load', () {
test('creates the bumpers pools', () async { test('creates the bumpers pools', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
verify( verify(
() => createAudioPool.onCall( () => createAudioPool.onCall(
@ -122,7 +124,9 @@ void main() {
}); });
test('creates the kicker pools', () async { test('creates the kicker pools', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
verify( verify(
() => createAudioPool.onCall( () => createAudioPool.onCall(
@ -142,7 +146,9 @@ void main() {
}); });
test('configures the audio cache instance', () async { test('configures the audio cache instance', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
verify(() => configureAudioCache.onCall(FlameAudio.audioCache)) verify(() => configureAudioCache.onCall(FlameAudio.audioCache))
.called(1); .called(1);
@ -154,13 +160,17 @@ void main() {
playSingleAudio: playSingleAudio.onCall, playSingleAudio: playSingleAudio.onCall,
preCacheSingleAudio: preCacheSingleAudio.onCall, preCacheSingleAudio: preCacheSingleAudio.onCall,
); );
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
expect(FlameAudio.audioCache.prefix, equals('')); expect(FlameAudio.audioCache.prefix, equals(''));
}); });
test('pre cache the assets', () async { test('pre cache the assets', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
verify( verify(
() => preCacheSingleAudio () => preCacheSingleAudio
@ -242,7 +252,9 @@ void main() {
group('when seed is true', () { group('when seed is true', () {
test('plays the bumper A sound pool', () async { test('plays the bumper A sound pool', () async {
when(seed.nextBool).thenReturn(true); when(seed.nextBool).thenReturn(true);
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.bumper); audioPlayer.play(PinballAudio.bumper);
verify(() => bumperAPool.start(volume: 0.6)).called(1); verify(() => bumperAPool.start(volume: 0.6)).called(1);
@ -252,7 +264,9 @@ void main() {
group('when seed is false', () { group('when seed is false', () {
test('plays the bumper B sound pool', () async { test('plays the bumper B sound pool', () async {
when(seed.nextBool).thenReturn(false); when(seed.nextBool).thenReturn(false);
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.bumper); audioPlayer.play(PinballAudio.bumper);
verify(() => bumperBPool.start(volume: 0.6)).called(1); verify(() => bumperBPool.start(volume: 0.6)).called(1);
@ -291,7 +305,9 @@ void main() {
group('when seed is true', () { group('when seed is true', () {
test('plays the kicker A sound pool', () async { test('plays the kicker A sound pool', () async {
when(seed.nextBool).thenReturn(true); when(seed.nextBool).thenReturn(true);
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.kicker); audioPlayer.play(PinballAudio.kicker);
verify(() => kickerAPool.start(volume: 0.6)).called(1); verify(() => kickerAPool.start(volume: 0.6)).called(1);
@ -301,7 +317,9 @@ void main() {
group('when seed is false', () { group('when seed is false', () {
test('plays the kicker B sound pool', () async { test('plays the kicker B sound pool', () async {
when(seed.nextBool).thenReturn(false); when(seed.nextBool).thenReturn(false);
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.kicker); audioPlayer.play(PinballAudio.kicker);
verify(() => kickerBPool.start(volume: 0.6)).called(1); verify(() => kickerBPool.start(volume: 0.6)).called(1);
@ -311,7 +329,9 @@ void main() {
group('cow moo', () { group('cow moo', () {
test('plays the correct file', () async { test('plays the correct file', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.cowMoo); audioPlayer.play(PinballAudio.cowMoo);
verify( verify(
@ -324,7 +344,9 @@ void main() {
final clock = _MockClock(); final clock = _MockClock();
await withClock(clock, () async { await withClock(clock, () async {
when(clock.now).thenReturn(DateTime(2022)); when(clock.now).thenReturn(DateTime(2022));
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer audioPlayer
..play(PinballAudio.cowMoo) ..play(PinballAudio.cowMoo)
..play(PinballAudio.cowMoo); ..play(PinballAudio.cowMoo);
@ -347,7 +369,9 @@ void main() {
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().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.google); audioPlayer.play(PinballAudio.google);
verify( verify(
@ -361,7 +385,9 @@ void main() {
group('sparky', () { group('sparky', () {
test('plays the correct file', () async { test('plays the correct file', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.sparky); audioPlayer.play(PinballAudio.sparky);
verify( verify(
@ -375,7 +401,9 @@ void main() {
group('dino', () { group('dino', () {
test('plays the correct file', () async { test('plays the correct file', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.dino); audioPlayer.play(PinballAudio.dino);
verify( verify(
@ -389,7 +417,9 @@ void main() {
group('android', () { group('android', () {
test('plays the correct file', () async { test('plays the correct file', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.android); audioPlayer.play(PinballAudio.android);
verify( verify(
@ -403,7 +433,9 @@ void main() {
group('dash', () { group('dash', () {
test('plays the correct file', () async { test('plays the correct file', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.dash); audioPlayer.play(PinballAudio.dash);
verify( verify(
@ -417,7 +449,9 @@ void main() {
group('launcher', () { group('launcher', () {
test('plays the correct file', () async { test('plays the correct file', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.launcher); audioPlayer.play(PinballAudio.launcher);
verify( verify(
@ -431,7 +465,9 @@ void main() {
group('rollover', () { group('rollover', () {
test('plays the correct file', () async { test('plays the correct file', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.rollover); audioPlayer.play(PinballAudio.rollover);
verify( verify(
@ -445,7 +481,9 @@ void main() {
group('ioPinballVoiceOver', () { group('ioPinballVoiceOver', () {
test('plays the correct file', () async { test('plays the correct file', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.ioPinballVoiceOver); audioPlayer.play(PinballAudio.ioPinballVoiceOver);
verify( verify(
@ -459,7 +497,9 @@ void main() {
group('gameOverVoiceOver', () { group('gameOverVoiceOver', () {
test('plays the correct file', () async { test('plays the correct file', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.gameOverVoiceOver); audioPlayer.play(PinballAudio.gameOverVoiceOver);
verify( verify(
@ -473,7 +513,9 @@ void main() {
group('backgroundMusic', () { group('backgroundMusic', () {
test('plays the correct file', () async { test('plays the correct file', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.backgroundMusic); audioPlayer.play(PinballAudio.backgroundMusic);
verify( verify(
@ -485,7 +527,9 @@ void main() {
}); });
test('plays only once', () async { test('plays only once', () async {
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer audioPlayer
..play(PinballAudio.backgroundMusic) ..play(PinballAudio.backgroundMusic)
..play(PinballAudio.backgroundMusic); ..play(PinballAudio.backgroundMusic);
@ -503,7 +547,9 @@ void main() {
'throws assertions error when playing an unregistered audio', 'throws assertions error when playing an unregistered audio',
() async { () async {
audioPlayer.audios.remove(PinballAudio.google); audioPlayer.audios.remove(PinballAudio.google);
await Future.wait(audioPlayer.load()); await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
expect( expect(
() => audioPlayer.play(PinballAudio.google), () => audioPlayer.play(PinballAudio.google),

@ -57,7 +57,7 @@ class _TurboChargeSpriteAnimationComponent extends SpriteAnimationComponent
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final spriteSheet = await gameRef.images.load( final spriteSheet = gameRef.images.fromCache(
Assets.images.ball.flameEffect.keyName, Assets.images.ball.flameEffect.keyName,
); );

@ -109,7 +109,7 @@ class _OuterBoundary extends BodyComponent with InitialPosition, ZIndex {
final topLeftCurve = BezierCurveShape( final topLeftCurve = BezierCurveShape(
controlPoints: [ controlPoints: [
topWall.vertex1, topWall.vertex2,
Vector2(-31.5, -69.9), Vector2(-31.5, -69.9),
Vector2(-32.3, -57.2), Vector2(-32.3, -57.2),
], ],
@ -123,7 +123,7 @@ class _OuterBoundary extends BodyComponent with InitialPosition, ZIndex {
final upperLeftWallCurve = BezierCurveShape( final upperLeftWallCurve = BezierCurveShape(
controlPoints: [ controlPoints: [
topLeftWall.vertex1, topLeftWall.vertex2,
Vector2(-33.9, -40.7), Vector2(-33.9, -40.7),
Vector2(-32.5, -39), Vector2(-32.5, -39),
], ],

@ -1,2 +1,3 @@
export 'flipper_jointing_behavior.dart'; export 'flipper_jointing_behavior.dart';
export 'flipper_key_controlling_behavior.dart'; export 'flipper_key_controlling_behavior.dart';
export 'flipper_moving_behavior.dart';

@ -1,11 +1,11 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// Allows controlling the [Flipper]'s movement with keyboard input. /// Allows controlling the [Flipper]'s movement with keyboard input.
class FlipperKeyControllingBehavior extends Component class FlipperKeyControllingBehavior extends Component
with KeyboardHandler, ParentIsA<Flipper> { with KeyboardHandler, FlameBlocReader<FlipperCubit, FlipperState> {
/// The [LogicalKeyboardKey]s that will control the [Flipper]. /// The [LogicalKeyboardKey]s that will control the [Flipper].
/// ///
/// [onKeyEvent] method listens to when one of these keys is pressed. /// [onKeyEvent] method listens to when one of these keys is pressed.
@ -14,8 +14,8 @@ class FlipperKeyControllingBehavior extends Component
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final flipper = parent!.parent! as Flipper;
switch (parent.side) { switch (flipper.side) {
case BoardSide.left: case BoardSide.left:
_keys = [ _keys = [
LogicalKeyboardKey.arrowLeft, LogicalKeyboardKey.arrowLeft,
@ -39,9 +39,9 @@ class FlipperKeyControllingBehavior extends Component
if (!_keys.contains(event.logicalKey)) return true; if (!_keys.contains(event.logicalKey)) return true;
if (event is RawKeyDownEvent) { if (event is RawKeyDownEvent) {
parent.moveUp(); bloc.moveUp();
} else if (event is RawKeyUpEvent) { } else if (event is RawKeyUpEvent) {
parent.moveDown(); bloc.moveDown();
} }
return false; return false;

@ -0,0 +1,40 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball_components/pinball_components.dart';
class FlipperMovingBehavior extends Component
with
FlameBlocListenable<FlipperCubit, FlipperState>,
FlameBlocReader<FlipperCubit, FlipperState> {
FlipperMovingBehavior({
required double strength,
}) : assert(strength >= 0, "Strength can't be negative"),
_strength = strength;
final double _strength;
late final Flipper _flipper;
void _moveUp() => _flipper.body.linearVelocity = Vector2(0, -_strength);
void _moveDown() => _flipper.body.linearVelocity = Vector2(0, _strength);
@override
void onNewState(FlipperState state) {
super.onNewState(state);
if (bloc.state.isMovingDown) _moveDown();
}
@override
void update(double dt) {
super.update(dt);
if (bloc.state.isMovingUp) _moveUp();
}
@override
Future<void> onLoad() async {
await super.onLoad();
_flipper = parent!.parent! as Flipper;
_moveDown();
}
}

@ -0,0 +1,11 @@
import 'package:bloc/bloc.dart';
part 'flipper_state.dart';
class FlipperCubit extends Cubit<FlipperState> {
FlipperCubit() : super(FlipperState.movingDown);
void moveUp() => emit(FlipperState.movingUp);
void moveDown() => emit(FlipperState.movingDown);
}

@ -0,0 +1,11 @@
part of 'flipper_cubit.dart';
enum FlipperState {
movingDown,
movingUp,
}
extension FlipperStateX on FlipperState {
bool get isMovingDown => this == FlipperState.movingDown;
bool get isMovingUp => this == FlipperState.movingUp;
}

@ -1,11 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
export 'behaviors/behaviors.dart'; export 'behaviors/behaviors.dart';
export 'cubit/flipper_cubit.dart';
/// {@template flipper} /// {@template flipper}
/// A bat, typically found in pairs at the bottom of the board. /// A bat, typically found in pairs at the bottom of the board.
@ -21,6 +23,10 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
children: [ children: [
_FlipperSpriteComponent(side: side), _FlipperSpriteComponent(side: side),
FlipperJointingBehavior(), FlipperJointingBehavior(),
FlameBlocProvider<FlipperCubit, FlipperState>(
create: FlipperCubit.new,
children: [FlipperMovingBehavior(strength: 90)],
),
], ],
); );
@ -33,29 +39,12 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
/// The size of the [Flipper]. /// The size of the [Flipper].
static final size = Vector2(13.5, 4.3); static final size = Vector2(13.5, 4.3);
/// The speed required to move the [Flipper] to its highest position.
///
/// The higher the value, the faster the [Flipper] will move.
static const double _speed = 90;
/// Whether the [Flipper] is on the left or right side of the board. /// Whether the [Flipper] is on the left or right side of the board.
/// ///
/// A [Flipper] with [BoardSide.left] has a counter-clockwise arc motion, /// A [Flipper] with [BoardSide.left] has a counter-clockwise arc motion,
/// whereas a [Flipper] with [BoardSide.right] has a clockwise arc motion. /// whereas a [Flipper] with [BoardSide.right] has a clockwise arc motion.
final BoardSide side; final BoardSide side;
/// Applies downward linear velocity to the [Flipper], moving it to its
/// resting position.
void moveDown() {
body.linearVelocity = Vector2(0, _speed);
}
/// Applies upward linear velocity to the [Flipper], moving it to its highest
/// position.
void moveUp() {
body.linearVelocity = Vector2(0, -_speed);
}
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final direction = side.direction; final direction = side.direction;

@ -5,11 +5,7 @@ part 'plunger_state.dart';
class PlungerCubit extends Cubit<PlungerState> { class PlungerCubit extends Cubit<PlungerState> {
PlungerCubit() : super(PlungerState.releasing); PlungerCubit() : super(PlungerState.releasing);
void pulled() { void pulled() => emit(PlungerState.pulling);
emit(PlungerState.pulling);
}
void released() { void released() => emit(PlungerState.releasing);
emit(PlungerState.releasing);
}
} }

@ -108,7 +108,7 @@ class _PlungerSpriteAnimationGroupComponent
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final spriteSheet = await gameRef.images.load( final spriteSheet = gameRef.images.fromCache(
Assets.images.plunger.plunger.keyName, Assets.images.plunger.plunger.keyName,
); );
const amountPerRow = 20; const amountPerRow = 20;

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart';
@ -29,11 +30,14 @@ class AndroidSpaceshipGame extends BallGame {
await super.onLoad(); await super.onLoad();
camera.followVector2(Vector2.zero()); camera.followVector2(Vector2.zero());
await addAll( await add(
[ FlameBlocProvider<AndroidSpaceshipCubit, AndroidSpaceshipState>(
create: AndroidSpaceshipCubit.new,
children: [
AndroidSpaceship(position: Vector2.zero()), AndroidSpaceship(position: Vector2.zero()),
AndroidAnimatronic(), AndroidAnimatronic(),
], ],
),
); );
await traceAllBodies(); await traceAllBodies();

@ -10,6 +10,11 @@ class LaunchRampGame extends BallGame {
: super( : super(
ballPriority: ZIndexes.ballOnLaunchRamp, ballPriority: ZIndexes.ballOnLaunchRamp,
ballLayer: Layer.launcher, ballLayer: Layer.launcher,
imagesFileNames: [
Assets.images.launchRamp.ramp.keyName,
Assets.images.launchRamp.backgroundRailing.keyName,
Assets.images.launchRamp.foregroundRailing.keyName,
],
); );
static const description = ''' static const description = '''

@ -1,10 +1,18 @@
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart'; import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart';
class PlungerGame extends BallGame class PlungerGame extends BallGame
with HasKeyboardHandlerComponents, Traceable { with HasKeyboardHandlerComponents, Traceable {
PlungerGame()
: super(
imagesFileNames: [
Assets.images.plunger.plunger.keyName,
],
);
static const description = ''' static const description = '''
Shows how Plunger is rendered. Shows how Plunger is rendered.
@ -19,7 +27,12 @@ class PlungerGame extends BallGame
final center = screenToWorld(camera.viewport.canvasSize! / 2); final center = screenToWorld(camera.viewport.canvasSize! / 2);
final plunger = Plunger() final plunger = Plunger()
..initialPosition = Vector2(center.x - 8.8, center.y); ..initialPosition = Vector2(center.x - 8.8, center.y);
await add(plunger); await add(
FlameBlocProvider<PlungerCubit, PlungerState>(
create: PlungerCubit.new,
children: [plunger],
),
);
await plunger.add(PlungerKeyControllingBehavior()); await plunger.add(PlungerKeyControllingBehavior());
await traceAllBodies(); await traceAllBodies();

@ -128,7 +128,7 @@ packages:
source: hosted source: hosted
version: "1.0.2" version: "1.0.2"
flame_bloc: flame_bloc:
dependency: transitive dependency: "direct main"
description: description:
name: flame_bloc name: flame_bloc
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"

@ -9,6 +9,7 @@ environment:
dependencies: dependencies:
dashbook: ^0.1.7 dashbook: ^0.1.7
flame: ^1.1.1 flame: ^1.1.1
flame_bloc: ^1.4.0
flame_forge2d: flame_forge2d:
git: git:
url: https://github.com/flame-engine/flame url: https://github.com/flame-engine/flame

@ -14,8 +14,11 @@ void main() {
group( group(
'BallTurboChargingBehavior', 'BallTurboChargingBehavior',
() { () {
final asset = theme.Assets.images.dash.ball.keyName; final assets = [
final flameTester = FlameTester(() => TestGame([asset])); theme.Assets.images.dash.ball.keyName,
Assets.images.ball.flameEffect.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
test('can be instantiated', () { test('can be instantiated', () {
expect( expect(

@ -1,15 +1,14 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
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_components/src/components/components.dart'; import 'package:pinball_components/src/components/components.dart';
import '../../../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
group('FlipperJointingBehavior', () { group('FlipperJointingBehavior', () {
final flameTester = FlameTester(TestGame.new); final flameTester = FlameTester(Forge2DGame.new);
test('can be instantiated', () { test('can be instantiated', () {
expect( expect(

@ -1,5 +1,7 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
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/foundation.dart'; import 'package:flutter/foundation.dart';
@ -9,6 +11,25 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
class _TestGame extends Forge2DGame {
Future<void> pump(
FlipperKeyControllingBehavior behavior, {
required BoardSide side,
FlipperCubit? flipperBloc,
}) async {
final flipper = Flipper.test(side: side);
await ensureAdd(flipper);
await flipper.ensureAdd(
FlameBlocProvider<FlipperCubit, FlipperState>.value(
value: flipperBloc ?? FlipperCubit(),
children: [behavior],
),
);
}
}
class _MockFlipperCubit extends Mock implements FlipperCubit {}
class _MockRawKeyDownEvent extends Mock implements RawKeyDownEvent { class _MockRawKeyDownEvent extends Mock implements RawKeyDownEvent {
@override @override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
@ -26,26 +47,32 @@ class _MockRawKeyUpEvent extends Mock implements RawKeyUpEvent {
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
group('FlipperKeyControllingBehavior', () { group('FlipperKeyControllingBehavior', () {
final flameTester = FlameTester(Forge2DGame.new); final flameTester = FlameTester(_TestGame.new);
group( group(
'onKeyEvent', 'onKeyEvent',
() { () {
late Flipper rightFlipper; late FlipperCubit flipperBloc;
late Flipper leftFlipper;
setUp(() { setUp(() {
rightFlipper = Flipper.test(side: BoardSide.right); flipperBloc = _MockFlipperCubit();
leftFlipper = Flipper.test(side: BoardSide.left); whenListen<FlipperState>(
flipperBloc,
const Stream.empty(),
initialState: FlipperState.movingDown,
);
}); });
group('on right Flipper', () { group('on right Flipper', () {
flameTester.test( flameTester.test(
'moves upwards when right arrow is pressed', 'moves upwards when right arrow is pressed',
(game) async { (game) async {
await game.ensureAdd(rightFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await rightFlipper.ensureAdd(behavior); await game.pump(
behavior,
flipperBloc: flipperBloc,
side: BoardSide.right,
);
final event = _MockRawKeyDownEvent(); final event = _MockRawKeyDownEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -54,17 +81,20 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(rightFlipper.body.linearVelocity.y, isNegative); await Future<void>.delayed(Duration.zero);
expect(rightFlipper.body.linearVelocity.x, isZero); verify(flipperBloc.moveUp).called(1);
}, },
); );
flameTester.test( flameTester.test(
'moves downwards when right arrow is released', 'moves downwards when right arrow is released',
(game) async { (game) async {
await game.ensureAdd(rightFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await rightFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.right,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyUpEvent(); final event = _MockRawKeyUpEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -73,17 +103,20 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(rightFlipper.body.linearVelocity.y, isPositive); await Future<void>.delayed(Duration.zero);
expect(rightFlipper.body.linearVelocity.x, isZero); verify(flipperBloc.moveDown).called(1);
}, },
); );
flameTester.test( flameTester.test(
'moves upwards when D is pressed', 'moves upwards when D is pressed',
(game) async { (game) async {
await game.ensureAdd(rightFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await rightFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.right,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyDownEvent(); final event = _MockRawKeyDownEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -92,17 +125,20 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(rightFlipper.body.linearVelocity.y, isNegative); await Future<void>.delayed(Duration.zero);
expect(rightFlipper.body.linearVelocity.x, isZero); verify(flipperBloc.moveUp).called(1);
}, },
); );
flameTester.test( flameTester.test(
'moves downwards when D is released', 'moves downwards when D is released',
(game) async { (game) async {
await game.ensureAdd(rightFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await rightFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.right,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyUpEvent(); final event = _MockRawKeyUpEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -111,8 +147,8 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(rightFlipper.body.linearVelocity.y, isPositive); await Future<void>.delayed(Duration.zero);
expect(rightFlipper.body.linearVelocity.x, isZero); verify(flipperBloc.moveDown).called(1);
}, },
); );
@ -120,9 +156,12 @@ void main() {
flameTester.test( flameTester.test(
'left arrow is pressed', 'left arrow is pressed',
(game) async { (game) async {
await game.ensureAdd(rightFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await rightFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.right,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyDownEvent(); final event = _MockRawKeyDownEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -131,17 +170,20 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(rightFlipper.body.linearVelocity.y, isZero); verifyNever(flipperBloc.moveDown);
expect(rightFlipper.body.linearVelocity.x, isZero); verifyNever(flipperBloc.moveUp);
}, },
); );
flameTester.test( flameTester.test(
'left arrow is released', 'left arrow is released',
(game) async { (game) async {
await game.ensureAdd(rightFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await rightFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.right,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyUpEvent(); final event = _MockRawKeyUpEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -150,17 +192,20 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(rightFlipper.body.linearVelocity.y, isZero); verifyNever(flipperBloc.moveDown);
expect(rightFlipper.body.linearVelocity.x, isZero); verifyNever(flipperBloc.moveUp);
}, },
); );
flameTester.test( flameTester.test(
'A is pressed', 'A is pressed',
(game) async { (game) async {
await game.ensureAdd(rightFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await rightFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.right,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyDownEvent(); final event = _MockRawKeyDownEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -169,17 +214,20 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(rightFlipper.body.linearVelocity.y, isZero); verifyNever(flipperBloc.moveDown);
expect(rightFlipper.body.linearVelocity.x, isZero); verifyNever(flipperBloc.moveUp);
}, },
); );
flameTester.test( flameTester.test(
'A is released', 'A is released',
(game) async { (game) async {
await game.ensureAdd(rightFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await rightFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.right,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyUpEvent(); final event = _MockRawKeyUpEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -188,8 +236,8 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(rightFlipper.body.linearVelocity.y, isZero); verifyNever(flipperBloc.moveDown);
expect(rightFlipper.body.linearVelocity.x, isZero); verifyNever(flipperBloc.moveUp);
}, },
); );
}); });
@ -199,9 +247,12 @@ void main() {
flameTester.test( flameTester.test(
'moves upwards when left arrow is pressed', 'moves upwards when left arrow is pressed',
(game) async { (game) async {
await game.ensureAdd(leftFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await leftFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.left,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyDownEvent(); final event = _MockRawKeyDownEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -210,17 +261,20 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(leftFlipper.body.linearVelocity.y, isNegative); await Future<void>.delayed(Duration.zero);
expect(leftFlipper.body.linearVelocity.x, isZero); verify(flipperBloc.moveUp).called(1);
}, },
); );
flameTester.test( flameTester.test(
'moves downwards when left arrow is released', 'moves downwards when left arrow is released',
(game) async { (game) async {
await game.ensureAdd(leftFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await leftFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.left,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyUpEvent(); final event = _MockRawKeyUpEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -229,17 +283,20 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(leftFlipper.body.linearVelocity.y, isPositive); await Future<void>.delayed(Duration.zero);
expect(leftFlipper.body.linearVelocity.x, isZero); verify(flipperBloc.moveDown).called(1);
}, },
); );
flameTester.test( flameTester.test(
'moves upwards when A is pressed', 'moves upwards when A is pressed',
(game) async { (game) async {
await game.ensureAdd(leftFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await leftFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.left,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyDownEvent(); final event = _MockRawKeyDownEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -248,17 +305,20 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(leftFlipper.body.linearVelocity.y, isNegative); await Future<void>.delayed(Duration.zero);
expect(leftFlipper.body.linearVelocity.x, isZero); verify(flipperBloc.moveUp).called(1);
}, },
); );
flameTester.test( flameTester.test(
'moves downwards when A is released', 'moves downwards when A is released',
(game) async { (game) async {
await game.ensureAdd(leftFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await leftFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.left,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyUpEvent(); final event = _MockRawKeyUpEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -267,8 +327,8 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(leftFlipper.body.linearVelocity.y, isPositive); await Future<void>.delayed(Duration.zero);
expect(leftFlipper.body.linearVelocity.x, isZero); verify(flipperBloc.moveDown).called(1);
}, },
); );
@ -276,9 +336,12 @@ void main() {
flameTester.test( flameTester.test(
'right arrow is pressed', 'right arrow is pressed',
(game) async { (game) async {
await game.ensureAdd(leftFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await leftFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.left,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyDownEvent(); final event = _MockRawKeyDownEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -287,17 +350,20 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(leftFlipper.body.linearVelocity.y, isZero); verifyNever(flipperBloc.moveDown);
expect(leftFlipper.body.linearVelocity.x, isZero); verifyNever(flipperBloc.moveUp);
}, },
); );
flameTester.test( flameTester.test(
'right arrow is released', 'right arrow is released',
(game) async { (game) async {
await game.ensureAdd(leftFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await leftFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.left,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyUpEvent(); final event = _MockRawKeyUpEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -306,17 +372,20 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(leftFlipper.body.linearVelocity.y, isZero); verifyNever(flipperBloc.moveDown);
expect(leftFlipper.body.linearVelocity.x, isZero); verifyNever(flipperBloc.moveUp);
}, },
); );
flameTester.test( flameTester.test(
'D is pressed', 'D is pressed',
(game) async { (game) async {
await game.ensureAdd(leftFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await leftFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.left,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyDownEvent(); final event = _MockRawKeyDownEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -325,17 +394,20 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(leftFlipper.body.linearVelocity.y, isZero); verifyNever(flipperBloc.moveDown);
expect(leftFlipper.body.linearVelocity.x, isZero); verifyNever(flipperBloc.moveUp);
}, },
); );
flameTester.test( flameTester.test(
'D is released', 'D is released',
(game) async { (game) async {
await game.ensureAdd(leftFlipper);
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await leftFlipper.ensureAdd(behavior); await game.pump(
behavior,
side: BoardSide.left,
flipperBloc: flipperBloc,
);
final event = _MockRawKeyUpEvent(); final event = _MockRawKeyUpEvent();
when(() => event.logicalKey).thenReturn( when(() => event.logicalKey).thenReturn(
@ -344,8 +416,8 @@ void main() {
behavior.onKeyEvent(event, {}); behavior.onKeyEvent(event, {});
expect(leftFlipper.body.linearVelocity.y, isZero); verifyNever(flipperBloc.moveDown);
expect(leftFlipper.body.linearVelocity.x, isZero); verifyNever(flipperBloc.moveUp);
}, },
); );
}); });

@ -0,0 +1,101 @@
// ignore_for_file: avoid_dynamic_calls, cascade_invocations
import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_bloc/flame_bloc.dart';
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_components/pinball_components.dart';
class _TestGame extends Forge2DGame {
Future<void> pump(
FlipperMovingBehavior behavior, {
FlipperCubit? flipperBloc,
}) async {
final flipper = Flipper.test(side: BoardSide.left);
await ensureAdd(flipper);
await flipper.ensureAdd(
FlameBlocProvider<FlipperCubit, FlipperState>.value(
value: flipperBloc ?? FlipperCubit(),
children: [behavior],
),
);
}
}
class _MockFlipperCubit extends Mock implements FlipperCubit {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(_TestGame.new);
group('FlipperMovingBehavior', () {
test('can be instantiated', () {
expect(
FlipperMovingBehavior(strength: 0),
isA<FlipperMovingBehavior>(),
);
});
test('throws assertion error when strength is negative', () {
expect(
() => FlipperMovingBehavior(strength: -1),
throwsAssertionError,
);
});
flameTester.test('can be loaded', (game) async {
final behavior = FlipperMovingBehavior(strength: 0);
await game.pump(behavior);
expect(game.descendants(), contains(behavior));
});
flameTester.test(
'applies vertical velocity to flipper when moving down',
(game) async {
final bloc = _MockFlipperCubit();
final streamController = StreamController<FlipperState>();
whenListen(
bloc,
streamController.stream,
initialState: FlipperState.movingUp,
);
const strength = 10.0;
final behavior = FlipperMovingBehavior(strength: strength);
await game.pump(behavior, flipperBloc: bloc);
streamController.add(FlipperState.movingDown);
await Future<void>.delayed(Duration.zero);
final flipper = behavior.ancestors().whereType<Flipper>().single;
expect(flipper.body.linearVelocity.x, 0);
expect(flipper.body.linearVelocity.y, strength);
},
);
flameTester.test(
'applies vertical velocity to flipper when moving up',
(game) async {
final bloc = _MockFlipperCubit();
whenListen(
bloc,
Stream.value(FlipperState.movingUp),
initialState: FlipperState.movingUp,
);
const strength = 10.0;
final behavior = FlipperMovingBehavior(strength: strength);
await game.pump(behavior, flipperBloc: bloc);
game.update(0);
final flipper = behavior.ancestors().whereType<Flipper>().single;
expect(flipper.body.linearVelocity.x, 0);
expect(flipper.body.linearVelocity.y, -strength);
},
);
});
}

@ -0,0 +1,23 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
void main() {
group('FlipperCubit', () {
test('can be instantiated', () {
expect(FlipperCubit(), isA<FlipperCubit>());
});
blocTest<FlipperCubit, FlipperState>(
'moves',
build: FlipperCubit.new,
act: (cubit) => cubit
..moveUp()
..moveDown(),
expect: () => [
FlipperState.movingUp,
FlipperState.movingDown,
],
);
});
}

@ -128,31 +128,5 @@ void main() {
}, },
); );
}); });
flameTester.test(
'moveDown applies downward velocity',
(game) async {
final flipper = Flipper(side: BoardSide.left);
await game.ensureAdd(flipper);
expect(flipper.body.linearVelocity, equals(Vector2.zero()));
flipper.moveDown();
expect(flipper.body.linearVelocity.y, isPositive);
},
);
flameTester.test(
'moveUp applies upward velocity',
(game) async {
final flipper = Flipper(side: BoardSide.left);
await game.ensureAdd(flipper);
expect(flipper.body.linearVelocity, equals(Vector2.zero()));
flipper.moveUp();
expect(flipper.body.linearVelocity.y, isNegative);
},
);
}); });
} }

@ -10,7 +10,8 @@ import '../../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new); final asset = Assets.images.plunger.plunger.keyName;
final flameTester = FlameTester(() => TestGame([asset]));
group('Plunger', () { group('Plunger', () {
test('can be instantiated', () { test('can be instantiated', () {
@ -69,6 +70,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'pulling', 'pulling',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.load(asset);
await game.ensureAdd(Plunger()); await game.ensureAdd(Plunger());
game.camera.followVector2(Vector2.zero()); game.camera.followVector2(Vector2.zero());
game.camera.zoom = 4.1; game.camera.zoom = 4.1;
@ -92,6 +94,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'releasing', 'releasing',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.load(asset);
await game.ensureAdd(Plunger()); await game.ensureAdd(Plunger());
game.camera.followVector2(Vector2.zero()); game.camera.followVector2(Vector2.zero());
game.camera.zoom = 4.1; game.camera.zoom = 4.1;

@ -30,8 +30,10 @@ class PinballButton extends StatelessWidget {
), ),
), ),
child: Center( child: Center(
child: GestureDetector( child: InkWell(
onTap: onTap, onTap: onTap,
highlightColor: PinballColors.transparent,
splashColor: PinballColors.transparent,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 32, horizontal: 32,

@ -53,8 +53,10 @@ class PinballDpadButton extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return Material(
color: PinballColors.transparent, color: PinballColors.transparent,
child: GestureDetector( child: InkWell(
onTap: onTap, onTap: onTap,
highlightColor: PinballColors.transparent,
splashColor: PinballColors.transparent,
child: Image.asset( child: Image.asset(
direction.toAsset(), direction.toAsset(),
width: 60, width: 60,

@ -37,7 +37,7 @@ void main() {
shareRepository = _MockShareRepository(); shareRepository = _MockShareRepository();
pinballAudioPlayer = _MockPinballAudioPlayer(); pinballAudioPlayer = _MockPinballAudioPlayer();
platformHelper = _MockPlatformHelper(); platformHelper = _MockPlatformHelper();
when(pinballAudioPlayer.load).thenAnswer((_) => [Future.value()]); when(pinballAudioPlayer.load).thenAnswer((_) => [Future.value]);
}); });
testWidgets('renders PinballGamePage', (tester) async { testWidgets('renders PinballGamePage', (tester) async {

@ -7,7 +7,7 @@ void main() {
group('AssetsManagerState', () { group('AssetsManagerState', () {
test('can be instantiated', () { test('can be instantiated', () {
expect( expect(
AssetsManagerState(loadables: const [], loaded: const []), AssetsManagerState(assetsCount: 0, loaded: 0),
isNotNull, isNotNull,
); );
}); });
@ -17,22 +17,19 @@ void main() {
AssetsManagerState.initial(), AssetsManagerState.initial(),
equals( equals(
AssetsManagerState( AssetsManagerState(
loadables: const [], assetsCount: 0,
loaded: const [], loaded: 0,
), ),
), ),
); );
}); });
group('progress', () { group('progress', () {
final future1 = Future<void>.value();
final future2 = Future<void>.value();
test('returns 0 when no future is loaded', () { test('returns 0 when no future is loaded', () {
expect( expect(
AssetsManagerState( AssetsManagerState(
loadables: [future1, future2], assetsCount: 2,
loaded: const [], loaded: 0,
).progress, ).progress,
equals(0), equals(0),
); );
@ -41,8 +38,8 @@ void main() {
test('returns the correct value when some of the futures are loaded', () { test('returns the correct value when some of the futures are loaded', () {
expect( expect(
AssetsManagerState( AssetsManagerState(
loadables: [future1, future2], assetsCount: 2,
loaded: [future1], loaded: 1,
).progress, ).progress,
equals(0.5), equals(0.5),
); );
@ -51,8 +48,8 @@ void main() {
test('returns the 1 when all futures are loaded', () { test('returns the 1 when all futures are loaded', () {
expect( expect(
AssetsManagerState( AssetsManagerState(
loadables: [future1, future2], assetsCount: 2,
loaded: [future1, future2], loaded: 2,
).progress, ).progress,
equals(1), equals(1),
); );
@ -60,18 +57,16 @@ void main() {
}); });
group('copyWith', () { group('copyWith', () {
final future = Future<void>.value(); test('returns a copy with the updated assetsCount', () {
test('returns a copy with the updated loadables', () {
expect( expect(
AssetsManagerState( AssetsManagerState(
loadables: const [], assetsCount: 0,
loaded: const [], loaded: 0,
).copyWith(loadables: [future]), ).copyWith(assetsCount: 1),
equals( equals(
AssetsManagerState( AssetsManagerState(
loadables: [future], assetsCount: 1,
loaded: const [], loaded: 0,
), ),
), ),
); );
@ -80,13 +75,13 @@ void main() {
test('returns a copy with the updated loaded', () { test('returns a copy with the updated loaded', () {
expect( expect(
AssetsManagerState( AssetsManagerState(
loadables: const [], assetsCount: 0,
loaded: const [], loaded: 0,
).copyWith(loaded: [future]), ).copyWith(loaded: 1),
equals( equals(
AssetsManagerState( AssetsManagerState(
loadables: const [], assetsCount: 0,
loaded: [future], loaded: 1,
), ),
), ),
); );
@ -94,47 +89,29 @@ void main() {
}); });
test('supports value comparison', () { test('supports value comparison', () {
final future1 = Future<void>.value();
final future2 = Future<void>.value();
expect(
AssetsManagerState(
loadables: const [],
loaded: const [],
),
equals(
AssetsManagerState(
loadables: const [],
loaded: const [],
),
),
);
expect( expect(
AssetsManagerState( AssetsManagerState(
loadables: [future1], assetsCount: 0,
loaded: const [], loaded: 0,
), ),
isNot(
equals( equals(
AssetsManagerState( AssetsManagerState(
loadables: [future2], assetsCount: 0,
loaded: const [], loaded: 0,
),
), ),
), ),
); );
expect( expect(
AssetsManagerState( AssetsManagerState(
loadables: const [], assetsCount: 1,
loaded: [future1], loaded: 0,
), ),
isNot( isNot(
equals( equals(
AssetsManagerState( AssetsManagerState(
loadables: const [], assetsCount: 1,
loaded: [future2], loaded: 1,
), ),
), ),
), ),

@ -12,9 +12,9 @@ void main() {
late AssetsManagerCubit assetsManagerCubit; late AssetsManagerCubit assetsManagerCubit;
setUp(() { setUp(() {
final initialAssetsState = AssetsManagerState( const initialAssetsState = AssetsManagerState(
loadables: [Future<void>.value()], assetsCount: 1,
loaded: const [], loaded: 0,
); );
assetsManagerCubit = _MockAssetsManagerCubit(); assetsManagerCubit = _MockAssetsManagerCubit();
whenListen( whenListen(

@ -53,6 +53,20 @@ void main() {
}, },
); );
flameTester.test('sets zoom on resize', (game) async {
final behavior = CameraFocusingBehavior();
await game.ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [behavior],
),
);
game.onGameResize(game.canvasSize * 2);
expect(game.camera.zoom, equals(6.55));
});
flameTester.test( flameTester.test(
'listenWhen only listens when status changes', 'listenWhen only listens when status changes',
(game) async { (game) async {

@ -86,6 +86,8 @@ class _MockPlungerCubit extends Mock implements PlungerCubit {}
class _MockGoogleWordCubit extends Mock implements GoogleWordCubit {} class _MockGoogleWordCubit extends Mock implements GoogleWordCubit {}
class _MockFlipperCubit extends Mock implements FlipperCubit {}
class _MockAppLocalizations extends Mock implements AppLocalizations { class _MockAppLocalizations extends Mock implements AppLocalizations {
@override @override
String get score => ''; String get score => '';
@ -199,7 +201,12 @@ void main() {
final behavior = FlipperKeyControllingBehavior(); final behavior = FlipperKeyControllingBehavior();
await game.pump([component, backbox, flipper]); await game.pump([component, backbox, flipper]);
await flipper.ensureAdd(behavior); await flipper.ensureAdd(
FlameBlocProvider<FlipperCubit, FlipperState>(
create: _MockFlipperCubit.new,
children: [behavior],
),
);
expect(state.status, GameStatus.gameOver); expect(state.status, GameStatus.gameOver);
component.onNewState(state); component.onNewState(state);
@ -366,12 +373,18 @@ void main() {
final flipper = Flipper.test(side: BoardSide.left); final flipper = Flipper.test(side: BoardSide.left);
await game.pump([component, backbox, flipper]); await game.pump([component, backbox, flipper]);
await flipper.ensureAdd(
FlameBlocProvider<FlipperCubit, FlipperState>(
create: _MockFlipperCubit.new,
),
);
component.onNewState(state); component.onNewState(state);
await game.ready(); await game.ready();
expect( expect(
flipper.children flipper
.descendants()
.whereType<FlipperKeyControllingBehavior>() .whereType<FlipperKeyControllingBehavior>()
.length, .length,
equals(1), equals(1),

@ -35,7 +35,7 @@ class _TestPinballGame extends PinballGame {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
images.prefix = ''; images.prefix = '';
final futures = preLoadAssets(); final futures = preLoadAssets().map((loadableBuilder) => loadableBuilder());
await Future.wait<void>(futures); await Future.wait<void>(futures);
await super.onLoad(); await super.onLoad();
} }
@ -56,7 +56,7 @@ class _TestDebugPinballGame extends DebugPinballGame {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
images.prefix = ''; images.prefix = '';
final futures = preLoadAssets(); final futures = preLoadAssets().map((loadableBuilder) => loadableBuilder());
await Future.wait<void>(futures); await Future.wait<void>(futures);
await super.onLoad(); await super.onLoad();
} }
@ -215,7 +215,8 @@ void main() {
'paints sprites with FilterQuality.medium', 'paints sprites with FilterQuality.medium',
setUp: (game, tester) async { setUp: (game, tester) async {
game.images.prefix = ''; game.images.prefix = '';
final futures = game.preLoadAssets(); final futures =
game.preLoadAssets().map((loadableBuilder) => loadableBuilder());
await Future.wait<void>(futures); await Future.wait<void>(futures);
await game.ready(); await game.ready();
@ -259,13 +260,19 @@ void main() {
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition); when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw); when(() => tapDownEvent.raw).thenReturn(raw);
final flippers = game.descendants().whereType<Flipper>().where(
(flipper) => flipper.side == BoardSide.left,
);
game.onTapDown(0, tapDownEvent); game.onTapDown(0, tapDownEvent);
await Future<void>.delayed(Duration.zero);
expect(flippers.first.body.linearVelocity.y, isNegative); final flipperBloc = game
.descendants()
.whereType<Flipper>()
.where((flipper) => flipper.side == BoardSide.left)
.single
.descendants()
.whereType<FlameBlocProvider<FlipperCubit, FlipperState>>()
.first
.bloc;
expect(flipperBloc.state, FlipperState.movingUp);
}); });
flameTester.test('tap down moves right flipper up', (game) async { flameTester.test('tap down moves right flipper up', (game) async {
@ -282,13 +289,19 @@ void main() {
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition); when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw); when(() => tapDownEvent.raw).thenReturn(raw);
final flippers = game.descendants().whereType<Flipper>().where(
(flipper) => flipper.side == BoardSide.right,
);
game.onTapDown(0, tapDownEvent); game.onTapDown(0, tapDownEvent);
final flipperBloc = game
.descendants()
.whereType<Flipper>()
.where((flipper) => flipper.side == BoardSide.right)
.single
.descendants()
.whereType<FlameBlocProvider<FlipperCubit, FlipperState>>()
.first
.bloc;
expect(flippers.first.body.linearVelocity.y, isNegative); await Future<void>.delayed(Duration.zero);
expect(flipperBloc.state, FlipperState.movingUp);
}); });
flameTester.test('tap up moves flipper down', (game) async { flameTester.test('tap up moves flipper down', (game) async {
@ -298,28 +311,22 @@ void main() {
when(() => eventPosition.game).thenReturn(Vector2.zero()); when(() => eventPosition.game).thenReturn(Vector2.zero());
when(() => eventPosition.widget).thenReturn(Vector2.zero()); when(() => eventPosition.widget).thenReturn(Vector2.zero());
final raw = _MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = _MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final flippers = game.descendants().whereType<Flipper>().where(
(flipper) => flipper.side == BoardSide.left,
);
game.onTapDown(0, tapDownEvent);
expect(flippers.first.body.linearVelocity.y, isNegative);
final tapUpEvent = _MockTapUpInfo(); final tapUpEvent = _MockTapUpInfo();
when(() => tapUpEvent.eventPosition).thenReturn(eventPosition); when(() => tapUpEvent.eventPosition).thenReturn(eventPosition);
game.onTapUp(0, tapUpEvent); game.onTapUp(0, tapUpEvent);
await game.ready(); await game.ready();
expect(flippers.first.body.linearVelocity.y, isPositive); final flipperBloc = game
.descendants()
.whereType<Flipper>()
.where((flipper) => flipper.side == BoardSide.left)
.single
.descendants()
.whereType<FlameBlocProvider<FlipperCubit, FlipperState>>()
.first
.bloc;
expect(flipperBloc.state, FlipperState.movingDown);
}); });
flameTester.test('tap cancel moves flipper down', (game) async { flameTester.test('tap cancel moves flipper down', (game) async {
@ -336,17 +343,19 @@ void main() {
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition); when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw); when(() => tapDownEvent.raw).thenReturn(raw);
final flippers = game.descendants().whereType<Flipper>().where( final flipperBloc = game
(flipper) => flipper.side == BoardSide.left, .descendants()
); .whereType<Flipper>()
.where((flipper) => flipper.side == BoardSide.left)
.single
.descendants()
.whereType<FlameBlocProvider<FlipperCubit, FlipperState>>()
.first
.bloc;
game.onTapDown(0, tapDownEvent); game.onTapDown(0, tapDownEvent);
expect(flippers.first.body.linearVelocity.y, isNegative);
game.onTapCancel(0); game.onTapCancel(0);
expect(flipperBloc.state, FlipperState.movingDown);
expect(flippers.first.body.linearVelocity.y, isPositive);
}); });
flameTester.test( flameTester.test(
@ -375,17 +384,25 @@ void main() {
.thenReturn(rightEventPosition); .thenReturn(rightEventPosition);
when(() => rightTapDownEvent.raw).thenReturn(raw); when(() => rightTapDownEvent.raw).thenReturn(raw);
game.onTapDown(0, leftTapDownEvent);
game.onTapDown(1, rightTapDownEvent);
final flippers = game.descendants().whereType<Flipper>(); final flippers = game.descendants().whereType<Flipper>();
final rightFlipper = flippers.elementAt(0); final rightFlipper = flippers.elementAt(0);
final leftFlipper = flippers.elementAt(1); final leftFlipper = flippers.elementAt(1);
final leftFlipperBloc = leftFlipper
.descendants()
.whereType<FlameBlocProvider<FlipperCubit, FlipperState>>()
.first
.bloc;
final rightFlipperBloc = rightFlipper
.descendants()
.whereType<FlameBlocProvider<FlipperCubit, FlipperState>>()
.first
.bloc;
game.onTapDown(0, leftTapDownEvent); expect(leftFlipperBloc.state, equals(FlipperState.movingUp));
game.onTapDown(1, rightTapDownEvent); expect(rightFlipperBloc.state, equals(FlipperState.movingUp));
expect(leftFlipper.body.linearVelocity.y, isNegative);
expect(leftFlipper.side, equals(BoardSide.left));
expect(rightFlipper.body.linearVelocity.y, isNegative);
expect(rightFlipper.side, equals(BoardSide.right));
expect( expect(
game.focusedBoardSide, game.focusedBoardSide,
@ -396,7 +413,7 @@ void main() {
}); });
group('plunger control', () { group('plunger control', () {
flameTester.test('tap down emits plunging', (game) async { flameTester.test('plunger control tap down emits plunging', (game) async {
await game.ready(); await game.ready();
final eventPosition = _MockEventPosition(); final eventPosition = _MockEventPosition();

@ -37,9 +37,13 @@ class _TestPinballGame extends PinballGame {
images.prefix = ''; images.prefix = '';
final futures = [ final futures = [
...preLoadAssets(), ...preLoadAssets(),
preFetchLeaderboard(), ...BonusAnimation.loadAssets(),
...SelectedCharacter.loadAssets(),
preFetchLeaderboard,
]; ];
await Future.wait<void>(futures); await Future.wait<void>(
futures.map((loadableBuilder) => loadableBuilder()).toList(),
);
return super.onLoad(); return super.onLoad();
} }
@ -78,7 +82,9 @@ void main() {
late GameBloc gameBloc; late GameBloc gameBloc;
setUp(() async { setUp(() async {
await Future.wait<void>(game.preLoadAssets()); await Future.wait<void>(
game.preLoadAssets().map((loadableBuilder) => loadableBuilder()),
);
characterThemeCubit = _MockCharacterThemeCubit(); characterThemeCubit = _MockCharacterThemeCubit();
gameBloc = _MockGameBloc(); gameBloc = _MockGameBloc();
@ -122,8 +128,8 @@ void main() {
(tester) async { (tester) async {
final assetsManagerCubit = _MockAssetsManagerCubit(); final assetsManagerCubit = _MockAssetsManagerCubit();
final initialAssetsState = AssetsManagerState( final initialAssetsState = AssetsManagerState(
loadables: [Future<void>.value()], assetsCount: 1,
loaded: const [], loaded: 0,
); );
whenListen( whenListen(
assetsManagerCubit, assetsManagerCubit,
@ -146,8 +152,8 @@ void main() {
final startGameBloc = _MockStartGameBloc(); final startGameBloc = _MockStartGameBloc();
final loadedAssetsState = AssetsManagerState( final loadedAssetsState = AssetsManagerState(
loadables: [Future<void>.value()], assetsCount: 1,
loaded: [Future<void>.value()], loaded: 1,
); );
whenListen( whenListen(
assetsManagerCubit, assetsManagerCubit,
@ -179,7 +185,9 @@ void main() {
final startGameBloc = _MockStartGameBloc(); final startGameBloc = _MockStartGameBloc();
setUp(() async { setUp(() async {
await Future.wait<void>(game.preLoadAssets()); await Future.wait<void>(
game.preLoadAssets().map((loadableBuilder) => loadableBuilder()),
);
whenListen( whenListen(
gameBloc, gameBloc,

@ -34,15 +34,15 @@ class _MockPlatformHelper extends Mock implements PlatformHelper {}
PinballAudioPlayer _buildDefaultPinballAudioPlayer() { PinballAudioPlayer _buildDefaultPinballAudioPlayer() {
final audioPlayer = _MockPinballAudioPlayer(); final audioPlayer = _MockPinballAudioPlayer();
when(audioPlayer.load).thenAnswer((_) => [Future.value()]); when(audioPlayer.load).thenAnswer((_) => [Future.value]);
return audioPlayer; return audioPlayer;
} }
AssetsManagerCubit _buildDefaultAssetsManagerCubit() { AssetsManagerCubit _buildDefaultAssetsManagerCubit() {
final cubit = _MockAssetsManagerCubit(); final cubit = _MockAssetsManagerCubit();
final state = AssetsManagerState( const state = AssetsManagerState(
loadables: [Future<void>.value()], assetsCount: 1,
loaded: [Future<void>.value()], loaded: 1,
); );
whenListen( whenListen(
cubit, cubit,

Loading…
Cancel
Save