feat: loop animatronics (#452)

* feat: loop animatronics

* chore: rename animationCooldown

* fix: appease cspell

Co-authored-by: Tom Arra <tarra3@gmail.com>
pull/453/head
Allison Ryan 2 years ago committed by GitHub
parent 5431f2058a
commit 1c8813e039
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,26 @@
import 'package:flame/components.dart';
import 'package:pinball_flame/pinball_flame.dart';
class AnimatronicLoopingBehavior extends TimerComponent
with ParentIsA<SpriteAnimationComponent> {
AnimatronicLoopingBehavior({
required double animationCoolDown,
}) : super(period: animationCoolDown);
@override
Future<void> onLoad() async {
await super.onLoad();
parent.animation?.onComplete = () {
parent.animation?.reset();
parent.playing = false;
timer
..reset()
..start();
};
}
@override
void onTick() {
parent.playing = true;
}
}

@ -1,3 +1,4 @@
export 'animatronic_looping_behavior.dart';
export 'ball_spawning_behavior.dart';
export 'bonus_ball_spawning_behavior.dart';
export 'bonus_noise_behavior.dart';

@ -21,7 +21,6 @@ class FlutterForestBonusBehavior extends Component
final bumpers = parent.children.whereType<DashBumper>();
final signpost = parent.firstChild<Signpost>()!;
final animatronic = parent.firstChild<DashAnimatronic>()!;
for (final bumper in bumpers) {
bumper.bloc.stream.listen((state) {
@ -38,7 +37,6 @@ class FlutterForestBonusBehavior extends Component
if (signpost.bloc.isFullyProgressed()) {
bloc.add(const BonusActivated(GameBonus.dashNest));
add(BonusBallSpawningBehavior());
animatronic.playing = true;
signpost.bloc.onProgressed();
}
}

@ -40,7 +40,11 @@ class FlutterForest extends Component with ZIndex {
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(21.8, -46.75),
DashAnimatronic()..position = Vector2(20, -66),
DashAnimatronic(
children: [
AnimatronicLoopingBehavior(animationCoolDown: 4),
],
)..position = Vector2(20, -66),
FlutterForestBonusBehavior(),
],
) {

@ -12,13 +12,11 @@ class SparkyComputerBonusBehavior extends Component
void onMount() {
super.onMount();
final sparkyComputer = parent.firstChild<SparkyComputer>()!;
final animatronic = parent.firstChild<SparkyAnimatronic>()!;
sparkyComputer.bloc.stream.listen((state) async {
final listenWhen = state == SparkyComputerState.withBall;
if (!listenWhen) return;
bloc.add(const BonusActivated(GameBonus.sparkyTurboCharge));
animatronic.playing = true;
});
}
}

@ -33,7 +33,11 @@ class SparkyScorch extends Component {
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(-3.3, -52.55),
SparkyAnimatronic()..position = Vector2(-14, -58.2),
SparkyAnimatronic(
children: [
AnimatronicLoopingBehavior(animationCoolDown: 3),
],
)..position = Vector2(-14, -58.2),
SparkyComputer(
children: [
ScoringContactBehavior(points: Points.twoHundredThousand)

@ -6,10 +6,11 @@ import 'package:pinball_components/pinball_components.dart';
/// {@endtemplate}
class DashAnimatronic extends SpriteAnimationComponent with HasGameRef {
/// {@macro dash_animatronic}
DashAnimatronic()
DashAnimatronic({Iterable<Component>? children})
: super(
anchor: Anchor.center,
playing: false,
children: children,
);
@override
@ -37,9 +38,6 @@ class DashAnimatronic extends SpriteAnimationComponent with HasGameRef {
textureSize: textureSize,
loop: false,
),
)..onComplete = () {
animation?.reset();
playing = false;
};
);
}
}

@ -8,10 +8,11 @@ import 'package:pinball_flame/pinball_flame.dart';
class SparkyAnimatronic extends SpriteAnimationComponent
with HasGameRef, ZIndex {
/// {@macro sparky_animatronic}
SparkyAnimatronic()
SparkyAnimatronic({Iterable<Component>? children})
: super(
anchor: Anchor.center,
playing: false,
children: children,
) {
zIndex = ZIndexes.sparkyAnimatronic;
}
@ -41,9 +42,6 @@ class SparkyAnimatronic extends SpriteAnimationComponent
textureSize: textureSize,
loop: false,
),
)..onComplete = () {
animation?.reset();
playing = false;
};
);
}
}

