feat: add Sparky's computer (#155)
* feat: add sparky computer and mechanics * test: sparky mechanics * refactor: reposition * test: contact callback test * refactor: new separated sprite components * fix: lint * refactor: improve ball turbo charge testspull/171/head
@ -0,0 +1,84 @@
|
||||
// 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/flame/flame.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template controlled_sparky_computer}
|
||||
/// [SparkyComputer] with a [SparkyComputerController] attached.
|
||||
/// {@endtemplate}
|
||||
class ControlledSparkyComputer extends SparkyComputer
|
||||
with Controls<SparkyComputerController>, HasGameRef<PinballGame> {
|
||||
/// {@macro controlled_sparky_computer}
|
||||
ControlledSparkyComputer() {
|
||||
controller = SparkyComputerController(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void build(Forge2DGame _) {
|
||||
addContactCallback(SparkyTurboChargeSensorBallContactCallback());
|
||||
final sparkyTurboChargeSensor = SparkyTurboChargeSensor()
|
||||
..initialPosition = Vector2(-13, 49.8);
|
||||
add(sparkyTurboChargeSensor);
|
||||
super.build(_);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@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_turbo_charge_sensor}
|
||||
/// Small sensor body used to detect when a ball has entered the
|
||||
/// [SparkyComputer] with the [SparkyTurboChargeSensorBallContactCallback].
|
||||
/// {@endtemplate}
|
||||
@visibleForTesting
|
||||
class SparkyTurboChargeSensor extends BodyComponent with InitialPosition {
|
||||
/// {@macro sparky_turbo_charge_sensor}
|
||||
SparkyTurboChargeSensor() {
|
||||
renderBody = false;
|
||||
}
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = CircleShape()..radius = 0.1;
|
||||
|
||||
final fixtureDef = FixtureDef(shape)..isSensor = true;
|
||||
|
||||
final bodyDef = BodyDef()
|
||||
..position = initialPosition
|
||||
..userData = this;
|
||||
|
||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template sparky_turbo_charge_sensor_ball_contact_callback}
|
||||
/// Turbo charges the [Ball] on contact with [SparkyTurboChargeSensor].
|
||||
/// {@endtemplate}
|
||||
@visibleForTesting
|
||||
class SparkyTurboChargeSensorBallContactCallback
|
||||
extends ContactCallback<SparkyTurboChargeSensor, ControlledBall> {
|
||||
/// {@macro sparky_turbo_charge_sensor_ball_contact_callback}
|
||||
SparkyTurboChargeSensorBallContactCallback();
|
||||
|
||||
@override
|
||||
void begin(
|
||||
SparkyTurboChargeSensor sparkyTurboChargeSensor,
|
||||
ControlledBall ball,
|
||||
_,
|
||||
) {
|
||||
ball.controller.turboCharge();
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 4.5 KiB |
@ -0,0 +1,115 @@
|
||||
// ignore_for_file: avoid_renaming_method_parameters
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template sparky_computer}
|
||||
/// A [Blueprint] which creates the [_ComputerBase] and
|
||||
/// [_ComputerTopSpriteComponent].
|
||||
/// {@endtemplate}
|
||||
class SparkyComputer extends Forge2DBlueprint {
|
||||
@override
|
||||
void build(_) {
|
||||
final computerBase = _ComputerBase();
|
||||
final computerTop = _ComputerTopSpriteComponent();
|
||||
|
||||
addAll([
|
||||
computerBase,
|
||||
computerTop,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class _ComputerBase extends BodyComponent with InitialPosition {
|
||||
_ComputerBase();
|
||||
|
||||
List<FixtureDef> _createFixtureDefs() {
|
||||
final fixturesDef = <FixtureDef>[];
|
||||
|
||||
final leftEdge = EdgeShape()
|
||||
..set(
|
||||
Vector2(-14.9, 46),
|
||||
Vector2(-15.3, 49.6),
|
||||
);
|
||||
final leftEdgeFixtureDef = FixtureDef(leftEdge);
|
||||
fixturesDef.add(leftEdgeFixtureDef);
|
||||
|
||||
final topEdge = EdgeShape()
|
||||
..set(
|
||||
Vector2(-15.3, 49.6),
|
||||
Vector2(-10.7, 50.6),
|
||||
);
|
||||
final topEdgeFixtureDef = FixtureDef(topEdge);
|
||||
fixturesDef.add(topEdgeFixtureDef);
|
||||
|
||||
final rightEdge = EdgeShape()
|
||||
..set(
|
||||
Vector2(-10.7, 50.6),
|
||||
Vector2(-9, 47.2),
|
||||
);
|
||||
final rightEdgeFixtureDef = FixtureDef(rightEdge);
|
||||
fixturesDef.add(rightEdgeFixtureDef);
|
||||
|
||||
return fixturesDef;
|
||||
}
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final bodyDef = BodyDef()
|
||||
..userData = this
|
||||
..position = initialPosition;
|
||||
|
||||
final body = world.createBody(bodyDef);
|
||||
_createFixtureDefs().forEach(body.createFixture);
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
renderBody = false;
|
||||
|
||||
await add(_ComputerBaseSpriteComponent());
|
||||
}
|
||||
}
|
||||
|
||||
class _ComputerBaseSpriteComponent extends SpriteComponent with HasGameRef {
|
||||
_ComputerBaseSpriteComponent()
|
||||
: super(
|
||||
anchor: Anchor.center,
|
||||
position: Vector2(-11.95, -48.35),
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
final sprite = await gameRef.loadSprite(
|
||||
Assets.images.sparky.computer.base.keyName,
|
||||
);
|
||||
this.sprite = sprite;
|
||||
size = sprite.originalSize / 10;
|
||||
}
|
||||
}
|
||||
|
||||
class _ComputerTopSpriteComponent extends SpriteComponent with HasGameRef {
|
||||
_ComputerTopSpriteComponent()
|
||||
: super(
|
||||
anchor: Anchor.center,
|
||||
position: Vector2(-12.45, -49.75),
|
||||
priority: 1,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
final sprite = await gameRef.loadSprite(
|
||||
Assets.images.sparky.computer.top.keyName,
|
||||
);
|
||||
this.sprite = sprite;
|
||||
size = sprite.originalSize / 10;
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 39 KiB |
@ -0,0 +1,30 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:flame_forge2d/flame_forge2d.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() {
|
||||
group('SparkyComputer', () {
|
||||
final tester = FlameTester(TestGame.new);
|
||||
|
||||
tester.testGameWidget(
|
||||
'renders correctly',
|
||||
setUp: (game, tester) async {
|
||||
await game.addFromBlueprint(SparkyComputer());
|
||||
await game.ready();
|
||||
game.camera.followVector2(Vector2(-15, -50));
|
||||
},
|
||||
// TODO(allisonryan0002): enable test when workflows are fixed.
|
||||
// verify: (game, tester) async {
|
||||
// await expectLater(
|
||||
// find.byGame<Forge2DGame>(),
|
||||
// matchesGoldenFile('golden/sparky-computer.png'),
|
||||
// );
|
||||
// },
|
||||
);
|
||||
});
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
// 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('SparkyComputerController', () {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
final flameTester = FlameTester(EmptyPinballGameTest.new);
|
||||
|
||||
late ControlledSparkyComputer controlledSparkyComputer;
|
||||
|
||||
setUp(() {
|
||||
controlledSparkyComputer = ControlledSparkyComputer();
|
||||
});
|
||||
|
||||
test('can be instantiated', () {
|
||||
expect(
|
||||
SparkyComputerController(controlledSparkyComputer),
|
||||
isA<SparkyComputerController>(),
|
||||
);
|
||||
});
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'SparkyTurboChargeSensorBallContactCallback turbo charges the ball',
|
||||
setUp: (game, tester) async {
|
||||
final contackCallback = SparkyTurboChargeSensorBallContactCallback();
|
||||
final sparkyTurboChargeSensor = MockSparkyTurboChargeSensor();
|
||||
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);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|