Merge branch 'main' into refactor/rename-dash-bumper

pull/381/head
Alejandro Santiago 3 years ago committed by GitHub
commit 571bb419eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,5 +1,6 @@
export 'ball_spawning_behavior.dart'; export 'ball_spawning_behavior.dart';
export 'ball_theming_behavior.dart'; export 'ball_theming_behavior.dart';
export 'bonus_ball_spawning_behavior.dart';
export 'bonus_noise_behavior.dart'; export 'bonus_noise_behavior.dart';
export 'bumper_noise_behavior.dart'; export 'bumper_noise_behavior.dart';
export 'camera_focusing_behavior.dart'; export 'camera_focusing_behavior.dart';

@ -0,0 +1,30 @@
import 'package:flame/components.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template bonus_ball_spawning_behavior}
/// After a duration, spawns a bonus ball from the [DinoWalls] and boosts it
/// into the middle of the board.
/// {@endtemplate}
class BonusBallSpawningBehavior extends TimerComponent with HasGameRef {
/// {@macro bonus_ball_spawning_behavior}
BonusBallSpawningBehavior()
: super(
period: 5,
removeOnFinish: true,
);
@override
void onTick() {
final characterTheme = readBloc<CharacterThemeCubit, CharacterThemeState>()
.state
.characterTheme;
gameRef.descendants().whereType<ZCanvasComponent>().single.add(
Ball(assetPath: characterTheme.ball.keyName)
..add(BallImpulsingBehavior(impulse: Vector2(-40, 0)))
..initialPosition = Vector2(29.2, -24.5)
..zIndex = ZIndexes.ballOnBoard,
);
}
}

