Merge branch 'main' into chore/bump-flame-versions

pull/185/head
Allison Ryan 3 years ago
commit de354e9355

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

@ -8,6 +8,7 @@ export 'controlled_plunger.dart';
export 'controlled_sparky_computer.dart'; export 'controlled_sparky_computer.dart';
export 'flutter_forest.dart'; export 'flutter_forest.dart';
export 'game_flow_controller.dart'; export 'game_flow_controller.dart';
export 'launcher.dart';
export 'score_effect_controller.dart'; export 'score_effect_controller.dart';
export 'score_points.dart'; export 'score_points.dart';
export 'sparky_fire_zone.dart'; export 'sparky_fire_zone.dart';

@ -19,6 +19,7 @@ class ControlledBall extends Ball with Controls<BallController> {
}) : super(baseColor: theme.characterTheme.ballColor) { }) : super(baseColor: theme.characterTheme.ballColor) {
controller = BallController(this); controller = BallController(this);
priority = Ball.launchRampPriority; priority = Ball.launchRampPriority;
layer = Layer.launcher;
} }
/// {@template bonus_ball} /// {@template bonus_ball}

@ -0,0 +1,26 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/components/components.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
/// {@template launcher}
/// A [Blueprint] which creates the [Plunger], [RocketSpriteComponent] and
/// [LaunchRamp].
/// {@endtemplate}
class Launcher extends Forge2DBlueprint {
/// {@macro launcher}
Launcher();
/// [Plunger] to launch the [Ball] onto the board.
late final Plunger plunger;
@override
void build(Forge2DGame gameRef) {
plunger = ControlledPlunger(compressionDistance: 12.3)
..initialPosition = Vector2(40.1, -38);
final _rocket = RocketSpriteComponent()..position = Vector2(43, 62);
addAll([_rocket, plunger]);
addBlueprint(LaunchRamp());
}
}

@ -47,6 +47,7 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.chromeDino.mouth.keyName), images.load(components.Assets.images.chromeDino.mouth.keyName),
images.load(components.Assets.images.chromeDino.head.keyName), images.load(components.Assets.images.chromeDino.head.keyName),
images.load(components.Assets.images.plunger.plunger.keyName), images.load(components.Assets.images.plunger.plunger.keyName),
images.load(components.Assets.images.plunger.rocket.keyName),
images.load(components.Assets.images.sparky.computer.base.keyName), images.load(components.Assets.images.sparky.computer.base.keyName),
images.load(components.Assets.images.sparky.computer.top.keyName), images.load(components.Assets.images.sparky.computer.top.keyName),
images.load(components.Assets.images.sparky.bumper.a.active.keyName), images.load(components.Assets.images.sparky.bumper.a.active.keyName),

@ -45,13 +45,10 @@ class PinballGame extends Forge2DGame
await _addGameBoundaries(); await _addGameBoundaries();
unawaited(addFromBlueprint(Boundaries())); unawaited(addFromBlueprint(Boundaries()));
unawaited(addFromBlueprint(LaunchRamp()));
unawaited(addFromBlueprint(ControlledSparkyComputer())); unawaited(addFromBlueprint(ControlledSparkyComputer()));
final plunger = ControlledPlunger(compressionDistance: 29) final launcher = Launcher();
..initialPosition = Vector2(38, 19); unawaited(addFromBlueprint(launcher));
await add(plunger);
unawaited(add(Board())); unawaited(add(Board()));
unawaited(add(AlienZone())); unawaited(add(AlienZone()));
unawaited(add(SparkyFireZone())); unawaited(add(SparkyFireZone()));
@ -68,7 +65,7 @@ class PinballGame extends Forge2DGame
); );
unawaited(addFromBlueprint(SpaceshipRail())); unawaited(addFromBlueprint(SpaceshipRail()));
controller.attachTo(plunger); controller.attachTo(launcher.plunger);
await super.onLoad(); await super.onLoad();
} }
@ -168,7 +165,7 @@ class DebugPinballGame extends PinballGame with TapDetector {
anchor: Anchor.center, anchor: Anchor.center,
) )
..position = Vector2(0, -7.8) ..position = Vector2(0, -7.8)
..priority = -2; ..priority = -4;
await add(spriteComponent); await add(spriteComponent);
} }

