Merge branch 'main' into feat/multiball-asset

pull/235/head
RuiAlonso 3 years ago
commit e7368000d1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 200 KiB

@ -18,17 +18,17 @@ class AndroidAcres extends Component {
AndroidSpaceship(position: Vector2(-26.5, -28.5)),
AndroidBumper.a(
children: [
ScoringBehavior(points: 20000),
ScoringBehavior(points: Points.twentyThousand),
],
)..initialPosition = Vector2(-25, 1.3),
AndroidBumper.b(
children: [
ScoringBehavior(points: 20000),
ScoringBehavior(points: Points.twentyThousand),
],
)..initialPosition = Vector2(-32.8, -9.2),
AndroidBumper.cow(
children: [
ScoringBehavior(points: 20),
ScoringBehavior(points: Points.twentyThousand),
],
)..initialPosition = Vector2(-20.5, -13.8),
],

@ -51,7 +51,7 @@ class _BottomGroupSide extends Component {
final kicker = Kicker(
side: _side,
children: [
ScoringBehavior(points: 5000)..applyTo(['bouncy_edge']),
ScoringBehavior(points: Points.fiveThousand)..applyTo(['bouncy_edge']),
],
)..initialPosition = Vector2(
(22.64 * direction) + centerXAdjustment,

@ -14,7 +14,8 @@ class DinoDesert extends Component {
children: [
ChromeDino(
children: [
ScoringBehavior(points: 200000)..applyTo(['inside_mouth']),
ScoringBehavior(points: Points.twoHundredThousand)
..applyTo(['inside_mouth']),
],
)..initialPosition = Vector2(12.6, -6.9),
_BarrierBehindDino(),

@ -18,22 +18,22 @@ class FlutterForest extends Component with ZIndex {
children: [
Signpost(
children: [
ScoringBehavior(points: 20),
ScoringBehavior(points: Points.fiveThousand),
],
)..initialPosition = Vector2(8.35, -58.3),
DashNestBumper.main(
children: [
ScoringBehavior(points: 200000),
ScoringBehavior(points: Points.twoHundredThousand),
],
)..initialPosition = Vector2(18.55, -59.35),
DashNestBumper.a(
children: [
ScoringBehavior(points: 20000),
ScoringBehavior(points: Points.twentyThousand),
],
)..initialPosition = Vector2(8.95, -51.95),
DashNestBumper.b(
children: [
ScoringBehavior(points: 20000),
ScoringBehavior(points: Points.twentyThousand),
],
)..initialPosition = Vector2(22.3, -46.75),
DashAnimatronic()..position = Vector2(20, -66),

@ -16,27 +16,27 @@ class GoogleWord extends Component with ZIndex {
children: [
GoogleLetter(
0,
children: [ScoringBehavior(points: 5000)],
children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(-13.1, 1.72),
GoogleLetter(
1,
children: [ScoringBehavior(points: 5000)],
children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(-8.33, -0.75),
GoogleLetter(
2,
children: [ScoringBehavior(points: 5000)],
children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(-2.88, -1.85),
GoogleLetter(
3,
children: [ScoringBehavior(points: 5000)],
children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(2.88, -1.85),
GoogleLetter(
4,
children: [ScoringBehavior(points: 5000)],
children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(8.33, -0.75),
GoogleLetter(
5,
children: [ScoringBehavior(points: 5000)],
children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(13.1, 1.72),
GoogleWordBonusBehavior(),
],

@ -12,21 +12,21 @@ import 'package:pinball_flame/pinball_flame.dart';
class ScoringBehavior extends ContactBehavior with HasGameRef<PinballGame> {
/// {@macro scoring_behavior}
ScoringBehavior({
required int points,
required Points points,
}) : _points = points;
final int _points;
final Points _points;
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
gameRef.read<GameBloc>().add(Scored(points: _points));
gameRef.read<GameBloc>().add(Scored(points: _points.value));
gameRef.audio.score();
gameRef.firstChild<ZCanvasComponent>()!.add(
ScoreText(
text: _points.toString(),
ScoreComponent(
points: _points,
position: other.body.position,
),
);

@ -16,17 +16,17 @@ class SparkyScorch extends Component {
children: [
SparkyBumper.a(
children: [
ScoringBehavior(points: 20000),
ScoringBehavior(points: Points.twentyThousand),
],
)..initialPosition = Vector2(-22.9, -41.65),
SparkyBumper.b(
children: [
ScoringBehavior(points: 20000),
ScoringBehavior(points: Points.twentyThousand),
],
)..initialPosition = Vector2(-21.25, -57.9),
SparkyBumper.c(
children: [
ScoringBehavior(points: 20000),
ScoringBehavior(points: Points.twentyThousand),
],
)..initialPosition = Vector2(-3.3, -52.55),
SparkyComputerSensor()..initialPosition = Vector2(-13, -49.9),
@ -47,7 +47,7 @@ class SparkyComputerSensor extends BodyComponent
: super(
renderBody: false,
children: [
ScoringBehavior(points: 200000),
ScoringBehavior(points: Points.twentyThousand),
],
);

@ -126,6 +126,10 @@ extension PinballGameAssetsX on PinballGame {
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(dashTheme.leaderboardIcon.keyName),
images.load(sparkyTheme.leaderboardIcon.keyName),
images.load(androidTheme.leaderboardIcon.keyName),

@ -44,15 +44,14 @@ class PinballGamePage extends StatelessWidget {
...game.preLoadAssets(),
pinballAudio.load(),
...BonusAnimation.loadAssets(),
...SelectedCharacter.loadAssets(),
];
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => StartGameBloc(game: game)),
BlocProvider(create: (_) => GameBloc()),
BlocProvider(
create: (_) => AssetsManagerCubit(loadables)..load(),
),
BlocProvider(create: (_) => AssetsManagerCubit(loadables)..load()),
],
child: PinballGameView(game: game),
);

@ -126,7 +126,7 @@ class _BonusAnimationState extends State<BonusAnimation>
);
animation = spriteSheet.createAnimation(
row: 0,
stepTime: 1 / 24,
stepTime: 1 / 12,
to: spriteSheet.rows * spriteSheet.columns,
loop: false,
);

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/how_to_play/how_to_play.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/cubit/character_theme_cubit.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_theme/pinball_theme.dart';
import 'package:pinball_ui/pinball_ui.dart';
@ -118,19 +117,7 @@ class _CharacterPreview extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<CharacterThemeCubit, CharacterThemeState>(
builder: (context, state) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
state.characterTheme.name,
style: Theme.of(context).textTheme.headline2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
Expanded(child: state.characterTheme.icon.image()),
],
);
return SelectedCharacter(currentCharacter: state.characterTheme);
},
);
}

@ -0,0 +1,102 @@
import 'package:flame/components.dart';
import 'package:flame/flame.dart';
import 'package:flame/sprite.dart';
import 'package:flutter/material.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
/// {@template selected_character}
/// Shows an animated version of the character currently selected.
/// {@endtemplate}
class SelectedCharacter extends StatefulWidget {
/// {@macro selected_character}
const SelectedCharacter({
Key? key,
required this.currentCharacter,
}) : super(key: key);
/// The character that is selected at the moment.
final CharacterTheme currentCharacter;
@override
State<SelectedCharacter> createState() => _SelectedCharacterState();
/// Returns a list of assets to be loaded.
static List<Future> 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),
];
}
}
class _SelectedCharacterState extends State<SelectedCharacter>
with TickerProviderStateMixin {
SpriteAnimationController? _controller;
@override
void initState() {
super.initState();
_setupCharacterAnimation();
}
@override
void didUpdateWidget(covariant SelectedCharacter oldWidget) {
super.didUpdateWidget(oldWidget);
_setupCharacterAnimation();
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
widget.currentCharacter.name,
style: Theme.of(context).textTheme.headline2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
child: SpriteAnimationWidget(
controller: _controller!,
anchor: Anchor.center,
),
);
},
),
),
],
);
}
void _setupCharacterAnimation() {
final spriteSheet = SpriteSheet.fromColumnsAndRows(
image: Flame.images.fromCache(widget.currentCharacter.animation.keyName),
columns: 12,
rows: 6,
);
final animation = spriteSheet.createAnimation(
row: 0,
stepTime: 1 / 24,
to: spriteSheet.rows * spriteSheet.columns,
);
if (_controller != null) _controller?.dispose();
_controller = SpriteAnimationController(vsync: this, animation: animation)
..forward()
..repeat();
}
}

@ -1 +1,2 @@
export 'character_selection_page.dart';
export 'selected_character.dart';

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

@ -32,6 +32,7 @@ class $AssetsImagesGen {
$AssetsImagesMultiplierGen get multiplier =>
const $AssetsImagesMultiplierGen();
$AssetsImagesPlungerGen get plunger => const $AssetsImagesPlungerGen();
$AssetsImagesScoreGen get score => const $AssetsImagesScoreGen();
$AssetsImagesSignpostGen get signpost => const $AssetsImagesSignpostGen();
$AssetsImagesSlingshotGen get slingshot => const $AssetsImagesSlingshotGen();
$AssetsImagesSparkyGen get sparky => const $AssetsImagesSparkyGen();
@ -214,6 +215,26 @@ class $AssetsImagesPlungerGen {
const AssetGenImage('assets/images/plunger/rocket.png');
}
class $AssetsImagesScoreGen {
const $AssetsImagesScoreGen();
/// File path: assets/images/score/five-thousand.png
AssetGenImage get fiveThousand =>
const AssetGenImage('assets/images/score/five-thousand.png');
/// File path: assets/images/score/one-million.png
AssetGenImage get oneMillion =>
const AssetGenImage('assets/images/score/one-million.png');
/// File path: assets/images/score/twenty-thousand.png
AssetGenImage get twentyThousand =>
const AssetGenImage('assets/images/score/twenty-thousand.png');
/// File path: assets/images/score/two-hundred-thousand.png
AssetGenImage get twoHundredThousand =>
const AssetGenImage('assets/images/score/two-hundred-thousand.png');
}
class $AssetsImagesSignpostGen {
const $AssetsImagesSignpostGen();

@ -25,7 +25,7 @@ export 'multiball/multiball.dart';
export 'multiplier/multiplier.dart';
export 'plunger.dart';
export 'rocket.dart';
export 'score_text.dart';
export 'score_component.dart';
export 'shapes/shapes.dart';
export 'signpost.dart';
export 'slingshot.dart';

@ -0,0 +1,92 @@
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
enum Points {
fiveThousand,
twentyThousand,
twoHundredThousand,
oneMillion,
}
/// {@template score_component}
/// A [ScoreComponent] that spawns at a given [position] with a moving
/// animation.
/// {@endtemplate}
class ScoreComponent extends SpriteComponent with HasGameRef, ZIndex {
/// {@macro score_component}
ScoreComponent({
required this.points,
required Vector2 position,
}) : super(
position: position,
anchor: Anchor.center,
) {
zIndex = ZIndexes.score;
}
late final Effect _effect;
late Points points;
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(points.asset),
);
this.sprite = sprite;
size = sprite.originalSize / 55;
await add(
_effect = MoveEffect.by(
Vector2(0, -5),
EffectController(duration: 1),
),
);
}
@override
void update(double dt) {
super.update(dt);
if (_effect.controller.completed) {
removeFromParent();
}
}
}
extension PointsX on Points {
int get value {
switch (this) {
case Points.fiveThousand:
return 5000;
case Points.twentyThousand:
return 20000;
case Points.twoHundredThousand:
return 200000;
case Points.oneMillion:
return 1000000;
}
}
}
extension on Points {
String get asset {
switch (this) {
case Points.fiveThousand:
return Assets.images.score.fiveThousand.keyName;
case Points.twentyThousand:
return Assets.images.score.twentyThousand.keyName;
case Points.twoHundredThousand:
return Assets.images.score.twoHundredThousand.keyName;
case Points.oneMillion:
return Assets.images.score.oneMillion.keyName;
}
}
}

@ -1,57 +0,0 @@
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template score_text}
/// A [TextComponent] that spawns at a given [position] with a moving animation.
/// {@endtemplate}
class ScoreText extends TextComponent with ZIndex {
/// {@macro score_text}
ScoreText({
required String text,
required Vector2 position,
this.color = Colors.black,
}) : super(
text: text,
position: position,
anchor: Anchor.center,
) {
zIndex = ZIndexes.scoreText;
}
late final Effect _effect;
/// The [text]'s [Color].
final Color color;
@override
Future<void> onLoad() async {
textRenderer = TextPaint(
style: TextStyle(
fontFamily: PinballFonts.pixeloidMono,
color: color,
fontSize: 4,
),
);
await add(
_effect = MoveEffect.by(
Vector2(0, -5),
EffectController(duration: 1),
),
);
}
@override
void update(double dt) {
super.update(dt);
if (_effect.controller.completed) {
removeFromParent();
}
}
}

