feat: spaceship drop tube (#79)

* feat: added spaceship drop ramp to board

* refactor: removed findNested extensions (#77)

* refactor: placed drop ramp below spaceship, fixed position and layers

* test: tests coverage

* chore: analysis errors

* refactor: named SpaceshipExitRail

* chore: doc

* Update lib/game/components/spaceship_exit_rail.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update test/game/components/spaceship_exit_rail_test.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* Update test/game/components/spaceship_exit_rail_test.dart

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* refactor: changed name for spaceshipexit layer

* refactor: placed curve for spaceship exit rail

* fix: fixed drop tube sizes

* refactor: placed drop tube and fixed layers from spaceship

* fix: fixed exit hole contact size

* refactor: reordered mocks at tests

* chore: reorder params and typo

* fix: moved drop tube

* test: fixed spaceship exit test

Co-authored-by: Alejandro Santiago <dev@alestiago.com>
Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
Co-authored-by: Erick <erickzanardoo@gmail.com>
pull/114/head
Rui Miguel Alonso 3 years ago committed by GitHub
parent e7ba6dc91d
commit f44c2bc25e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,4 +10,5 @@ export 'kicker.dart';
export 'launcher_ramp.dart';
export 'plunger.dart';
export 'score_points.dart';
export 'spaceship_exit_rail.dart';
export 'wall.dart';

@ -114,7 +114,7 @@ class _JetpackRampOpening extends RampOpening {
final double _rotation;
static final Vector2 _size = Vector2(JetpackRamp.width / 3, .1);
static final Vector2 _size = Vector2(JetpackRamp.width / 4, .1);
@override
Shape get shape => PolygonShape()

@ -0,0 +1,198 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'dart:math' as math;
import 'dart:ui';
import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
/// {@template spaceship_exit_rail}
/// A [Blueprint] for the spaceship drop tube.
/// {@endtemplate}
class SpaceshipExitRail extends Forge2DBlueprint {
/// {@macro spaceship_exit_rail}
SpaceshipExitRail({required this.position});
/// The [position] where the elements will be created
final Vector2 position;
@override
void build(_) {
addAllContactCallback([
SpaceshipExitRailEndBallContactCallback(),
]);
final spaceshipExitRailRamp = _SpaceshipExitRailRamp()
..initialPosition = position;
final exitRail = SpaceshipExitRailEnd()
..initialPosition = position + _SpaceshipExitRailRamp.exitPoint;
addAll([
spaceshipExitRailRamp,
exitRail,
]);
}
}
class _SpaceshipExitRailRamp extends BodyComponent
with InitialPosition, Layered {
_SpaceshipExitRailRamp() : super(priority: 2) {
layer = Layer.spaceshipExitRail;
// TODO(ruimiguel): remove color once asset is placed.
paint = Paint()
..color = const Color.fromARGB(255, 249, 65, 3)
..style = PaintingStyle.stroke;
}
static final exitPoint = Vector2(9.2, -48.5);
List<FixtureDef> _createFixtureDefs() {
const entranceRotationAngle = 175 * math.pi / 180;
const curveRotationAngle = 275 * math.pi / 180;
const exitRotationAngle = 340 * math.pi / 180;
const width = 5.5;
final fixturesDefs = <FixtureDef>[];
final entranceWall = ArcShape(
center: Vector2(width / 2, 0),
arcRadius: width / 2,
angle: math.pi,
rotation: entranceRotationAngle,
);
final entranceFixtureDef = FixtureDef(entranceWall);
fixturesDefs.add(entranceFixtureDef);
final topLeftControlPoints = [
Vector2(0, 0),
Vector2(10, .5),
Vector2(7, 4),
Vector2(15.5, 8.3),
];
final topLeftCurveShape = BezierCurveShape(
controlPoints: topLeftControlPoints,
)..rotate(curveRotationAngle);
final topLeftFixtureDef = FixtureDef(topLeftCurveShape);
fixturesDefs.add(topLeftFixtureDef);
final topRightControlPoints = [
Vector2(0, width),
Vector2(10, 6.5),
Vector2(7, 10),
Vector2(15.5, 13.2),
];
final topRightCurveShape = BezierCurveShape(
controlPoints: topRightControlPoints,
)..rotate(curveRotationAngle);
final topRightFixtureDef = FixtureDef(topRightCurveShape);
fixturesDefs.add(topRightFixtureDef);
final mediumLeftControlPoints = [
topLeftControlPoints.last,
Vector2(21, 12.9),
Vector2(30, 7.1),
Vector2(32, 4.8),
];
final mediumLeftCurveShape = BezierCurveShape(
controlPoints: mediumLeftControlPoints,
)..rotate(curveRotationAngle);
final mediumLeftFixtureDef = FixtureDef(mediumLeftCurveShape);
fixturesDefs.add(mediumLeftFixtureDef);
final mediumRightControlPoints = [
topRightControlPoints.last,
Vector2(21, 17.2),
Vector2(30, 12.1),
Vector2(32, 10.2),
];
final mediumRightCurveShape = BezierCurveShape(
controlPoints: mediumRightControlPoints,
)..rotate(curveRotationAngle);
final mediumRightFixtureDef = FixtureDef(mediumRightCurveShape);
fixturesDefs.add(mediumRightFixtureDef);
final bottomLeftControlPoints = [
mediumLeftControlPoints.last,
Vector2(40, -1),
Vector2(48, 1.9),
Vector2(50.5, 2.5),
];
final bottomLeftCurveShape = BezierCurveShape(
controlPoints: bottomLeftControlPoints,
)..rotate(curveRotationAngle);
final bottomLeftFixtureDef = FixtureDef(bottomLeftCurveShape);
fixturesDefs.add(bottomLeftFixtureDef);
final bottomRightControlPoints = [
mediumRightControlPoints.last,
Vector2(40, 4),
Vector2(46, 6.5),
Vector2(48.8, 7.6),
];
final bottomRightCurveShape = BezierCurveShape(
controlPoints: bottomRightControlPoints,
)..rotate(curveRotationAngle);
final bottomRightFixtureDef = FixtureDef(bottomRightCurveShape);
fixturesDefs.add(bottomRightFixtureDef);
final exitWall = ArcShape(
center: exitPoint,
arcRadius: width / 2,
angle: math.pi,
rotation: exitRotationAngle,
);
final exitFixtureDef = FixtureDef(exitWall);
fixturesDefs.add(exitFixtureDef);
return fixturesDefs;
}
@override
Body createBody() {
final bodyDef = BodyDef()
..userData = this
..position = initialPosition;
final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture);
return body;
}
}
/// {@template spaceship_exit_rail_end}
/// A sensor [BodyComponent] responsible for sending the [Ball]
/// back to the board.
/// {@endtemplate}
class SpaceshipExitRailEnd extends RampOpening {
/// {@macro spaceship_exit_rail_end}
SpaceshipExitRailEnd()
: super(
pathwayLayer: Layer.spaceshipExitRail,
orientation: RampOrientation.down,
) {
layer = Layer.spaceshipExitRail;
}
@override
Shape get shape {
return CircleShape()..radius = 1;
}
}
/// [ContactCallback] that handles the contact between the [Ball]
/// and a [SpaceshipExitRailEnd].
///
/// It resets the [Ball] priority and filter data so it will "be back" on the
/// board.
class SpaceshipExitRailEndBallContactCallback
extends ContactCallback<SpaceshipExitRailEnd, Ball> {
@override
void begin(SpaceshipExitRailEnd exitRail, Ball ball, _) {
ball
..priority = 1
..gameRef.reorderChildren()
..layer = exitRail.outsideLayer;
}
}

@ -53,6 +53,13 @@ class PinballGame extends Forge2DGame
),
),
);
unawaited(
addFromBlueprint(
SpaceshipExitRail(
position: Vector2(-34.3, 23.8),
),
),
);
// Fix camera on the center of the board.
camera

