@ -0,0 +1,28 @@
|
|||||||
|
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] is activated.
|
||||||
|
class AndroidSpaceshipBonusBehavior extends Component
|
||||||
|
with HasGameRef<PinballGame>, ParentIsA<AndroidAcres> {
|
||||||
|
@override
|
||||||
|
void onMount() {
|
||||||
|
super.onMount();
|
||||||
|
|
||||||
|
final androidSpaceship =
|
||||||
|
parent.children.whereType<AndroidSpaceship>().single;
|
||||||
|
|
||||||
|
// TODO(alestiago): Refactor subscription management once the following is
|
||||||
|
// merged:
|
||||||
|
// https://github.com/flame-engine/flame/pull/1538
|
||||||
|
androidSpaceship.bloc.stream.listen((_) {
|
||||||
|
if (androidSpaceship.bloc.state == AndroidSpaceshipState.activated) {
|
||||||
|
gameRef
|
||||||
|
.read<GameBloc>()
|
||||||
|
.add(const BonusActivated(GameBonus.androidSpaceship));
|
||||||
|
androidSpaceship.bloc.onBonusAwarded();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export 'android_spaceship_bonus_behavior.dart';
|
@ -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.onEntered();
|
||||||
|
}
|
||||||
|
}
|
@ -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.idle);
|
||||||
|
|
||||||
|
void onEntered() => emit(AndroidSpaceshipState.activated);
|
||||||
|
|
||||||
|
void onBonusAwarded() => emit(AndroidSpaceshipState.idle);
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
part of 'android_spaceship_cubit.dart';
|
||||||
|
|
||||||
|
enum AndroidSpaceshipState {
|
||||||
|
idle,
|
||||||
|
activated,
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
// 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,55 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:bloc_test/bloc_test.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 {
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(TestGame.new);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'AndroidSpaceshipEntranceBallContactBehavior',
|
||||||
|
() {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(
|
||||||
|
AndroidSpaceshipEntranceBallContactBehavior(),
|
||||||
|
isA<AndroidSpaceshipEntranceBallContactBehavior>(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'beginContact emits onEntered when entrance contacts with a ball',
|
||||||
|
(game) async {
|
||||||
|
final behavior = AndroidSpaceshipEntranceBallContactBehavior();
|
||||||
|
final bloc = _MockAndroidSpaceshipCubit();
|
||||||
|
whenListen(
|
||||||
|
bloc,
|
||||||
|
const Stream<AndroidSpaceshipState>.empty(),
|
||||||
|
initialState: AndroidSpaceshipState.idle,
|
||||||
|
);
|
||||||
|
|
||||||
|
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.onEntered).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>(
|
||||||
|
'onEntered emits activated',
|
||||||
|
build: AndroidSpaceshipCubit.new,
|
||||||
|
act: (bloc) => bloc.onEntered(),
|
||||||
|
expect: () => [AndroidSpaceshipState.activated],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<AndroidSpaceshipCubit, AndroidSpaceshipState>(
|
||||||
|
'onBonusAwarded emits idle',
|
||||||
|
build: AndroidSpaceshipCubit.new,
|
||||||
|
act: (bloc) => bloc.onBonusAwarded(),
|
||||||
|
expect: () => [AndroidSpaceshipState.idle],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
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 |
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 114 KiB |
@ -0,0 +1,79 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import 'package:flame/extensions.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
import '../../../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final assets = [
|
||||||
|
Assets.images.android.spaceship.saucer.keyName,
|
||||||
|
Assets.images.android.spaceship.animatronic.keyName,
|
||||||
|
Assets.images.android.spaceship.lightBeam.keyName,
|
||||||
|
Assets.images.android.ramp.boardOpening.keyName,
|
||||||
|
Assets.images.android.ramp.railingForeground.keyName,
|
||||||
|
Assets.images.android.ramp.railingBackground.keyName,
|
||||||
|
Assets.images.android.ramp.main.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.inactive.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active1.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active2.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active3.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active4.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active5.keyName,
|
||||||
|
Assets.images.android.rail.main.keyName,
|
||||||
|
Assets.images.android.rail.exit.keyName,
|
||||||
|
Assets.images.android.bumper.a.lit.keyName,
|
||||||
|
Assets.images.android.bumper.a.dimmed.keyName,
|
||||||
|
Assets.images.android.bumper.b.lit.keyName,
|
||||||
|
Assets.images.android.bumper.b.dimmed.keyName,
|
||||||
|
Assets.images.android.bumper.cow.lit.keyName,
|
||||||
|
Assets.images.android.bumper.cow.dimmed.keyName,
|
||||||
|
];
|
||||||
|
|
||||||
|
group('AndroidSpaceshipBonusBehavior', () {
|
||||||
|
late GameBloc gameBloc;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
gameBloc = MockGameBloc();
|
||||||
|
whenListen(
|
||||||
|
gameBloc,
|
||||||
|
const Stream<GameState>.empty(),
|
||||||
|
initialState: const GameState.initial(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
|
||||||
|
gameBuilder: EmptyPinballTestGame.new,
|
||||||
|
blocBuilder: () => gameBloc,
|
||||||
|
assets: assets,
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
'adds GameBonus.androidSpaceship to the game '
|
||||||
|
'when android spacehship is activated',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final behavior = AndroidSpaceshipBonusBehavior();
|
||||||
|
final parent = AndroidAcres.test();
|
||||||
|
final androidSpaceship = AndroidSpaceship(position: Vector2.zero());
|
||||||
|
|
||||||
|
await parent.add(androidSpaceship);
|
||||||
|
await game.ensureAdd(parent);
|
||||||
|
await parent.ensureAdd(behavior);
|
||||||
|
|
||||||
|
androidSpaceship.bloc.onEntered();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
verify(
|
||||||
|
() => gameBloc.add(const BonusActivated(GameBonus.androidSpaceship)),
|
||||||
|
).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|