feat: connect spaceship ramp (#65)

* refactor: improve layer and ramp to allow connection between different layers outside from board

* refactor: modified spaceship to be Layered and RampOpening

* refactor: moved ramp and game components to connect jetpack ramp with spaceship

* test: test coverage and removed layer unnecessary tests for spaceship

* refactor: jetpack ramp removed rotation

* refactor: hardcoded layer spaceship inside each component

Co-authored-by: Erick <erickzanardoo@gmail.com>
pull/69/head
Rui Miguel Alonso 4 years ago committed by GitHub
parent 38b28243a7
commit aa1a2d7674
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -30,22 +30,18 @@ class JetpackRamp extends Component with HasGameRef<PinballGame> {
// TODO(ruialonso): Use a bezier curve once control points are defined. // TODO(ruialonso): Use a bezier curve once control points are defined.
color: const Color.fromARGB(255, 8, 218, 241), color: const Color.fromARGB(255, 8, 218, 241),
center: position, center: position,
width: 80, width: 62,
radius: 200, radius: 200,
angle: 7 * math.pi / 6, angle: math.pi,
rotation: -math.pi / 18,
) )
..initialPosition = position ..initialPosition = position
..layer = layer; ..layer = layer;
final leftOpening = _JetpackRampOpening( final leftOpening = _JetpackRampOpening(outsideLayer: Layer.spaceship)
rotation: 15 * math.pi / 180, ..initialPosition = position + Vector2(-27.6, 25.3)
) ..layer = Layer.jetpack;
..initialPosition = position + Vector2(-27, 21)
..layer = Layer.opening; final rightOpening = _JetpackRampOpening()
final rightOpening = _JetpackRampOpening( ..initialPosition = position + Vector2(-10.6, 25.3)
rotation: -math.pi / 20,
)
..initialPosition = position + Vector2(-11.2, 22.5)
..layer = Layer.opening; ..layer = Layer.opening;
await addAll([ await addAll([
@ -63,25 +59,21 @@ class JetpackRamp extends Component with HasGameRef<PinballGame> {
class _JetpackRampOpening extends RampOpening { class _JetpackRampOpening extends RampOpening {
/// {@macro jetpack_ramp_opening} /// {@macro jetpack_ramp_opening}
_JetpackRampOpening({ _JetpackRampOpening({
required double rotation, Layer? outsideLayer,
}) : _rotation = rotation, }) : super(
super(
pathwayLayer: Layer.jetpack, pathwayLayer: Layer.jetpack,
outsideLayer: outsideLayer,
orientation: RampOrientation.down, orientation: RampOrientation.down,
); );
final double _rotation; // TODO(ruialonso): Avoid magic number 2, should be proportional to
// TODO(ruialonso): Avoid magic number 3, should be propotional to
// [JetpackRamp]. // [JetpackRamp].
static final Vector2 _size = Vector2(3, .1); static const _size = 2;
@override @override
Shape get shape => PolygonShape() Shape get shape => PolygonShape()
..setAsBox( ..setAsEdge(
_size.x, Vector2(initialPosition.x - _size, initialPosition.y),
_size.y, Vector2(initialPosition.x + _size, initialPosition.y),
initialPosition,
_rotation,
); );
} }

@ -55,6 +55,9 @@ enum Layer {
/// Collide only with Launcher group elements. /// Collide only with Launcher group elements.
launcher, launcher,
/// Collide only with Spaceship group elements.
spaceship,
} }
/// {@template layer_mask_bits} /// {@template layer_mask_bits}
@ -81,6 +84,8 @@ extension LayerMaskBits on Layer {
return 0x0002; return 0x0002;
case Layer.launcher: case Layer.launcher:
return 0x0005; return 0x0005;
case Layer.spaceship:
return 0x000A;
} }
} }
} }