@ -1,6 +1,6 @@
// ignore_for_file: cascade_invocations
import 'package:flame/extensions.dart';
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';
@ -56,17 +56,13 @@ void main() {
},
);
flameTester.test(
'stops animating after animation completes',
(game) async {
final dashAnimatronic = DashAnimatronic();
await game.ensureAdd(dashAnimatronic);
dashAnimatronic.playing = true;
game.update(4);
expect(dashAnimatronic.playing, isFalse);
},
);
flameTester.test('adds new children', (game) async {
final component = Component();
final dashAnimatronic = DashAnimatronic(
children: [component],
);
await game.ensureAdd(dashAnimatronic);
expect(dashAnimatronic.children, contains(component));
});
});
}

@ -1,6 +1,6 @@
// ignore_for_file: cascade_invocations
import 'package:flame/extensions.dart';
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';
@ -58,19 +58,13 @@ void main() {
},
);
flameTester.test(
'stops animating after animation completes',
(game) async {
final sparkyAnimatronic = SparkyAnimatronic();
await game.ensureAdd(sparkyAnimatronic);
sparkyAnimatronic.playing = true;
final animationDuration =
game.firstChild<SparkyAnimatronic>()!.animation!.totalDuration();
game.update(animationDuration);
expect(sparkyAnimatronic.playing, isFalse);
},
);
flameTester.test('adds new children', (game) async {
final component = Component();
final sparkyAnimatronic = SparkyAnimatronic(
children: [component],
);
await game.ensureAdd(sparkyAnimatronic);
expect(sparkyAnimatronic.children, contains(component));
});
});
}

@ -0,0 +1,105 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball_components/pinball_components.dart';
class _TestGame extends Forge2DGame {
@override
Future<void> onLoad() async {
images.prefix = '';
await images.load(Assets.images.dash.animatronic.keyName);
}
}
class _TestSpriteAnimationComponent extends SpriteAnimationComponent {}
class _MockSpriteAnimation extends Mock implements SpriteAnimation {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(_TestGame.new);
group('AnimatronicLoopingBehavior', () {
test('can be instantiated', () {
expect(
AnimatronicLoopingBehavior(animationCoolDown: 1),
isA<AnimatronicLoopingBehavior>(),
);
});
flameTester.test(
'can be added',
(game) async {
final behavior = AnimatronicLoopingBehavior(animationCoolDown: 1);
final animation = _MockSpriteAnimation();
final spriteAnimationComponent = _TestSpriteAnimationComponent()
..animation = animation;
await game.ensureAdd(spriteAnimationComponent);
await spriteAnimationComponent.add(behavior);
await game.ready();
expect(game.contains(spriteAnimationComponent), isTrue);
expect(spriteAnimationComponent.contains(behavior), isTrue);
},
);
flameTester.test(
'onTick starts playing the animation',
(game) async {
final behavior = AnimatronicLoopingBehavior(animationCoolDown: 1);
final spriteAnimationComponent = _TestSpriteAnimationComponent();
await game.ensureAdd(spriteAnimationComponent);
await spriteAnimationComponent.add(behavior);
spriteAnimationComponent.playing = false;
game.update(behavior.timer.limit);
expect(spriteAnimationComponent.playing, isTrue);
},
);
flameTester.test(
'animation onComplete resets and stops playing the animation',
(game) async {
final behavior = AnimatronicLoopingBehavior(animationCoolDown: 1);
final spriteAnimationComponent = DashAnimatronic();
await game.ensureAdd(spriteAnimationComponent);
await spriteAnimationComponent.add(behavior);
game.update(1);
expect(spriteAnimationComponent.playing, isTrue);
spriteAnimationComponent.animation!.onComplete!.call();
expect(spriteAnimationComponent.playing, isFalse);
expect(spriteAnimationComponent.animation!.currentIndex, equals(0));
},
);
flameTester.test(
'animation onComplete resets and starts the timer',
(game) async {
final behavior = AnimatronicLoopingBehavior(animationCoolDown: 1);
final spriteAnimationComponent = DashAnimatronic();
await game.ensureAdd(spriteAnimationComponent);
await spriteAnimationComponent.add(behavior);
game.update(0.5);
expect(behavior.timer.current, equals(0.5));
spriteAnimationComponent.animation!.onComplete!.call();
expect(behavior.timer.current, equals(0));
expect(behavior.timer.isRunning(), isTrue);
},
);
});
}

