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
/// the UI paint first, and then we start loading the assets.
await Future<void>.delayed(const Duration(seconds: 1));
emit(
state.copyWith(
loadables: [
_game.preFetchLeaderboard(),
final loadables = <Future<void> Function()>[
_game.preFetchLeaderboard,
..._game.preLoadAssets(),
..._audioPlayer.load(),
...BonusAnimation.loadAssets(),
...SelectedCharacter.loadAssets(),
],
];
emit(
state.copyWith(
assetsCount: loadables.length,
),
);
final all = state.loadables.map((loadable) async {
await loadable;
emit(state.copyWith(loaded: [...state.loaded, loadable]));
}).toList();
await Future.wait(all);
late void Function() _triggerLoad;
_triggerLoad = () async {
if (loadables.isEmpty) return;
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';
/// {@template assets_manager_state}
/// State used to load the game assets
/// State used to load the game assets.
/// {@endtemplate}
class AssetsManagerState extends Equatable {
/// {@macro assets_manager_state}
const AssetsManagerState({
required this.loadables,
required this.assetsCount,
required this.loaded,
});
/// {@macro assets_manager_state}
const AssetsManagerState.initial()
: this(loadables: const [], loaded: const []);
const AssetsManagerState.initial() : this(assetsCount: 0, loaded: 0);
/// List of futures to load
final List<Future> loadables;
/// Number of assets to load.
final int assetsCount;
/// List of loaded futures
final List<Future> loaded;
/// Number of already loaded assets.
final int loaded;
/// Returns a value between 0 and 1 to indicate the loading progress
double get progress =>
loadables.isEmpty ? 0 : loaded.length / loadables.length;
/// Returns a value between 0 and 1 to indicate the loading progress.
double get progress => loaded == 0 ? 0 : loaded / assetsCount;
/// 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;
/// Returns a copy of this instance with the given parameters
/// updated
/// updated.
AssetsManagerState copyWith({
List<Future>? loadables,
List<Future>? loaded,
int? assetsCount,
int? loaded,
}) {
return AssetsManagerState(
loadables: loadables ?? this.loadables,
assetsCount: assetsCount ?? this.assetsCount,
loaded: loaded ?? this.loaded,
);
}
@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/game.dart';
import 'package:flame_bloc/flame_bloc.dart';
@ -7,9 +9,9 @@ import 'package:pinball_components/pinball_components.dart';
/// {@template focus_data}
/// Defines a [Camera] focus point.
/// {@endtemplate}
class FocusData {
/// {@template focus_data}
FocusData({
class _FocusData {
/// {@macro focus_data}
const _FocusData({
required this.zoom,
required this.position,
});
@ -24,7 +26,11 @@ class FocusData {
/// Changes the game focus when the [GameBloc] status changes.
class CameraFocusingBehavior extends Component
with FlameBlocListenable<GameBloc, GameState>, HasGameRef {
late final Map<String, FocusData> _foci;
final Map<GameStatus, _FocusData> _foci = {};
GameStatus? _activeFocus;
final _previousSize = Vector2.zero();
@override
bool listenWhen(GameState? previousState, GameState newState) {
@ -32,51 +38,62 @@ class CameraFocusingBehavior extends Component
}
@override
void onNewState(GameState state) {
switch (state.status) {
case GameStatus.waiting:
break;
case GameStatus.playing:
_zoom(_foci['game']!);
break;
case GameStatus.gameOver:
_zoom(_foci['backbox']!);
break;
}
}
void onNewState(GameState state) => _zoomTo(state.status);
@override
Future<void> onLoad() async {
await super.onLoad();
_foci = {
'game': FocusData(
zoom: gameRef.size.y / 16,
position: Vector2(0, -7.8),
),
'waiting': FocusData(
zoom: gameRef.size.y / 18,
void onGameResize(Vector2 size) {
super.onGameResize(size);
if (size == _previousSize) {
return;
}
_previousSize.setFrom(size);
final maxWidth = size.x / 90;
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),
),
'backbox': FocusData(
zoom: gameRef.size.y / 10,
GameStatus.playing: _FocusData(
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),
),
};
});
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
..speed = 100
..followVector2(data.position)
..zoom = data.zoom;
..followVector2(focusData.position)
..zoom = focusData.zoom;
}
void _zoom(FocusData data) {
final zoom = CameraZoom(value: data.zoom);
void _zoomTo(GameStatus focusKey) {
final focusData = _foci[_activeFocus = focusKey]!;
final zoom = CameraZoom(value: focusData.zoom);
zoom.completed.then((_) {
gameRef.camera.moveTo(data.position);
gameRef.camera.moveTo(focusData.position);
});
add(zoom);
}

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

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

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

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

@ -334,10 +334,10 @@ class PinballAudioPlayer {
late final Map<PinballAudio, _Audio> audios;
/// Loads the sounds effects into the memory.
List<Future<void>> load() {
List<Future<void> Function()> load() {
_configureAudioCache(FlameAudio.audioCache);
return audios.values.map((a) => a.load()).toList();
return audios.values.map((a) => a.load).toList();
}
/// Plays the received audio.

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

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

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

@ -1,2 +1,3 @@
export 'flipper_jointing_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_bloc/flame_bloc.dart';
import 'package:flutter/services.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// Allows controlling the [Flipper]'s movement with keyboard input.
class FlipperKeyControllingBehavior extends Component
with KeyboardHandler, ParentIsA<Flipper> {
with KeyboardHandler, FlameBlocReader<FlipperCubit, FlipperState> {
/// The [LogicalKeyboardKey]s that will control the [Flipper].
///
/// [onKeyEvent] method listens to when one of these keys is pressed.
@ -14,8 +14,8 @@ class FlipperKeyControllingBehavior extends Component
@override
Future<void> onLoad() async {
await super.onLoad();
switch (parent.side) {
final flipper = parent!.parent! as Flipper;
switch (flipper.side) {
case BoardSide.left:
_keys = [
LogicalKeyboardKey.arrowLeft,
@ -39,9 +39,9 @@ class FlipperKeyControllingBehavior extends Component
if (!_keys.contains(event.logicalKey)) return true;
if (event is RawKeyDownEvent) {
parent.moveUp();
bloc.moveUp();
} else if (event is RawKeyUpEvent) {
parent.moveDown();
bloc.moveDown();
}
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 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/foundation.dart';
import 'package:pinball_components/pinball_components.dart';
export 'behaviors/behaviors.dart';
export 'cubit/flipper_cubit.dart';
/// {@template flipper}
/// A bat, typically found in pairs at the bottom of the board.
@ -21,6 +23,10 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
children: [
_FlipperSpriteComponent(side: side),
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].
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.
///
/// A [Flipper] with [BoardSide.left] has a counter-clockwise arc motion,
/// whereas a [Flipper] with [BoardSide.right] has a clockwise arc motion.
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() {
final direction = side.direction;

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

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

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

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

@ -1,10 +1,18 @@
import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class PlungerGame extends BallGame
with HasKeyboardHandlerComponents, Traceable {
PlungerGame()
: super(
imagesFileNames: [
Assets.images.plunger.plunger.keyName,
],
);
static const description = '''
Shows how Plunger is rendered.
@ -19,7 +27,12 @@ class PlungerGame extends BallGame
final center = screenToWorld(camera.viewport.canvasSize! / 2);
final plunger = Plunger()
..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 traceAllBodies();

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

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

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

@ -1,15 +1,14 @@
// ignore_for_file: cascade_invocations
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/src/components/components.dart';
import '../../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('FlipperJointingBehavior', () {
final flameTester = FlameTester(TestGame.new);
final flameTester = FlameTester(Forge2DGame.new);
test('can be instantiated', () {
expect(

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

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

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

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

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

@ -12,9 +12,9 @@ void main() {
late AssetsManagerCubit assetsManagerCubit;
setUp(() {
final initialAssetsState = AssetsManagerState(
loadables: [Future<void>.value()],
loaded: const [],
const initialAssetsState = AssetsManagerState(
assetsCount: 1,
loaded: 0,
);
assetsManagerCubit = _MockAssetsManagerCubit();
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(
'listenWhen only listens when status changes',
(game) async {

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

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

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

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

Loading…
Cancel
Save