refactor: moving layering to `LayerFilteringBehavior` (#340)

pull/344/head
Alejandro Santiago 3 years ago committed by GitHub
parent f6719c93cf
commit 0595e82649
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -21,7 +21,7 @@ export 'joint_anchor.dart';
export 'kicker/kicker.dart';
export 'launch_ramp.dart';
export 'layer.dart';
export 'layer_sensor.dart';
export 'layer_sensor/layer_sensor.dart';
export 'multiball/multiball.dart';
export 'multiplier/multiplier.dart';
export 'plunger.dart';

@ -1,90 +0,0 @@
// 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].
///
/// By default the base [layer] is set to [Layer.board] and the
/// [_outsideZIndex] is set to [ZIndexes.ballOnBoard].
/// {@endtemplate}
abstract class LayerSensor extends BodyComponent
with InitialPosition, Layered, ContactCallbacks {
/// {@macro layer_sensor}
LayerSensor({
required Layer insideLayer,
Layer? outsideLayer,
required int insideZIndex,
int? outsideZIndex,
required this.orientation,
}) : _insideLayer = insideLayer,
_outsideLayer = outsideLayer ?? Layer.board,
_insideZIndex = insideZIndex,
_outsideZIndex = outsideZIndex ?? ZIndexes.ballOnBoard,
super(renderBody: false) {
layer = Layer.opening;
}
final Layer _insideLayer;
final Layer _outsideLayer;
final int _insideZIndex;
final int _outsideZIndex;
/// 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);
}
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
if (other.layer != _insideLayer) {
final isBallEnteringOpening =
(orientation == LayerEntranceOrientation.down &&
other.body.linearVelocity.y < 0) ||
(orientation == LayerEntranceOrientation.up &&
other.body.linearVelocity.y > 0);
if (isBallEnteringOpening) {
other
..layer = _insideLayer
..zIndex = _insideZIndex;
}
} else {
other
..layer = _outsideLayer
..zIndex = _outsideZIndex;
}
}
}

@ -0,0 +1,2 @@
export 'behaviors.dart';
export 'layer_filtering_behavior.dart';

@ -0,0 +1,31 @@
// ignore_for_file: public_member_api_docs
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
class LayerFilteringBehavior extends ContactBehavior<LayerSensor> {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
if (other.layer != parent.insideLayer) {
final isBallEnteringOpening =
(parent.orientation == LayerEntranceOrientation.down &&
other.body.linearVelocity.y < 0) ||
(parent.orientation == LayerEntranceOrientation.up &&
other.body.linearVelocity.y > 0);
if (isBallEnteringOpening) {
other
..layer = parent.insideLayer
..zIndex = parent.insideZIndex;
}
} else {
other
..layer = parent.outsideLayer
..zIndex = parent.outsideZIndex;
}
}
}