@ -16,10 +16,7 @@ class _TestGame extends Forge2DGame {
@override
Future<void> onLoad() async {
images.prefix = '';
await images.loadAll([
Assets.images.dash.animatronic.keyName,
theme.Assets.images.dash.ball.keyName,
]);
await images.load(theme.Assets.images.dash.ball.keyName);
}
Future<void> pump(
@ -67,10 +64,9 @@ void main() {
DashBumper.test(bloc: DashBumperCubit()),
DashBumper.test(bloc: DashBumperCubit()),
];
final animatronic = DashAnimatronic();
final signpost = Signpost.test(bloc: SignpostCubit());
await game.pump(parent, gameBloc: gameBloc);
await parent.ensureAddAll([...bumpers, animatronic, signpost]);
await parent.ensureAddAll([...bumpers, signpost]);
await parent.ensureAdd(behavior);
expect(game.descendants().whereType<DashBumper>(), equals(bumpers));
@ -99,10 +95,9 @@ void main() {
DashBumper.test(bloc: DashBumperCubit()),
DashBumper.test(bloc: DashBumperCubit()),
];
final animatronic = DashAnimatronic();
final signpost = Signpost.test(bloc: SignpostCubit());
await game.pump(parent, gameBloc: gameBloc);
await parent.ensureAddAll([...bumpers, animatronic, signpost]);
await parent.ensureAddAll([...bumpers, signpost]);
await parent.ensureAdd(behavior);
expect(game.descendants().whereType<DashBumper>(), equals(bumpers));
@ -133,10 +128,9 @@ void main() {
DashBumper.test(bloc: DashBumperCubit()),
DashBumper.test(bloc: DashBumperCubit()),
];
final animatronic = DashAnimatronic();
final signpost = Signpost.test(bloc: SignpostCubit());
await game.pump(parent, gameBloc: gameBloc);
await parent.ensureAddAll([...bumpers, animatronic, signpost]);
await parent.ensureAddAll([...bumpers, signpost]);
await parent.ensureAdd(behavior);
expect(game.descendants().whereType<DashBumper>(), equals(bumpers));

@ -17,7 +17,6 @@ class _TestGame extends Forge2DGame {
Assets.images.sparky.computer.top.keyName,
Assets.images.sparky.computer.base.keyName,
Assets.images.sparky.computer.glow.keyName,
Assets.images.sparky.animatronic.keyName,
Assets.images.sparky.bumper.a.lit.keyName,
Assets.images.sparky.bumper.a.dimmed.keyName,
Assets.images.sparky.bumper.b.lit.keyName,
@ -58,18 +57,14 @@ void main() {
final flameTester = FlameTester(_TestGame.new);
flameTester.testGameWidget(
'adds GameBonus.sparkyTurboCharge to the game and plays animatronic '
'adds GameBonus.sparkyTurboCharge to the game '
'when SparkyComputerState.withBall is emitted',
setUp: (game, tester) async {
final behavior = SparkyComputerBonusBehavior();
final parent = SparkyScorch.test();
final sparkyComputer = SparkyComputer();
final animatronic = SparkyAnimatronic();
await parent.addAll([
sparkyComputer,
animatronic,
]);
await parent.add(sparkyComputer);
await game.pump(parent, gameBloc: gameBloc);
await parent.ensureAdd(behavior);
@ -79,7 +74,6 @@ void main() {
verify(
() => gameBloc.add(const BonusActivated(GameBonus.sparkyTurboCharge)),
).called(1);
expect(animatronic.playing, isTrue);
},
);
});

Loading…
Cancel
Save