@ -27,15 +27,21 @@ abstract class RampOpening extends BodyComponent with InitialPosition, Layered {
/// {@macro ramp_opening} /// {@macro ramp_opening}
RampOpening({ RampOpening({
required Layer pathwayLayer, required Layer pathwayLayer,
Layer? outsideLayer,
required this.orientation, required this.orientation,
}) : _pathwayLayer = pathwayLayer { }) : _pathwayLayer = pathwayLayer,
_outsideLayer = outsideLayer ?? Layer.board {
layer = Layer.board; layer = Layer.board;
} }
final Layer _pathwayLayer; final Layer _pathwayLayer;
final Layer _outsideLayer;
/// Mask of category bits for collision inside [Pathway]. /// Mask of category bits for collision inside [Pathway].
Layer get pathwayLayer => _pathwayLayer; Layer get pathwayLayer => _pathwayLayer;
/// Mask of category bits for collision outside [Pathway].
Layer get outsideLayer => _outsideLayer;
/// The [Shape] of the [RampOpening]. /// The [Shape] of the [RampOpening].
Shape get shape; Shape get shape;
@ -85,7 +91,7 @@ class RampOpeningBallContactCallback<Opening extends RampOpening>
@override @override
void end(Ball ball, Opening opening, Contact _) { void end(Ball ball, Opening opening, Contact _) {
if (!_ballsInside.contains(ball)) { if (!_ballsInside.contains(ball)) {
ball.layer = Layer.board; ball.layer = opening.outsideLayer;
} else { } else {
// TODO(ruimiguel): change this code. Check what happens with ball that // TODO(ruimiguel): change this code. Check what happens with ball that
// slightly touch Opening and goes out again. With InitialPosition change // slightly touch Opening and goes out again. With InitialPosition change
@ -97,7 +103,7 @@ class RampOpeningBallContactCallback<Opening extends RampOpening>
ball.body.linearVelocity.y > 0); ball.body.linearVelocity.y > 0);
if (isBallOutsideOpening) { if (isBallOutsideOpening) {
ball.layer = Layer.board; ball.layer = opening.outsideLayer;
_ballsInside.remove(ball); _ballsInside.remove(ball);
} }
} }

@ -8,10 +8,6 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/flame/blueprint.dart'; import 'package:pinball/flame/blueprint.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
// TODO(erickzanardo): change this to use the layer class
// that will be introduced on the path PR
const _spaceShipBits = 0x0002;
/// A [Blueprint] which creates the spaceship feature. /// A [Blueprint] which creates the spaceship feature.
class Spaceship extends Forge2DBlueprint { class Spaceship extends Forge2DBlueprint {
/// Total size of the spaceship /// Total size of the spaceship
@ -19,7 +15,7 @@ class Spaceship extends Forge2DBlueprint {
@override @override
void build() { void build() {
final position = Vector2(20, -24); final position = Vector2(30, -50);
addAllContactCallback([ addAllContactCallback([
SpaceshipHoleBallContactCallback(), SpaceshipHoleBallContactCallback(),
@ -41,9 +37,11 @@ class Spaceship extends Forge2DBlueprint {
/// {@template spaceship_saucer} /// {@template spaceship_saucer}
/// A [BodyComponent] for the base, or the saucer of the spaceship /// A [BodyComponent] for the base, or the saucer of the spaceship
/// {@endtemplate} /// {@endtemplate}
class SpaceshipSaucer extends BodyComponent with InitialPosition { class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered {
/// {@macro spaceship_saucer} /// {@macro spaceship_saucer}
SpaceshipSaucer() : super(priority: 2); SpaceshipSaucer() : super(priority: 2) {
layer = Layer.spaceship;
}
/// Path for the base sprite /// Path for the base sprite
static const saucerSpritePath = 'components/spaceship/saucer.png'; static const saucerSpritePath = 'components/spaceship/saucer.png';
@ -90,10 +88,7 @@ class SpaceshipSaucer extends BodyComponent with InitialPosition {
return world.createBody(bodyDef) return world.createBody(bodyDef)
..createFixture( ..createFixture(
FixtureDef(circleShape) FixtureDef(circleShape)..isSensor = true,
..isSensor = true
..filter.maskBits = _spaceShipBits
..filter.categoryBits = _spaceShipBits,
); );
} }
} }
@ -138,9 +133,11 @@ class SpaceshipBridgeTop extends BodyComponent with InitialPosition {
/// The main part of the [SpaceshipBridge], this [BodyComponent] /// The main part of the [SpaceshipBridge], this [BodyComponent]
/// provides both the collision and the rotation animation for the bridge. /// provides both the collision and the rotation animation for the bridge.
/// {@endtemplate} /// {@endtemplate}
class SpaceshipBridge extends BodyComponent with InitialPosition { class SpaceshipBridge extends BodyComponent with InitialPosition, Layered {
/// {@macro spaceship_bridge} /// {@macro spaceship_bridge}
SpaceshipBridge() : super(priority: 3); SpaceshipBridge() : super(priority: 3) {
layer = Layer.spaceship;
}
/// Path to the spaceship bridge /// Path to the spaceship bridge
static const spritePath = 'components/spaceship/android-bottom.png'; static const spritePath = 'components/spaceship/android-bottom.png';
@ -177,10 +174,7 @@ class SpaceshipBridge extends BodyComponent with InitialPosition {
return world.createBody(bodyDef) return world.createBody(bodyDef)
..createFixture( ..createFixture(
FixtureDef(circleShape) FixtureDef(circleShape)..restitution = 0.4,
..restitution = 0.4
..filter.maskBits = _spaceShipBits
..filter.categoryBits = _spaceShipBits,
); );
} }
} }
@ -190,34 +184,29 @@ class SpaceshipBridge extends BodyComponent with InitialPosition {
/// the spaceship area in order to modify its filter data so the ball /// the spaceship area in order to modify its filter data so the ball
/// can correctly collide only with the Spaceship /// can correctly collide only with the Spaceship
/// {@endtemplate} /// {@endtemplate}
// TODO(erickzanardo): Use RampOpening once provided. class SpaceshipEntrance extends RampOpening {
class SpaceshipEntrance extends BodyComponent with InitialPosition {
/// {@macro spaceship_entrance} /// {@macro spaceship_entrance}
SpaceshipEntrance(); SpaceshipEntrance()
: super(
pathwayLayer: Layer.spaceship,
orientation: RampOrientation.up,
) {
layer = Layer.spaceship;
}
@override @override
Body createBody() { Shape get shape {
final entranceShape = PolygonShape() const radius = Spaceship.radius * 2;
return PolygonShape()
..setAsEdge( ..setAsEdge(
Vector2( Vector2(
Spaceship.radius * cos(20 * pi / 180), radius * cos(20 * pi / 180),
Spaceship.radius * sin(20 * pi / 180), radius * sin(20 * pi / 180),
), )..rotate(90 * pi / 180),
Vector2( Vector2(
Spaceship.radius * cos(340 * pi / 180), radius * cos(340 * pi / 180),
Spaceship.radius * sin(340 * pi / 180), radius * sin(340 * pi / 180),
), )..rotate(90 * pi / 180),
);
final bodyDef = BodyDef()
..userData = this
..position = initialPosition
..angle = 90 * pi / 180
..type = BodyType.static;
return world.createBody(bodyDef)
..createFixture(
FixtureDef(entranceShape)..isSensor = true,
); );
} }
} }
@ -226,9 +215,11 @@ class SpaceshipEntrance extends BodyComponent with InitialPosition {
/// A sensor [BodyComponent] responsible for sending the [Ball] /// A sensor [BodyComponent] responsible for sending the [Ball]
/// back to the board. /// back to the board.
/// {@endtemplate} /// {@endtemplate}
class SpaceshipHole extends BodyComponent with InitialPosition { class SpaceshipHole extends BodyComponent with InitialPosition, Layered {
/// {@macro spaceship_hole} /// {@macro spaceship_hole}
SpaceshipHole(); SpaceshipHole() {
layer = Layer.spaceship;
}
@override @override
Body createBody() { Body createBody() {
@ -242,10 +233,7 @@ class SpaceshipHole extends BodyComponent with InitialPosition {
return world.createBody(bodyDef) return world.createBody(bodyDef)
..createFixture( ..createFixture(
FixtureDef(circleShape) FixtureDef(circleShape)..isSensor = true,
..isSensor = true
..filter.maskBits = _spaceShipBits
..filter.categoryBits = _spaceShipBits,
); );
} }
} }
@ -256,9 +244,11 @@ class SpaceshipHole extends BodyComponent with InitialPosition {
/// [Ball] to get inside the spaceship saucer. /// [Ball] to get inside the spaceship saucer.
/// It also contains the [SpriteComponent] for the lower wall /// It also contains the [SpriteComponent] for the lower wall
/// {@endtemplate} /// {@endtemplate}
class SpaceshipWall extends BodyComponent with InitialPosition { class SpaceshipWall extends BodyComponent with InitialPosition, Layered {
/// {@macro spaceship_wall} /// {@macro spaceship_wall}
SpaceshipWall() : super(priority: 4); SpaceshipWall() : super(priority: 4) {
layer = Layer.spaceship;
}
/// Sprite path for the lower wall /// Sprite path for the lower wall
static const lowerWallPath = 'components/spaceship/lower.png'; static const lowerWallPath = 'components/spaceship/lower.png';
@ -303,10 +293,7 @@ class SpaceshipWall extends BodyComponent with InitialPosition {
return world.createBody(bodyDef) return world.createBody(bodyDef)
..createFixture( ..createFixture(
FixtureDef(wallShape) FixtureDef(wallShape)..restitution = 1,
..restitution = 1
..filter.maskBits = _spaceShipBits
..filter.categoryBits = _spaceShipBits,
); );
} }
} }
@ -316,19 +303,14 @@ class SpaceshipWall extends BodyComponent with InitialPosition {
/// ///
/// It modifies the [Ball] priority and filter data so it can appear on top of /// It modifies the [Ball] priority and filter data so it can appear on top of
/// the spaceship and also only collide with the spaceship. /// the spaceship and also only collide with the spaceship.
// TODO(alestiago): modify once Layer is implemented in Spaceship.
class SpaceshipEntranceBallContactCallback class SpaceshipEntranceBallContactCallback
extends ContactCallback<SpaceshipEntrance, Ball> { extends ContactCallback<SpaceshipEntrance, Ball> {
@override @override
void begin(SpaceshipEntrance entrance, Ball ball, _) { void begin(SpaceshipEntrance entrance, Ball ball, _) {
ball ball
..priority = 3 ..priority = 3
..gameRef.reorderChildren(); ..gameRef.reorderChildren()
..layer = Layer.spaceship;
for (final fixture in ball.body.fixtures) {
fixture.filterData.categoryBits = _spaceShipBits;
fixture.filterData.maskBits = _spaceShipBits;
}
} }
} }
@ -337,18 +319,13 @@ class SpaceshipEntranceBallContactCallback
/// ///
/// It resets the [Ball] priority and filter data so it will "be back" on the /// It resets the [Ball] priority and filter data so it will "be back" on the
/// board. /// board.
// TODO(alestiago): modify once Layer is implemented in Spaceship.
class SpaceshipHoleBallContactCallback class SpaceshipHoleBallContactCallback
extends ContactCallback<SpaceshipHole, Ball> { extends ContactCallback<SpaceshipHole, Ball> {
@override @override
void begin(SpaceshipHole hole, Ball ball, _) { void begin(SpaceshipHole hole, Ball ball, _) {
ball ball
..priority = 1 ..priority = 1
..gameRef.reorderChildren(); ..gameRef.reorderChildren()
..layer = Layer.board;
for (final fixture in ball.body.fixtures) {
fixture.filterData.categoryBits = 0xFFFF;
fixture.filterData.maskBits = 0x0001;
}
} }
} }

@ -27,33 +27,22 @@ class PinballGame extends Forge2DGame
_addContactCallbacks(); _addContactCallbacks();
await _addGameBoundaries(); await _addGameBoundaries();
unawaited(_addBoard());
unawaited(_addPlunger()); unawaited(_addPlunger());
unawaited(_addBonusWord());
unawaited(_addPaths()); unawaited(_addPaths());
unawaited(addFromBlueprint(Spaceship())); unawaited(addFromBlueprint(Spaceship()));
}
// Corner wall above plunger so the ball deflects into the rest of the void _addContactCallbacks() {
// board. addContactCallback(BallScorePointsCallback());
// TODO(allisonryan0002): remove once we have the launch track for the ball. addContactCallback(BottomWallBallContactCallback());
await add( addContactCallback(BonusLetterBallContactCallback());
Wall( }
start: screenToWorld(
Vector2(
camera.viewport.effectiveSize.x,
100,
),
),
end: screenToWorld(
Vector2(
camera.viewport.effectiveSize.x - 100,
0,
),
),
),
);
unawaited(_addBonusWord()); Future<void> _addGameBoundaries() async {
unawaited(_addBoard()); await add(BottomWall(this));
createBoundaries(this).forEach(add);
} }
Future<void> _addBoard() async { Future<void> _addBoard() async {
@ -68,6 +57,20 @@ class PinballGame extends Forge2DGame
await add(board); await add(board);
} }
Future<void> _addPlunger() async {
plunger = Plunger(
compressionDistance: camera.viewport.effectiveSize.y / 12,
);
plunger.initialPosition = screenToWorld(
Vector2(
camera.viewport.effectiveSize.x / 2 + 450,
camera.viewport.effectiveSize.y - plunger.compressionDistance,
),
);
await add(plunger);
}
Future<void> _addBonusWord() async { Future<void> _addBonusWord() async {
await add( await add(
BonusWord( BonusWord(
@ -81,33 +84,9 @@ class PinballGame extends Forge2DGame
); );
} }
void spawnBall() {
final ball = Ball();
add(
ball
..initialPosition = plunger.body.position + Vector2(0, ball.size.y / 2),
);
}
void _addContactCallbacks() {
addContactCallback(BallScorePointsCallback());
addContactCallback(BottomWallBallContactCallback());
addContactCallback(BonusLetterBallContactCallback());
}
Future<void> _addGameBoundaries() async {
await add(BottomWall(this));
createBoundaries(this).forEach(add);
}
Future<void> _addPaths() async { Future<void> _addPaths() async {
final jetpackRamp = JetpackRamp( final jetpackRamp = JetpackRamp(
position: screenToWorld( position: Vector2(42.6, -45),
Vector2(
camera.viewport.effectiveSize.x / 2 - 150,
camera.viewport.effectiveSize.y / 2 - 250,
),
),
); );
final launcherRamp = LauncherRamp( final launcherRamp = LauncherRamp(
position: screenToWorld( position: screenToWorld(
@ -121,18 +100,12 @@ class PinballGame extends Forge2DGame
await addAll([jetpackRamp, launcherRamp]); await addAll([jetpackRamp, launcherRamp]);
} }
Future<void> _addPlunger() async { void spawnBall() {
plunger = Plunger( final ball = Ball();
compressionDistance: camera.viewport.effectiveSize.y / 12, add(
); ball
plunger.initialPosition = screenToWorld( ..initialPosition = plunger.body.position + Vector2(0, ball.size.y / 2),
Vector2(
camera.viewport.effectiveSize.x / 2 + 450,
camera.viewport.effectiveSize.y - plunger.compressionDistance,
),
); );
await add(plunger);
} }
} }

@ -54,17 +54,6 @@ void main() {
verify(game.reorderChildren).called(1); verify(game.reorderChildren).called(1);
}); });
test('changes the filter data from the ball fixtures', () {
SpaceshipEntranceBallContactCallback().begin(
entrance,
ball,
MockContact(),
);
verify(() => filterData.maskBits = 0x0002).called(1);
verify(() => filterData.categoryBits = 0x0002).called(1);
});
}); });
group('SpaceshipHoleBallContactCallback', () { group('SpaceshipHoleBallContactCallback', () {
@ -87,17 +76,6 @@ void main() {
verify(game.reorderChildren).called(1); verify(game.reorderChildren).called(1);
}); });
test('changes the filter data from the ball fixtures', () {
SpaceshipHoleBallContactCallback().begin(
hole,
ball,
MockContact(),
);
verify(() => filterData.categoryBits = 0xFFFF).called(1);
verify(() => filterData.maskBits = 0x0001).called(1);
});
}); });
}); });
} }

@ -25,9 +25,7 @@ void main() {
final walls = game.children.where( final walls = game.children.where(
(component) => component is Wall && component is! BottomWall, (component) => component is Wall && component is! BottomWall,
); );
// TODO(allisonryan0002): expect 3 when launch track is added and expect(walls.length, 3);
// temporary wall is removed.
expect(walls.length, 4);
}, },
); );

Loading…
Cancel
Save