@ -1,7 +1,7 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
@ -22,7 +22,6 @@ class FlutterForestBonusBehavior extends Component
final bumpers = parent.children.whereType<DashBumper>(); final bumpers = parent.children.whereType<DashBumper>();
final signpost = parent.firstChild<Signpost>()!; final signpost = parent.firstChild<Signpost>()!;
final animatronic = parent.firstChild<DashAnimatronic>()!; final animatronic = parent.firstChild<DashAnimatronic>()!;
final canvas = gameRef.descendants().whereType<ZCanvasComponent>().single;
for (final bumper in bumpers) { for (final bumper in bumpers) {
bumper.bloc.stream.listen((state) { bumper.bloc.stream.listen((state) {
@ -38,15 +37,7 @@ class FlutterForestBonusBehavior extends Component
if (signpost.bloc.isFullyProgressed()) { if (signpost.bloc.isFullyProgressed()) {
bloc.add(const BonusActivated(GameBonus.dashNest)); bloc.add(const BonusActivated(GameBonus.dashNest));
final characterTheme = add(BonusBallSpawningBehavior());
readBloc<CharacterThemeCubit, CharacterThemeState>()
.state
.characterTheme;
canvas.add(
Ball(assetPath: characterTheme.ball.keyName)
..initialPosition = Vector2(29.2, -24.5)
..zIndex = ZIndexes.ballOnBoard,
);
animatronic.playing = true; animatronic.playing = true;
signpost.bloc.onProgressed(); signpost.bloc.onProgressed();
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB

After

Width:  |  Height:  |  Size: 266 KiB

@ -0,0 +1,22 @@
import 'package:flame/components.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template ball_impulsing_behavior}
/// Impulses the [Ball] in a given direction.
/// {@endtemplate}
class BallImpulsingBehavior extends Component with ParentIsA<Ball> {
/// {@macro ball_impulsing_behavior}
BallImpulsingBehavior({
required Vector2 impulse,
}) : _impulse = impulse;
final Vector2 _impulse;
@override
Future<void> onLoad() async {
await super.onLoad();
parent.body.linearVelocity = _impulse;
shouldRemove = true;
}
}

@ -1,3 +1,4 @@
export 'ball_gravitating_behavior.dart'; export 'ball_gravitating_behavior.dart';
export 'ball_impulsing_behavior.dart';
export 'ball_scaling_behavior.dart'; export 'ball_scaling_behavior.dart';
export 'ball_turbo_charging_behavior.dart'; export 'ball_turbo_charging_behavior.dart';

@ -42,7 +42,7 @@ class SpaceshipRamp extends Component {
_SpaceshipRampBackground(), _SpaceshipRampBackground(),
_SpaceshipRampBoardOpening()..initialPosition = Vector2(3.4, -39.5), _SpaceshipRampBoardOpening()..initialPosition = Vector2(3.4, -39.5),
_SpaceshipRampForegroundRailing(), _SpaceshipRampForegroundRailing(),
_SpaceshipRampBase()..initialPosition = Vector2(3.4, -42.5), SpaceshipRampBase()..initialPosition = Vector2(3.4, -42.5),
_SpaceshipRampBackgroundRailingSpriteComponent(), _SpaceshipRampBackgroundRailingSpriteComponent(),
SpaceshipRampArrowSpriteComponent( SpaceshipRampArrowSpriteComponent(
current: bloc.state.hits, current: bloc.state.hits,
@ -255,9 +255,14 @@ class _SpaceshipRampBoardOpening extends BodyComponent
_SpaceshipRampBoardOpeningSpriteComponent(), _SpaceshipRampBoardOpeningSpriteComponent(),
LayerContactBehavior(layer: Layer.spaceshipEntranceRamp) LayerContactBehavior(layer: Layer.spaceshipEntranceRamp)
..applyTo(['inside']), ..applyTo(['inside']),
LayerContactBehavior(layer: Layer.board)..applyTo(['outside']), LayerContactBehavior(
ZIndexContactBehavior(zIndex: ZIndexes.ballOnBoard) layer: Layer.board,
..applyTo(['outside']), onBegin: false,
)..applyTo(['outside']),
ZIndexContactBehavior(
zIndex: ZIndexes.ballOnBoard,
onBegin: false,
)..applyTo(['outside']),
ZIndexContactBehavior(zIndex: ZIndexes.ballOnSpaceshipRamp) ZIndexContactBehavior(zIndex: ZIndexes.ballOnSpaceshipRamp)
..applyTo(['middle', 'inside']), ..applyTo(['middle', 'inside']),
], ],
@ -426,9 +431,19 @@ class _SpaceshipRampForegroundRailingSpriteComponent extends SpriteComponent
} }
} }
class _SpaceshipRampBase extends BodyComponent with Layered, InitialPosition { @visibleForTesting
_SpaceshipRampBase() : super(renderBody: false) { class SpaceshipRampBase extends BodyComponent
layer = Layer.board; with InitialPosition, ContactCallbacks {
SpaceshipRampBase() : super(renderBody: false);
@override
void preSolve(Object other, Contact contact, Manifold oldManifold) {
super.preSolve(other, contact, oldManifold);
if (other is! Layered) return;
// Although, the Layer should already be taking care of the contact
// filtering, this is to ensure the ball doesn't collide with the ramp base
// when the filtering is calculated on different time steps.
contact.setEnabled(other.layer == Layer.board);
} }
@override @override
@ -441,7 +456,7 @@ class _SpaceshipRampBase extends BodyComponent with Layered, InitialPosition {
Vector2(4.1, 1.5), Vector2(4.1, 1.5),
], ],
); );
final bodyDef = BodyDef(position: initialPosition); final bodyDef = BodyDef(position: initialPosition, userData: this);
return world.createBody(bodyDef)..createFixtureFromShape(shape); return world.createBody(bodyDef)..createFixtureFromShape(shape);
} }
} }

