@ -1,8 +1 @@
|
||||
// Copyright (c) 2021, Very Good Ventures
|
||||
// https://verygood.ventures
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
export 'view/app.dart';
|
||||
|
@ -0,0 +1,2 @@
|
||||
export 'cubit/assets_manager_cubit.dart';
|
||||
export 'views/views.dart';
|
@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:pinball/assets_manager/assets_manager.dart';
|
||||
import 'package:pinball/l10n/l10n.dart';
|
||||
import 'package:pinball_ui/pinball_ui.dart';
|
||||
|
||||
/// {@template assets_loading_page}
|
||||
/// Widget used to indicate the loading progress of the different assets used
|
||||
/// in the game
|
||||
/// {@endtemplate}
|
||||
class AssetsLoadingPage extends StatelessWidget {
|
||||
/// {@macro assets_loading_page}
|
||||
const AssetsLoadingPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final headline1 = Theme.of(context).textTheme.headline1;
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
l10n.ioPinball,
|
||||
style: headline1!.copyWith(fontSize: 80),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
AnimatedEllipsisText(
|
||||
l10n.loading,
|
||||
style: headline1,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.8,
|
||||
child: BlocBuilder<AssetsManagerCubit, AssetsManagerState>(
|
||||
builder: (context, state) {
|
||||
return PinballLoadingIndicator(value: state.progress);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export 'assets_loading_page.dart';
|
@ -0,0 +1,27 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// Adds a [GameBonus.androidSpaceship] when [AndroidSpaceship] has a bonus.
|
||||
class AndroidSpaceshipBonusBehavior extends Component
|
||||
with HasGameRef<PinballGame>, ParentIsA<AndroidAcres> {
|
||||
@override
|
||||
void onMount() {
|
||||
super.onMount();
|
||||
final androidSpaceship = parent.firstChild<AndroidSpaceship>()!;
|
||||
|
||||
// TODO(alestiago): Refactor subscription management once the following is
|
||||
// merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
androidSpaceship.bloc.stream.listen((state) {
|
||||
final listenWhen = state == AndroidSpaceshipState.withBonus;
|
||||
if (!listenWhen) return;
|
||||
|
||||
gameRef
|
||||
.read<GameBloc>()
|
||||
.add(const BonusActivated(GameBonus.androidSpaceship));
|
||||
androidSpaceship.bloc.onBonusAwarded();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export 'android_spaceship_bonus_behavior.dart';
|
@ -0,0 +1 @@
|
||||
export 'chrome_dino_bonus_behavior.dart';
|
@ -0,0 +1,24 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// Adds a [GameBonus.dinoChomp] when a [Ball] is chomped by the [ChromeDino].
|
||||
class ChromeDinoBonusBehavior extends Component
|
||||
with HasGameRef<PinballGame>, ParentIsA<DinoDesert> {
|
||||
@override
|
||||
void onMount() {
|
||||
super.onMount();
|
||||
final chromeDino = parent.firstChild<ChromeDino>()!;
|
||||
|
||||
// TODO(alestiago): Refactor subscription management once the following is
|
||||
// merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
chromeDino.bloc.stream.listen((state) {
|
||||
final listenWhen = state.status == ChromeDinoStatus.chomping;
|
||||
if (!listenWhen) return;
|
||||
|
||||
gameRef.read<GameBloc>().add(const BonusActivated(GameBonus.dinoChomp));
|
||||
});
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export 'mocks.dart';
|
@ -1,34 +0,0 @@
|
||||
// ignore_for_file: one_member_abstracts
|
||||
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:flame_audio/audio_pool.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
abstract class _CreateAudioPoolStub {
|
||||
Future<AudioPool> onCall(
|
||||
String sound, {
|
||||
bool? repeating,
|
||||
int? maxPlayers,
|
||||
int? minPlayers,
|
||||
String? prefix,
|
||||
});
|
||||
}
|
||||
|
||||
class CreateAudioPoolStub extends Mock implements _CreateAudioPoolStub {}
|
||||
|
||||
abstract class _ConfigureAudioCacheStub {
|
||||
void onCall(AudioCache cache);
|
||||
}
|
||||
|
||||
class ConfigureAudioCacheStub extends Mock implements _ConfigureAudioCacheStub {
|
||||
}
|
||||
|
||||
abstract class _PlaySingleAudioStub {
|
||||
Future<void> onCall(String url);
|
||||
}
|
||||
|
||||
class PlaySingleAudioStub extends Mock implements _PlaySingleAudioStub {}
|
||||
|
||||
class MockAudioPool extends Mock implements AudioPool {}
|
||||
|
||||
class MockAudioCache extends Mock implements AudioCache {}
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 588 B |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 45 KiB |
@ -0,0 +1,71 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// {@template android_animatronic}
|
||||
/// Animated Android that sits on top of the [AndroidSpaceship].
|
||||
/// {@endtemplate}
|
||||
class AndroidAnimatronic extends BodyComponent
|
||||
with InitialPosition, Layered, ZIndex {
|
||||
/// {@macro android_animatronic}
|
||||
AndroidAnimatronic({Iterable<Component>? children})
|
||||
: super(
|
||||
children: [
|
||||
_AndroidAnimatronicSpriteAnimationComponent(),
|
||||
...?children,
|
||||
],
|
||||
renderBody: false,
|
||||
) {
|
||||
layer = Layer.spaceship;
|
||||
zIndex = ZIndexes.androidHead;
|
||||
}
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = EllipseShape(
|
||||
center: Vector2.zero(),
|
||||
majorRadius: 3.1,
|
||||
minorRadius: 2,
|
||||
)..rotate(1.4);
|
||||
final bodyDef = BodyDef(position: initialPosition);
|
||||
|
||||
return world.createBody(bodyDef)..createFixtureFromShape(shape);
|
||||
}
|
||||
}
|
||||
|
||||
class _AndroidAnimatronicSpriteAnimationComponent
|
||||
extends SpriteAnimationComponent with HasGameRef {
|
||||
_AndroidAnimatronicSpriteAnimationComponent()
|
||||
: super(
|
||||
anchor: Anchor.center,
|
||||
position: Vector2(-0.24, -2.6),
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
final spriteSheet = gameRef.images.fromCache(
|
||||
Assets.images.android.spaceship.animatronic.keyName,
|
||||
);
|
||||
|
||||
const amountPerRow = 18;
|
||||
const amountPerColumn = 4;
|
||||
final textureSize = Vector2(
|
||||
spriteSheet.width / amountPerRow,
|
||||
spriteSheet.height / amountPerColumn,
|
||||
);
|
||||
size = textureSize / 10;
|
||||
|
||||
animation = SpriteAnimation.fromFrameData(
|
||||
spriteSheet,
|
||||
SpriteAnimationData.sequenced(
|
||||
amount: amountPerRow * amountPerColumn,
|
||||
amountPerRow: amountPerRow,
|
||||
stepTime: 1 / 24,
|
||||
textureSize: textureSize,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
class AndroidSpaceshipEntranceBallContactBehavior
|
||||
extends ContactBehavior<AndroidSpaceshipEntrance> {
|
||||
@override
|
||||
void beginContact(Object other, Contact contact) {
|
||||
super.beginContact(other, contact);
|
||||
if (other is! Ball) return;
|
||||
|
||||
parent.parent.bloc.onBallEntered();
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export 'android_spaceship_entrance_ball_contact_behavior.dart.dart';
|
@ -0,0 +1,13 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
|
||||
part 'android_spaceship_state.dart';
|
||||
|
||||
class AndroidSpaceshipCubit extends Cubit<AndroidSpaceshipState> {
|
||||
AndroidSpaceshipCubit() : super(AndroidSpaceshipState.withoutBonus);
|
||||
|
||||
void onBallEntered() => emit(AndroidSpaceshipState.withBonus);
|
||||
|
||||
void onBonusAwarded() => emit(AndroidSpaceshipState.withoutBonus);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
part of 'android_spaceship_cubit.dart';
|
||||
|
||||
enum AndroidSpaceshipState {
|
||||
withoutBonus,
|
||||
withBonus,
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// Represents the [Signpost]'s current [Sprite] state.
|
||||
@visibleForTesting
|
||||
enum SignpostSpriteState {
|
||||
/// Signpost with no active dashes.
|
||||
inactive,
|
||||
|
||||
/// Signpost with a single sign of active dashes.
|
||||
active1,
|
||||
|
||||
/// Signpost with two signs of active dashes.
|
||||
active2,
|
||||
|
||||
/// Signpost with all signs of active dashes.
|
||||
active3,
|
||||
}
|
||||
|
||||
extension on SignpostSpriteState {
|
||||
String get path {
|
||||
switch (this) {
|
||||
case SignpostSpriteState.inactive:
|
||||
return Assets.images.signpost.inactive.keyName;
|
||||
case SignpostSpriteState.active1:
|
||||
return Assets.images.signpost.active1.keyName;
|
||||
case SignpostSpriteState.active2:
|
||||
return Assets.images.signpost.active2.keyName;
|
||||
case SignpostSpriteState.active3:
|
||||
return Assets.images.signpost.active3.keyName;
|
||||
}
|
||||
}
|
||||
|
||||
SignpostSpriteState get next {
|
||||
return SignpostSpriteState
|
||||
.values[(index + 1) % SignpostSpriteState.values.length];
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template signpost}
|
||||
/// A sign, found in the Flutter Forest.
|
||||
///
|
||||
/// Lights up a new sign whenever all three [DashNestBumper]s are hit.
|
||||
/// {@endtemplate}
|
||||
class Signpost extends BodyComponent with InitialPosition {
|
||||
/// {@macro signpost}
|
||||
Signpost({
|
||||
Iterable<Component>? children,
|
||||
}) : super(
|
||||
renderBody: false,
|
||||
children: [
|
||||
_SignpostSpriteComponent(),
|
||||
...?children,
|
||||
],
|
||||
);
|
||||
|
||||
/// Forwards the sprite to the next [SignpostSpriteState].
|
||||
///
|
||||
/// If the current state is the last one it cycles back to the initial state.
|
||||
void progress() => firstChild<_SignpostSpriteComponent>()!.progress();
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = CircleShape()..radius = 0.25;
|
||||
final fixtureDef = FixtureDef(shape);
|
||||
final bodyDef = BodyDef(
|
||||
position: initialPosition,
|
||||
);
|
||||
|
||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||
}
|
||||
}
|
||||
|
||||
class _SignpostSpriteComponent extends SpriteGroupComponent<SignpostSpriteState>
|
||||
with HasGameRef {
|
||||
_SignpostSpriteComponent()
|
||||
: super(
|
||||
anchor: Anchor.bottomCenter,
|
||||
position: Vector2(0.65, 0.45),
|
||||
);
|
||||
|
||||
void progress() => current = current?.next;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
final sprites = <SignpostSpriteState, Sprite>{};
|
||||
this.sprites = sprites;
|
||||
for (final spriteState in SignpostSpriteState.values) {
|
||||
sprites[spriteState] = Sprite(
|
||||
gameRef.images.fromCache(spriteState.path),
|
||||
);
|
||||
}
|
||||
|
||||
current = SignpostSpriteState.inactive;
|
||||
size = sprites[current]!.originalSize / 10;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
|
||||
part 'signpost_state.dart';
|
||||
|
||||
class SignpostCubit extends Cubit<SignpostState> {
|
||||
SignpostCubit() : super(SignpostState.inactive);
|
||||
|
||||
void onProgressed() {
|
||||
final index = SignpostState.values.indexOf(state);
|
||||
emit(
|
||||
SignpostState.values[(index + 1) % SignpostState.values.length],
|
||||
);
|
||||
}
|
||||
|
||||
bool isFullyProgressed() => state == SignpostState.active3;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
part of 'signpost_cubit.dart';
|
||||
|
||||
enum SignpostState {
|
||||
/// Signpost with no active eggs.
|
||||
inactive,
|
||||
|
||||
/// Signpost with a single sign of lit up eggs.
|
||||
active1,
|
||||
|
||||
/// Signpost with two signs of lit up eggs.
|
||||
active2,
|
||||
|
||||
/// Signpost with all signs of lit up eggs.
|
||||
active3,
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
export 'cubit/signpost_cubit.dart';
|
||||
|
||||
/// {@template signpost}
|
||||
/// A sign, found in the Flutter Forest.
|
||||
///
|
||||
/// Lights up a new sign whenever all three [DashNestBumper]s are hit.
|
||||
/// {@endtemplate}
|
||||
class Signpost extends BodyComponent with InitialPosition {
|
||||
/// {@macro signpost}
|
||||
Signpost({
|
||||
Iterable<Component>? children,
|
||||
}) : this._(
|
||||
children: children,
|
||||
bloc: SignpostCubit(),
|
||||
);
|
||||
|
||||
Signpost._({
|
||||
Iterable<Component>? children,
|
||||
required this.bloc,
|
||||
}) : super(
|
||||
renderBody: false,
|
||||
children: [
|
||||
_SignpostSpriteComponent(
|
||||
current: bloc.state,
|
||||
),
|
||||
...?children,
|
||||
],
|
||||
);
|
||||
|
||||
/// Creates a [Signpost] without any children.
|
||||
///
|
||||
/// This can be used for testing [Signpost]'s behaviors in isolation.
|
||||
// TODO(alestiago): Refactor injecting bloc once the following is merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
@visibleForTesting
|
||||
Signpost.test({
|
||||
required this.bloc,
|
||||
});
|
||||
|
||||
// TODO(alestiago): Consider refactoring once the following is merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
// ignore: public_member_api_docs
|
||||
final SignpostCubit bloc;
|
||||
|
||||
@override
|
||||
void onRemove() {
|
||||
bloc.close();
|
||||
super.onRemove();
|
||||
}
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = CircleShape()..radius = 0.25;
|
||||
final bodyDef = BodyDef(
|
||||
position: initialPosition,
|
||||
);
|
||||
|
||||
return world.createBody(bodyDef)..createFixtureFromShape(shape);
|
||||
}
|
||||
}
|
||||
|
||||
class _SignpostSpriteComponent extends SpriteGroupComponent<SignpostState>
|
||||
with HasGameRef, ParentIsA<Signpost> {
|
||||
_SignpostSpriteComponent({
|
||||
required SignpostState current,
|
||||
}) : super(
|
||||
anchor: Anchor.bottomCenter,
|
||||
position: Vector2(0.65, 0.45),
|
||||
current: current,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
parent.bloc.stream.listen((state) => current = state);
|
||||
|
||||
final sprites = <SignpostState, Sprite>{};
|
||||
this.sprites = sprites;
|
||||
for (final spriteState in SignpostState.values) {
|
||||
sprites[spriteState] = Sprite(
|
||||
gameRef.images.fromCache(spriteState.path),
|
||||
);
|
||||
}
|
||||
|
||||
current = SignpostState.inactive;
|
||||
size = sprites[current]!.originalSize / 10;
|
||||
}
|
||||
}
|
||||
|
||||
extension on SignpostState {
|
||||
String get path {
|
||||
switch (this) {
|
||||
case SignpostState.inactive:
|
||||
return Assets.images.signpost.inactive.keyName;
|
||||
case SignpostState.active1:
|
||||
return Assets.images.signpost.active1.keyName;
|
||||
case SignpostState.active2:
|
||||
return Assets.images.signpost.active2.keyName;
|
||||
case SignpostState.active3:
|
||||
return Assets.images.signpost.active3.keyName;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import 'package:dashbook/dashbook.dart';
|
||||
import 'package:sandbox/common/common.dart';
|
||||
import 'package:sandbox/stories/chrome_dino/chrome_dino_game.dart';
|
||||
|
||||
void addChromeDinoStories(Dashbook dashbook) {
|
||||
dashbook.storiesOf('Chrome Dino').addGame(
|
||||
title: 'Traced',
|
||||
description: ChromeDinoGame.description,
|
||||
gameBuilder: (_) => ChromeDinoGame(),
|
||||
);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import 'package:dashbook/dashbook.dart';
|
||||
import 'package:sandbox/common/common.dart';
|
||||
import 'package:sandbox/stories/dino_desert/chrome_dino_game.dart';
|
||||
import 'package:sandbox/stories/dino_desert/dino_walls_game.dart';
|
||||
import 'package:sandbox/stories/dino_desert/slingshots_game.dart';
|
||||
|
||||
void addDinoDesertStories(Dashbook dashbook) {
|
||||
dashbook.storiesOf('Dino Desert')
|
||||
..addGame(
|
||||
title: 'Chrome Dino',
|
||||
description: ChromeDinoGame.description,
|
||||
gameBuilder: (_) => ChromeDinoGame(),
|
||||
)
|
||||
..addGame(
|
||||
title: 'Dino Walls',
|
||||
description: DinoWallsGame.description,
|
||||
gameBuilder: (_) => DinoWallsGame(),
|
||||
)
|
||||
..addGame(
|
||||
title: 'Slingshots',
|
||||
description: SlingshotsGame.description,
|
||||
gameBuilder: (_) => SlingshotsGame(),
|
||||
);
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import 'package:dashbook/dashbook.dart';
|
||||
import 'package:sandbox/common/common.dart';
|
||||
import 'package:sandbox/stories/dino_wall/dino_wall_game.dart';
|
||||
|
||||
void addDinoWallStories(Dashbook dashbook) {
|
||||
dashbook.storiesOf('DinoWall').addGame(
|
||||
title: 'Traced',
|
||||
description: DinoWallGame.description,
|
||||
gameBuilder: (_) => DinoWallGame(),
|
||||
);
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import 'package:dashbook/dashbook.dart';
|
||||
import 'package:sandbox/common/common.dart';
|
||||
import 'package:sandbox/stories/slingshot/slingshot_game.dart';
|
||||
|
||||
void addSlingshotStories(Dashbook dashbook) {
|
||||
dashbook.storiesOf('Slingshots').addGame(
|
||||
title: 'Traced',
|
||||
description: SlingshotGame.description,
|
||||
gameBuilder: (_) => SlingshotGame(),
|
||||
);
|
||||
}
|
@ -1,2 +1 @@
|
||||
export 'mocks.dart';
|
||||
export 'test_game.dart';
|
||||
|
@ -1,32 +0,0 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
class MockFilter extends Mock implements Filter {}
|
||||
|
||||
class MockFixture extends Mock implements Fixture {}
|
||||
|
||||
class MockBody extends Mock implements Body {}
|
||||
|
||||
class MockBall extends Mock implements Ball {}
|
||||
|
||||
class MockGame extends Mock implements Forge2DGame {}
|
||||
|
||||
class MockContact extends Mock implements Contact {}
|
||||
|
||||
class MockComponent extends Mock implements Component {}
|
||||
|
||||
class MockAndroidBumperCubit extends Mock implements AndroidBumperCubit {}
|
||||
|
||||
class MockGoogleLetterCubit extends Mock implements GoogleLetterCubit {}
|
||||
|
||||
class MockSparkyBumperCubit extends Mock implements SparkyBumperCubit {}
|
||||
|
||||
class MockDashNestBumperCubit extends Mock implements DashNestBumperCubit {}
|
||||
|
||||
class MockMultiballCubit extends Mock implements MultiballCubit {}
|
||||
|
||||
class MockMultiplierCubit extends Mock implements MultiplierCubit {}
|
||||
|
||||
class MockChromeDinoCubit extends Mock implements ChromeDinoCubit {}
|
@ -0,0 +1,70 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:flame/components.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 asset = Assets.images.android.spaceship.animatronic.keyName;
|
||||
final flameTester = FlameTester(() => TestGame([asset]));
|
||||
|
||||
group('AndroidAnimatronic', () {
|
||||
flameTester.testGameWidget(
|
||||
'renders correctly',
|
||||
setUp: (game, tester) async {
|
||||
await game.images.load(asset);
|
||||
await game.ensureAdd(AndroidAnimatronic());
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
await tester.pump();
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
final animationDuration = game
|
||||
.firstChild<AndroidAnimatronic>()!
|
||||
.firstChild<SpriteAnimationComponent>()!
|
||||
.animation!
|
||||
.totalDuration();
|
||||
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('golden/android_animatronic/start.png'),
|
||||
);
|
||||
|
||||
game.update(animationDuration * 0.5);
|
||||
await tester.pump();
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('golden/android_animatronic/middle.png'),
|
||||
);
|
||||
|
||||
game.update(animationDuration * 0.5);
|
||||
await tester.pump();
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('golden/android_animatronic/end.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.test(
|
||||
'loads correctly',
|
||||
(game) async {
|
||||
final androidAnimatronic = AndroidAnimatronic();
|
||||
await game.ensureAdd(androidAnimatronic);
|
||||
expect(game.contains(androidAnimatronic), isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.test('adds new children', (game) async {
|
||||
final component = Component();
|
||||
final androidAnimatronic = AndroidAnimatronic(
|
||||
children: [component],
|
||||
);
|
||||
await game.ensureAdd(androidAnimatronic);
|
||||
expect(androidAnimatronic.children, contains(component));
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flame/components.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';
|
||||
import 'package:pinball_components/src/components/android_spaceship/behaviors/behaviors.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
import '../../../helpers/helpers.dart';
|
||||
|
||||
class _MockAndroidSpaceshipCubit extends Mock implements AndroidSpaceshipCubit {
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('AndroidSpaceship', () {
|
||||
final assets = [
|
||||
Assets.images.android.spaceship.saucer.keyName,
|
||||
Assets.images.android.spaceship.lightBeam.keyName,
|
||||
];
|
||||
final flameTester = FlameTester(() => TestGame(assets));
|
||||
|
||||
flameTester.test('loads correctly', (game) async {
|
||||
final component = AndroidSpaceship(position: Vector2.zero());
|
||||
await game.ensureAdd(component);
|
||||
expect(game.contains(component), isTrue);
|
||||
});
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'renders correctly',
|
||||
setUp: (game, tester) async {
|
||||
await game.images.loadAll(assets);
|
||||
final canvas = ZCanvasComponent(
|
||||
children: [AndroidSpaceship(position: Vector2.zero())],
|
||||
);
|
||||
await game.ensureAdd(canvas);
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
await game.ready();
|
||||
await tester.pump();
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
const goldenFilePath = '../golden/android_spaceship/';
|
||||
final animationDuration = game
|
||||
.descendants()
|
||||
.whereType<SpriteAnimationComponent>()
|
||||
.single
|
||||
.animation!
|
||||
.totalDuration();
|
||||
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('${goldenFilePath}start.png'),
|
||||
);
|
||||
|
||||
game.update(animationDuration * 0.5);
|
||||
await tester.pump();
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('${goldenFilePath}middle.png'),
|
||||
);
|
||||
|
||||
game.update(animationDuration * 0.5);
|
||||
await tester.pump();
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('${goldenFilePath}end.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// TODO(alestiago): Consider refactoring once the following is merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
// ignore: public_member_api_docs
|
||||
flameTester.test('closes bloc when removed', (game) async {
|
||||
final bloc = _MockAndroidSpaceshipCubit();
|
||||
whenListen(
|
||||
bloc,
|
||||
const Stream<AndroidSpaceshipState>.empty(),
|
||||
initialState: AndroidSpaceshipState.withoutBonus,
|
||||
);
|
||||
when(bloc.close).thenAnswer((_) async {});
|
||||
final androidSpaceship = AndroidSpaceship.test(bloc: bloc);
|
||||
|
||||
await game.ensureAdd(androidSpaceship);
|
||||
game.remove(androidSpaceship);
|
||||
await game.ready();
|
||||
|
||||
verify(bloc.close).called(1);
|
||||
});
|
||||
|
||||
flameTester.test(
|
||||
'AndroidSpaceshipEntrance has an '
|
||||
'AndroidSpaceshipEntranceBallContactBehavior', (game) async {
|
||||
final androidSpaceship = AndroidSpaceship(position: Vector2.zero());
|
||||
await game.ensureAdd(androidSpaceship);
|
||||
|
||||
final androidSpaceshipEntrance =
|
||||
androidSpaceship.firstChild<AndroidSpaceshipEntrance>();
|
||||
expect(
|
||||
androidSpaceshipEntrance!.children
|
||||
.whereType<AndroidSpaceshipEntranceBallContactBehavior>()
|
||||
.single,
|
||||
isNotNull,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:bloc_test/bloc_test.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';
|
||||
import 'package:pinball_components/src/components/android_spaceship/behaviors/behaviors.dart';
|
||||
|
||||
import '../../../../helpers/helpers.dart';
|
||||
|
||||
class _MockAndroidSpaceshipCubit extends Mock implements AndroidSpaceshipCubit {
|
||||
}
|
||||
|
||||
class _MockBall extends Mock implements Ball {}
|
||||
|
||||
class _MockContact extends Mock implements Contact {}
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
final flameTester = FlameTester(TestGame.new);
|
||||
|
||||
group(
|
||||
'AndroidSpaceshipEntranceBallContactBehavior',
|
||||
() {
|
||||
test('can be instantiated', () {
|
||||
expect(
|
||||
AndroidSpaceshipEntranceBallContactBehavior(),
|
||||
isA<AndroidSpaceshipEntranceBallContactBehavior>(),
|
||||
);
|
||||
});
|
||||
|
||||
flameTester.test(
|
||||
'beginContact calls onBallEntered when entrance contacts with a ball',
|
||||
(game) async {
|
||||
final behavior = AndroidSpaceshipEntranceBallContactBehavior();
|
||||
final bloc = _MockAndroidSpaceshipCubit();
|
||||
whenListen(
|
||||
bloc,
|
||||
const Stream<AndroidSpaceshipState>.empty(),
|
||||
initialState: AndroidSpaceshipState.withoutBonus,
|
||||
);
|
||||
|
||||
final entrance = AndroidSpaceshipEntrance();
|
||||
final androidSpaceship = AndroidSpaceship.test(
|
||||
bloc: bloc,
|
||||
children: [entrance],
|
||||
);
|
||||
await entrance.add(behavior);
|
||||
await game.ensureAdd(androidSpaceship);
|
||||
|
||||
behavior.beginContact(_MockBall(), _MockContact());
|
||||
|
||||
verify(androidSpaceship.bloc.onBallEntered).called(1);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
void main() {
|
||||
group(
|
||||
'AndroidSpaceshipCubit',
|
||||
() {
|
||||
blocTest<AndroidSpaceshipCubit, AndroidSpaceshipState>(
|
||||
'onBallEntered emits withBonus',
|
||||
build: AndroidSpaceshipCubit.new,
|
||||
act: (bloc) => bloc.onBallEntered(),
|
||||
expect: () => [AndroidSpaceshipState.withBonus],
|
||||
);
|
||||
|
||||
blocTest<AndroidSpaceshipCubit, AndroidSpaceshipState>(
|
||||
'onBonusAwarded emits withoutBonus',
|
||||
build: AndroidSpaceshipCubit.new,
|
||||
act: (bloc) => bloc.onBonusAwarded(),
|
||||
expect: () => [AndroidSpaceshipState.withoutBonus],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
import '../../helpers/helpers.dart';
|
||||
|
||||
void main() {
|
||||
group('AndroidSpaceship', () {
|
||||
final assets = [
|
||||
Assets.images.android.spaceship.saucer.keyName,
|
||||
Assets.images.android.spaceship.animatronic.keyName,
|
||||
Assets.images.android.spaceship.lightBeam.keyName,
|
||||
];
|
||||
final flameTester = FlameTester(() => TestGame(assets));
|
||||
|
||||
flameTester.test('loads correctly', (game) async {
|
||||
final component = AndroidSpaceship(position: Vector2.zero());
|
||||
await game.ensureAdd(component);
|
||||
expect(game.contains(component), isTrue);
|
||||
});
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'renders correctly',
|
||||
setUp: (game, tester) async {
|
||||
await game.images.loadAll(assets);
|
||||
final canvas = ZCanvasComponent(
|
||||
children: [AndroidSpaceship(position: Vector2.zero())],
|
||||
);
|
||||
await game.ensureAdd(canvas);
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
await game.ready();
|
||||
await tester.pump();
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
final animationDuration = game
|
||||
.descendants()
|
||||
.whereType<SpriteAnimationComponent>()
|
||||
.last
|
||||
.animation!
|
||||
.totalDuration();
|
||||
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('golden/android_spaceship/start.png'),
|
||||
);
|
||||
|
||||
game.update(animationDuration * 0.5);
|
||||
await tester.pump();
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('golden/android_spaceship/middle.png'),
|
||||
);
|
||||
|
||||
game.update(animationDuration * 0.5);
|
||||
await tester.pump();
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('golden/android_spaceship/end.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 114 KiB |