@ -20,10 +20,6 @@ class $AssetsImagesComponentsGen {
/// File path: assets/images/components/background.png /// File path: assets/images/components/background.png
AssetGenImage get background => AssetGenImage get background =>
const AssetGenImage('assets/images/components/background.png'); const AssetGenImage('assets/images/components/background.png');
/// File path: assets/images/components/plunger.png
AssetGenImage get plunger =>
const AssetGenImage('assets/images/components/plunger.png');
} }
class Assets { class Assets {

@ -0,0 +1,58 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:pinball/game/game.dart';
part 'start_game_event.dart';
part 'start_game_state.dart';
/// {@template start_game_bloc}
/// Bloc that manages the app flow before the game starts.
/// {@endtemplate}
class StartGameBloc extends Bloc<StartGameEvent, StartGameState> {
/// {@macro start_game_bloc}
StartGameBloc({
required PinballGame game,
}) : _game = game,
super(const StartGameState.initial()) {
on<PlayTapped>(_onPlayTapped);
on<CharacterSelected>(_onCharacterSelected);
on<HowToPlayFinished>(_onHowToPlayFinished);
}
final PinballGame _game;
void _onPlayTapped(
PlayTapped event,
Emitter<StartGameState> emit,
) {
_game.gameFlowController.start();
emit(
state.copyWith(
status: StartGameStatus.selectCharacter,
),
);
}
void _onCharacterSelected(
CharacterSelected event,
Emitter<StartGameState> emit,
) {
emit(
state.copyWith(
status: StartGameStatus.howToPlay,
),
);
}
void _onHowToPlayFinished(
HowToPlayFinished event,
Emitter<StartGameState> emit,
) {
emit(
state.copyWith(
status: StartGameStatus.play,
),
);
}
}

@ -0,0 +1,42 @@
part of 'start_game_bloc.dart';
/// {@template start_game_event}
/// Event added during the start game flow.
/// {@endtemplate}
abstract class StartGameEvent extends Equatable {
/// {@macro start_game_event}
const StartGameEvent();
}
/// {@template play_tapped}
/// Play tapped event.
/// {@endtemplate}
class PlayTapped extends StartGameEvent {
/// {@macro play_tapped}
const PlayTapped();
@override
List<Object> get props => [];
}
/// {@template character_selected}
/// Character selected event.
/// {@endtemplate}
class CharacterSelected extends StartGameEvent {
/// {@macro character_selected}
const CharacterSelected();
@override
List<Object> get props => [];
}
/// {@template how_to_play_finished}
/// How to play finished event.
/// {@endtemplate}
class HowToPlayFinished extends StartGameEvent {
/// {@macro how_to_play_finished}
const HowToPlayFinished();
@override
List<Object> get props => [];
}

@ -0,0 +1,44 @@
part of 'start_game_bloc.dart';
/// Defines status of start game flow.
enum StartGameStatus {
/// Initial status.
initial,
/// Selection characters status.
selectCharacter,
/// How to play status.
howToPlay,
/// Play status.
play,
}
/// {@template start_game_state}
/// Represents the state of flow before the game starts.
/// {@endtemplate}
class StartGameState extends Equatable {
/// {@macro start_game_state}
const StartGameState({
required this.status,
});
/// Initial [StartGameState].
const StartGameState.initial() : this(status: StartGameStatus.initial);
/// Status of [StartGameState].
final StartGameStatus status;
/// Creates a copy of [StartGameState].
StartGameState copyWith({
StartGameStatus? status,
}) {
return StartGameState(
status: status ?? this.status,
);
}
@override
List<Object> get props => [status];
}

@ -0,0 +1 @@
export 'bloc/start_game_bloc.dart';

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

@ -187,6 +187,10 @@ class $AssetsImagesPlungerGen {
/// File path: assets/images/plunger/plunger.png /// File path: assets/images/plunger/plunger.png
AssetGenImage get plunger => AssetGenImage get plunger =>
const AssetGenImage('assets/images/plunger/plunger.png'); const AssetGenImage('assets/images/plunger/plunger.png');
/// File path: assets/images/plunger/rocket.png
AssetGenImage get rocket =>
const AssetGenImage('assets/images/plunger/rocket.png');
} }
class $AssetsImagesSlingshotGen { class $AssetsImagesSlingshotGen {

@ -35,7 +35,7 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
static const int spaceshipRailPriority = 2; static const int spaceshipRailPriority = 2;
/// Render priority for the [Ball] while it's on the [LaunchRamp]. /// Render priority for the [Ball] while it's on the [LaunchRamp].
static const int launchRampPriority = 0; static const int launchRampPriority = -2;
/// The size of the [Ball]. /// The size of the [Ball].
static final Vector2 size = Vector2.all(4.13); static final Vector2 size = Vector2.all(4.13);

@ -13,7 +13,7 @@ class Boundaries extends Forge2DBlueprint {
final bottomBoundary = _BottomBoundary(); final bottomBoundary = _BottomBoundary();
final outerBoundary = _OuterBoundary(); final outerBoundary = _OuterBoundary();
addAll([bottomBoundary, outerBoundary]); addAll([outerBoundary, bottomBoundary]);
} }
} }
@ -23,7 +23,7 @@ class Boundaries extends Forge2DBlueprint {
/// {@endtemplate bottom_boundary} /// {@endtemplate bottom_boundary}
class _BottomBoundary extends BodyComponent with InitialPosition { class _BottomBoundary extends BodyComponent with InitialPosition {
/// {@macro bottom_boundary} /// {@macro bottom_boundary}
_BottomBoundary() : super(priority: 2); _BottomBoundary() : super(priority: 1);
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final fixturesDefs = <FixtureDef>[]; final fixturesDefs = <FixtureDef>[];
@ -88,7 +88,7 @@ class _BottomBoundarySpriteComponent extends SpriteComponent with HasGameRef {
/// {@endtemplate outer_boundary} /// {@endtemplate outer_boundary}
class _OuterBoundary extends BodyComponent with InitialPosition { class _OuterBoundary extends BodyComponent with InitialPosition {
/// {@macro outer_boundary} /// {@macro outer_boundary}
_OuterBoundary() : super(priority: -1); _OuterBoundary() : super(priority: Ball.launchRampPriority - 1);
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final fixturesDefs = <FixtureDef>[]; final fixturesDefs = <FixtureDef>[];

@ -21,6 +21,7 @@ export 'launch_ramp.dart';
export 'layer.dart'; export 'layer.dart';
export 'plunger.dart'; export 'plunger.dart';
export 'ramp_opening.dart'; export 'ramp_opening.dart';
export 'rocket.dart';
export 'score_text.dart'; export 'score_text.dart';
export 'shapes/shapes.dart'; export 'shapes/shapes.dart';
export 'slingshot.dart'; export 'slingshot.dart';

@ -124,7 +124,7 @@ class _DinoTopWallSpriteComponent extends SpriteComponent with HasGameRef {
/// {@endtemplate} /// {@endtemplate}
class _DinoBottomWall extends BodyComponent with InitialPosition { class _DinoBottomWall extends BodyComponent with InitialPosition {
///{@macro dino_top_wall} ///{@macro dino_top_wall}
_DinoBottomWall() : super(priority: 1); _DinoBottomWall();
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[]; final fixturesDef = <FixtureDef>[];

@ -17,20 +17,21 @@ class LaunchRamp extends Forge2DBlueprint {
RampOpeningBallContactCallback<_LaunchRampExit>(), RampOpeningBallContactCallback<_LaunchRampExit>(),
]); ]);
final launchRampBase = _LaunchRampBase()..layer = Layer.launcher; final launchRampBase = _LaunchRampBase();
final launchRampForegroundRailing = _LaunchRampForegroundRailing() final launchRampForegroundRailing = _LaunchRampForegroundRailing();
..layer = Layer.launcher;
final launchRampExit = _LaunchRampExit(rotation: math.pi / 2) final launchRampExit = _LaunchRampExit(rotation: math.pi / 2)
..initialPosition = Vector2(1.8, -34.2) ..initialPosition = Vector2(0.6, -34);
..layer = Layer.opening
..renderBody = false; final launchRampCloseWall = _LaunchRampCloseWall()
..initialPosition = Vector2(4, -66.5);
addAll([ addAll([
launchRampBase, launchRampBase,
launchRampForegroundRailing, launchRampForegroundRailing,
launchRampExit, launchRampExit,
launchRampCloseWall,
]); ]);
} }
} }
@ -40,10 +41,7 @@ class LaunchRamp extends Forge2DBlueprint {
/// {@endtemplate} /// {@endtemplate}
class _LaunchRampBase extends BodyComponent with InitialPosition, Layered { class _LaunchRampBase extends BodyComponent with InitialPosition, Layered {
/// {@macro launch_ramp_base} /// {@macro launch_ramp_base}
_LaunchRampBase() _LaunchRampBase() : super(priority: Ball.launchRampPriority - 1) {
: super(
priority: Ball.launchRampPriority - 1,
) {
layer = Layer.launcher; layer = Layer.launcher;
} }
@ -143,15 +141,9 @@ class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef {
/// Foreground railing for the [_LaunchRampBase] to render in front of the /// Foreground railing for the [_LaunchRampBase] to render in front of the
/// [Ball]. /// [Ball].
/// {@endtemplate} /// {@endtemplate}
class _LaunchRampForegroundRailing extends BodyComponent class _LaunchRampForegroundRailing extends BodyComponent with InitialPosition {
with InitialPosition, Layered {
/// {@macro launch_ramp_foreground_railing} /// {@macro launch_ramp_foreground_railing}
_LaunchRampForegroundRailing() _LaunchRampForegroundRailing() : super(priority: Ball.launchRampPriority + 1);
: super(
priority: Ball.launchRampPriority + 1,
) {
layer = Layer.launcher;
}
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[]; final fixturesDef = <FixtureDef>[];
@ -221,6 +213,26 @@ class _LaunchRampForegroundRailingSpriteComponent extends SpriteComponent
} }
} }
class _LaunchRampCloseWall extends BodyComponent with InitialPosition, Layered {
_LaunchRampCloseWall() {
layer = Layer.board;
// renderBody = false;
}
@override
Body createBody() {
final shape = EdgeShape()..set(Vector2.zero(), Vector2(0, 4));
final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef()
..userData = this
..position = initialPosition;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
/// {@template launch_ramp_exit} /// {@template launch_ramp_exit}
/// [RampOpening] with [Layer.launcher] to filter [Ball]s exiting the /// [RampOpening] with [Layer.launcher] to filter [Ball]s exiting the
/// [LaunchRamp]. /// [LaunchRamp].
@ -232,9 +244,14 @@ class _LaunchRampExit extends RampOpening {
}) : _rotation = rotation, }) : _rotation = rotation,
super( super(
insideLayer: Layer.launcher, insideLayer: Layer.launcher,
outsideLayer: Layer.board,
orientation: RampOrientation.down, orientation: RampOrientation.down,
insidePriority: Ball.launchRampPriority, insidePriority: Ball.launchRampPriority,
); outsidePriority: 0,
) {
layer = Layer.launcher;
renderBody = false;
}
final double _rotation; final double _rotation;

@ -88,7 +88,7 @@ extension LayerMaskBits on Layer {
case Layer.spaceshipEntranceRamp: case Layer.spaceshipEntranceRamp:
return 0x0002; return 0x0002;
case Layer.launcher: case Layer.launcher:
return 0x0005; return 0x0008;
case Layer.spaceship: case Layer.spaceship:
return 0x000A; return 0x000A;
case Layer.spaceshipExitRail: case Layer.spaceshipExitRail:

@ -8,13 +8,15 @@ import 'package:pinball_components/pinball_components.dart';
/// ///
/// [Plunger] ignores gravity so the player controls its downward [pull]. /// [Plunger] ignores gravity so the player controls its downward [pull].
/// {@endtemplate} /// {@endtemplate}
class Plunger extends BodyComponent with InitialPosition { class Plunger extends BodyComponent with InitialPosition, Layered {
/// {@macro plunger} /// {@macro plunger}
Plunger({ Plunger({
required this.compressionDistance, required this.compressionDistance,
// TODO(ruimiguel): set to priority +1 over LaunchRamp once all priorities // TODO(ruimiguel): set to priority +1 over LaunchRamp once all priorities
// are fixed. // are fixed.
}) : super(priority: 0); }) : super(priority: 0) {
layer = Layer.launcher;
}
/// Distance the plunger can lower. /// Distance the plunger can lower.
final double compressionDistance; final double compressionDistance;
@ -90,8 +92,8 @@ class _PlungerSpriteComponent extends SpriteComponent with HasGameRef {
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;
anchor = Anchor.center; anchor = Anchor.center;
position = Vector2(2, 19); position = Vector2(1.5, 13.4);
angle = -0.033; angle = -0.008;
} }
} }

@ -0,0 +1,24 @@
import 'package:flame/components.dart';
import 'package:pinball_components/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
/// {@template rocket_sprite_component}
/// A [SpriteComponent] for the rocket over [Plunger].
/// {@endtemplate}
class RocketSpriteComponent extends SpriteComponent with HasGameRef {
// TODO(ruimiguel): change this priority to be over launcher ramp and bottom
// wall.
/// {@macro rocket_sprite_component}
RocketSpriteComponent() : super(priority: 5);
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(
Assets.images.plunger.rocket.keyName,
);
this.sprite = sprite;
size = sprite.originalSize / 10;
anchor = Anchor.center;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

@ -26,13 +26,12 @@ void main() {
game.camera.followVector2(Vector2.zero()); game.camera.followVector2(Vector2.zero());
game.camera.zoom = 4.1; game.camera.zoom = 4.1;
}, },
// TODO(ruimiguel): enable test when workflows are fixed. verify: (game, tester) async {
// verify: (game, tester) async { await expectLater(
// await expectLater( find.byGame<Forge2DGame>(),
// find.byGame<Forge2DGame>(), matchesGoldenFile('golden/plunger/plunger.png'),
// matchesGoldenFile('golden/plunger.png'), );
// ); },
// },
); );
flameTester.test( flameTester.test(

@ -0,0 +1,28 @@
// 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 '../../helpers/helpers.dart';
void main() {
group('RocketSpriteComponent', () {
final tester = FlameTester(TestGame.new);
tester.testGameWidget(
'renders correctly',
setUp: (game, tester) async {
game.camera.followVector2(Vector2.zero());
await game.ensureAdd(RocketSpriteComponent());
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/plunger/rocket.png'),
);
},
);
});
}

@ -0,0 +1,62 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/start_game/bloc/start_game_bloc.dart';
import '../../helpers/helpers.dart';
void main() {
late PinballGame pinballGame;
setUp(() {
pinballGame = MockPinballGame();
when(
() => pinballGame.gameFlowController,
).thenReturn(
MockGameFlowController(),
);
});
group('StartGameBloc', () {
blocTest<StartGameBloc, StartGameState>(
'on PlayTapped changes status to selectCharacter',
build: () => StartGameBloc(
game: pinballGame,
),
act: (bloc) => bloc.add(const PlayTapped()),
expect: () => [
const StartGameState(
status: StartGameStatus.selectCharacter,
)
],
);
blocTest<StartGameBloc, StartGameState>(
'on CharacterSelected changes status to howToPlay',
build: () => StartGameBloc(
game: pinballGame,
),
act: (bloc) => bloc.add(const CharacterSelected()),
expect: () => [
const StartGameState(
status: StartGameStatus.howToPlay,
)
],
);
blocTest<StartGameBloc, StartGameState>(
'on HowToPlayFinished changes status to play',
build: () => StartGameBloc(
game: pinballGame,
),
act: (bloc) => bloc.add(const HowToPlayFinished()),
expect: () => [
const StartGameState(
status: StartGameStatus.play,
)
],
);
});
}

@ -0,0 +1,29 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/start_game/bloc/start_game_bloc.dart';
void main() {
group('StartGameEvent', () {
test('PlayTapped supports value equality', () {
expect(
PlayTapped(),
equals(PlayTapped()),
);
});
test('CharacterSelected supports value equality', () {
expect(
CharacterSelected(),
equals(CharacterSelected()),
);
});
test('HowToPlayFinished supports value equality', () {
expect(
HowToPlayFinished(),
equals(HowToPlayFinished()),
);
});
});
}

@ -0,0 +1,43 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/start_game/bloc/start_game_bloc.dart';
void main() {
group('StartGameState', () {
final testState = StartGameState(
status: StartGameStatus.selectCharacter,
);
test('initial state has correct values', () {
final state = StartGameState(
status: StartGameStatus.initial,
);
expect(state, StartGameState.initial());
});
test('supports value equality', () {
final secondState = StartGameState(
status: StartGameStatus.selectCharacter,
);
expect(testState, secondState);
});
test('supports copyWith', () {
final secondState = testState.copyWith();
expect(testState, secondState);
});
test('has correct props', () {
expect(
testState.props,
equals([
StartGameStatus.selectCharacter,
]),
);
});
});
}
Loading…
Cancel
Save