refactor: ramp opening logic and naming (#197)

* refactor: ramp opening logic and naming

* fix: unused import
pull/202/head
Allison Ryan 3 years ago committed by GitHub
parent 831d374705
commit 2d076cceca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -19,8 +19,8 @@ export 'joint_anchor.dart';
export 'kicker.dart';
export 'launch_ramp.dart';
export 'layer.dart';
export 'layer_sensor.dart';
export 'plunger.dart';
export 'ramp_opening.dart';
export 'rocket.dart';
export 'score_text.dart';
export 'shapes/shapes.dart';

@ -15,7 +15,7 @@ class LaunchRamp extends Forge2DBlueprint {
@override
void build(_) {
addAllContactCallback([
RampOpeningBallContactCallback<_LaunchRampExit>(),
LayerSensorBallContactCallback<_LaunchRampExit>(),
]);
final launchRampBase = _LaunchRampBase();
@ -236,10 +236,10 @@ class _LaunchRampCloseWall extends BodyComponent with InitialPosition, Layered {
}
/// {@template launch_ramp_exit}
/// [RampOpening] with [Layer.launcher] to filter [Ball]s exiting the
/// [LayerSensor] with [Layer.launcher] to filter [Ball]s exiting the
/// [LaunchRamp].
/// {@endtemplate}
class _LaunchRampExit extends RampOpening {
class _LaunchRampExit extends LayerSensor {
/// {@macro launch_ramp_exit}
_LaunchRampExit({
required double rotation,
@ -247,7 +247,7 @@ class _LaunchRampExit extends RampOpening {
super(
insideLayer: Layer.launcher,
outsideLayer: Layer.board,
orientation: RampOrientation.down,
orientation: LayerEntranceOrientation.down,
insidePriority: Ball.launchRampPriority,
outsidePriority: 0,
) {

@ -0,0 +1,110 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template layer_entrance_orientation}
/// Determines if a layer entrance is oriented [up] or [down] on the board.
/// {@endtemplate}
enum LayerEntranceOrientation {
/// Facing up on the Board.
up,
/// Facing down on the Board.
down,
}
/// {@template layer_sensor}
/// [BodyComponent] located at the entrance and exit of a [Layer].
///
/// [LayerSensorBallContactCallback] detects when a [Ball] passes
/// through this sensor.
///
/// By default the base [layer] is set to [Layer.board] and the
/// [outsidePriority] is set to the lowest possible [Layer].
/// {@endtemplate}
abstract class LayerSensor extends BodyComponent with InitialPosition, Layered {
/// {@macro layer_sensor}
LayerSensor({
required Layer insideLayer,
Layer? outsideLayer,
required int insidePriority,
int? outsidePriority,
required this.orientation,
}) : _insideLayer = insideLayer,
_outsideLayer = outsideLayer ?? Layer.board,
_insidePriority = insidePriority,
_outsidePriority = outsidePriority ?? Ball.boardPriority {
layer = Layer.opening;
}
final Layer _insideLayer;
final Layer _outsideLayer;
final int _insidePriority;
final int _outsidePriority;
/// Mask bits value for collisions on [Layer].
Layer get insideLayer => _insideLayer;
/// Mask bits value for collisions outside of [Layer].
Layer get outsideLayer => _outsideLayer;
/// Render priority for the [Ball] on [Layer].
int get insidePriority => _insidePriority;
/// Render priority for the [Ball] outside of [Layer].
int get outsidePriority => _outsidePriority;
/// The [Shape] of the [LayerSensor].
Shape get shape;
/// {@macro layer_entrance_orientation}
// TODO(ruimiguel): Try to remove the need of [LayerEntranceOrientation] for
// collision calculations.
final LayerEntranceOrientation orientation;
@override
Body createBody() {
final fixtureDef = FixtureDef(
shape,
isSensor: true,
);
final bodyDef = BodyDef(
position: initialPosition,
userData: this,
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
/// {@template layer_sensor_ball_contact_callback}
/// Detects when a [Ball] enters or exits a [Layer] through a [LayerSensor].
///
/// Modifies [Ball]'s [Layer] and render priority depending on whether the
/// [Ball] is on or outside of a [Layer].
/// {@endtemplate}
class LayerSensorBallContactCallback<LayerEntrance extends LayerSensor>
extends ContactCallback<Ball, LayerEntrance> {
@override
void begin(Ball ball, LayerEntrance layerEntrance, Contact _) {
if (ball.layer != layerEntrance.insideLayer) {
final isBallEnteringOpening =
(layerEntrance.orientation == LayerEntranceOrientation.down &&
ball.body.linearVelocity.y < 0) ||
(layerEntrance.orientation == LayerEntranceOrientation.up &&
ball.body.linearVelocity.y > 0);
if (isBallEnteringOpening) {
ball
..layer = layerEntrance.insideLayer
..priority = layerEntrance.insidePriority
..reorderChildren();
}
} else {
ball
..layer = layerEntrance.outsideLayer
..priority = layerEntrance.outsidePriority
..reorderChildren();
}
}
}

@ -1,130 +0,0 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template ramp_orientation}
/// Determines if a ramp is facing [up] or [down] on the Board.
/// {@endtemplate}
enum RampOrientation {
/// Facing up on the Board.
up,
/// Facing down on the Board.
down,
}
/// {@template ramp_opening}
/// [BodyComponent] located at the entrance and exit of a ramp.
///
/// [RampOpeningBallContactCallback] detects when a [Ball] passes
/// through this opening.
///
/// By default the base [layer] is set to [Layer.board] and the
/// [outsidePriority] is set to the lowest possible [Layer].
/// {@endtemplate}
// TODO(ruialonso): Consider renaming the class.
abstract class RampOpening extends BodyComponent with InitialPosition, Layered {
/// {@macro ramp_opening}
RampOpening({
required Layer insideLayer,
Layer? outsideLayer,
required int insidePriority,
int? outsidePriority,
required this.orientation,
}) : _insideLayer = insideLayer,
_outsideLayer = outsideLayer ?? Layer.board,
_insidePriority = insidePriority,
_outsidePriority = outsidePriority ?? Ball.boardPriority {
layer = Layer.opening;
}
final Layer _insideLayer;
final Layer _outsideLayer;
final int _insidePriority;
final int _outsidePriority;
/// Mask of category bits for collision inside ramp.
Layer get insideLayer => _insideLayer;
/// Mask of category bits for collision outside ramp.
Layer get outsideLayer => _outsideLayer;
/// Priority for the [Ball] inside ramp.
int get insidePriority => _insidePriority;
/// Priority for the [Ball] outside ramp.
int get outsidePriority => _outsidePriority;
/// The [Shape] of the [RampOpening].
Shape get shape;
/// {@macro ramp_orientation}
// TODO(ruimiguel): Try to remove the need of [RampOrientation] for collision
// calculations.
final RampOrientation orientation;
@override
Body createBody() {
final fixtureDef = FixtureDef(
shape,
isSensor: true,
);
final bodyDef = BodyDef(
position: initialPosition,
userData: this,
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
/// {@template ramp_opening_ball_contact_callback}
/// Detects when a [Ball] enters or exits a ramp through a [RampOpening].
///
/// Modifies [Ball]'s [Layer] accordingly depending on whether the [Ball] is
/// outside or inside a ramp.
/// {@endtemplate}
class RampOpeningBallContactCallback<Opening extends RampOpening>
extends ContactCallback<Ball, Opening> {
/// [Ball]s currently inside the ramp.
final _ballsInside = <Ball>{};
@override
void begin(Ball ball, Opening opening, Contact _) {
Layer layer;
if (!_ballsInside.contains(ball)) {
layer = opening.insideLayer;
_ballsInside.add(ball);
ball
..sendTo(opening.insidePriority)
..layer = layer;
} else {
_ballsInside.remove(ball);
}
}
@override
void end(Ball ball, Opening opening, Contact _) {
if (!_ballsInside.contains(ball)) {
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
// now doesn't work position.y comparison
final isBallOutsideOpening =
(opening.orientation == RampOrientation.down &&
ball.body.linearVelocity.y > 0) ||
(opening.orientation == RampOrientation.up &&
ball.body.linearVelocity.y < 0);
if (isBallOutsideOpening) {
ball
..sendTo(opening.outsidePriority)
..layer = opening.outsideLayer;
_ballsInside.remove(ball);
}
}
}
}

@ -25,19 +25,19 @@ class Spaceship extends Forge2DBlueprint {
@override
void build(_) {
addAllContactCallback([
SpaceshipHoleBallContactCallback(),
SpaceshipEntranceBallContactCallback(),
LayerSensorBallContactCallback<_SpaceshipEntrance>(),
LayerSensorBallContactCallback<_SpaceshipHole>(),
]);
addAll([
SpaceshipSaucer()..initialPosition = position,
SpaceshipEntrance()..initialPosition = position,
_SpaceshipEntrance()..initialPosition = position,
AndroidHead()..initialPosition = position,
SpaceshipHole(
_SpaceshipHole(
outsideLayer: Layer.spaceshipExitRail,
outsidePriority: Ball.spaceshipRailPriority,
)..initialPosition = position - Vector2(5.2, -4.8),
SpaceshipHole()..initialPosition = position - Vector2(-7.2, -0.8),
_SpaceshipHole()..initialPosition = position - Vector2(-7.2, -0.8),
SpaceshipWall()..initialPosition = position,
]);
}
@ -140,17 +140,11 @@ class _AndroidHeadSpriteAnimation extends SpriteAnimationComponent
}
}
/// {@template spaceship_entrance}
/// A sensor [BodyComponent] used to detect when the ball enters the
/// the spaceship area in order to modify its filter data so the ball
/// can correctly collide only with the Spaceship
/// {@endtemplate}
class SpaceshipEntrance extends RampOpening {
/// {@macro spaceship_entrance}
SpaceshipEntrance()
class _SpaceshipEntrance extends LayerSensor {
_SpaceshipEntrance()
: super(
insideLayer: Layer.spaceship,
orientation: RampOrientation.up,
orientation: LayerEntranceOrientation.up,
insidePriority: Ball.spaceshipPriority,
) {
layer = Layer.spaceship;
@ -174,17 +168,12 @@ class SpaceshipEntrance extends RampOpening {
}
}
/// {@template spaceship_hole}
/// A sensor [BodyComponent] responsible for sending the [Ball]
/// out from the [Spaceship].
/// {@endtemplate}
class SpaceshipHole extends RampOpening {
/// {@macro spaceship_hole}
SpaceshipHole({Layer? outsideLayer, int? outsidePriority = 1})
class _SpaceshipHole extends LayerSensor {
_SpaceshipHole({Layer? outsideLayer, int? outsidePriority = 1})
: super(
insideLayer: Layer.spaceship,
outsideLayer: outsideLayer,
orientation: RampOrientation.up,
orientation: LayerEntranceOrientation.down,
insidePriority: 4,
outsidePriority: outsidePriority,
) {
@ -256,33 +245,3 @@ class SpaceshipWall extends BodyComponent with InitialPosition, Layered {
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
/// [ContactCallback] that handles the contact between the [Ball]
/// and the [SpaceshipEntrance].
///
/// It modifies the [Ball] priority and filter data so it can appear on top of
/// the spaceship and also only collide with the spaceship.
class SpaceshipEntranceBallContactCallback
extends ContactCallback<SpaceshipEntrance, Ball> {
@override
void begin(SpaceshipEntrance entrance, Ball ball, _) {
ball
..sendTo(entrance.insidePriority)
..layer = Layer.spaceship;
}
}
/// [ContactCallback] that handles the contact between the [Ball]
/// and a [SpaceshipHole].
///
/// It sets the [Ball] priority and filter data so it will outside of the
/// [Spaceship].
class SpaceshipHoleBallContactCallback
extends ContactCallback<SpaceshipHole, Ball> {
@override
void begin(SpaceshipHole hole, Ball ball, _) {
ball
..sendTo(hole.outsidePriority)
..layer = hole.outsideLayer;
}
}

@ -18,11 +18,11 @@ class SpaceshipRail extends Forge2DBlueprint {
@override
void build(_) {
addAllContactCallback([
SpaceshipRailExitBallContactCallback(),
LayerSensorBallContactCallback<_SpaceshipRailExit>(),
]);
final railRamp = _SpaceshipRailRamp();
final railEnd = SpaceshipRailExit();
final railEnd = _SpaceshipRailExit();
final topBase = _SpaceshipRailBase(radius: 0.55)
..initialPosition = Vector2(-26.15, -18.65);
final bottomBase = _SpaceshipRailBase(radius: 0.8)
@ -201,15 +201,10 @@ class _SpaceshipRailBase extends BodyComponent with InitialPosition, Layered {
}
}
/// {@template spaceship_rail_exit}
/// A sensor [BodyComponent] responsible for sending the [Ball]
/// back to the board.
/// {@endtemplate}
class SpaceshipRailExit extends RampOpening {
/// {@macro spaceship_rail_exit}
SpaceshipRailExit()
class _SpaceshipRailExit extends LayerSensor {
_SpaceshipRailExit()
: super(
orientation: RampOrientation.down,
orientation: LayerEntranceOrientation.down,
insideLayer: Layer.spaceshipExitRail,
insidePriority: Ball.spaceshipRailPriority,
) {
@ -227,18 +222,3 @@ class SpaceshipRailExit extends RampOpening {
);
}
}
/// [ContactCallback] that handles the contact between the [Ball]
/// and a [SpaceshipRailExit].
///
/// It resets the [Ball] priority and filter data so it will "be back" on the
/// board.
class SpaceshipRailExitBallContactCallback
extends ContactCallback<SpaceshipRailExit, Ball> {
@override
void begin(SpaceshipRailExit exitRail, Ball ball, _) {
ball
..sendTo(exitRail.outsidePriority)
..layer = exitRail.outsideLayer;
}
}

@ -18,7 +18,7 @@ class SpaceshipRamp extends Forge2DBlueprint {
@override
void build(_) {
addAllContactCallback([
RampOpeningBallContactCallback<_SpaceshipRampOpening>(),
LayerSensorBallContactCallback<_SpaceshipRampOpening>(),
]);
final rightOpening = _SpaceshipRampOpening(
@ -273,10 +273,10 @@ class _SpaceshipRampBase extends BodyComponent with InitialPosition, Layered {
}
/// {@template spaceship_ramp_opening}
/// [RampOpening] with [Layer.spaceshipEntranceRamp] to filter [Ball] collisions
/// [LayerSensor] with [Layer.spaceshipEntranceRamp] to filter [Ball] collisions
/// inside [_SpaceshipRampBackground].
/// {@endtemplate}
class _SpaceshipRampOpening extends RampOpening {
class _SpaceshipRampOpening extends LayerSensor {
/// {@macro spaceship_ramp_opening}
_SpaceshipRampOpening({
Layer? outsideLayer,
@ -286,7 +286,7 @@ class _SpaceshipRampOpening extends RampOpening {
super(
insideLayer: Layer.spaceshipEntranceRamp,
outsideLayer: outsideLayer,
orientation: RampOrientation.down,
orientation: LayerEntranceOrientation.down,
insidePriority: Ball.spaceshipRampPriority,
outsidePriority: outsidePriority,
) {

@ -13,12 +13,6 @@ class MockBall extends Mock implements Ball {}
class MockGame extends Mock implements Forge2DGame {}
class MockSpaceshipEntrance extends Mock implements SpaceshipEntrance {}
class MockSpaceshipHole extends Mock implements SpaceshipHole {}
class MockSpaceshipRailExit extends Mock implements SpaceshipRailExit {}
class MockContact extends Mock implements Contact {}
class MockContactCallback extends Mock

@ -0,0 +1,181 @@
// 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:mocktail/mocktail.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
class TestLayerSensor extends LayerSensor {
TestLayerSensor({
required LayerEntranceOrientation orientation,
required int insidePriority,
required Layer insideLayer,
}) : super(
insideLayer: insideLayer,
insidePriority: insidePriority,
orientation: orientation,
);
@override
Shape get shape => PolygonShape()..setAsBoxXY(1, 1);
}
class TestLayerSensorBallContactCallback
extends LayerSensorBallContactCallback<TestLayerSensor> {
TestLayerSensorBallContactCallback() : super();
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
const insidePriority = 1;
group('LayerSensor', () {
flameTester.test(
'loads correctly',
(game) async {
final layerSensor = TestLayerSensor(
orientation: LayerEntranceOrientation.down,
insidePriority: insidePriority,
insideLayer: Layer.spaceshipEntranceRamp,
);
await game.ensureAdd(layerSensor);
expect(game.contains(layerSensor), isTrue);
},
);
group('body', () {
flameTester.test(
'is static',
(game) async {
final layerSensor = TestLayerSensor(
orientation: LayerEntranceOrientation.down,
insidePriority: insidePriority,
insideLayer: Layer.spaceshipEntranceRamp,
);
await game.ensureAdd(layerSensor);
expect(layerSensor.body.bodyType, equals(BodyType.static));
},
);
group('first fixture', () {
const pathwayLayer = Layer.spaceshipEntranceRamp;
const openingLayer = Layer.opening;
flameTester.test(
'exists',
(game) async {
final layerSensor = TestLayerSensor(
orientation: LayerEntranceOrientation.down,
insidePriority: insidePriority,
insideLayer: pathwayLayer,
)..layer = openingLayer;
await game.ensureAdd(layerSensor);
expect(layerSensor.body.fixtures[0], isA<Fixture>());
},
);
flameTester.test(
'shape is a polygon',
(game) async {
final layerSensor = TestLayerSensor(
orientation: LayerEntranceOrientation.down,
insidePriority: insidePriority,
insideLayer: pathwayLayer,
)..layer = openingLayer;
await game.ensureAdd(layerSensor);
final fixture = layerSensor.body.fixtures[0];
expect(fixture.shape.shapeType, equals(ShapeType.polygon));
},
);
flameTester.test(
'is sensor',
(game) async {
final layerSensor = TestLayerSensor(
orientation: LayerEntranceOrientation.down,
insidePriority: insidePriority,
insideLayer: pathwayLayer,
)..layer = openingLayer;
await game.ensureAdd(layerSensor);
final fixture = layerSensor.body.fixtures[0];
expect(fixture.isSensor, isTrue);
},
);
});
});
});
group('LayerSensorBallContactCallback', () {
late Ball ball;
late Body body;
setUp(() {
ball = MockBall();
body = MockBody();
when(() => ball.body).thenReturn(body);
when(() => ball.priority).thenReturn(1);
when(() => ball.layer).thenReturn(Layer.board);
});
flameTester.test(
'changes ball layer and priority '
'when a ball enters and exits a downward oriented LayerSensor',
(game) async {
final sensor = TestLayerSensor(
orientation: LayerEntranceOrientation.down,
insidePriority: insidePriority,
insideLayer: Layer.spaceshipEntranceRamp,
)..initialPosition = Vector2(0, 10);
final callback = TestLayerSensorBallContactCallback();
when(() => body.linearVelocity).thenReturn(Vector2(0, -1));
callback.begin(ball, sensor, MockContact());
verify(() => ball.layer = sensor.insideLayer).called(1);
verify(() => ball.priority = sensor.insidePriority).called(1);
verify(ball.reorderChildren).called(1);
when(() => ball.layer).thenReturn(sensor.insideLayer);
callback.begin(ball, sensor, MockContact());
verify(() => ball.layer = Layer.board);
verify(() => ball.priority = Ball.boardPriority).called(1);
verify(ball.reorderChildren).called(1);
});
flameTester.test(
'changes ball layer and priority '
'when a ball enters and exits an upward oriented LayerSensor',
(game) async {
final sensor = TestLayerSensor(
orientation: LayerEntranceOrientation.up,
insidePriority: insidePriority,
insideLayer: Layer.spaceshipEntranceRamp,
)..initialPosition = Vector2(0, 10);
final callback = TestLayerSensorBallContactCallback();
when(() => body.linearVelocity).thenReturn(Vector2(0, 1));
callback.begin(ball, sensor, MockContact());
verify(() => ball.layer = sensor.insideLayer).called(1);
verify(() => ball.priority = sensor.insidePriority).called(1);
verify(ball.reorderChildren).called(1);
when(() => ball.layer).thenReturn(sensor.insideLayer);
callback.begin(ball, sensor, MockContact());
verify(() => ball.layer = Layer.board);
verify(() => ball.priority = Ball.boardPriority).called(1);
verify(ball.reorderChildren).called(1);
});
});
}

@ -1,249 +0,0 @@
// 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:mocktail/mocktail.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
class TestRampOpening extends RampOpening {
TestRampOpening({
required RampOrientation orientation,
required int insidePriority,
required Layer pathwayLayer,
}) : super(
insideLayer: pathwayLayer,
insidePriority: insidePriority,
orientation: orientation,
);
@override
Shape get shape => PolygonShape()
..set([
Vector2(0, 0),
Vector2(0, 1),
Vector2(1, 1),
Vector2(1, 0),
]);
}
class TestRampOpeningBallContactCallback
extends RampOpeningBallContactCallback<TestRampOpening> {
TestRampOpeningBallContactCallback() : super();
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
const insidePriority = 1;
group('RampOpening', () {
flameTester.test(
'loads correctly',
(game) async {
final ramp = TestRampOpening(
orientation: RampOrientation.down,
insidePriority: insidePriority,
pathwayLayer: Layer.spaceshipEntranceRamp,
);
await game.ready();
await game.ensureAdd(ramp);
expect(game.contains(ramp), isTrue);
},
);
group('body', () {
flameTester.test(
'is static',
(game) async {
final ramp = TestRampOpening(
orientation: RampOrientation.down,
insidePriority: insidePriority,
pathwayLayer: Layer.spaceshipEntranceRamp,
);
await game.ensureAdd(ramp);
expect(ramp.body.bodyType, equals(BodyType.static));
},
);
group('first fixture', () {
const pathwayLayer = Layer.spaceshipEntranceRamp;
const openingLayer = Layer.opening;
flameTester.test(
'exists',
(game) async {
final ramp = TestRampOpening(
orientation: RampOrientation.down,
insidePriority: insidePriority,
pathwayLayer: pathwayLayer,
)..layer = openingLayer;
await game.ensureAdd(ramp);
expect(ramp.body.fixtures[0], isA<Fixture>());
},
);
flameTester.test(
'shape is a polygon',
(game) async {
final ramp = TestRampOpening(
orientation: RampOrientation.down,
insidePriority: insidePriority,
pathwayLayer: pathwayLayer,
)..layer = openingLayer;
await game.ensureAdd(ramp);
final fixture = ramp.body.fixtures[0];
expect(fixture.shape.shapeType, equals(ShapeType.polygon));
},
);
flameTester.test(
'is sensor',
(game) async {
final ramp = TestRampOpening(
orientation: RampOrientation.down,
insidePriority: insidePriority,
pathwayLayer: pathwayLayer,
)..layer = openingLayer;
await game.ensureAdd(ramp);
final fixture = ramp.body.fixtures[0];
expect(fixture.isSensor, isTrue);
},
);
});
});
});
group('RampOpeningBallContactCallback', () {
flameTester.test(
'changes ball layer '
'when a ball enters upwards into a downward ramp opening',
(game) async {
final ball = MockBall();
final body = MockBody();
final area = TestRampOpening(
orientation: RampOrientation.down,
insidePriority: insidePriority,
pathwayLayer: Layer.spaceshipEntranceRamp,
);
final callback = TestRampOpeningBallContactCallback();
when(() => ball.body).thenReturn(body);
when(() => ball.priority).thenReturn(1);
when(() => body.position).thenReturn(Vector2.zero());
when(() => ball.layer).thenReturn(Layer.board);
callback.begin(ball, area, MockContact());
verify(() => ball.layer = area.insideLayer).called(1);
});
flameTester.test(
'changes ball layer '
'when a ball enters downwards into a upward ramp opening',
(game) async {
final ball = MockBall();
final body = MockBody();
final area = TestRampOpening(
orientation: RampOrientation.up,
insidePriority: insidePriority,
pathwayLayer: Layer.spaceshipEntranceRamp,
);
final callback = TestRampOpeningBallContactCallback();
when(() => ball.body).thenReturn(body);
when(() => ball.priority).thenReturn(1);
when(() => body.position).thenReturn(Vector2.zero());
when(() => ball.layer).thenReturn(Layer.board);
callback.begin(ball, area, MockContact());
verify(() => ball.layer = area.insideLayer).called(1);
});
flameTester.test(
'changes ball layer '
'when a ball exits from a downward oriented ramp', (game) async {
final ball = MockBall();
final body = MockBody();
final area = TestRampOpening(
orientation: RampOrientation.down,
insidePriority: insidePriority,
pathwayLayer: Layer.spaceshipEntranceRamp,
)..initialPosition = Vector2(0, 10);
final callback = TestRampOpeningBallContactCallback();
when(() => ball.body).thenReturn(body);
when(() => ball.priority).thenReturn(1);
when(() => body.position).thenReturn(Vector2.zero());
when(() => body.linearVelocity).thenReturn(Vector2(0, 1));
when(() => ball.layer).thenReturn(Layer.board);
callback.begin(ball, area, MockContact());
verify(() => ball.layer = area.insideLayer).called(1);
callback.end(ball, area, MockContact());
verify(() => ball.layer = Layer.board);
});
flameTester.test(
'changes ball layer '
'when a ball exits from a upward oriented ramp', (game) async {
final ball = MockBall();
final body = MockBody();
final area = TestRampOpening(
orientation: RampOrientation.up,
insidePriority: insidePriority,
pathwayLayer: Layer.spaceshipEntranceRamp,
)..initialPosition = Vector2(0, 10);
final callback = TestRampOpeningBallContactCallback();
when(() => ball.body).thenReturn(body);
when(() => ball.priority).thenReturn(1);
when(() => body.position).thenReturn(Vector2.zero());
when(() => body.linearVelocity).thenReturn(Vector2(0, -1));
when(() => ball.layer).thenReturn(Layer.board);
callback.begin(ball, area, MockContact());
verify(() => ball.layer = area.insideLayer).called(1);
callback.end(ball, area, MockContact());
verify(() => ball.layer = Layer.board);
});
flameTester.test(
'change ball layer from pathwayLayer to Layer.board '
'when a ball enters and exits from ramp', (game) async {
final ball = MockBall();
final body = MockBody();
final area = TestRampOpening(
orientation: RampOrientation.down,
insidePriority: insidePriority,
pathwayLayer: Layer.spaceshipEntranceRamp,
)..initialPosition = Vector2(0, 10);
final callback = TestRampOpeningBallContactCallback();
when(() => ball.body).thenReturn(body);
when(() => ball.priority).thenReturn(1);
when(() => body.position).thenReturn(Vector2.zero());
when(() => body.linearVelocity).thenReturn(Vector2(0, -1));
when(() => ball.layer).thenReturn(Layer.board);
callback.begin(ball, area, MockContact());
verify(() => ball.layer = area.insideLayer).called(1);
callback.end(ball, area, MockContact());
verifyNever(() => ball.layer = Layer.board);
callback.begin(ball, area, MockContact());
verifyNever(() => ball.layer = area.insideLayer);
callback.end(ball, area, MockContact());
verify(() => ball.layer = Layer.board);
});
});
}

@ -3,7 +3,6 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
@ -42,57 +41,4 @@ void main() {
},
);
});
// TODO(alestiago): Make ContactCallback private and use `beginContact`
// instead.
group('SpaceshipRailExitBallContactCallback', () {
late Forge2DGame game;
late SpaceshipRailExit railExit;
late Ball ball;
late Body body;
late Fixture fixture;
late Filter filterData;
setUp(() {
game = MockGame();
railExit = MockSpaceshipRailExit();
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);
});
setUp(() {
when(() => ball.priority).thenReturn(1);
when(() => railExit.outsideLayer).thenReturn(Layer.board);
when(() => railExit.outsidePriority).thenReturn(0);
});
test('changes the ball priority on contact', () {
SpaceshipRailExitBallContactCallback().begin(
railExit,
ball,
MockContact(),
);
verify(() => ball.sendTo(railExit.outsidePriority)).called(1);
});
test('changes the ball layer on contact', () {
SpaceshipRailExitBallContactCallback().begin(
railExit,
ball,
MockContact(),
);
verify(() => ball.layer = railExit.outsideLayer).called(1);
});
});
}

@ -15,8 +15,6 @@ void main() {
late Fixture fixture;
late Body body;
late Ball ball;
late SpaceshipEntrance entrance;
late SpaceshipHole hole;
late Forge2DGame game;
setUp(() {
@ -33,9 +31,6 @@ void main() {
ball = MockBall();
when(() => ball.gameRef).thenReturn(game);
when(() => ball.body).thenReturn(body);
entrance = MockSpaceshipEntrance();
hole = MockSpaceshipHole();
});
group('Spaceship', () {
@ -57,36 +52,5 @@ void main() {
},
);
});
group('SpaceshipEntranceBallContactCallback', () {
test('changes the ball priority on contact', () {
when(() => ball.priority).thenReturn(2);
when(() => entrance.insidePriority).thenReturn(3);
SpaceshipEntranceBallContactCallback().begin(
entrance,
ball,
MockContact(),
);
verify(() => ball.sendTo(entrance.insidePriority)).called(1);
});
});
group('SpaceshipHoleBallContactCallback', () {
test('changes the ball priority on contact', () {
when(() => ball.priority).thenReturn(2);
when(() => hole.outsideLayer).thenReturn(Layer.board);
when(() => hole.outsidePriority).thenReturn(1);
SpaceshipHoleBallContactCallback().begin(
hole,
ball,
MockContact(),
);
verify(() => ball.sendTo(hole.outsidePriority)).called(1);
});
});
});
}

@ -3,4 +3,3 @@ library pinball_flame;
export 'src/blueprint.dart';
export 'src/component_controller.dart';
export 'src/keyboard_input_controller.dart';
export 'src/priority.dart';

@ -1,39 +0,0 @@
import 'dart:math' as math;
import 'package:flame/components.dart';
/// Helper methods to change the [priority] of a [Component].
extension ComponentPriorityX on Component {
static const _lowestPriority = 0;
/// Changes the priority to a specific one.
void sendTo(int destinationPriority) {
if (priority != destinationPriority) {
priority = math.max(destinationPriority, _lowestPriority);
reorderChildren();
}
}
/// Changes the priority to the lowest possible.
void sendToBack() {
if (priority != _lowestPriority) {
priority = _lowestPriority;
reorderChildren();
}
}
/// Decreases the priority to be lower than another [Component].
void showBehindOf(Component other) {
if (priority >= other.priority) {
priority = math.max(other.priority - 1, _lowestPriority);
reorderChildren();
}
}
/// Increases the priority to be higher than another [Component].
void showInFrontOf(Component other) {
if (priority <= other.priority) {
priority = other.priority + 1;
reorderChildren();
}
}
}

@ -1,221 +0,0 @@
// 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:mocktail/mocktail.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../helpers/helpers.dart';
class TestBodyComponent extends BodyComponent {
@override
Body createBody() {
final fixtureDef = FixtureDef(CircleShape());
return world.createBody(BodyDef())..createFixture(fixtureDef);
}
}
void main() {
final flameTester = FlameTester(Forge2DGame.new);
group('ComponentPriorityX', () {
group('sendTo', () {
flameTester.test(
'changes the priority correctly to other level',
(game) async {
const newPriority = 5;
final component = TestBodyComponent()..priority = 4;
component.sendTo(newPriority);
expect(component.priority, equals(newPriority));
},
);
flameTester.test(
'calls reorderChildren if the new priority is different',
(game) async {
const newPriority = 5;
final component = MockComponent();
when(() => component.priority).thenReturn(4);
component.sendTo(newPriority);
verify(component.reorderChildren).called(1);
},
);
flameTester.test(
"doesn't call reorderChildren if the priority is the same",
(game) async {
const newPriority = 5;
final component = MockComponent();
when(() => component.priority).thenReturn(newPriority);
component.sendTo(newPriority);
verifyNever(component.reorderChildren);
},
);
});
group('sendToBack', () {
flameTester.test(
'changes the priority correctly to board level',
(game) async {
final component = TestBodyComponent()..priority = 4;
component.sendToBack();
expect(component.priority, equals(0));
},
);
flameTester.test(
'calls reorderChildren if the priority is greater than lowest level',
(game) async {
final component = MockComponent();
when(() => component.priority).thenReturn(4);
component.sendToBack();
verify(component.reorderChildren).called(1);
},
);
flameTester.test(
"doesn't call reorderChildren if the priority is the lowest level",
(game) async {
final component = MockComponent();
when(() => component.priority).thenReturn(0);
component.sendToBack();
verifyNever(component.reorderChildren);
},
);
});
group('showBehindOf', () {
flameTester.test(
'changes the priority if it is greater than other component',
(game) async {
const startPriority = 2;
final component = TestBodyComponent()..priority = startPriority;
final otherComponent = TestBodyComponent()
..priority = startPriority - 1;
component.showBehindOf(otherComponent);
expect(component.priority, equals(otherComponent.priority - 1));
},
);
flameTester.test(
"doesn't change the priority if it is lower than other component",
(game) async {
const startPriority = 2;
final component = TestBodyComponent()..priority = startPriority;
final otherComponent = TestBodyComponent()
..priority = startPriority + 1;
component.showBehindOf(otherComponent);
expect(component.priority, equals(startPriority));
},
);
flameTester.test(
'calls reorderChildren if the priority is greater than other component',
(game) async {
const startPriority = 2;
final component = MockComponent();
final otherComponent = MockComponent();
when(() => component.priority).thenReturn(startPriority);
when(() => otherComponent.priority).thenReturn(startPriority - 1);
component.showBehindOf(otherComponent);
verify(component.reorderChildren).called(1);
},
);
flameTester.test(
"doesn't call reorderChildren if the priority is lower than other "
'component',
(game) async {
const startPriority = 2;
final component = MockComponent();
final otherComponent = MockComponent();
when(() => component.priority).thenReturn(startPriority);
when(() => otherComponent.priority).thenReturn(startPriority + 1);
component.showBehindOf(otherComponent);
verifyNever(component.reorderChildren);
},
);
});
group('showInFrontOf', () {
flameTester.test(
'changes the priority if it is lower than other component',
(game) async {
const startPriority = 2;
final component = TestBodyComponent()..priority = startPriority;
final otherComponent = TestBodyComponent()
..priority = startPriority + 1;
component.showInFrontOf(otherComponent);
expect(component.priority, equals(otherComponent.priority + 1));
},
);
flameTester.test(
"doesn't change the priority if it is greater than other component",
(game) async {
const startPriority = 2;
final component = TestBodyComponent()..priority = startPriority;
final otherComponent = TestBodyComponent()
..priority = startPriority - 1;
component.showInFrontOf(otherComponent);
expect(component.priority, equals(startPriority));
},
);
flameTester.test(
'calls reorderChildren if the priority is lower than other component',
(game) async {
const startPriority = 2;
final component = MockComponent();
final otherComponent = MockComponent();
when(() => component.priority).thenReturn(startPriority);
when(() => otherComponent.priority).thenReturn(startPriority + 1);
component.showInFrontOf(otherComponent);
verify(component.reorderChildren).called(1);
},
);
flameTester.test(
"doesn't call reorderChildren if the priority is greater than other "
'component',
(game) async {
const startPriority = 2;
final component = MockComponent();
final otherComponent = MockComponent();
when(() => component.priority).thenReturn(startPriority);
when(() => otherComponent.priority).thenReturn(startPriority - 1);
component.showInFrontOf(otherComponent);
verifyNever(component.reorderChildren);
},
);
});
});
}

@ -31,11 +31,6 @@ class MockContact extends Mock implements Contact {}
class MockContactCallback extends Mock
implements ContactCallback<Object, Object> {}
class MockRampOpening extends Mock implements RampOpening {}
class MockRampOpeningBallContactCallback extends Mock
implements RampOpeningBallContactCallback {}
class MockGameBloc extends Mock implements GameBloc {}
class MockGameState extends Mock implements GameState {}

Loading…
Cancel
Save