@ -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);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|