@ -101,10 +101,10 @@ abstract class ZIndexes {
static const androidBumper = _above + ballOnBoard;
// Score Text
// Score
static const scoreText = _above + spaceshipRampForegroundRailing;
static const score = _above + spaceshipRampForegroundRailing;
// Debug information
static const debugInfo = _above + scoreText;
static const debugInfo = _above + score;
}

@ -87,6 +87,7 @@ flutter:
- assets/images/multiplier/x4/
- assets/images/multiplier/x5/
- assets/images/multiplier/x6/
- assets/images/score/
flutter_gen:
line_length: 80

@ -24,7 +24,7 @@ void main() {
addBoundariesStories(dashbook);
addGoogleWordStories(dashbook);
addLaunchRampStories(dashbook);
addScoreTextStories(dashbook);
addScoreStories(dashbook);
addBackboardStories(dashbook);
addDinoWallStories(dashbook);
addMultiballStories(dashbook);

@ -1,6 +1,5 @@
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_components/pinball_components.dart' as components;
import 'package:pinball_theme/pinball_theme.dart';
import 'package:sandbox/common/common.dart';
@ -8,7 +7,13 @@ class BackboardGameOverGame extends AssetsGame
with HasKeyboardHandlerComponents {
BackboardGameOverGame(this.score, this.character)
: super(
imagesFileNames: characterIconPaths.values.toList(),
imagesFileNames: [
components.Assets.images.score.fiveThousand.keyName,
components.Assets.images.score.twentyThousand.keyName,
components.Assets.images.score.twoHundredThousand.keyName,
components.Assets.images.score.oneMillion.keyName,
...characterIconPaths.values.toList(),
],
);
static const description = '''
@ -30,21 +35,23 @@ class BackboardGameOverGame extends AssetsGame
@override
Future<void> onLoad() async {
await super.onLoad();
camera
..followVector2(Vector2.zero())
..zoom = 5;
await add(
Backboard.gameOver(
components.Backboard.gameOver(
position: Vector2(0, 20),
score: score,
characterIconPath: characterIconPaths[character]!,
onSubmit: (initials) {
add(
ScoreText(
text: 'User $initials made $score',
components.ScoreComponent(
points: components.Points.values
.firstWhere((element) => element.value == score),
position: Vector2(0, 50),
color: Colors.pink,
),
);
},

@ -1,4 +1,5 @@
import 'package:dashbook/dashbook.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/backboard/backboard_game_over_game.dart';
import 'package:sandbox/stories/backboard/backboard_waiting_game.dart';
@ -14,7 +15,11 @@ void addBackboardStories(Dashbook dashbook) {
title: 'Game over',
description: BackboardGameOverGame.description,
gameBuilder: (context) => BackboardGameOverGame(
context.numberProperty('Score', 9000000000).toInt(),
context.listProperty(
'Score',
Points.values.first.value,
Points.values.map((score) => score.value).toList(),
),
context.listProperty(
'Character',
BackboardGameOverGame.characterIconPaths.keys.first,

@ -0,0 +1,44 @@
import 'dart:math';
import 'package:flame/input.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class ScoreGame extends AssetsGame with TapDetector {
ScoreGame()
: super(
imagesFileNames: [
Assets.images.score.fiveThousand.keyName,
Assets.images.score.twentyThousand.keyName,
Assets.images.score.twoHundredThousand.keyName,
Assets.images.score.oneMillion.keyName,
],
);
static const description = '''
Simple game to show how score component works,
- Tap anywhere on the screen to spawn an image on the given location.
''';
final random = Random();
@override
Future<void> onLoad() async {
await super.onLoad();
camera.followVector2(Vector2.zero());
}
@override
void onTapUp(TapUpInfo info) {
final index = random.nextInt(Points.values.length);
final score = Points.values[index];
add(
ScoreComponent(
points: score,
position: info.eventPosition.game..multiply(Vector2(1, -1)),
),
);
}
}

@ -0,0 +1,11 @@
import 'package:dashbook/dashbook.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/score/score_game.dart';
void addScoreStories(Dashbook dashbook) {
dashbook.storiesOf('Score').addGame(
title: 'Basic',
description: ScoreGame.description,
gameBuilder: (_) => ScoreGame(),
);
}

@ -1,32 +0,0 @@
import 'dart:math';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class ScoreTextGame extends AssetsGame with TapDetector {
static const description = '''
Simple game to show how score text works,
- Tap anywhere on the screen to spawn an text on the given location.
''';
final random = Random();
@override
Future<void> onLoad() async {
camera.followVector2(Vector2.zero());
}
@override
void onTapUp(TapUpInfo info) {
add(
ScoreText(
text: random.nextInt(100000).toString(),
color: Colors.white,
position: info.eventPosition.game..multiply(Vector2(1, -1)),
),
);
}
}

@ -1,11 +0,0 @@
import 'package:dashbook/dashbook.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/score_text/score_text_game.dart';
void addScoreTextStories(Dashbook dashbook) {
dashbook.storiesOf('ScoreText').addGame(
title: 'Basic',
description: ScoreTextGame.description,
gameBuilder: (_) => ScoreTextGame(),
);
}

@ -13,6 +13,6 @@ export 'layer/stories.dart';
export 'multiball/stories.dart';
export 'multipliers/stories.dart';
export 'plunger/stories.dart';
export 'score_text/stories.dart';
export 'score/stories.dart';
export 'slingshot/stories.dart';
export 'sparky_scorch/stories.dart';

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

@ -0,0 +1,202 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.score.fiveThousand.keyName,
Assets.images.score.twentyThousand.keyName,
Assets.images.score.twoHundredThousand.keyName,
Assets.images.score.oneMillion.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
group('ScoreComponent', () {
flameTester.testGameWidget(
'loads correctly',
setUp: (game, tester) async {
await game.images.loadAll(assets);
game.camera.followVector2(Vector2.zero());
await game.ensureAdd(
ScoreComponent(
points: Points.oneMillion,
position: Vector2.zero(),
),
);
},
verify: (game, tester) async {
final texts = game.descendants().whereType<SpriteComponent>().length;
expect(texts, equals(1));
},
);
flameTester.testGameWidget(
'has a movement effect',
setUp: (game, tester) async {
await game.images.loadAll(assets);
game.camera.followVector2(Vector2.zero());
await game.ensureAdd(
ScoreComponent(
points: Points.oneMillion,
position: Vector2.zero(),
),
);
game.update(0.5);
await tester.pump();
},
verify: (game, tester) async {
final text = game.descendants().whereType<SpriteComponent>().first;
expect(text.firstChild<MoveEffect>(), isNotNull);
},
);
flameTester.testGameWidget(
'is removed once finished',
setUp: (game, tester) async {
await game.images.loadAll(assets);
game.camera.followVector2(Vector2.zero());
await game.ensureAdd(
ScoreComponent(
points: Points.oneMillion,
position: Vector2.zero(),
),
);
game.update(1);
game.update(0); // Ensure all component removals
await tester.pump();
},
verify: (game, tester) async {
expect(game.children.length, equals(0));
},
);
group('renders correctly', () {
flameTester.testGameWidget(
'5000 points',
setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.ensureAdd(
ScoreComponent(
points: Points.fiveThousand,
position: Vector2.zero(),
),
);
game.camera
..followVector2(Vector2.zero())
..zoom = 8;
await tester.pump();
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/score/5k.png'),
);
},
);
flameTester.testGameWidget(
'20000 points',
setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.ensureAdd(
ScoreComponent(
points: Points.twentyThousand,
position: Vector2.zero(),
),
);
game.camera
..followVector2(Vector2.zero())
..zoom = 8;
await tester.pump();
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/score/20k.png'),
);
},
);
flameTester.testGameWidget(
'200000 points',
setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.ensureAdd(
ScoreComponent(
points: Points.twoHundredThousand,
position: Vector2.zero(),
),
);
game.camera
..followVector2(Vector2.zero())
..zoom = 8;
await tester.pump();
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/score/200k.png'),
);
},
);
flameTester.testGameWidget(
'1000000 points',
setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.ensureAdd(
ScoreComponent(
points: Points.oneMillion,
position: Vector2.zero(),
),
);
game.camera
..followVector2(Vector2.zero())
..zoom = 8;
await tester.pump();
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/score/1m.png'),
);
},
);
});
});
group('PointsX', () {
test('5k value return 5000', () {
expect(Points.fiveThousand.value, 5000);
});
test('20k value return 20000', () {
expect(Points.twentyThousand.value, 20000);
});
test('200k value return 200000', () {
expect(Points.twoHundredThousand.value, 200000);
});
test('1m value return 1000000', () {
expect(Points.oneMillion.value, 1000000);
});
});
}

@ -1,75 +0,0 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
group('ScoreText', () {
final flameTester = FlameTester(TestGame.new);
flameTester.testGameWidget(
'renders correctly',
setUp: (game, tester) async {
game.camera.followVector2(Vector2.zero());
await game.ensureAdd(
ScoreText(
text: '123',
position: Vector2.zero(),
color: Colors.white,
),
);
},
verify: (game, tester) async {
final texts = game.descendants().whereType<TextComponent>().length;
expect(texts, equals(1));
},
);
flameTester.testGameWidget(
'has a movement effect',
setUp: (game, tester) async {
game.camera.followVector2(Vector2.zero());
await game.ensureAdd(
ScoreText(
text: '123',
position: Vector2.zero(),
color: Colors.white,
),
);
game.update(0.5);
await tester.pump();
},
verify: (game, tester) async {
final text = game.descendants().whereType<TextComponent>().first;
expect(text.firstChild<MoveEffect>(), isNotNull);
},
);
flameTester.testGameWidget(
'is removed once finished',
setUp: (game, tester) async {
game.camera.followVector2(Vector2.zero());
await game.ensureAdd(
ScoreText(
text: '123',
position: Vector2.zero(),
color: Colors.white,
),
);
game.update(1);
game.update(0); // Ensure all component removals
},
verify: (game, tester) async {
expect(game.children.length, equals(0));
},
);
});
}

@ -401,13 +401,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
mockingjay:
dependency: "direct dev"
description:
name: mockingjay
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
mocktail:
dependency: "direct dev"
description:

@ -49,7 +49,6 @@ dev_dependencies:
flame_test: ^1.3.0
flutter_test:
sdk: flutter
mockingjay: ^0.3.0
mocktail: ^0.3.0
very_good_analysis: ^2.4.0

@ -3,7 +3,7 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockingjay/mockingjay.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/components/google_word/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';

@ -5,7 +5,7 @@ import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockingjay/mockingjay.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/components/multipliers/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';

@ -18,6 +18,14 @@ class _TestBodyComponent extends BodyComponent {
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.score.fiveThousand.keyName,
Assets.images.score.twentyThousand.keyName,
Assets.images.score.twoHundredThousand.keyName,
Assets.images.score.oneMillion.keyName,
];
group('ScoringBehavior', () {
group('beginContact', () {
late GameBloc bloc;
@ -51,12 +59,13 @@ void main() {
whenListen(bloc, Stream.value(state), initialState: state);
return bloc;
},
assets: assets,
);
flameBlocTester.testGameWidget(
'emits Scored event with points',
setUp: (game, tester) async {
const points = 20;
const points = Points.oneMillion;
final scoringBehavior = ScoringBehavior(points: points);
await parent.add(scoringBehavior);
final canvas = ZCanvasComponent(children: [parent]);
@ -66,7 +75,7 @@ void main() {
verify(
() => bloc.add(
const Scored(points: points),
Scored(points: points.value),
),
).called(1);
},
@ -75,8 +84,7 @@ void main() {
flameBlocTester.testGameWidget(
'plays score sound',
setUp: (game, tester) async {
const points = 20;
final scoringBehavior = ScoringBehavior(points: points);
final scoringBehavior = ScoringBehavior(points: Points.oneMillion);
await parent.add(scoringBehavior);
final canvas = ZCanvasComponent(children: [parent]);
await game.ensureAdd(canvas);
@ -88,9 +96,9 @@ void main() {
);
flameBlocTester.testGameWidget(
"adds a ScoreText component at Ball's position with points",
"adds a ScoreComponent at Ball's position with points",
setUp: (game, tester) async {
const points = 20;
const points = Points.oneMillion;
final scoringBehavior = ScoringBehavior(points: points);
await parent.add(scoringBehavior);
final canvas = ZCanvasComponent(children: [parent]);
@ -99,11 +107,11 @@ void main() {
scoringBehavior.beginContact(ball, MockContact());
await game.ready();
final scoreText = game.descendants().whereType<ScoreText>();
final scoreText = game.descendants().whereType<ScoreComponent>();
expect(scoreText.length, equals(1));
expect(
scoreText.first.text,
equals(points.toString()),
scoreText.first.points,
equals(points),
);
expect(
scoreText.first.position,

@ -24,6 +24,7 @@ class MockCallback extends Mock {
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
const animationDuration = 6;
setUp(() async {
// TODO(arturplaczek): need to find for a better solution for loading image
@ -102,7 +103,7 @@ void main() {
await tester.pump();
await Future<void>.delayed(const Duration(seconds: 4));
await Future<void>.delayed(const Duration(seconds: animationDuration));
await tester.pump();
@ -133,7 +134,7 @@ void main() {
.state(find.byType(BonusAnimation))
.didUpdateWidget(secondAnimation);
await Future<void>.delayed(const Duration(seconds: 4));
await Future<void>.delayed(const Duration(seconds: animationDuration));
await tester.pump();

@ -147,7 +147,7 @@ void main() {
await tester.pump();
// TODO(arturplaczek): remove magic number once this is merged:
// https://github.com/flame-engine/flame/pull/1564
await Future<void>.delayed(const Duration(seconds: 4));
await Future<void>.delayed(const Duration(seconds: 6));
await expectLater(find.byType(ScoreView), findsOneWidget);
});

@ -1,8 +1,10 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/flame.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_theme/pinball_theme.dart';
import '../../../helpers/helpers.dart';
@ -12,7 +14,12 @@ void main() {
late GameFlowController gameFlowController;
late CharacterThemeCubit characterThemeCubit;
setUp(() {
setUp(() async {
Flame.images.prefix = '';
await Flame.images.load(const DashTheme().animation.keyName);
await Flame.images.load(const AndroidTheme().animation.keyName);
await Flame.images.load(const DinoTheme().animation.keyName);
await Flame.images.load(const SparkyTheme().animation.keyName);
game = MockPinballGame();
gameFlowController = MockGameFlowController();
characterThemeCubit = MockCharacterThemeCubit();
@ -49,7 +56,7 @@ void main() {
characterThemeCubit: characterThemeCubit,
);
await tester.tap(find.text('Play'));
await tester.pumpAndSettle();
await tester.pump();
expect(find.byType(CharacterSelectionDialog), findsOneWidget);
});
});

@ -9,7 +9,6 @@ export 'fakes.dart';
export 'forge2d.dart';
export 'key_testers.dart';
export 'mocks.dart';
export 'navigator.dart';
export 'pump_app.dart';
export 'test_games.dart';
export 'text_span.dart';

@ -1,37 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'helpers.dart';
Future<void> expectNavigatesToRoute<Type>(
WidgetTester tester,
Route route, {
bool hasFlameGameInside = false,
}) async {
// ignore: avoid_dynamic_calls
await tester.pumpApp(
Scaffold(
body: Builder(
builder: (context) {
return ElevatedButton(
onPressed: () {
Navigator.of(context).push<void>(route);
},
child: const Text('Tap me'),
);
},
),
),
);
await tester.tap(find.text('Tap me'));
if (hasFlameGameInside) {
// We can't use pumpAndSettle here because the page renders a Flame game
// which is an infinity animation, so it will timeout
await tester.pump(); // Runs the button action
await tester.pump(); // Runs the navigation
} else {
await tester.pumpAndSettle();
}
expect(find.byType(Type), findsOneWidget);
}

@ -11,7 +11,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mockingjay/mockingjay.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.dart';
@ -51,7 +51,6 @@ MockAssetsManagerCubit _buildDefaultAssetsManagerCubit() {
extension PumpApp on WidgetTester {
Future<void> pumpApp(
Widget widget, {
MockNavigator? navigator,
GameBloc? gameBloc,
StartGameBloc? startGameBloc,
AssetsManagerCubit? assetsManagerCubit,
@ -92,9 +91,7 @@ extension PumpApp on WidgetTester {
GlobalMaterialLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
home: navigator != null
? MockNavigatorProvider(navigator: navigator, child: widget)
: widget,
home: widget,
),
),
),

@ -1,4 +1,5 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/flame.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
@ -10,9 +11,15 @@ import 'package:pinball_ui/pinball_ui.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
late CharacterThemeCubit characterThemeCubit;
setUp(() {
setUp(() async {
Flame.images.prefix = '';
await Flame.images.load(const DashTheme().animation.keyName);
await Flame.images.load(const AndroidTheme().animation.keyName);
await Flame.images.load(const DinoTheme().animation.keyName);
await Flame.images.load(const SparkyTheme().animation.keyName);
characterThemeCubit = MockCharacterThemeCubit();
whenListen(
characterThemeCubit,
@ -38,7 +45,7 @@ void main() {
characterThemeCubit: characterThemeCubit,
);
await tester.tap(find.text('test'));
await tester.pumpAndSettle();
await tester.pump();
expect(find.byType(CharacterSelectionDialog), findsOneWidget);
});
});
@ -50,7 +57,7 @@ void main() {
characterThemeCubit: characterThemeCubit,
);
await tester.tap(find.byKey(const Key('sparky_character_selection')));
await tester.pumpAndSettle();
await tester.pump();
verify(
() => characterThemeCubit.characterSelected(const SparkyTheme()),
).called(1);
@ -68,5 +75,47 @@ void main() {
expect(find.byType(CharacterSelectionDialog), findsNothing);
expect(find.byType(HowToPlayDialog), findsOneWidget);
});
testWidgets('updating the selected character updates the preview',
(tester) async {
await tester.pumpApp(_TestCharacterPreview());
expect(find.text('Dash'), findsOneWidget);
await tester.tap(find.text('test'));
await tester.pump();
expect(find.text('Android'), findsOneWidget);
});
});
}
class _TestCharacterPreview extends StatefulWidget {
@override
State<StatefulWidget> createState() => _TestCharacterPreviewState();
}
class _TestCharacterPreviewState extends State<_TestCharacterPreview> {
late CharacterTheme currentCharacter;
@override
void initState() {
super.initState();
currentCharacter = const DashTheme();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(child: SelectedCharacter(currentCharacter: currentCharacter)),
TextButton(
onPressed: () {
setState(() {
currentCharacter = const AndroidTheme();
});
},
child: const Text('test'),
)
],
);
}
}

Loading…
Cancel
Save