@ -0,0 +1,53 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.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_theme/pinball_theme.dart' as theme;
import '../../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group(
'BallImpulsingBehavior',
() {
final asset = theme.Assets.images.dash.ball.keyName;
final flameTester = FlameTester(() => TestGame([asset]));
test('can be instantiated', () {
expect(
BallImpulsingBehavior(impulse: Vector2.zero()),
isA<BallImpulsingBehavior>(),
);
});
flameTester.test(
'impulses the ball with the given velocity when loaded '
'and then removes itself',
(game) async {
final ball = Ball.test();
await game.ensureAdd(ball);
final impulse = Vector2.all(1);
final behavior = BallImpulsingBehavior(impulse: impulse);
await ball.ensureAdd(behavior);
expect(
ball.body.linearVelocity.x,
equals(impulse.x),
);
expect(
ball.body.linearVelocity.y,
equals(impulse.y),
);
expect(
game.descendants().whereType<BallImpulsingBehavior>().isEmpty,
isTrue,
);
},
);
},
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

@ -2,6 +2,7 @@
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@ -12,6 +13,12 @@ import '../../../helpers/helpers.dart';
class _MockSpaceshipRampCubit extends Mock implements SpaceshipRampCubit {} class _MockSpaceshipRampCubit extends Mock implements SpaceshipRampCubit {}
class _MockBall extends Mock implements Ball {}
class _MockContact extends Mock implements Contact {}
class _MockManifold extends Mock implements Manifold {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [ final assets = [
@ -275,4 +282,46 @@ void main() {
}); });
}); });
}); });
group('SpaceshipRampBase', () {
test('can be instantiated', () {
expect(SpaceshipRampBase(), isA<SpaceshipRampBase>());
});
flameTester.test('can be loaded', (game) async {
final component = SpaceshipRampBase();
await game.ensureAdd(component);
expect(game.children, contains(component));
});
flameTester.test(
'postSolves disables contact when ball is not on Layer.board',
(game) async {
final ball = _MockBall();
final contact = _MockContact();
when(() => ball.layer).thenReturn(Layer.spaceshipEntranceRamp);
final component = SpaceshipRampBase();
await game.ensureAdd(component);
component.preSolve(ball, contact, _MockManifold());
verify(() => contact.setEnabled(false)).called(1);
},
);
flameTester.test(
'postSolves enables contact when ball is on Layer.board',
(game) async {
final ball = _MockBall();
final contact = _MockContact();
when(() => ball.layer).thenReturn(Layer.board);
final component = SpaceshipRampBase();
await game.ensureAdd(component);
component.preSolve(ball, contact, _MockManifold());
verify(() => contact.setEnabled(true)).called(1);
},
);
});
} }

@ -6,15 +6,20 @@ import 'package:pinball_flame/pinball_flame.dart';
/// {@endtemplate} /// {@endtemplate}
class LayerContactBehavior extends ContactBehavior<BodyComponent> { class LayerContactBehavior extends ContactBehavior<BodyComponent> {
/// {@macro layer_contact_behavior} /// {@macro layer_contact_behavior}
LayerContactBehavior({required Layer layer}) : _layer = layer; LayerContactBehavior({
required Layer layer,
final Layer _layer; bool onBegin = true,
}) {
if (onBegin) {
onBeginContact = (other, _) => _changeLayer(other, layer);
} else {
onEndContact = (other, _) => _changeLayer(other, layer);
}
}
@override void _changeLayer(Object other, Layer layer) {
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Layered) return; if (other is! Layered) return;
if (other.layer == _layer) return; if (other.layer == layer) return;
other.layer = _layer; other.layer = layer;
} }
} }

@ -6,15 +6,20 @@ import 'package:pinball_flame/pinball_flame.dart';
/// {@endtemplate} /// {@endtemplate}
class ZIndexContactBehavior extends ContactBehavior<BodyComponent> { class ZIndexContactBehavior extends ContactBehavior<BodyComponent> {
/// {@macro layer_contact_behavior} /// {@macro layer_contact_behavior}
ZIndexContactBehavior({required int zIndex}) : _zIndex = zIndex; ZIndexContactBehavior({
required int zIndex,
final int _zIndex; bool onBegin = true,
}) {
if (onBegin) {
onBeginContact = (other, _) => _changeZIndex(other, zIndex);
} else {
onEndContact = (other, _) => _changeZIndex(other, zIndex);
}
}
@override void _changeZIndex(Object other, int zIndex) {
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! ZIndex) return; if (other is! ZIndex) return;
if (other.zIndex == _zIndex) return; if (other.zIndex == zIndex) return;
other.zIndex = _zIndex; other.zIndex = zIndex;
} }
} }

