feat: add sparky animatronic (#194)
* feat: add sparky animatronic * refactor: move computer into sparky fire zone * test: sparky animatronic and controller * chore: removed old test * test: remove test for sparky fire zone component * refactor: use onComplete * chore: included SparkyTurboChargeSensor * feat: specified priority * fix: specified render priority * refactor: spacing changes * refactor: used children param * feat: made SparkyFireZone a Blueprint * feat: updated assets * refactor: moved SparkyComputerSensor * test: fix contact callback tests * test: updated SparkyComputer loading test * test: updated golden animatronic tests * feat: modified turboCharge duration * fix: included missing cached image * fix: coverage * refactor: removed unused HasGameRef mixin * fix: included missing plunger assets * Update test/game/components/sparky_fire_zone_test.dart * fix: unused import Co-authored-by: alestiago <dev@alestiago.com>pull/228/head
@ -1,52 +0,0 @@
|
|||||||
// ignore_for_file: avoid_renaming_method_parameters
|
|
||||||
|
|
||||||
import 'package:flame/components.dart';
|
|
||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:pinball/game/game.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
|
||||||
import 'package:pinball_flame/pinball_flame.dart';
|
|
||||||
|
|
||||||
/// {@template controlled_sparky_computer}
|
|
||||||
/// [SparkyComputer] with a [SparkyComputerController] attached.
|
|
||||||
/// {@endtemplate}
|
|
||||||
class ControlledSparkyComputer extends SparkyComputer
|
|
||||||
with Controls<SparkyComputerController>, HasGameRef<Forge2DGame> {
|
|
||||||
/// {@macro controlled_sparky_computer}
|
|
||||||
ControlledSparkyComputer() : super() {
|
|
||||||
controller = SparkyComputerController(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onLoad() async {
|
|
||||||
await super.onLoad();
|
|
||||||
gameRef.addContactCallback(SparkyComputerSensorBallContactCallback());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// {@template sparky_computer_controller}
|
|
||||||
/// Controller attached to a [SparkyComputer] that handles its game related
|
|
||||||
/// logic.
|
|
||||||
/// {@endtemplate}
|
|
||||||
// TODO(allisonryan0002): listen for turbo charge game bonus and animate Sparky.
|
|
||||||
class SparkyComputerController
|
|
||||||
extends ComponentController<ControlledSparkyComputer> {
|
|
||||||
/// {@macro sparky_computer_controller}
|
|
||||||
SparkyComputerController(ControlledSparkyComputer controlledComputer)
|
|
||||||
: super(controlledComputer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// {@template sparky_computer_sensor_ball_contact_callback}
|
|
||||||
/// Turbo charges the [Ball] when it enters the [SparkyComputer]
|
|
||||||
/// {@endtemplate}
|
|
||||||
@visibleForTesting
|
|
||||||
class SparkyComputerSensorBallContactCallback
|
|
||||||
extends ContactCallback<SparkyComputerSensor, ControlledBall> {
|
|
||||||
/// {@macro sparky_computer_sensor_ball_contact_callback}
|
|
||||||
SparkyComputerSensorBallContactCallback();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void begin(_, ControlledBall ball, __) {
|
|
||||||
ball.controller.turboCharge();
|
|
||||||
}
|
|
||||||
}
|
|
After Width: | Height: | Size: 365 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 7.1 KiB |
@ -0,0 +1,46 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
/// {@template sparky_animatronic}
|
||||||
|
/// Animated Sparky that sits on top of the [SparkyComputer].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class SparkyAnimatronic extends SpriteAnimationComponent with HasGameRef {
|
||||||
|
/// {@macro sparky_animatronic}
|
||||||
|
SparkyAnimatronic()
|
||||||
|
: super(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
playing: false,
|
||||||
|
priority: RenderPriority.sparkyAnimatronic,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
final spriteSheet = gameRef.images.fromCache(
|
||||||
|
Assets.images.sparky.animatronic.keyName,
|
||||||
|
);
|
||||||
|
|
||||||
|
const amountPerRow = 9;
|
||||||
|
const amountPerColumn = 7;
|
||||||
|
final textureSize = Vector2(
|
||||||
|
spriteSheet.width / amountPerRow,
|
||||||
|
spriteSheet.height / amountPerColumn,
|
||||||
|
);
|
||||||
|
size = textureSize / 10;
|
||||||
|
|
||||||
|
animation = SpriteAnimation.fromFrameData(
|
||||||
|
spriteSheet,
|
||||||
|
SpriteAnimationData.sequenced(
|
||||||
|
amount: (amountPerRow * amountPerColumn) - 1,
|
||||||
|
amountPerRow: amountPerRow,
|
||||||
|
stepTime: 1 / 24,
|
||||||
|
textureSize: textureSize,
|
||||||
|
loop: false,
|
||||||
|
),
|
||||||
|
)..onComplete = () {
|
||||||
|
animation?.reset();
|
||||||
|
playing = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 34 KiB |
@ -0,0 +1,76 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:flame/extensions.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();
|
||||||
|
|
||||||
|
group('SparkyAnimatronic', () {
|
||||||
|
final asset = Assets.images.sparky.animatronic.keyName;
|
||||||
|
final flameTester = FlameTester(() => TestGame([asset]));
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'renders correctly',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
await game.images.load(asset);
|
||||||
|
await game.ensureAdd(SparkyAnimatronic()..playing = true);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
game.camera.followVector2(Vector2.zero());
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
final animationDuration =
|
||||||
|
game.firstChild<SparkyAnimatronic>()!.animation!.totalDuration();
|
||||||
|
|
||||||
|
await expectLater(
|
||||||
|
find.byGame<TestGame>(),
|
||||||
|
matchesGoldenFile('golden/sparky_animatronic/start.png'),
|
||||||
|
);
|
||||||
|
|
||||||
|
game.update(animationDuration * 0.25);
|
||||||
|
await tester.pump();
|
||||||
|
await expectLater(
|
||||||
|
find.byGame<TestGame>(),
|
||||||
|
matchesGoldenFile('golden/sparky_animatronic/middle.png'),
|
||||||
|
);
|
||||||
|
|
||||||
|
game.update(animationDuration * 0.75);
|
||||||
|
await tester.pump();
|
||||||
|
await expectLater(
|
||||||
|
find.byGame<TestGame>(),
|
||||||
|
matchesGoldenFile('golden/sparky_animatronic/end.png'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final sparkyAnimatronic = SparkyAnimatronic();
|
||||||
|
await game.ensureAdd(sparkyAnimatronic);
|
||||||
|
|
||||||
|
expect(game.contains(sparkyAnimatronic), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -1,38 +0,0 @@
|
|||||||
// ignore_for_file: cascade_invocations
|
|
||||||
|
|
||||||
import 'package:flame_test/flame_test.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:mocktail/mocktail.dart';
|
|
||||||
import 'package:pinball/game/game.dart';
|
|
||||||
|
|
||||||
import '../../helpers/helpers.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('ControlledSparkyComputer', () {
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
final flameTester = FlameTester(EmptyPinballTestGame.new);
|
|
||||||
|
|
||||||
flameTester.test('loads correctly', (game) async {
|
|
||||||
final sparkyComputer = ControlledSparkyComputer();
|
|
||||||
await game.ensureAdd(sparkyComputer);
|
|
||||||
expect(game.children, contains(sparkyComputer));
|
|
||||||
});
|
|
||||||
|
|
||||||
flameTester.testGameWidget(
|
|
||||||
'SparkyTurboChargeSensorBallContactCallback turbo charges the ball',
|
|
||||||
setUp: (game, tester) async {
|
|
||||||
final contackCallback = SparkyComputerSensorBallContactCallback();
|
|
||||||
final sparkyTurboChargeSensor = MockSparkyComputerSensor();
|
|
||||||
final ball = MockControlledBall();
|
|
||||||
final controller = MockBallController();
|
|
||||||
|
|
||||||
when(() => ball.controller).thenReturn(controller);
|
|
||||||
when(controller.turboCharge).thenAnswer((_) async {});
|
|
||||||
|
|
||||||
contackCallback.begin(sparkyTurboChargeSensor, ball, MockContact());
|
|
||||||
|
|
||||||
verify(() => ball.controller.turboCharge()).called(1);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|