@ -61,6 +61,9 @@ enum Layer {
/// Collide only with Spaceship group elements.
spaceship,
/// Collide only with Spaceship exit rail group elements.
spaceshipExitRail,
}
/// {@template layer_mask_bits}
@ -89,6 +92,8 @@ extension LayerMaskBits on Layer {
return 0x0005;
case Layer.spaceship:
return 0x000A;
case Layer.spaceshipExitRail:
return 0x0004;
}
}
}

@ -32,7 +32,10 @@ class Spaceship extends Forge2DBlueprint {
SpaceshipSaucer()..initialPosition = position,
SpaceshipEntrance()..initialPosition = position,
AndroidHead()..initialPosition = position,
SpaceshipHole()..initialPosition = position - Vector2(5.2, 4.8),
SpaceshipHole(
onExitLayer: Layer.spaceshipExitRail,
onExitElevation: 2,
)..initialPosition = position - Vector2(5.2, 4.8),
SpaceshipHole()..initialPosition = position - Vector2(-7.2, 0.8),
SpaceshipWall()..initialPosition = position,
]);
@ -44,7 +47,8 @@ class Spaceship extends Forge2DBlueprint {
/// {@endtemplate}
class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered {
/// {@macro spaceship_saucer}
SpaceshipSaucer() : super(priority: 2) {
// TODO(ruimiguel): apply Elevated when PR merged.
SpaceshipSaucer() : super(priority: 3) {
layer = Layer.spaceship;
}
@ -88,7 +92,7 @@ class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered {
/// {@endtemplate}
class AndroidHead extends BodyComponent with InitialPosition, Layered {
/// {@macro spaceship_bridge}
AndroidHead() : super(priority: 3) {
AndroidHead() : super(priority: 4) {
layer = Layer.spaceship;
}
@ -149,6 +153,10 @@ class SpaceshipEntrance extends RampOpening {
layer = Layer.spaceship;
}
/// Priority order for [SpaceshipHole] on enter.
// TODO(ruimiguel): apply Elevated when PR merged.
final int onEnterElevation = 4;
@override
Shape get shape {
renderBody = false;
@ -169,29 +177,31 @@ class SpaceshipEntrance extends RampOpening {
/// {@template spaceship_hole}
/// A sensor [BodyComponent] responsible for sending the [Ball]
/// back to the board.
/// out from the [Spaceship].
/// {@endtemplate}
class SpaceshipHole extends BodyComponent with InitialPosition, Layered {
class SpaceshipHole extends RampOpening {
/// {@macro spaceship_hole}
SpaceshipHole() {
SpaceshipHole({Layer? onExitLayer, this.onExitElevation = 1})
: super(
pathwayLayer: Layer.spaceship,
outsideLayer: onExitLayer,
orientation: RampOrientation.up,
) {
layer = Layer.spaceship;
}
@override
Body createBody() {
renderBody = false;
final shape = ArcShape(center: Vector2(-3.5, 2), arcRadius: 6, angle: 1);
/// Priority order for [SpaceshipHole] on exit.
// TODO(ruimiguel): apply Elevated when PR merged.
final int onExitElevation;
final bodyDef = BodyDef()
..userData = this
..position = initialPosition
..angle = 5.2
..type = BodyType.static;
return world.createBody(bodyDef)
..createFixture(
FixtureDef(shape)..isSensor = true,
);
@override
Shape get shape {
return ArcShape(
center: Vector2(0, 4.2),
arcRadius: 6,
angle: 1,
rotation: 60 * pi / 180,
);
}
}
@ -225,6 +235,7 @@ class _SpaceshipWallShape extends ChainShape {
/// {@endtemplate}
class SpaceshipWall extends BodyComponent with InitialPosition, Layered {
/// {@macro spaceship_wall}
// TODO(ruimiguel): apply Elevated when PR merged
SpaceshipWall() : super(priority: 4) {
layer = Layer.spaceship;
}
@ -258,7 +269,8 @@ class SpaceshipEntranceBallContactCallback
@override
void begin(SpaceshipEntrance entrance, Ball ball, _) {
ball
..priority = 3
// TODO(ruimiguel): apply Elevated when PR merged.
..priority = entrance.onEnterElevation
..gameRef.reorderChildren()
..layer = Layer.spaceship;
}
@ -267,15 +279,16 @@ class SpaceshipEntranceBallContactCallback
/// [ContactCallback] that handles the contact between the [Ball]
/// and a [SpaceshipHole].
///
/// It resets the [Ball] priority and filter data so it will "be back" on the
/// It sets the [Ball] priority and filter data so it will "be back" on the
/// board.
class SpaceshipHoleBallContactCallback
extends ContactCallback<SpaceshipHole, Ball> {
@override
void begin(SpaceshipHole hole, Ball ball, _) {
ball
..priority = 1
// TODO(ruimiguel): apply Elevated when PR merged.
..priority = hole.onExitElevation
..gameRef.reorderChildren()
..layer = Layer.board;
..layer = hole.outsideLayer;
}
}

@ -61,16 +61,20 @@ void main() {
group('SpaceshipEntranceBallContactCallback', () {
test('changes the ball priority on contact', () {
when(() => entrance.onEnterElevation).thenReturn(3);
SpaceshipEntranceBallContactCallback().begin(
entrance,
ball,
MockContact(),
);
verify(() => ball.priority = 3).called(1);
verify(() => ball.priority = entrance.onEnterElevation).called(1);
});
test('re order the game children', () {
when(() => entrance.onEnterElevation).thenReturn(3);
SpaceshipEntranceBallContactCallback().begin(
entrance,
ball,
@ -83,16 +87,22 @@ void main() {
group('SpaceshipHoleBallContactCallback', () {
test('changes the ball priority on contact', () {
when(() => hole.outsideLayer).thenReturn(Layer.board);
when(() => hole.onExitElevation).thenReturn(1);
SpaceshipHoleBallContactCallback().begin(
hole,
ball,
MockContact(),
);
verify(() => ball.priority = 1).called(1);
verify(() => ball.priority = hole.onExitElevation).called(1);
});
test('re order the game children', () {
when(() => hole.outsideLayer).thenReturn(Layer.board);
when(() => hole.onExitElevation).thenReturn(1);
SpaceshipHoleBallContactCallback().begin(
hole,
ball,

@ -0,0 +1,60 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
group('SpaceshipExitRail', () {
late PinballGame game;
late SpaceshipExitRailEnd exitRailEnd;
late Ball ball;
late Body body;
late Fixture fixture;
late Filter filterData;
setUp(() {
game = MockPinballGame();
exitRailEnd = MockSpaceshipExitRailEnd();
ball = MockBall();
body = MockBody();
when(() => ball.gameRef).thenReturn(game);
when(() => ball.body).thenReturn(body);
fixture = MockFixture();
filterData = MockFilter();
when(() => body.fixtures).thenReturn([fixture]);
when(() => fixture.filterData).thenReturn(filterData);
});
group('SpaceshipExitHoleBallContactCallback', () {
test('changes the ball priority on contact', () {
when(() => exitRailEnd.outsideLayer).thenReturn(Layer.board);
SpaceshipExitRailEndBallContactCallback().begin(
exitRailEnd,
ball,
MockContact(),
);
verify(() => ball.priority = 1).called(1);
});
test('reorders the game children', () {
when(() => exitRailEnd.outsideLayer).thenReturn(Layer.board);
SpaceshipExitRailEndBallContactCallback().begin(
exitRailEnd,
ball,
MockContact(),
);
verify(game.reorderChildren).called(1);
});
});
});
}

@ -70,6 +70,8 @@ class MockSpaceshipEntrance extends Mock implements SpaceshipEntrance {}
class MockSpaceshipHole extends Mock implements SpaceshipHole {}
class MockSpaceshipExitRailEnd extends Mock implements SpaceshipExitRailEnd {}
class MockComponentSet extends Mock implements ComponentSet {}
class MockDashNestBumper extends Mock implements DashNestBumper {}

Loading…
Cancel
Save