@ -56,5 +56,23 @@ void main() {
expect(component.layer, newLayer); expect(component.layer, newLayer);
}); });
flameTester.test('endContact changes layer', (game) async {
const oldLayer = Layer.all;
const newLayer = Layer.board;
final behavior = LayerContactBehavior(
layer: newLayer,
onBegin: false,
);
final parent = _TestBodyComponent();
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
final component = _TestLayeredBodyComponent(layer: oldLayer);
behavior.endContact(component, _MockContact());
expect(component.layer, newLayer);
});
}); });
} }

@ -56,5 +56,20 @@ void main() {
expect(component.zIndex, newIndex); expect(component.zIndex, newIndex);
}); });
flameTester.test('endContact changes zIndex', (game) async {
const oldIndex = 0;
const newIndex = 1;
final behavior = ZIndexContactBehavior(zIndex: newIndex, onBegin: false);
final parent = _TestBodyComponent();
await game.ensureAdd(parent);
await parent.ensureAdd(behavior);
final component = _TestZIndexBodyComponent(zIndex: oldIndex);
behavior.endContact(component, _MockContact());
expect(component.zIndex, newIndex);
});
}); });
} }

@ -0,0 +1,61 @@
// ignore_for_file: cascade_invocations
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/forge2d_game.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
class _TestGame extends Forge2DGame {
@override
Future<void> onLoad() async {
images.prefix = '';
await images.loadAll([
theme.Assets.images.dash.ball.keyName,
]);
}
Future<void> pump(BonusBallSpawningBehavior child) async {
await ensureAdd(
FlameBlocProvider<CharacterThemeCubit, CharacterThemeState>.value(
value: CharacterThemeCubit(),
children: [
ZCanvasComponent(
children: [child],
),
],
),
);
}
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('FlutterForestBonusBehavior', () {
final flameTester = FlameTester(_TestGame.new);
flameTester.test(
'adds a ball with a BallImpulsingBehavior to the game onTick '
'resulting in a -40 x impulse',
(game) async {
await game.onLoad();
final behavior = BonusBallSpawningBehavior();
await game.pump(behavior);
game.update(behavior.timer.limit);
await game.ready();
final ball = game.descendants().whereType<Ball>().single;
expect(ball.body.linearVelocity.x, equals(-40));
expect(ball.body.linearVelocity.y, equals(0));
},
);
});
}

@ -5,9 +5,9 @@ import 'package:flame_forge2d/forge2d_game.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/components/flutter_forest/behaviors/behaviors.dart'; import 'package:pinball/game/components/flutter_forest/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme; import 'package:pinball_theme/pinball_theme.dart' as theme;
@ -27,13 +27,8 @@ class _TestGame extends Forge2DGame {
required GameBloc gameBloc, required GameBloc gameBloc,
}) async { }) async {
await ensureAdd( await ensureAdd(
FlameMultiBlocProvider( FlameBlocProvider<GameBloc, GameState>.value(
providers: [ value: gameBloc,
FlameBlocProvider<GameBloc, GameState>.value(value: gameBloc),
FlameBlocProvider<CharacterThemeCubit, CharacterThemeState>.value(
value: CharacterThemeCubit(),
),
],
children: [ children: [
ZCanvasComponent( ZCanvasComponent(
children: [child], children: [child],
@ -93,7 +88,7 @@ void main() {
); );
flameTester.testGameWidget( flameTester.testGameWidget(
'adds a new Ball to the game ' 'adds BonusBallSpawningBehavior to the game '
'when bumpers are activated three times', 'when bumpers are activated three times',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.onLoad(); await game.onLoad();
@ -120,7 +115,7 @@ void main() {
await game.ready(); await game.ready();
expect( expect(
game.descendants().whereType<Ball>().length, game.descendants().whereType<BonusBallSpawningBehavior>().length,
equals(1), equals(1),
); );
}, },

Loading…
Cancel
Save