fix: flying ball when entering SpaceshipRamp (#380)

pull/358/head
Alejandro Santiago 2 years ago committed by alestiago
parent e20c231f51
commit 0534e2dd84

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

@ -1,6 +1,7 @@
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';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template layer_entrance_orientation}
/// Determines if a layer entrance is oriented [up] or [down] on the board.

@ -32,12 +32,6 @@ class SpaceshipRamp extends Component {
RampBallAscendingContactBehavior(),
],
)..initialPosition = Vector2(1.7, -20.4),
_SpaceshipRampOpening(
outsidePriority: ZIndexes.ballOnBoard,
rotation: math.pi,
)
..initialPosition = Vector2(1.7, -19.8)
..layer = Layer.opening,
_SpaceshipRampOpening(
outsideLayer: Layer.spaceship,
outsidePriority: ZIndexes.ballOnSpaceship,
@ -46,10 +40,9 @@ class SpaceshipRamp extends Component {
..initialPosition = Vector2(-13.7, -18.6)
..layer = Layer.spaceshipEntranceRamp,
_SpaceshipRampBackground(),
_SpaceshipRampBoardOpeningSpriteComponent()
..position = Vector2(3.4, -39.5),
_SpaceshipRampBoardOpening()..initialPosition = Vector2(3.4, -39.5),
_SpaceshipRampForegroundRailing(),
_SpaceshipRampBase()..initialPosition = Vector2(1.7, -20),
_SpaceshipRampBase()..initialPosition = Vector2(3.4, -42.5),
_SpaceshipRampBackgroundRailingSpriteComponent(),
SpaceshipRampArrowSpriteComponent(
current: bloc.state.hits,
@ -253,12 +246,103 @@ extension on SpaceshipRampArrowSpriteState {
}
}
class _SpaceshipRampBoardOpeningSpriteComponent extends SpriteComponent
with HasGameRef, ZIndex {
_SpaceshipRampBoardOpeningSpriteComponent() : super(anchor: Anchor.center) {
class _SpaceshipRampBoardOpening extends BodyComponent
with Layered, ZIndex, InitialPosition {
_SpaceshipRampBoardOpening()
: super(
renderBody: false,
children: [
_SpaceshipRampBoardOpeningSpriteComponent(),
LayerContactBehavior(layer: Layer.spaceshipEntranceRamp)
..applyTo(['inside']),
LayerContactBehavior(layer: Layer.board)..applyTo(['outside']),
ZIndexContactBehavior(zIndex: ZIndexes.ballOnBoard)
..applyTo(['outside']),
ZIndexContactBehavior(zIndex: ZIndexes.ballOnSpaceshipRamp)
..applyTo(['middle', 'inside']),
],
) {
zIndex = ZIndexes.spaceshipRampBoardOpening;
layer = Layer.opening;
}
List<FixtureDef> _createFixtureDefs() {
final topEdge = EdgeShape()
..set(
Vector2(-3.4, -1.2),
Vector2(3.4, -1.6),
);
final bottomEdge = EdgeShape()
..set(
Vector2(-6.2, 1.5),
Vector2(6.2, 1.5),
);
final middleCurve = BezierCurveShape(
controlPoints: [
topEdge.vertex1,
Vector2(0, 2.3),
Vector2(7.5, 2.3),
topEdge.vertex2,
],
);
final leftCurve = BezierCurveShape(
controlPoints: [
Vector2(-4.4, -1.2),
Vector2(-4.65, 0),
bottomEdge.vertex1,
],
);
final rightCurve = BezierCurveShape(
controlPoints: [
Vector2(4.4, -1.6),
Vector2(4.65, 0),
bottomEdge.vertex2,
],
);
const outsideKey = 'outside';
return [
FixtureDef(
topEdge,
isSensor: true,
userData: 'inside',
),
FixtureDef(
bottomEdge,
isSensor: true,
userData: outsideKey,
),
FixtureDef(
middleCurve,
isSensor: true,
userData: 'middle',
),
FixtureDef(
leftCurve,
isSensor: true,
userData: outsideKey,
),
FixtureDef(
rightCurve,
isSensor: true,
userData: outsideKey,
),
];
}
@override
Body createBody() {
final bodyDef = BodyDef(position: initialPosition);
final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture);
return body;
}
}
class _SpaceshipRampBoardOpeningSpriteComponent extends SpriteComponent
with HasGameRef {
_SpaceshipRampBoardOpeningSpriteComponent() : super(anchor: Anchor.center);
@override
Future<void> onLoad() async {
await super.onLoad();
@ -342,28 +426,23 @@ class _SpaceshipRampForegroundRailingSpriteComponent extends SpriteComponent
}
}
class _SpaceshipRampBase extends BodyComponent with InitialPosition, Layered {
class _SpaceshipRampBase extends BodyComponent with Layered, InitialPosition {
_SpaceshipRampBase() : super(renderBody: false) {
layer = Layer.board;
}
@override
Body createBody() {
const baseWidth = 9;
final baseShape = BezierCurveShape(
final shape = BezierCurveShape(
controlPoints: [
Vector2(initialPosition.x - baseWidth / 2, initialPosition.y),
Vector2(initialPosition.x - baseWidth / 2, initialPosition.y) +
Vector2(2, -5),
Vector2(initialPosition.x + baseWidth / 2, initialPosition.y) +
Vector2(-2, -5),
Vector2(initialPosition.x + baseWidth / 2, initialPosition.y)
Vector2(-4.25, 1.75),
Vector2(-2, -2.1),
Vector2(2, -2.3),
Vector2(4.1, 1.5),
],
);
final fixtureDef = FixtureDef(baseShape);
final bodyDef = BodyDef(position: initialPosition);
return world.createBody(bodyDef)..createFixture(fixtureDef);
return world.createBody(bodyDef)..createFixtureFromShape(shape);
}
}
@ -419,7 +498,6 @@ class RampScoringSensor extends BodyComponent
}
/// Creates a [RampScoringSensor] without any children.
///
@visibleForTesting
RampScoringSensor.test();

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flame/input.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class AndroidSpaceshipGame extends BallGame {

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flame/input.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class SpaceshipRailGame extends BallGame {

@ -4,6 +4,7 @@ import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class SpaceshipRampGame extends BallGame with KeyboardEvents {
@ -33,6 +34,9 @@ class SpaceshipRampGame extends BallGame with KeyboardEvents {
- Press space to progress arrow sprites.
''';
@override
Color backgroundColor() => Colors.white;
late final SpaceshipRamp _spaceshipRamp;
@override

@ -1,5 +1,6 @@
import 'package:flame/input.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
import 'package:sandbox/common/common.dart';

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flame/input.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class LaunchRampGame extends BallGame {

@ -2,6 +2,7 @@ import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class LayerGame extends BallGame with TapDetector {

@ -5,6 +5,7 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
import '../../../helpers/helpers.dart';

@ -6,6 +6,7 @@ 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 'package:pinball_flame/pinball_flame.dart';
import '../../../../helpers/helpers.dart';

@ -4,6 +4,7 @@ import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/layer_sensor/behaviors/behaviors.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../../helpers/helpers.dart';

@ -1,10 +1,11 @@
library pinball_flame;
export 'src/behaviors/behaviors.dart';
export 'src/canvas/canvas.dart';
export 'src/component_controller.dart';
export 'src/contact_behavior.dart';
export 'src/flame_provider.dart';
export 'src/keyboard_input_controller.dart';
export 'src/layer.dart';
export 'src/parent_is_a.dart';
export 'src/pinball_forge2d_game.dart';
export 'src/sprite_animation.dart';

@ -0,0 +1,3 @@
export 'contact_behavior.dart';
export 'layer_contact_behavior.dart';
export 'z_index_contact_behavior.dart';

@ -0,0 +1,20 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template layer_contact_behavior}
/// Switches the [Layer] of any [Layered] body that contacts with it.
/// {@endtemplate}
class LayerContactBehavior extends ContactBehavior<BodyComponent> {
/// {@macro layer_contact_behavior}
LayerContactBehavior({required Layer layer}) : _layer = layer;
final Layer _layer;
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Layered) return;
if (other.layer == _layer) return;
other.layer = _layer;
}
}

@ -0,0 +1,20 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template layer_contact_behavior}
/// Switches the z-index of any [ZIndex] body that contacts with it.
/// {@endtemplate}
class ZIndexContactBehavior extends ContactBehavior<BodyComponent> {
/// {@macro layer_contact_behavior}
ZIndexContactBehavior({required int zIndex}) : _zIndex = zIndex;
final int _zIndex;
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! ZIndex) return;
if (other.zIndex == _zIndex) return;
other.zIndex = _zIndex;
}
}

@ -4,9 +4,9 @@ import 'dart:math' as math;
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
class TestLayeredBodyComponent extends BodyComponent with Layered {
class _TestLayeredBodyComponent extends BodyComponent with Layered {
@override
Body createBody() {
final fixtureDef = FixtureDef(CircleShape());
@ -14,7 +14,7 @@ class TestLayeredBodyComponent extends BodyComponent with Layered {
}
}
class TestBodyComponent extends BodyComponent {
class _TestBodyComponent extends BodyComponent {
@override
Body createBody() {
final fixtureDef = FixtureDef(CircleShape());
@ -41,12 +41,12 @@ void main() {
}
flameTester.test('TestBodyComponent has fixtures', (game) async {
final component = TestBodyComponent();
final component = _TestBodyComponent();
await game.ensureAdd(component);
});
test('correctly sets and gets', () {
final component = TestLayeredBodyComponent()
final component = _TestLayeredBodyComponent()
..layer = Layer.spaceshipEntranceRamp;
expect(component.layer, Layer.spaceshipEntranceRamp);
});
@ -55,7 +55,7 @@ void main() {
'layers correctly before being loaded',
(game) async {
const expectedLayer = Layer.spaceshipEntranceRamp;
final component = TestLayeredBodyComponent()..layer = expectedLayer;
final component = _TestLayeredBodyComponent()..layer = expectedLayer;
await game.ensureAdd(component);
_expectLayerOnFixtures(
@ -70,7 +70,7 @@ void main() {
'when multiple different sets',
(game) async {
const expectedLayer = Layer.launcher;
final component = TestLayeredBodyComponent()
final component = _TestLayeredBodyComponent()
..layer = Layer.spaceshipEntranceRamp;
expect(component.layer, isNot(equals(expectedLayer)));
@ -89,7 +89,7 @@ void main() {
'layers correctly after being loaded',
(game) async {
const expectedLayer = Layer.spaceshipEntranceRamp;
final component = TestLayeredBodyComponent();
final component = _TestLayeredBodyComponent();
await game.ensureAdd(component);
component.layer = expectedLayer;
_expectLayerOnFixtures(
@ -104,7 +104,7 @@ void main() {
'when multiple different sets',
(game) async {
const expectedLayer = Layer.launcher;
final component = TestLayeredBodyComponent();
final component = _TestLayeredBodyComponent();
await game.ensureAdd(component);
component.layer = Layer.spaceshipEntranceRamp;
@ -122,7 +122,7 @@ void main() {
'defaults to Layer.all '
'when no layer is given',
(game) async {
final component = TestLayeredBodyComponent();
final component = _TestLayeredBodyComponent();
await game.ensureAdd(component);
expect(component.layer, equals(Layer.all));
},
@ -134,15 +134,18 @@ void main() {
const parentLayer = Layer.spaceshipEntranceRamp;
const childLayer = Layer.board;
final component = TestLayeredBodyComponent()..layer = parentLayer;
final childComponent = TestLayeredBodyComponent()..layer = childLayer;
final component = _TestLayeredBodyComponent()..layer = parentLayer;
final childComponent = _TestLayeredBodyComponent()..layer = childLayer;
await component.add(childComponent);
await game.ensureAdd(component);
expect(childLayer, isNot(equals(parentLayer)));
for (final child in component.children) {
expect((child as TestLayeredBodyComponent).layer, equals(childLayer));
expect(
(child as _TestLayeredBodyComponent).layer,
equals(childLayer),
);
}
},
);
@ -152,15 +155,15 @@ void main() {
(game) async {
const parentLayer = Layer.spaceshipEntranceRamp;
final component = TestLayeredBodyComponent()..layer = parentLayer;
final childComponent = TestBodyComponent();
final component = _TestLayeredBodyComponent()..layer = parentLayer;
final childComponent = _TestBodyComponent();
await component.add(childComponent);
await game.ensureAdd(component);
for (final child in component.children) {
expect(
(child as TestBodyComponent)
(child as _TestBodyComponent)
.body
.fixtures
.first

@ -0,0 +1,60 @@
// 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';
class _TestBodyComponent extends BodyComponent {
@override
Body createBody() {
final shape = CircleShape()..radius = 1;
return world.createBody(BodyDef())..createFixtureFromShape(shape);
}
}
class _TestLayeredBodyComponent extends _TestBodyComponent with Layered {
_TestLayeredBodyComponent({required Layer layer}) {
layer = layer;
}
}
class _MockContact extends Mock implements Contact {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(Forge2DGame.new);
group('LayerContactBehavior', () {
test('can be instantiated', () {
expect(
LayerContactBehavior(layer: Layer.all),
isA<LayerContactBehavior>(),
);
});
flameTester.test('can be loaded', (game) async {
final behavior = LayerContactBehavior(layer: Layer.all);
final parent = _TestBodyComponent();
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
expect(parent.children, contains(behavior));
});
flameTester.test('beginContact changes layer', (game) async {
const oldLayer = Layer.all;
const newLayer = Layer.board;
final behavior = LayerContactBehavior(layer: newLayer);
final parent = _TestBodyComponent();
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
final component = _TestLayeredBodyComponent(layer: oldLayer);
behavior.beginContact(component, _MockContact());
expect(component.layer, newLayer);
});
});
}

@ -0,0 +1,60 @@
// 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';
class _TestBodyComponent extends BodyComponent {
@override
Body createBody() {
final shape = CircleShape()..radius = 1;
return world.createBody(BodyDef())..createFixtureFromShape(shape);
}
}
class _TestZIndexBodyComponent extends _TestBodyComponent with ZIndex {
_TestZIndexBodyComponent({required int zIndex}) {
zIndex = zIndex;
}
}
class _MockContact extends Mock implements Contact {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(Forge2DGame.new);
group('ZIndexContactBehavior', () {
test('can be instantiated', () {
expect(
ZIndexContactBehavior(zIndex: 0),
isA<ZIndexContactBehavior>(),
);
});
flameTester.test('can be loaded', (game) async {
final behavior = ZIndexContactBehavior(zIndex: 0);
final parent = _TestBodyComponent();
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
expect(parent.children, contains(behavior));
});
flameTester.test('beginContact changes zIndex', (game) async {
const oldIndex = 0;
const newIndex = 1;
final behavior = ZIndexContactBehavior(zIndex: newIndex);
final parent = _TestBodyComponent();
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
final component = _TestZIndexBodyComponent(zIndex: oldIndex);
behavior.beginContact(component, _MockContact());
expect(component.zIndex, newIndex);
});
});
}
Loading…
Cancel
Save