feat: provide and listen to `CharacterThemeCubit` in game (#375)

* feat: connect game ball to theme selection

* chore: swap out ball assets

* chore: swap out flipper assets

* fix: ball spawn layer

* refactor: use readBloc and fix tests

* refactor: update ball sprite only
pull/358/head
Allison Ryan 2 years ago committed by alestiago
parent 2d0d01fa06
commit b6ef9f73f6

@ -1,9 +1,9 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
/// Spawns a new [Ball] into the game when all balls are lost and still
/// [GameStatus.playing].
@ -23,7 +23,9 @@ class BallSpawningBehavior extends Component
void onNewState(GameState state) {
final plunger = gameRef.descendants().whereType<Plunger>().single;
final canvas = gameRef.descendants().whereType<ZCanvasComponent>().single;
final characterTheme = readProvider<CharacterTheme>();
final characterTheme = readBloc<CharacterThemeCubit, CharacterThemeState>()
.state
.characterTheme;
final ball = Ball(assetPath: characterTheme.ball.keyName)
..initialPosition = Vector2(
plunger.body.position.x,

@ -0,0 +1,20 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_components/pinball_components.dart';
/// Updates the launch [Ball] to reflect character selections.
class BallThemingBehavior extends Component
with
FlameBlocListenable<CharacterThemeCubit, CharacterThemeState>,
HasGameRef {
@override
void onNewState(CharacterThemeState state) {
gameRef
.descendants()
.whereType<Ball>()
.single
.bloc
.onThemeChanged(state.characterTheme);
}
}

@ -1,4 +1,5 @@
export 'ball_spawning_behavior.dart';
export 'ball_theming_behavior.dart';
export 'bonus_noise_behavior.dart';
export 'bumper_noise_behavior.dart';
export 'camera_focusing_behavior.dart';

@ -1,9 +1,9 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
/// Bonus obtained at the [FlutterForest].
///
@ -41,11 +41,12 @@ class FlutterForestBonusBehavior extends Component
if (signpost.bloc.isFullyProgressed()) {
bloc.add(const BonusActivated(GameBonus.dashNest));
final characterTheme = readProvider<CharacterTheme>();
final characterTheme =
readBloc<CharacterThemeCubit, CharacterThemeState>()
.state
.characterTheme;
canvas.add(
Ball(
assetPath: characterTheme.ball.keyName,
)
Ball(assetPath: characterTheme.ball.keyName)
..initialPosition = Vector2(29.2, -24.5)
..zIndex = ZIndexes.ballOnBoard,
);

@ -1,9 +1,9 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
/// Listens to the [GameBloc] and updates the game accordingly.
class GameBlocStatusListener extends Component
@ -26,7 +26,9 @@ class GameBlocStatusListener extends Component
readProvider<PinballPlayer>().play(PinballAudio.gameOverVoiceOver);
gameRef.descendants().whereType<Backbox>().first.requestInitials(
score: state.displayScore,
character: readProvider<CharacterTheme>(),
character: readBloc<CharacterThemeCubit, CharacterThemeState>()
.state
.characterTheme,
);
break;
}

@ -11,15 +11,15 @@ import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
class PinballGame extends PinballForge2DGame
with HasKeyboardHandlerComponents, MultiTouchTapDetector {
PinballGame({
required CharacterTheme characterTheme,
required CharacterThemeCubit characterThemeBloc,
required this.leaderboardRepository,
required GameBloc gameBloc,
required AppLocalizations l10n,
@ -27,7 +27,7 @@ class PinballGame extends PinballForge2DGame
}) : focusNode = FocusNode(),
_gameBloc = gameBloc,
_player = player,
_characterTheme = characterTheme,
_characterThemeBloc = characterThemeBloc,
_l10n = l10n,
super(
gravity: Vector2(0, 30),
@ -43,7 +43,7 @@ class PinballGame extends PinballForge2DGame
final FocusNode focusNode;
final CharacterTheme _characterTheme;
final CharacterThemeCubit _characterThemeBloc;
final PinballPlayer _player;
@ -56,13 +56,19 @@ class PinballGame extends PinballForge2DGame
@override
Future<void> onLoad() async {
await add(
FlameBlocProvider<GameBloc, GameState>.value(
value: _gameBloc,
FlameMultiBlocProvider(
providers: [
FlameBlocProvider<GameBloc, GameState>.value(
value: _gameBloc,
),
FlameBlocProvider<CharacterThemeCubit, CharacterThemeState>.value(
value: _characterThemeBloc,
),
],
children: [
MultiFlameProvider(
providers: [
FlameProvider<PinballPlayer>.value(_player),
FlameProvider<CharacterTheme>.value(_characterTheme),
FlameProvider<LeaderboardRepository>.value(leaderboardRepository),
FlameProvider<AppLocalizations>.value(_l10n),
],
@ -70,6 +76,7 @@ class PinballGame extends PinballForge2DGame
BonusNoiseBehavior(),
GameBlocStatusListener(),
BallSpawningBehavior(),
BallThemingBehavior(),
CameraFocusingBehavior(),
CanvasComponent(
onSpritePainted: (paint) {
@ -161,13 +168,13 @@ class PinballGame extends PinballForge2DGame
class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
DebugPinballGame({
required CharacterTheme characterTheme,
required CharacterThemeCubit characterThemeBloc,
required LeaderboardRepository leaderboardRepository,
required AppLocalizations l10n,
required PinballPlayer player,
required GameBloc gameBloc,
}) : super(
characterTheme: characterTheme,
characterThemeBloc: characterThemeBloc,
player: player,
leaderboardRepository: leaderboardRepository,
l10n: l10n,

@ -31,21 +31,20 @@ class PinballGamePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final characterTheme =
context.read<CharacterThemeCubit>().state.characterTheme;
final characterThemeBloc = context.read<CharacterThemeCubit>();
final player = context.read<PinballPlayer>();
final leaderboardRepository = context.read<LeaderboardRepository>();
final gameBloc = context.read<GameBloc>();
final game = isDebugMode
? DebugPinballGame(
characterTheme: characterTheme,
characterThemeBloc: characterThemeBloc,
player: player,
leaderboardRepository: leaderboardRepository,
l10n: context.l10n,
gameBloc: gameBloc,
)
: PinballGame(
characterTheme: characterTheme,
characterThemeBloc: characterThemeBloc,
player: player,
leaderboardRepository: leaderboardRepository,
l10n: context.l10n,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 10 KiB

@ -1,6 +1,7 @@
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/material.dart';
import 'package:pinball_components/pinball_components.dart';
@ -8,27 +9,27 @@ import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
export 'behaviors/behaviors.dart';
export 'cubit/ball_cubit.dart';
/// {@template ball}
/// A solid, [BodyType.dynamic] sphere that rolls and bounces around.
/// {@endtemplate}
class Ball extends BodyComponent with Layered, InitialPosition, ZIndex {
/// {@macro ball}
Ball({
String? assetPath,
}) : super(
Ball({String? assetPath}) : this._(bloc: BallCubit(), assetPath: assetPath);
Ball._({required this.bloc, String? assetPath})
: super(
renderBody: false,
children: [
_BallSpriteComponent(assetPath: assetPath),
FlameBlocProvider<BallCubit, BallState>.value(
value: bloc,
children: [BallSpriteComponent(assetPath: assetPath)],
),
BallScalingBehavior(),
BallGravitatingBehavior(),
],
) {
// TODO(ruimiguel): while developing Ball can be launched by clicking mouse,
// and default layer is Layer.all. But on final game Ball will be always be
// be launched from Plunger and LauncherRamp will modify it to Layer.board.
// We need to see what happens if Ball appears from other place like nest
// bumper, it will need to explicit change layer to Layer.board then.
layer = Layer.board;
}
@ -36,11 +37,22 @@ class Ball extends BodyComponent with Layered, InitialPosition, ZIndex {
///
/// This can be used for testing [Ball]'s behaviors in isolation.
@visibleForTesting
Ball.test()
: super(
children: [_BallSpriteComponent()],
Ball.test({
BallCubit? bloc,
String? assetPath,
}) : bloc = bloc ?? BallCubit(),
super(
children: [
FlameBlocProvider<BallCubit, BallState>.value(
value: bloc ?? BallCubit(),
children: [BallSpriteComponent(assetPath: assetPath)],
)
],
);
/// Bloc to update the ball sprite when a new character is selected.
final BallCubit bloc;
/// The size of the [Ball].
static final Vector2 size = Vector2.all(4.13);
@ -76,21 +88,32 @@ class Ball extends BodyComponent with Layered, InitialPosition, ZIndex {
}
}
class _BallSpriteComponent extends SpriteComponent with HasGameRef {
_BallSpriteComponent({
this.assetPath,
}) : super(
anchor: Anchor.center,
);
/// {@template ball_sprite_component}
/// Visual representation of the [Ball].
/// {@endtemplate}
@visibleForTesting
class BallSpriteComponent extends SpriteComponent
with HasGameRef, FlameBlocListenable<BallCubit, BallState> {
/// {@macro ball_sprite_component}
BallSpriteComponent({required String? assetPath})
: _assetPath = assetPath,
super(anchor: Anchor.center);
final String? _assetPath;
final String? assetPath;
@override
void onNewState(BallState state) {
sprite = Sprite(
gameRef.images.fromCache(state.characterTheme.ball.keyName),
);
}
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = Sprite(
gameRef.images
.fromCache(assetPath ?? theme.Assets.images.dash.ball.keyName),
.fromCache(_assetPath ?? theme.Assets.images.dash.ball.keyName),
);
this.sprite = sprite;
size = sprite.originalSize / 12.5;

@ -16,9 +16,12 @@ class BallScalingBehavior extends Component with ParentIsA<Ball> {
parent.body.fixtures.first.shape.radius = (Ball.size.x / 2) * scaleFactor;
parent.firstChild<SpriteComponent>()!.scale.setValues(
scaleFactor,
scaleFactor,
);
final ballSprite = parent.descendants().whereType<SpriteComponent>();
if (ballSprite.isNotEmpty) {
ballSprite.single.scale.setValues(
scaleFactor,
scaleFactor,
);
}
}
}

@ -0,0 +1,15 @@
// ignore_for_file: public_member_api_docs
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:pinball_theme/pinball_theme.dart';
part 'ball_state.dart';
class BallCubit extends Cubit<BallState> {
BallCubit() : super(const BallState.initial());
void onThemeChanged(CharacterTheme characterTheme) {
emit(BallState(characterTheme: characterTheme));
}
}

@ -0,0 +1,14 @@
// ignore_for_file: public_member_api_docs
part of 'ball_cubit.dart';
class BallState extends Equatable {
const BallState({required this.characterTheme});
const BallState.initial() : this(characterTheme: const DashTheme());
final CharacterTheme characterTheme;
@override
List<Object> get props => [characterTheme];
}

@ -14,7 +14,7 @@ class ChromeDinoChompingBehavior extends ContactBehavior<ChromeDino> {
super.beginContact(other, contact);
if (other is! Ball) return;
other.firstChild<SpriteComponent>()!.setOpacity(0);
other.descendants().whereType<SpriteComponent>().single.setOpacity(0);
parent.bloc.onChomp(other);
}
}

@ -30,7 +30,7 @@ class ChromeDinoSpittingBehavior extends Component
void _spit() {
parent.bloc.state.ball!
..firstChild<SpriteComponent>()!.setOpacity(1)
..descendants().whereType<SpriteComponent>().single.setOpacity(1)
..body.linearVelocity = Vector2(-50, 0);
parent.bloc.onSpit();
}

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

@ -106,6 +106,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
flame_bloc:
dependency: transitive
description:
name: flame_bloc
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
flame_forge2d:
dependency: "direct main"
description:
@ -120,6 +127,13 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_bloc:
dependency: transitive
description:
name: flutter_bloc
url: "https://pub.dartlang.org"
source: hosted
version: "8.0.1"
flutter_colorpicker:
dependency: transitive
description:
@ -214,6 +228,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
nested:
dependency: transitive
description:
name: nested
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
ordered_set:
dependency: transitive
description:
@ -298,6 +319,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.4"
provider:
dependency: transitive
description:
name: provider
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.2"
shared_preferences:
dependency: transitive
description:

@ -1,5 +1,6 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
@ -39,6 +40,41 @@ void main() {
},
);
flameTester.test(
'has only one SpriteComponent',
(game) async {
final ball = Ball();
await game.ready();
await game.ensureAdd(ball);
expect(
ball.descendants().whereType<SpriteComponent>().length,
equals(1),
);
},
);
flameTester.test(
'BallSpriteComponent changes sprite onNewState',
(game) async {
final ball = Ball();
await game.ready();
await game.ensureAdd(ball);
final ballSprite =
ball.descendants().whereType<BallSpriteComponent>().single;
final originalSprite = ballSprite.sprite;
ballSprite.onNewState(
const BallState(characterTheme: theme.DinoTheme()),
);
await game.ready();
final newSprite = ballSprite.sprite;
expect(newSprite != originalSprite, isTrue);
},
);
group('adds', () {
flameTester.test('a BallScalingBehavior', (game) async {
final ball = Ball();

@ -62,8 +62,8 @@ void main() {
await game.ensureAddAll([ball1, ball2]);
game.update(1);
final sprite1 = ball1.firstChild<SpriteComponent>()!;
final sprite2 = ball2.firstChild<SpriteComponent>()!;
final sprite1 = ball1.descendants().whereType<SpriteComponent>().single;
final sprite2 = ball2.descendants().whereType<SpriteComponent>().single;
expect(
sprite1.scale.x,
greaterThan(sprite2.scale.x),

@ -0,0 +1,18 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_theme/pinball_theme.dart';
void main() {
group(
'BallCubit',
() {
blocTest<BallCubit, BallState>(
'onThemeChanged emits new theme',
build: BallCubit.new,
act: (bloc) => bloc.onThemeChanged(const DinoTheme()),
expect: () => [const BallState(characterTheme: DinoTheme())],
);
},
);
}

@ -0,0 +1,22 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_theme/pinball_theme.dart';
void main() {
group('BallState', () {
test('supports value equality', () {
expect(
BallState(characterTheme: DashTheme()),
equals(const BallState(characterTheme: DashTheme())),
);
});
group('constructor', () {
test('can be instantiated', () {
expect(const BallState(characterTheme: DashTheme()), isNotNull);
});
});
});
}

@ -61,7 +61,10 @@ void main() {
behavior.beginContact(ball, contact);
expect(ball.firstChild<SpriteComponent>()!.getOpacity(), isZero);
expect(
ball.descendants().whereType<SpriteComponent>().single.getOpacity(),
isZero,
);
verify(() => bloc.onChomp(ball)).called(1);
},

@ -66,7 +66,14 @@ void main() {
.timer
.onTick!();
expect(ball.firstChild<SpriteComponent>()!.getOpacity(), equals(1));
expect(
ball
.descendants()
.whereType<SpriteComponent>()
.single
.getOpacity(),
equals(1),
);
expect(ball.body.linearVelocity, equals(Vector2(-50, 0)));
},
);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/behaviors/ball_spawning_behavior.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
@ -18,18 +19,20 @@ class _TestGame extends Forge2DGame {
}
Future<void> pump(
Iterable<Component> children, {
List<Component> children, {
GameBloc? gameBloc,
}) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc ?? GameBloc(),
children: [
FlameProvider<theme.CharacterTheme>.value(
const theme.DashTheme(),
children: children,
FlameMultiBlocProvider(
providers: [
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc ?? GameBloc(),
),
FlameBlocProvider<CharacterThemeCubit, CharacterThemeState>.value(
value: CharacterThemeCubit(),
),
],
children: children,
),
);
}

@ -0,0 +1,93 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.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/game/behaviors/behaviors.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
class _TestGame extends Forge2DGame {
@override
Future<void> onLoad() async {
images.prefix = '';
await images.loadAll([
theme.Assets.images.dash.ball.keyName,
theme.Assets.images.dino.ball.keyName,
]);
}
Future<void> pump(
List<Component> children, {
CharacterThemeCubit? characterThemeBloc,
}) async {
await ensureAdd(
FlameBlocProvider<CharacterThemeCubit, CharacterThemeState>.value(
value: characterThemeBloc ?? CharacterThemeCubit(),
children: children,
),
);
}
}
class _MockBallCubit extends Mock implements BallCubit {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group(
'BallThemingBehavior',
() {
final flameTester = FlameTester(_TestGame.new);
test('can be instantiated', () {
expect(
BallThemingBehavior(),
isA<BallThemingBehavior>(),
);
});
flameTester.test(
'loads',
(game) async {
final behavior = BallThemingBehavior();
await game.pump([behavior]);
expect(game.descendants(), contains(behavior));
},
);
flameTester.test(
'onNewState calls onThemeChanged on the ball bloc',
(game) async {
final ballBloc = _MockBallCubit();
whenListen(
ballBloc,
const Stream<BallState>.empty(),
initialState: const BallState.initial(),
);
final ball = Ball.test(bloc: ballBloc);
final behavior = BallThemingBehavior();
await game.pump([
ball,
behavior,
ZCanvasComponent(),
Plunger.test(compressionDistance: 10),
]);
const dinoThemeState = CharacterThemeState(theme.DinoTheme());
behavior.onNewState(dinoThemeState);
await game.ready();
verify(() => ballBloc.onThemeChanged(dinoThemeState.characterTheme))
.called(1);
},
);
},
);
}

@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/components/flutter_forest/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
@ -26,16 +27,16 @@ class _TestGame extends Forge2DGame {
required GameBloc gameBloc,
}) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,
FlameMultiBlocProvider(
providers: [
FlameBlocProvider<GameBloc, GameState>.value(value: gameBloc),
FlameBlocProvider<CharacterThemeCubit, CharacterThemeState>.value(
value: CharacterThemeCubit(),
),
],
children: [
FlameProvider<theme.CharacterTheme>.value(
const theme.DashTheme(),
children: [
ZCanvasComponent(
children: [child],
),
],
ZCanvasComponent(
children: [child],
),
],
),

@ -8,10 +8,10 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
class _TestGame extends Forge2DGame {
@override
@ -25,18 +25,18 @@ class _TestGame extends Forge2DGame {
PinballPlayer? pinballPlayer,
}) async {
return ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
FlameMultiBlocProvider(
providers: [
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
),
FlameBlocProvider<CharacterThemeCubit, CharacterThemeState>.value(
value: CharacterThemeCubit(),
),
],
children: [
MultiFlameProvider(
providers: [
FlameProvider<PinballPlayer>.value(
pinballPlayer ?? _MockPinballPlayer(),
),
FlameProvider<theme.CharacterTheme>.value(
const theme.DashTheme(),
),
],
FlameProvider<PinballPlayer>.value(
pinballPlayer ?? _MockPinballPlayer(),
children: children,
),
],

@ -13,14 +13,14 @@ import 'package:leaderboard_repository/src/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_audio/src/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
class _TestPinballGame extends PinballGame {
_TestPinballGame()
: super(
characterTheme: const theme.DashTheme(),
characterThemeBloc: CharacterThemeCubit(),
leaderboardRepository: _MockLeaderboardRepository(),
gameBloc: GameBloc(),
l10n: _MockAppLocalizations(),
@ -39,7 +39,7 @@ class _TestPinballGame extends PinballGame {
class _TestDebugPinballGame extends DebugPinballGame {
_TestDebugPinballGame()
: super(
characterTheme: const theme.DashTheme(),
characterThemeBloc: CharacterThemeCubit(),
leaderboardRepository: _MockLeaderboardRepository(),
gameBloc: GameBloc(),
l10n: _MockAppLocalizations(),
@ -109,6 +109,17 @@ void main() {
},
);
flameTester.test(
'has only one BallThemingBehavior',
(game) async {
await game.ready();
expect(
game.descendants().whereType<BallThemingBehavior>().length,
equals(1),
);
},
);
flameTester.test(
'has only one Drain',
(game) async {

@ -16,14 +16,13 @@ import 'package:pinball/more_information/more_information.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
import '../../helpers/helpers.dart';
class _TestPinballGame extends PinballGame {
_TestPinballGame()
: super(
characterTheme: const theme.DashTheme(),
characterThemeBloc: CharacterThemeCubit(),
leaderboardRepository: _MockLeaderboardRepository(),
gameBloc: GameBloc(),
l10n: _MockAppLocalizations(),

Loading…
Cancel
Save