@ -0,0 +1,66 @@
// ignore_for_file: avoid_renaming_method_parameters, public_member_api_docs
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/layer_sensor/behaviors/layer_filtering_behavior.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].
///
/// By default the base [layer] is set to [Layer.board] and the
/// [outsideZIndex] is set to [ZIndexes.ballOnBoard].
/// {@endtemplate}
abstract class LayerSensor extends BodyComponent with InitialPosition, Layered {
/// {@macro layer_sensor}
LayerSensor({
required this.insideLayer,
Layer? outsideLayer,
required this.insideZIndex,
int? outsideZIndex,
required this.orientation,
}) : outsideLayer = outsideLayer ?? Layer.board,
outsideZIndex = outsideZIndex ?? ZIndexes.ballOnBoard,
super(
renderBody: false,
children: [LayerFilteringBehavior()],
) {
layer = Layer.opening;
}
final Layer insideLayer;
final Layer outsideLayer;
final int insideZIndex;
final int outsideZIndex;
/// 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);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}

@ -0,0 +1,136 @@
// 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 'package:pinball_components/src/components/layer_sensor/behaviors/behaviors.dart';
import '../../../../helpers/helpers.dart';
class _TestLayerSensor extends LayerSensor {
_TestLayerSensor({
required LayerEntranceOrientation orientation,
required int insideZIndex,
required Layer insideLayer,
}) : super(
insideLayer: insideLayer,
insideZIndex: insideZIndex,
orientation: orientation,
);
@override
Shape get shape => PolygonShape()..setAsBoxXY(1, 1);
}
class _MockBall extends Mock implements Ball {}
class _MockBody extends Mock implements Body {}
class _MockContact extends Mock implements Contact {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
group(
'LayerSensorBehavior',
() {
test('can be instantiated', () {
expect(
LayerFilteringBehavior(),
isA<LayerFilteringBehavior>(),
);
});
flameTester.test(
'loads',
(game) async {
final behavior = LayerFilteringBehavior();
final parent = _TestLayerSensor(
orientation: LayerEntranceOrientation.down,
insideZIndex: 1,
insideLayer: Layer.spaceshipEntranceRamp,
);
await parent.add(behavior);
await game.ensureAdd(parent);
expect(game.contains(parent), isTrue);
},
);
group('beginContact', () {
late Ball ball;
late Body body;
late int insideZIndex;
late Layer insideLayer;
setUp(() {
ball = _MockBall();
body = _MockBody();
insideZIndex = 1;
insideLayer = Layer.spaceshipEntranceRamp;
when(() => ball.body).thenReturn(body);
when(() => ball.layer).thenReturn(Layer.board);
});
flameTester.test(
'changes ball layer and zIndex '
'when a ball enters and exits a downward oriented LayerSensor',
(game) async {
final parent = _TestLayerSensor(
orientation: LayerEntranceOrientation.down,
insideZIndex: 1,
insideLayer: insideLayer,
)..initialPosition = Vector2(0, 10);
final behavior = LayerFilteringBehavior();
await parent.add(behavior);
await game.ensureAdd(parent);
when(() => body.linearVelocity).thenReturn(Vector2(0, -1));
behavior.beginContact(ball, _MockContact());
verify(() => ball.layer = insideLayer).called(1);
verify(() => ball.zIndex = insideZIndex).called(1);
when(() => ball.layer).thenReturn(insideLayer);
behavior.beginContact(ball, _MockContact());
verify(() => ball.layer = Layer.board);
verify(() => ball.zIndex = ZIndexes.ballOnBoard).called(1);
});
flameTester.test(
'changes ball layer and zIndex '
'when a ball enters and exits an upward oriented LayerSensor',
(game) async {
final parent = _TestLayerSensor(
orientation: LayerEntranceOrientation.up,
insideZIndex: 1,
insideLayer: insideLayer,
)..initialPosition = Vector2(0, 10);
final behavior = LayerFilteringBehavior();
await parent.add(behavior);
await game.ensureAdd(parent);
when(() => body.linearVelocity).thenReturn(Vector2(0, 1));
behavior.beginContact(ball, _MockContact());
verify(() => ball.layer = insideLayer).called(1);
verify(() => ball.zIndex = 1).called(1);
when(() => ball.layer).thenReturn(insideLayer);
behavior.beginContact(ball, _MockContact());
verify(() => ball.layer = Layer.board);
verify(() => ball.zIndex = ZIndexes.ballOnBoard).called(1);
});
});
},
);
}

@ -2,16 +2,10 @@
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_components/src/components/layer_sensor/behaviors/behaviors.dart';
import '../../helpers/helpers.dart';
class _MockBall extends Mock implements Ball {}
class _MockBody extends Mock implements Body {}
class _MockContact extends Mock implements Contact {}
import '../../../helpers/helpers.dart';
class TestLayerSensor extends LayerSensor {
TestLayerSensor({
@ -112,68 +106,22 @@ void main() {
);
});
});
});
group('beginContact', () {
late Ball ball;
late Body body;
late int insideZIndex;
late Layer insideLayer;
setUp(() {
ball = _MockBall();
body = _MockBody();
insideZIndex = 1;
insideLayer = Layer.spaceshipEntranceRamp;
when(() => ball.body).thenReturn(body);
when(() => ball.layer).thenReturn(Layer.board);
});
flameTester.test(
'changes ball layer and zIndex '
'when a ball enters and exits a downward oriented LayerSensor',
'adds a LayerFilteringBehavior',
(game) async {
final sensor = TestLayerSensor(
final layerSensor = TestLayerSensor(
orientation: LayerEntranceOrientation.down,
insideZIndex: insidePriority,
insideLayer: insideLayer,
)..initialPosition = Vector2(0, 10);
when(() => body.linearVelocity).thenReturn(Vector2(0, -1));
sensor.beginContact(ball, _MockContact());
verify(() => ball.layer = insideLayer).called(1);
verify(() => ball.zIndex = insideZIndex).called(1);
when(() => ball.layer).thenReturn(insideLayer);
sensor.beginContact(ball, _MockContact());
verify(() => ball.layer = Layer.board);
verify(() => ball.zIndex = ZIndexes.ballOnBoard).called(1);
});
flameTester.test(
'changes ball layer and zIndex '
'when a ball enters and exits an upward oriented LayerSensor',
(game) async {
final sensor = TestLayerSensor(
orientation: LayerEntranceOrientation.up,
insideZIndex: insidePriority,
insideLayer: insideLayer,
)..initialPosition = Vector2(0, 10);
when(() => body.linearVelocity).thenReturn(Vector2(0, 1));
sensor.beginContact(ball, _MockContact());
verify(() => ball.layer = insideLayer).called(1);
verify(() => ball.zIndex = insidePriority).called(1);
when(() => ball.layer).thenReturn(insideLayer);
insideLayer: Layer.spaceshipEntranceRamp,
);
await game.ensureAdd(layerSensor);
sensor.beginContact(ball, _MockContact());
verify(() => ball.layer = Layer.board);
verify(() => ball.zIndex = ZIndexes.ballOnBoard).called(1);
});
expect(
layerSensor.children.whereType<LayerFilteringBehavior>().length,
equals(1),
);
},
);
});
}
Loading…
Cancel
Save