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

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

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

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

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

@ -54,17 +54,6 @@ void main() {
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', () {
@ -87,17 +76,6 @@ void main() {
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(
(component) => component is Wall && component is! BottomWall,
);
// TODO(allisonryan0002): expect 3 when launch track is added and
// temporary wall is removed.
expect(walls.length, 4);
expect(walls.length, 3);
},
);

Loading…
Cancel
Save