mirror of https://github.com/flutter/pinball.git
refactor: moving layering to `LayerFilteringBehavior` (#340)
parent
f6719c93cf
commit
0595e82649
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in new issue