Merge branch 'main' of github.com:VGVentures/pinball into fix/plunger-on-mobile

pull/259/head
Jochum van der Ploeg 3 years ago
commit bdabf34fc0
No known key found for this signature in database
GPG Key ID: E961B7B51589CA09

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 200 KiB

@ -1,39 +1,36 @@
// ignore_for_file: avoid_renaming_method_parameters // ignore_for_file: avoid_renaming_method_parameters
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame/components.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template android_acres} /// {@template android_acres}
/// Area positioned on the left side of the board containing the /// Area positioned on the left side of the board containing the
/// [AndroidSpaceship], [SpaceshipRamp], [SpaceshipRail], and [AndroidBumper]s. /// [AndroidSpaceship], [SpaceshipRamp], [SpaceshipRail], and [AndroidBumper]s.
/// {@endtemplate} /// {@endtemplate}
class AndroidAcres extends Blueprint { class AndroidAcres extends Component {
/// {@macro android_acres} /// {@macro android_acres}
AndroidAcres() AndroidAcres()
: super( : super(
components: [ children: [
SpaceshipRamp(),
SpaceshipRail(),
AndroidSpaceship(position: Vector2(-26.5, -28.5)),
AndroidBumper.a( AndroidBumper.a(
children: [ children: [
ScoringBehavior(points: 20000), ScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(-25, 1.3), )..initialPosition = Vector2(-25, 1.3),
AndroidBumper.b( AndroidBumper.b(
children: [ children: [
ScoringBehavior(points: 20000), ScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(-32.8, -9.2), )..initialPosition = Vector2(-32.8, -9.2),
AndroidBumper.cow( AndroidBumper.cow(
children: [ children: [
ScoringBehavior(points: 20), ScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(-20.5, -13.8), )..initialPosition = Vector2(-20.5, -13.8),
], ],
blueprints: [
SpaceshipRamp(),
AndroidSpaceship(position: Vector2(-26.5, -28.5)),
SpaceshipRail(),
],
); );
} }

@ -1,6 +1,7 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template bottom_group} /// {@template bottom_group}
/// Grouping of the board's symmetrical bottom [Component]s. /// Grouping of the board's symmetrical bottom [Component]s.
@ -8,7 +9,7 @@ import 'package:pinball_components/pinball_components.dart';
/// The [BottomGroup] consists of [Flipper]s, [Baseboard]s and [Kicker]s. /// The [BottomGroup] consists of [Flipper]s, [Baseboard]s and [Kicker]s.
/// {@endtemplate} /// {@endtemplate}
// TODO(allisonryan0002): Consider renaming. // TODO(allisonryan0002): Consider renaming.
class BottomGroup extends Component { class BottomGroup extends Component with ZIndex {
/// {@macro bottom_group} /// {@macro bottom_group}
BottomGroup() BottomGroup()
: super( : super(
@ -16,8 +17,9 @@ class BottomGroup extends Component {
_BottomGroupSide(side: BoardSide.right), _BottomGroupSide(side: BoardSide.right),
_BottomGroupSide(side: BoardSide.left), _BottomGroupSide(side: BoardSide.left),
], ],
priority: RenderPriority.bottomGroup, ) {
); zIndex = ZIndexes.bottomGroup;
}
} }
/// {@template bottom_group_side} /// {@template bottom_group_side}
@ -36,7 +38,7 @@ class _BottomGroupSide extends Component {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
final direction = _side.direction; final direction = _side.direction;
final centerXAdjustment = _side.isLeft ? 0 : -6.5; final centerXAdjustment = _side.isLeft ? 0 : -6.66;
final flipper = ControlledFlipper( final flipper = ControlledFlipper(
side: _side, side: _side,
@ -44,16 +46,16 @@ class _BottomGroupSide extends Component {
final baseboard = Baseboard(side: _side) final baseboard = Baseboard(side: _side)
..initialPosition = Vector2( ..initialPosition = Vector2(
(25.58 * direction) + centerXAdjustment, (25.58 * direction) + centerXAdjustment,
28.69, 28.71,
); );
final kicker = Kicker( final kicker = Kicker(
side: _side, side: _side,
children: [ children: [
ScoringBehavior(points: 5000), ScoringBehavior(points: Points.fiveThousand)..applyTo(['bouncy_edge']),
], ],
)..initialPosition = Vector2( )..initialPosition = Vector2(
(22.4 * direction) + centerXAdjustment, (22.64 * direction) + centerXAdjustment,
25, 25.1,
); );
await addAll([flipper, baseboard, kicker]); await addAll([flipper, baseboard, kicker]);

@ -12,4 +12,4 @@ export 'google_word/google_word.dart';
export 'launcher.dart'; export 'launcher.dart';
export 'multipliers/multipliers.dart'; export 'multipliers/multipliers.dart';
export 'scoring_behavior.dart'; export 'scoring_behavior.dart';
export 'sparky_fire_zone.dart'; export 'sparky_scorch.dart';

@ -19,8 +19,8 @@ class ControlledBall extends Ball with Controls<BallController> {
required CharacterTheme characterTheme, required CharacterTheme characterTheme,
}) : super(baseColor: characterTheme.ballColor) { }) : super(baseColor: characterTheme.ballColor) {
controller = BallController(this); controller = BallController(this);
priority = RenderPriority.ballOnLaunchRamp;
layer = Layer.launcher; layer = Layer.launcher;
zIndex = ZIndexes.ballOnLaunchRamp;
} }
/// {@template bonus_ball} /// {@template bonus_ball}
@ -30,13 +30,13 @@ class ControlledBall extends Ball with Controls<BallController> {
required CharacterTheme characterTheme, required CharacterTheme characterTheme,
}) : super(baseColor: characterTheme.ballColor) { }) : super(baseColor: characterTheme.ballColor) {
controller = BallController(this); controller = BallController(this);
priority = RenderPriority.ballOnBoard; zIndex = ZIndexes.ballOnBoard;
} }
/// [Ball] used in [DebugPinballGame]. /// [Ball] used in [DebugPinballGame].
ControlledBall.debug() : super(baseColor: const Color(0xFFFF0000)) { ControlledBall.debug() : super(baseColor: const Color(0xFFFF0000)) {
controller = BallController(this); controller = BallController(this);
priority = RenderPriority.ballOnBoard; zIndex = ZIndexes.ballOnBoard;
} }
} }

@ -1,23 +1,39 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template dino_desert} /// {@template dino_desert}
/// Area located next to the [Launcher] containing the [ChromeDino] and /// Area located next to the [Launcher] containing the [ChromeDino] and
/// [DinoWalls]. /// [DinoWalls].
/// {@endtemplate} /// {@endtemplate}
// TODO(allisonryan0002): use a controller to initiate dino bonus when dino is class DinoDesert extends Component {
// fully implemented.
class DinoDesert extends Blueprint {
/// {@macro dino_desert} /// {@macro dino_desert}
DinoDesert() DinoDesert()
: super( : super(
components: [ children: [
ChromeDino()..initialPosition = Vector2(12.3, -6.9), ChromeDino(
children: [
ScoringBehavior(points: Points.twoHundredThousand)
..applyTo(['inside_mouth']),
], ],
blueprints: [ )..initialPosition = Vector2(12.6, -6.9),
_BarrierBehindDino(),
DinoWalls(), DinoWalls(),
Slingshots(),
], ],
); );
} }
class _BarrierBehindDino extends BodyComponent {
@override
Body createBody() {
final shape = EdgeShape()
..set(
Vector2(25, -14.2),
Vector2(25, -7.7),
);
return world.createBody(BodyDef())..createFixtureFromShape(shape);
}
}

@ -25,7 +25,7 @@ class FlutterForestBonusBehavior extends Component
gameRef gameRef
.read<GameBloc>() .read<GameBloc>()
.add(const BonusActivated(GameBonus.dashNest)); .add(const BonusActivated(GameBonus.dashNest));
gameRef.add( gameRef.firstChild<ZCanvasComponent>()!.add(
ControlledBall.bonus(characterTheme: gameRef.characterTheme) ControlledBall.bonus(characterTheme: gameRef.characterTheme)
..initialPosition = Vector2(17.2, -52.7), ..initialPosition = Vector2(17.2, -52.7),
); );

@ -5,41 +5,43 @@ import 'package:flutter/material.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_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template flutter_forest} /// {@template flutter_forest}
/// Area positioned at the top right of the board where the [Ball] can bounce /// Area positioned at the top right of the board where the [Ball] can bounce
/// off [DashNestBumper]s. /// off [DashNestBumper]s.
/// {@endtemplate} /// {@endtemplate}
class FlutterForest extends Component { class FlutterForest extends Component with ZIndex {
/// {@macro flutter_forest} /// {@macro flutter_forest}
FlutterForest() FlutterForest()
: super( : super(
priority: RenderPriority.flutterForest,
children: [ children: [
Signpost( Signpost(
children: [ children: [
ScoringBehavior(points: 20), ScoringBehavior(points: Points.fiveThousand),
], ],
)..initialPosition = Vector2(8.35, -58.3), )..initialPosition = Vector2(8.35, -58.3),
DashNestBumper.main( DashNestBumper.main(
children: [ children: [
ScoringBehavior(points: 200000), ScoringBehavior(points: Points.twoHundredThousand),
], ],
)..initialPosition = Vector2(18.55, -59.35), )..initialPosition = Vector2(18.55, -59.35),
DashNestBumper.a( DashNestBumper.a(
children: [ children: [
ScoringBehavior(points: 20000), ScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(8.95, -51.95), )..initialPosition = Vector2(8.95, -51.95),
DashNestBumper.b( DashNestBumper.b(
children: [ children: [
ScoringBehavior(points: 20000), ScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(23.3, -46.75), )..initialPosition = Vector2(22.3, -46.75),
DashAnimatronic()..position = Vector2(20, -66), DashAnimatronic()..position = Vector2(20, -66),
FlutterForestBonusBehavior(), FlutterForestBonusBehavior(),
], ],
); ) {
zIndex = ZIndexes.flutterForest;
}
/// Creates a [FlutterForest] without any children. /// Creates a [FlutterForest] without any children.
/// ///

@ -17,7 +17,7 @@ class GoogleWordBonusBehavior extends Component
// https://github.com/flame-engine/flame/pull/1538 // https://github.com/flame-engine/flame/pull/1538
letter.bloc.stream.listen((_) { letter.bloc.stream.listen((_) {
final achievedBonus = googleLetters final achievedBonus = googleLetters
.every((letter) => letter.bloc.state == GoogleLetterState.active); .every((letter) => letter.bloc.state == GoogleLetterState.lit);
if (achievedBonus) { if (achievedBonus) {
gameRef.audio.googleBonus(); gameRef.audio.googleBonus();

@ -3,11 +3,12 @@ import 'package:flutter/material.dart';
import 'package:pinball/game/components/google_word/behaviors/behaviors.dart'; import 'package:pinball/game/components/google_word/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template google_word} /// {@template google_word}
/// Loads all [GoogleLetter]s to compose a [GoogleWord]. /// Loads all [GoogleLetter]s to compose a [GoogleWord].
/// {@endtemplate} /// {@endtemplate}
class GoogleWord extends Component { class GoogleWord extends Component with ZIndex {
/// {@macro google_word} /// {@macro google_word}
GoogleWord({ GoogleWord({
required Vector2 position, required Vector2 position,
@ -15,31 +16,33 @@ class GoogleWord extends Component {
children: [ children: [
GoogleLetter( GoogleLetter(
0, 0,
children: [ScoringBehavior(points: 5000)], children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(-12.92, 1.82), )..initialPosition = position + Vector2(-13.1, 1.72),
GoogleLetter( GoogleLetter(
1, 1,
children: [ScoringBehavior(points: 5000)], children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(-8.33, -0.65), )..initialPosition = position + Vector2(-8.33, -0.75),
GoogleLetter( GoogleLetter(
2, 2,
children: [ScoringBehavior(points: 5000)], children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(-2.88, -1.75), )..initialPosition = position + Vector2(-2.88, -1.85),
GoogleLetter( GoogleLetter(
3, 3,
children: [ScoringBehavior(points: 5000)], children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(2.88, -1.75), )..initialPosition = position + Vector2(2.88, -1.85),
GoogleLetter( GoogleLetter(
4, 4,
children: [ScoringBehavior(points: 5000)], children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(8.33, -0.65), )..initialPosition = position + Vector2(8.33, -0.75),
GoogleLetter( GoogleLetter(
5, 5,
children: [ScoringBehavior(points: 5000)], children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(12.92, 1.82), )..initialPosition = position + Vector2(13.1, 1.72),
GoogleWordBonusBehavior(), GoogleWordBonusBehavior(),
], ],
); ) {
zIndex = ZIndexes.decal;
}
/// Creates a [GoogleWord] without any children. /// Creates a [GoogleWord] without any children.
/// ///

@ -1,21 +1,20 @@
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame/components.dart';
import 'package:pinball/game/components/components.dart'; import 'package:pinball/game/components/components.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets; import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_flame/pinball_flame.dart';
/// {@template launcher} /// {@template launcher}
/// A [Blueprint] which creates the [Plunger], [RocketSpriteComponent] and /// Channel on the right side of the board containing the [LaunchRamp],
/// [LaunchRamp]. /// [Plunger], and [RocketSpriteComponent].
/// {@endtemplate} /// {@endtemplate}
class Launcher extends Blueprint { class Launcher extends Component {
/// {@macro launcher} /// {@macro launcher}
Launcher() Launcher()
: super( : super(
components: [ children: [
ControlledPlunger(compressionDistance: 10.5) LaunchRamp(),
..initialPosition = Vector2(41.1, 43), ControlledPlunger(compressionDistance: 9.2)
..initialPosition = Vector2(41.2, 43.7),
RocketSpriteComponent()..position = Vector2(43, 62.3), RocketSpriteComponent()..position = Vector2(43, 62.3),
], ],
blueprints: [LaunchRamp()],
); );
} }

@ -3,11 +3,12 @@ import 'package:flame/components.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/game/components/multipliers/behaviors/behaviors.dart'; import 'package:pinball/game/components/multipliers/behaviors/behaviors.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template multipliers} /// {@template multipliers}
/// A group for the multipliers on the board. /// A group for the multipliers on the board.
/// {@endtemplate} /// {@endtemplate}
class Multipliers extends Component { class Multipliers extends Component with ZIndex {
/// {@macro multipliers} /// {@macro multipliers}
Multipliers() Multipliers()
: super( : super(
@ -34,7 +35,9 @@ class Multipliers extends Component {
), ),
MultipliersBehavior(), MultipliersBehavior(),
], ],
); ) {
zIndex = ZIndexes.decal;
}
/// Creates [Multipliers] without any children. /// Creates [Multipliers] without any children.
/// ///

@ -12,21 +12,21 @@ import 'package:pinball_flame/pinball_flame.dart';
class ScoringBehavior extends ContactBehavior with HasGameRef<PinballGame> { class ScoringBehavior extends ContactBehavior with HasGameRef<PinballGame> {
/// {@macro scoring_behavior} /// {@macro scoring_behavior}
ScoringBehavior({ ScoringBehavior({
required int points, required Points points,
}) : _points = points; }) : _points = points;
final int _points; final Points _points;
@override @override
void beginContact(Object other, Contact contact) { void beginContact(Object other, Contact contact) {
super.beginContact(other, contact); super.beginContact(other, contact);
if (other is! Ball) return; if (other is! Ball) return;
gameRef.read<GameBloc>().add(Scored(points: _points)); gameRef.read<GameBloc>().add(Scored(points: _points.value));
gameRef.audio.score(); gameRef.audio.score();
gameRef.add( gameRef.firstChild<ZCanvasComponent>()!.add(
ScoreText( ScoreComponent(
text: _points.toString(), points: _points,
position: other.body.position, position: other.body.position,
), ),
); );

@ -1,40 +1,36 @@
// ignore_for_file: avoid_renaming_method_parameters // ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template sparky_fire_zone} /// {@template sparky_scorch}
/// Area positioned at the top left of the board where the [Ball] /// Area positioned at the top left of the board containing the
/// can bounce off [SparkyBumper]s. /// [SparkyComputer], [SparkyAnimatronic], and [SparkyBumper]s.
///
/// When a [Ball] hits [SparkyBumper]s, the bumper animates.
/// {@endtemplate} /// {@endtemplate}
class SparkyFireZone extends Blueprint { class SparkyScorch extends Component {
/// {@macro sparky_fire_zone} /// {@macro sparky_scorch}
SparkyFireZone() SparkyScorch()
: super( : super(
components: [ children: [
SparkyBumper.a( SparkyBumper.a(
children: [ children: [
ScoringBehavior(points: 20000), ScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(-22.9, -41.65), )..initialPosition = Vector2(-22.9, -41.65),
SparkyBumper.b( SparkyBumper.b(
children: [ children: [
ScoringBehavior(points: 20000), ScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(-21.25, -57.9), )..initialPosition = Vector2(-21.25, -57.9),
SparkyBumper.c( SparkyBumper.c(
children: [ children: [
ScoringBehavior(points: 20000), ScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(-3.3, -52.55), )..initialPosition = Vector2(-3.3, -52.55),
SparkyComputerSensor()..initialPosition = Vector2(-13, -49.8), SparkyComputerSensor()..initialPosition = Vector2(-13, -49.9),
SparkyAnimatronic()..position = Vector2(-13.8, -58.2), SparkyAnimatronic()..position = Vector2(-13.8, -58.2),
],
blueprints: [
SparkyComputer(), SparkyComputer(),
], ],
); );
@ -51,13 +47,19 @@ class SparkyComputerSensor extends BodyComponent
: super( : super(
renderBody: false, renderBody: false,
children: [ children: [
ScoringBehavior(points: 200000), ScoringBehavior(points: Points.twentyThousand),
], ],
); );
@override @override
Body createBody() { Body createBody() {
final shape = CircleShape()..radius = 0.1; final shape = PolygonShape()
..setAsBox(
1,
0.1,
Vector2.zero(),
-0.18,
);
final fixtureDef = FixtureDef(shape, isSensor: true); final fixtureDef = FixtureDef(shape, isSensor: true);
final bodyDef = BodyDef( final bodyDef = BodyDef(
position: initialPosition, position: initialPosition,

@ -24,8 +24,10 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.flipper.right.keyName), images.load(components.Assets.images.flipper.right.keyName),
images.load(components.Assets.images.baseboard.left.keyName), images.load(components.Assets.images.baseboard.left.keyName),
images.load(components.Assets.images.baseboard.right.keyName), images.load(components.Assets.images.baseboard.right.keyName),
images.load(components.Assets.images.kicker.left.keyName), images.load(components.Assets.images.kicker.left.lit.keyName),
images.load(components.Assets.images.kicker.right.keyName), images.load(components.Assets.images.kicker.left.dimmed.keyName),
images.load(components.Assets.images.kicker.right.lit.keyName),
images.load(components.Assets.images.kicker.right.dimmed.keyName),
images.load(components.Assets.images.slingshot.upper.keyName), images.load(components.Assets.images.slingshot.upper.keyName),
images.load(components.Assets.images.slingshot.lower.keyName), images.load(components.Assets.images.slingshot.lower.keyName),
images.load(components.Assets.images.launchRamp.ramp.keyName), images.load(components.Assets.images.launchRamp.ramp.keyName),
@ -89,21 +91,28 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.android.bumper.cow.dimmed.keyName), images.load(components.Assets.images.android.bumper.cow.dimmed.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.computer.base.keyName), images.load(components.Assets.images.sparky.computer.base.keyName),
images.load(components.Assets.images.sparky.computer.glow.keyName),
images.load(components.Assets.images.sparky.animatronic.keyName), images.load(components.Assets.images.sparky.animatronic.keyName),
images.load(components.Assets.images.sparky.bumper.a.inactive.keyName), images.load(components.Assets.images.sparky.bumper.a.lit.keyName),
images.load(components.Assets.images.sparky.bumper.a.active.keyName), images.load(components.Assets.images.sparky.bumper.a.dimmed.keyName),
images.load(components.Assets.images.sparky.bumper.b.active.keyName), images.load(components.Assets.images.sparky.bumper.b.lit.keyName),
images.load(components.Assets.images.sparky.bumper.b.inactive.keyName), images.load(components.Assets.images.sparky.bumper.b.dimmed.keyName),
images.load(components.Assets.images.sparky.bumper.c.active.keyName), images.load(components.Assets.images.sparky.bumper.c.lit.keyName),
images.load(components.Assets.images.sparky.bumper.c.inactive.keyName), images.load(components.Assets.images.sparky.bumper.c.dimmed.keyName),
images.load(components.Assets.images.backboard.backboardScores.keyName), images.load(components.Assets.images.backboard.backboardScores.keyName),
images.load(components.Assets.images.backboard.backboardGameOver.keyName), images.load(components.Assets.images.backboard.backboardGameOver.keyName),
images.load(components.Assets.images.googleWord.letter1.keyName), images.load(components.Assets.images.googleWord.letter1.lit.keyName),
images.load(components.Assets.images.googleWord.letter2.keyName), images.load(components.Assets.images.googleWord.letter1.dimmed.keyName),
images.load(components.Assets.images.googleWord.letter3.keyName), images.load(components.Assets.images.googleWord.letter2.lit.keyName),
images.load(components.Assets.images.googleWord.letter4.keyName), images.load(components.Assets.images.googleWord.letter2.dimmed.keyName),
images.load(components.Assets.images.googleWord.letter5.keyName), images.load(components.Assets.images.googleWord.letter3.lit.keyName),
images.load(components.Assets.images.googleWord.letter6.keyName), images.load(components.Assets.images.googleWord.letter3.dimmed.keyName),
images.load(components.Assets.images.googleWord.letter4.lit.keyName),
images.load(components.Assets.images.googleWord.letter4.dimmed.keyName),
images.load(components.Assets.images.googleWord.letter5.lit.keyName),
images.load(components.Assets.images.googleWord.letter5.dimmed.keyName),
images.load(components.Assets.images.googleWord.letter6.lit.keyName),
images.load(components.Assets.images.googleWord.letter6.dimmed.keyName),
images.load(components.Assets.images.backboard.display.keyName), images.load(components.Assets.images.backboard.display.keyName),
images.load(components.Assets.images.multiplier.x2.lit.keyName), images.load(components.Assets.images.multiplier.x2.lit.keyName),
images.load(components.Assets.images.multiplier.x2.dimmed.keyName), images.load(components.Assets.images.multiplier.x2.dimmed.keyName),
@ -115,6 +124,10 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.multiplier.x5.dimmed.keyName), images.load(components.Assets.images.multiplier.x5.dimmed.keyName),
images.load(components.Assets.images.multiplier.x6.lit.keyName), images.load(components.Assets.images.multiplier.x6.lit.keyName),
images.load(components.Assets.images.multiplier.x6.dimmed.keyName), images.load(components.Assets.images.multiplier.x6.dimmed.keyName),
images.load(components.Assets.images.score.fiveThousand.keyName),
images.load(components.Assets.images.score.twentyThousand.keyName),
images.load(components.Assets.images.score.twoHundredThousand.keyName),
images.load(components.Assets.images.score.oneMillion.keyName),
images.load(dashTheme.leaderboardIcon.keyName), images.load(dashTheme.leaderboardIcon.keyName),
images.load(sparkyTheme.leaderboardIcon.keyName), images.load(sparkyTheme.leaderboardIcon.keyName),
images.load(androidTheme.leaderboardIcon.keyName), images.load(androidTheme.leaderboardIcon.keyName),

@ -23,7 +23,7 @@ class PinballGame extends Forge2DGame
PinballGame({ PinballGame({
required this.characterTheme, required this.characterTheme,
required this.audio, required this.audio,
}) { }) : super(gravity: Vector2(0, 30)) {
images.prefix = ''; images.prefix = '';
controller = _GameBallsController(this); controller = _GameBallsController(this);
} }
@ -42,32 +42,38 @@ class PinballGame extends Forge2DGame
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
unawaited(add(gameFlowController = GameFlowController(this))); await add(gameFlowController = GameFlowController(this));
unawaited(add(CameraController(this))); await add(CameraController(this));
unawaited(add(Backboard.waiting(position: Vector2(0, -88))));
await add(BoardBackgroundSpriteComponent()); final machine = [
await add(Drain()); BoardBackgroundSpriteComponent(),
await add(BottomGroup()); Boundaries(),
unawaited(addFromBlueprint(Boundaries())); Backboard.waiting(position: Vector2(0, -88)),
];
final launcher = Launcher(); final decals = [
unawaited(addFromBlueprint(launcher)); GoogleWord(position: Vector2(-4.25, 1.8)),
await add(Multipliers()); Multipliers(),
await add(FlutterForest()); ];
await addFromBlueprint(SparkyFireZone()); final characterAreas = [
await addFromBlueprint(AndroidAcres()); AndroidAcres(),
await addFromBlueprint(DinoDesert()); DinoDesert(),
unawaited(addFromBlueprint(Slingshots())); FlutterForest(),
SparkyScorch(),
];
await add( await add(
GoogleWord( ZCanvasComponent(
position: Vector2( children: [
BoardDimensions.bounds.center.dx - 4.1, ...machine,
BoardDimensions.bounds.center.dy + 1.8, ...decals,
), ...characterAreas,
Drain(),
BottomGroup(),
Launcher(),
],
), ),
); );
controller.attachTo(launcher.components.whereType<Plunger>().single);
await super.onLoad(); await super.onLoad();
} }
@ -76,12 +82,12 @@ class PinballGame extends Forge2DGame
@override @override
void onTapDown(TapDownInfo info) { void onTapDown(TapDownInfo info) {
if (info.raw.kind == PointerDeviceKind.touch) { if (info.raw.kind == PointerDeviceKind.touch) {
final rocket = children.whereType<RocketSpriteComponent>().first; final rocket = descendants().whereType<RocketSpriteComponent>().first;
final bounds = rocket.topLeftPosition & rocket.size; final bounds = rocket.topLeftPosition & rocket.size;
// NOTE(wolfen): As long as Flame does not have https://github.com/flame-engine/flame/issues/1586 we need to check it at the highest level manually. // NOTE(wolfen): As long as Flame does not have https://github.com/flame-engine/flame/issues/1586 we need to check it at the highest level manually.
if (bounds.contains(info.eventPosition.game.toOffset())) { if (bounds.contains(info.eventPosition.game.toOffset())) {
children.whereType<Plunger>().first.pullFor(2); descendants().whereType<Plunger>().single.pullFor(2);
} else { } else {
final leftSide = info.eventPosition.widget.x < canvasSize.x / 2; final leftSide = info.eventPosition.widget.x < canvasSize.x / 2;
focusedBoardSide = leftSide ? BoardSide.left : BoardSide.right; focusedBoardSide = leftSide ? BoardSide.left : BoardSide.right;
@ -122,8 +128,6 @@ class _GameBallsController extends ComponentController<PinballGame>
with BlocComponent<GameBloc, GameState> { with BlocComponent<GameBloc, GameState> {
_GameBallsController(PinballGame game) : super(game); _GameBallsController(PinballGame game) : super(game);
late final Plunger _plunger;
@override @override
bool listenWhen(GameState? previousState, GameState newState) { bool listenWhen(GameState? previousState, GameState newState) {
final noBallsLeft = component.descendants().whereType<Ball>().isEmpty; final noBallsLeft = component.descendants().whereType<Ball>().isEmpty;
@ -135,30 +139,27 @@ class _GameBallsController extends ComponentController<PinballGame>
@override @override
void onNewState(GameState state) { void onNewState(GameState state) {
super.onNewState(state); super.onNewState(state);
_spawnBall(); spawnBall();
} }
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
_spawnBall(); spawnBall();
} }
void _spawnBall() { void spawnBall() {
// TODO(alestiago): Refactor with behavioural pattern.
component.ready().whenComplete(() {
final plunger = parent!.descendants().whereType<Plunger>().single;
final ball = ControlledBall.launch( final ball = ControlledBall.launch(
characterTheme: component.characterTheme, characterTheme: component.characterTheme,
)..initialPosition = Vector2( )..initialPosition = Vector2(
_plunger.body.position.x, plunger.body.position.x,
_plunger.body.position.y - Ball.size.y, plunger.body.position.y - Ball.size.y,
); );
component.add(ball); component.firstChild<ZCanvasComponent>()?.add(ball);
} });
/// Attaches the controller to the plunger.
// TODO(alestiago): Remove this method and use onLoad instead.
// ignore: use_setters_to_change_properties
void attachTo(Plunger plunger) {
_plunger = plunger;
} }
} }
@ -170,7 +171,7 @@ class DebugPinballGame extends PinballGame with FPSCounter {
characterTheme: characterTheme, characterTheme: characterTheme,
audio: audio, audio: audio,
) { ) {
controller = _DebugGameBallsController(this); controller = _GameBallsController(this);
} }
@override @override
@ -179,42 +180,21 @@ class DebugPinballGame extends PinballGame with FPSCounter {
await add(_DebugInformation()); await add(_DebugInformation());
} }
// TODO(allisonryan0002): Remove after google letters have been correctly
// placed.
// Future<void> _loadBackground() async {
// final sprite = await loadSprite(
// Assets.images.components.background.path,
// );
// final spriteComponent = SpriteComponent(
// sprite: sprite,
// size: Vector2(120, 160),
// anchor: Anchor.center,
// )
// ..position = Vector2(0, -7.8)
// ..priority = RenderPriority.boardBackground;
// await add(spriteComponent);
// }
@override @override
void onTapUp(TapUpInfo info) { void onTapUp(TapUpInfo info) {
super.onTapUp(info); super.onTapUp(info);
if (info.raw.kind == PointerDeviceKind.mouse) { if (info.raw.kind == PointerDeviceKind.mouse) {
add(ControlledBall.debug()..initialPosition = info.eventPosition.game); final ball = ControlledBall.debug()
..initialPosition = info.eventPosition.game;
firstChild<ZCanvasComponent>()?.add(ball);
} }
} }
} }
class _DebugGameBallsController extends _GameBallsController {
_DebugGameBallsController(PinballGame game) : super(game);
}
// TODO(wolfenrain): investigate this CI failure. // TODO(wolfenrain): investigate this CI failure.
// coverage:ignore-start // coverage:ignore-start
class _DebugInformation extends Component with HasGameRef<DebugPinballGame> { class _DebugInformation extends Component with HasGameRef<DebugPinballGame> {
_DebugInformation() : super(priority: RenderPriority.debugInfo);
@override @override
PositionType get positionType => PositionType.widget; PositionType get positionType => PositionType.widget;

@ -126,7 +126,7 @@ class _BonusAnimationState extends State<BonusAnimation>
); );
animation = spriteSheet.createAnimation( animation = spriteSheet.createAnimation(
row: 0, row: 0,
stepTime: 1 / 24, stepTime: 1 / 12,
to: spriteSheet.rows * spriteSheet.columns, to: spriteSheet.rows * spriteSheet.columns,
loop: false, loop: false,
); );

@ -22,24 +22,9 @@ class PlayButtonOverlay extends StatelessWidget {
return Center( return Center(
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () async {
_game.gameFlowController.start(); _game.gameFlowController.start();
showDialog<void>( await showCharacterSelectionDialog(context);
context: context,
barrierDismissible: false,
builder: (_) {
// TODO(arturplaczek): remove after merge StarBlocListener
final height = MediaQuery.of(context).size.height * 0.5;
return Center(
child: SizedBox(
height: height,
width: height * 1.4,
child: const CharacterSelectionDialog(),
),
);
},
);
}, },
child: Text(l10n.play), child: Text(l10n.play),
), ),

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

@ -8,7 +8,6 @@ import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
import 'package:platform_helper/platform_helper.dart'; import 'package:platform_helper/platform_helper.dart';
@visibleForTesting
enum Control { enum Control {
left, left,
right, right,
@ -50,6 +49,14 @@ extension on Control {
} }
} }
Future<void> showHowToPlayDialog(BuildContext context) {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => HowToPlayDialog(),
);
}
class HowToPlayDialog extends StatefulWidget { class HowToPlayDialog extends StatefulWidget {
HowToPlayDialog({ HowToPlayDialog({
Key? key, Key? key,
@ -70,7 +77,7 @@ class _HowToPlayDialogState extends State<HowToPlayDialog> {
super.initState(); super.initState();
closeTimer = Timer(const Duration(seconds: 3), () { closeTimer = Timer(const Duration(seconds: 3), () {
if (mounted) { if (mounted) {
Navigator.of(context).maybePop(); Navigator.of(context).pop();
} }
}); });
} }
@ -84,9 +91,11 @@ class _HowToPlayDialogState extends State<HowToPlayDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isMobile = widget.platformHelper.isMobile; final isMobile = widget.platformHelper.isMobile;
return PixelatedDecoration( final l10n = context.l10n;
header: const _HowToPlayHeader(), return PinballDialog(
body: isMobile ? const _MobileBody() : const _DesktopBody(), title: l10n.howToPlay,
subtitle: l10n.tipsForFlips,
child: isMobile ? const _MobileBody() : const _DesktopBody(),
); );
} }
} }
@ -121,23 +130,20 @@ class _MobileLaunchControls extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
final textStyle = Theme.of(context).textTheme.headline3; final headline3 = Theme.of(context)
.textTheme
.headline3!
.copyWith(color: PinballColors.white);
return Column( return Column(
children: [ children: [
Text( Text(l10n.tapAndHoldRocket, style: headline3),
l10n.tapAndHoldRocket,
style: textStyle,
),
Text.rich( Text.rich(
TextSpan( TextSpan(
children: [ children: [
TextSpan( TextSpan(text: '${l10n.to} ', style: headline3),
text: '${l10n.to} ',
style: textStyle,
),
TextSpan( TextSpan(
text: l10n.launch, text: l10n.launch,
style: textStyle?.copyWith(color: PinballColors.blue), style: headline3.copyWith(color: PinballColors.blue),
), ),
], ],
), ),
@ -153,23 +159,20 @@ class _MobileFlipperControls extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
final textStyle = Theme.of(context).textTheme.headline3; final headline3 = Theme.of(context)
.textTheme
.headline3!
.copyWith(color: PinballColors.white);
return Column( return Column(
children: [ children: [
Text( Text(l10n.tapLeftRightScreen, style: headline3),
l10n.tapLeftRightScreen,
style: textStyle,
),
Text.rich( Text.rich(
TextSpan( TextSpan(
children: [ children: [
TextSpan( TextSpan(text: '${l10n.to} ', style: headline3),
text: '${l10n.to} ',
style: textStyle,
),
TextSpan( TextSpan(
text: l10n.flip, text: l10n.flip,
style: textStyle?.copyWith(color: PinballColors.orange), style: headline3.copyWith(color: PinballColors.orange),
), ),
], ],
), ),
@ -184,55 +187,23 @@ class _DesktopBody extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const spacing = SizedBox(height: 16);
return ListView( return ListView(
children: const [ children: const [
spacing, SizedBox(height: 16),
_DesktopLaunchControls(), _DesktopLaunchControls(),
spacing, SizedBox(height: 16),
_DesktopFlipperControls(), _DesktopFlipperControls(),
], ],
); );
} }
} }
class _HowToPlayHeader extends StatelessWidget {
const _HowToPlayHeader({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final textStyle = Theme.of(context).textTheme.headline3?.copyWith(
color: PinballColors.darkBlue,
);
return FittedBox(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
l10n.howToPlay,
style: textStyle?.copyWith(
fontWeight: FontWeight.bold,
),
),
Text(
l10n.tipsForFlips,
style: textStyle,
),
],
),
);
}
}
class _DesktopLaunchControls extends StatelessWidget { class _DesktopLaunchControls extends StatelessWidget {
const _DesktopLaunchControls({Key? key}) : super(key: key); const _DesktopLaunchControls({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
const spacing = SizedBox(width: 10);
return Column( return Column(
children: [ children: [
Text( Text(
@ -242,11 +213,11 @@ class _DesktopLaunchControls extends StatelessWidget {
const SizedBox(height: 10), const SizedBox(height: 10),
Wrap( Wrap(
children: const [ children: const [
KeyButton(control: Control.down), _KeyButton(control: Control.down),
spacing, SizedBox(width: 10),
KeyButton(control: Control.space), _KeyButton(control: Control.space),
spacing, SizedBox(width: 10),
KeyButton(control: Control.s), _KeyButton(control: Control.s),
], ],
) )
], ],
@ -260,8 +231,6 @@ class _DesktopFlipperControls extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
const rowSpacing = SizedBox(width: 20);
return Column( return Column(
children: [ children: [
Text( Text(
@ -275,17 +244,17 @@ class _DesktopFlipperControls extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: const [ children: const [
KeyButton(control: Control.left), _KeyButton(control: Control.left),
rowSpacing, SizedBox(width: 20),
KeyButton(control: Control.right), _KeyButton(control: Control.right),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Wrap( Wrap(
children: const [ children: const [
KeyButton(control: Control.a), _KeyButton(control: Control.a),
rowSpacing, SizedBox(width: 20),
KeyButton(control: Control.d), _KeyButton(control: Control.d),
], ],
) )
], ],
@ -295,29 +264,24 @@ class _DesktopFlipperControls extends StatelessWidget {
} }
} }
@visibleForTesting class _KeyButton extends StatelessWidget {
class KeyButton extends StatelessWidget { const _KeyButton({Key? key, required this.control}) : super(key: key);
const KeyButton({
Key? key,
required Control control,
}) : _control = control,
super(key: key);
final Control _control; final Control control;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme; final textTheme = Theme.of(context).textTheme;
final textStyle = final textStyle =
_control.isArrow ? textTheme.headline1 : textTheme.headline3; control.isArrow ? textTheme.headline1 : textTheme.headline3;
const height = 60.0; const height = 60.0;
final width = _control.isSpace ? height * 2.83 : height; final width = control.isSpace ? height * 2.83 : height;
return DecoratedBox( return DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
image: DecorationImage( image: DecorationImage(
fit: BoxFit.fill, fit: BoxFit.fill,
image: AssetImage( image: AssetImage(
_control.isSpace control.isSpace
? Assets.images.components.space.keyName ? Assets.images.components.space.keyName
: Assets.images.components.key.keyName, : Assets.images.components.key.keyName,
), ),
@ -328,9 +292,9 @@ class KeyButton extends StatelessWidget {
height: height, height: height,
child: Center( child: Center(
child: RotatedBox( child: RotatedBox(
quarterTurns: _control.isDown ? 1 : 0, quarterTurns: control.isDown ? 1 : 0,
child: Text( child: Text(
_control.getCharacter(context), control.getCharacter(context),
style: textStyle?.copyWith(color: PinballColors.white), style: textStyle?.copyWith(color: PinballColors.white),
), ),
), ),

@ -46,19 +46,19 @@
}, },
"select": "Select", "select": "Select",
"@select": { "@select": {
"description": "Text displayed on the character selection page select button" "description": "Text displayed on the character selection dialog - select button"
}, },
"space": "Space", "space": "Space",
"@space": { "@space": {
"description": "Text displayed on space control button" "description": "Text displayed on space control button"
}, },
"characterSelectionTitle": "Choose your character!", "characterSelectionTitle": "Select a Character",
"@characterSelectionTitle": { "@characterSelectionTitle": {
"description": "Title text displayed on the character selection page" "description": "Title text displayed on the character selection dialog"
}, },
"characterSelectionSubtitle": "Theres no wrong answer", "characterSelectionSubtitle": "Theres no wrong choice!",
"@characterSelectionSubtitle": { "@characterSelectionSubtitle": {
"description": "Text displayed on the selecting character dialog at game beginning" "description": "Subtitle text displayed on the character selection dialog"
}, },
"gameOver": "Game Over", "gameOver": "Game Over",
"@gameOver": { "@gameOver": {

@ -10,6 +10,14 @@ class CharacterThemeState extends Equatable {
final CharacterTheme characterTheme; final CharacterTheme characterTheme;
bool get isSparkySelected => characterTheme == const SparkyTheme();
bool get isDashSelected => characterTheme == const DashTheme();
bool get isAndroidSelected => characterTheme == const AndroidTheme();
bool get isDinoSelected => characterTheme == const DinoTheme();
@override @override
List<Object> get props => [characterTheme]; List<Object> get props => [characterTheme];
} }

@ -1,139 +1,160 @@
// ignore_for_file: public_member_api_docs
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/how_to_play/how_to_play.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/cubit/character_theme_cubit.dart';
import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_theme/pinball_theme.dart'; import 'package:pinball_theme/pinball_theme.dart';
import 'package:pinball_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
class CharacterSelectionDialog extends StatelessWidget { /// Inflates [CharacterSelectionDialog] using [showDialog].
const CharacterSelectionDialog({Key? key}) : super(key: key); Future<void> showCharacterSelectionDialog(BuildContext context) {
return showDialog<void>(
static Route route() { context: context,
return MaterialPageRoute<void>( barrierDismissible: false,
builder: (_) => const CharacterSelectionDialog(), builder: (_) => const CharacterSelectionDialog(),
); );
} }
/// {@template character_selection_dialog}
/// Dialog used to select the playing character of the game.
/// {@endtemplate character_selection_dialog}
class CharacterSelectionDialog extends StatelessWidget {
/// {@macro character_selection_dialog}
const CharacterSelectionDialog({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( final l10n = context.l10n;
create: (_) => CharacterThemeCubit(), return PinballDialog(
child: const CharacterSelectionView(), title: l10n.characterSelectionTitle,
subtitle: l10n.characterSelectionSubtitle,
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Row(
children: [
Expanded(child: _CharacterPreview()),
Expanded(child: _CharacterGrid()),
],
),
),
const SizedBox(height: 8),
const _SelectCharacterButton(),
],
),
),
); );
} }
} }
class CharacterSelectionView extends StatelessWidget { class _SelectCharacterButton extends StatelessWidget {
const CharacterSelectionView({Key? key}) : super(key: key); const _SelectCharacterButton({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
return PinballButton(
onTap: () async {
Navigator.of(context).pop();
await showHowToPlayDialog(context);
},
text: l10n.select,
);
}
}
return PixelatedDecoration( class _CharacterGrid extends StatelessWidget {
header: Text( @override
l10n.characterSelectionTitle, Widget build(BuildContext context) {
style: Theme.of(context).textTheme.headline3, return BlocBuilder<CharacterThemeCubit, CharacterThemeState>(
), builder: (context, state) {
body: SingleChildScrollView( return Row(
child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const _CharacterSelectionGridView(), Column(
const SizedBox(height: 20), children: [
TextButton( _Character(
onPressed: () { key: const Key('sparky_character_selection'),
Navigator.of(context).pop(); character: const SparkyTheme(),
// TODO(arturplaczek): remove after merge StarBlocListener isSelected: state.isSparkySelected,
final height = MediaQuery.of(context).size.height * 0.5;
showDialog<void>(
context: context,
builder: (_) => Center(
child: SizedBox(
height: height,
width: height * 1.4,
child: HowToPlayDialog(),
),
), ),
); const SizedBox(height: 6),
}, _Character(
child: Text(l10n.start), key: const Key('android_character_selection'),
character: const AndroidTheme(),
isSelected: state.isAndroidSelected,
), ),
], ],
), ),
const SizedBox(width: 6),
Column(
children: [
_Character(
key: const Key('dash_character_selection'),
character: const DashTheme(),
isSelected: state.isDashSelected,
),
const SizedBox(height: 6),
_Character(
key: const Key('dino_character_selection'),
character: const DinoTheme(),
isSelected: state.isDinoSelected,
), ),
],
),
],
);
},
); );
} }
} }
class _CharacterSelectionGridView extends StatelessWidget { class _CharacterPreview extends StatelessWidget {
const _CharacterSelectionGridView({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return BlocBuilder<CharacterThemeCubit, CharacterThemeState>(
padding: const EdgeInsets.all(20), builder: (context, state) {
child: GridView.count( return Column(
shrinkWrap: true, mainAxisAlignment: MainAxisAlignment.center,
crossAxisCount: 2, children: [
mainAxisSpacing: 20, Text(
crossAxisSpacing: 20, state.characterTheme.name,
children: const [ style: Theme.of(context).textTheme.headline2,
CharacterImageButton( overflow: TextOverflow.ellipsis,
DashTheme(), textAlign: TextAlign.center,
key: Key('characterSelectionPage_dashButton'),
),
CharacterImageButton(
SparkyTheme(),
key: Key('characterSelectionPage_sparkyButton'),
),
CharacterImageButton(
AndroidTheme(),
key: Key('characterSelectionPage_androidButton'),
),
CharacterImageButton(
DinoTheme(),
key: Key('characterSelectionPage_dinoButton'),
), ),
const SizedBox(height: 10),
Expanded(child: state.characterTheme.icon.image()),
], ],
), );
},
); );
} }
} }
// TODO(allisonryan0002): remove visibility when adding final UI. class _Character extends StatelessWidget {
@visibleForTesting const _Character({
class CharacterImageButton extends StatelessWidget {
const CharacterImageButton(
this.characterTheme, {
Key? key, Key? key,
required this.character,
required this.isSelected,
}) : super(key: key); }) : super(key: key);
final CharacterTheme characterTheme; final CharacterTheme character;
final bool isSelected;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final currentCharacterTheme = return Expanded(
context.select<CharacterThemeCubit, CharacterTheme>( child: Opacity(
(cubit) => cubit.state.characterTheme, opacity: isSelected ? 1 : 0.3,
); child: InkWell(
return GestureDetector(
onTap: () => onTap: () =>
context.read<CharacterThemeCubit>().characterSelected(characterTheme), context.read<CharacterThemeCubit>().characterSelected(character),
child: DecoratedBox( child: character.icon.image(fit: BoxFit.contain),
decoration: BoxDecoration(
color: (currentCharacterTheme == characterTheme)
? Colors.blue.withOpacity(0.5)
: null,
borderRadius: BorderRadius.circular(6),
),
child: Padding(
padding: const EdgeInsets.all(8),
child: characterTheme.icon.image(),
), ),
), ),
); );

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

@ -18,6 +18,7 @@ class $AssetsImagesGen {
/// File path: assets/images/board-background.png /// File path: assets/images/board-background.png
AssetGenImage get boardBackground => AssetGenImage get boardBackground =>
const AssetGenImage('assets/images/board-background.png'); const AssetGenImage('assets/images/board-background.png');
$AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen(); $AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen();
$AssetsImagesDashGen get dash => const $AssetsImagesDashGen(); $AssetsImagesDashGen get dash => const $AssetsImagesDashGen();
$AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen(); $AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen();
@ -30,6 +31,7 @@ class $AssetsImagesGen {
$AssetsImagesMultiplierGen get multiplier => $AssetsImagesMultiplierGen get multiplier =>
const $AssetsImagesMultiplierGen(); const $AssetsImagesMultiplierGen();
$AssetsImagesPlungerGen get plunger => const $AssetsImagesPlungerGen(); $AssetsImagesPlungerGen get plunger => const $AssetsImagesPlungerGen();
$AssetsImagesScoreGen get score => const $AssetsImagesScoreGen();
$AssetsImagesSignpostGen get signpost => const $AssetsImagesSignpostGen(); $AssetsImagesSignpostGen get signpost => const $AssetsImagesSignpostGen();
$AssetsImagesSlingshotGen get slingshot => const $AssetsImagesSlingshotGen(); $AssetsImagesSlingshotGen get slingshot => const $AssetsImagesSlingshotGen();
$AssetsImagesSparkyGen get sparky => const $AssetsImagesSparkyGen(); $AssetsImagesSparkyGen get sparky => const $AssetsImagesSparkyGen();
@ -141,41 +143,25 @@ class $AssetsImagesFlipperGen {
class $AssetsImagesGoogleWordGen { class $AssetsImagesGoogleWordGen {
const $AssetsImagesGoogleWordGen(); const $AssetsImagesGoogleWordGen();
/// File path: assets/images/google_word/letter1.png $AssetsImagesGoogleWordLetter1Gen get letter1 =>
AssetGenImage get letter1 => const $AssetsImagesGoogleWordLetter1Gen();
const AssetGenImage('assets/images/google_word/letter1.png'); $AssetsImagesGoogleWordLetter2Gen get letter2 =>
const $AssetsImagesGoogleWordLetter2Gen();
/// File path: assets/images/google_word/letter2.png $AssetsImagesGoogleWordLetter3Gen get letter3 =>
AssetGenImage get letter2 => const $AssetsImagesGoogleWordLetter3Gen();
const AssetGenImage('assets/images/google_word/letter2.png'); $AssetsImagesGoogleWordLetter4Gen get letter4 =>
const $AssetsImagesGoogleWordLetter4Gen();
/// File path: assets/images/google_word/letter3.png $AssetsImagesGoogleWordLetter5Gen get letter5 =>
AssetGenImage get letter3 => const $AssetsImagesGoogleWordLetter5Gen();
const AssetGenImage('assets/images/google_word/letter3.png'); $AssetsImagesGoogleWordLetter6Gen get letter6 =>
const $AssetsImagesGoogleWordLetter6Gen();
/// File path: assets/images/google_word/letter4.png
AssetGenImage get letter4 =>
const AssetGenImage('assets/images/google_word/letter4.png');
/// File path: assets/images/google_word/letter5.png
AssetGenImage get letter5 =>
const AssetGenImage('assets/images/google_word/letter5.png');
/// File path: assets/images/google_word/letter6.png
AssetGenImage get letter6 =>
const AssetGenImage('assets/images/google_word/letter6.png');
} }
class $AssetsImagesKickerGen { class $AssetsImagesKickerGen {
const $AssetsImagesKickerGen(); const $AssetsImagesKickerGen();
/// File path: assets/images/kicker/left.png $AssetsImagesKickerLeftGen get left => const $AssetsImagesKickerLeftGen();
AssetGenImage get left => $AssetsImagesKickerRightGen get right => const $AssetsImagesKickerRightGen();
const AssetGenImage('assets/images/kicker/left.png');
/// File path: assets/images/kicker/right.png
AssetGenImage get right =>
const AssetGenImage('assets/images/kicker/right.png');
} }
class $AssetsImagesLaunchRampGen { class $AssetsImagesLaunchRampGen {
@ -216,6 +202,26 @@ class $AssetsImagesPlungerGen {
const AssetGenImage('assets/images/plunger/rocket.png'); const AssetGenImage('assets/images/plunger/rocket.png');
} }
class $AssetsImagesScoreGen {
const $AssetsImagesScoreGen();
/// File path: assets/images/score/five-thousand.png
AssetGenImage get fiveThousand =>
const AssetGenImage('assets/images/score/five-thousand.png');
/// File path: assets/images/score/one-million.png
AssetGenImage get oneMillion =>
const AssetGenImage('assets/images/score/one-million.png');
/// File path: assets/images/score/twenty-thousand.png
AssetGenImage get twentyThousand =>
const AssetGenImage('assets/images/score/twenty-thousand.png');
/// File path: assets/images/score/two-hundred-thousand.png
AssetGenImage get twoHundredThousand =>
const AssetGenImage('assets/images/score/two-hundred-thousand.png');
}
class $AssetsImagesSignpostGen { class $AssetsImagesSignpostGen {
const $AssetsImagesSignpostGen(); const $AssetsImagesSignpostGen();
@ -344,6 +350,102 @@ class $AssetsImagesDinoAnimatronicGen {
const AssetGenImage('assets/images/dino/animatronic/mouth.png'); const AssetGenImage('assets/images/dino/animatronic/mouth.png');
} }
class $AssetsImagesGoogleWordLetter1Gen {
const $AssetsImagesGoogleWordLetter1Gen();
/// File path: assets/images/google_word/letter1/dimmed.png
AssetGenImage get dimmed =>
const AssetGenImage('assets/images/google_word/letter1/dimmed.png');
/// File path: assets/images/google_word/letter1/lit.png
AssetGenImage get lit =>
const AssetGenImage('assets/images/google_word/letter1/lit.png');
}
class $AssetsImagesGoogleWordLetter2Gen {
const $AssetsImagesGoogleWordLetter2Gen();
/// File path: assets/images/google_word/letter2/dimmed.png
AssetGenImage get dimmed =>
const AssetGenImage('assets/images/google_word/letter2/dimmed.png');
/// File path: assets/images/google_word/letter2/lit.png
AssetGenImage get lit =>
const AssetGenImage('assets/images/google_word/letter2/lit.png');
}
class $AssetsImagesGoogleWordLetter3Gen {
const $AssetsImagesGoogleWordLetter3Gen();
/// File path: assets/images/google_word/letter3/dimmed.png
AssetGenImage get dimmed =>
const AssetGenImage('assets/images/google_word/letter3/dimmed.png');
/// File path: assets/images/google_word/letter3/lit.png
AssetGenImage get lit =>
const AssetGenImage('assets/images/google_word/letter3/lit.png');
}
class $AssetsImagesGoogleWordLetter4Gen {
const $AssetsImagesGoogleWordLetter4Gen();
/// File path: assets/images/google_word/letter4/dimmed.png
AssetGenImage get dimmed =>
const AssetGenImage('assets/images/google_word/letter4/dimmed.png');
/// File path: assets/images/google_word/letter4/lit.png
AssetGenImage get lit =>
const AssetGenImage('assets/images/google_word/letter4/lit.png');
}
class $AssetsImagesGoogleWordLetter5Gen {
const $AssetsImagesGoogleWordLetter5Gen();
/// File path: assets/images/google_word/letter5/dimmed.png
AssetGenImage get dimmed =>
const AssetGenImage('assets/images/google_word/letter5/dimmed.png');
/// File path: assets/images/google_word/letter5/lit.png
AssetGenImage get lit =>
const AssetGenImage('assets/images/google_word/letter5/lit.png');
}
class $AssetsImagesGoogleWordLetter6Gen {
const $AssetsImagesGoogleWordLetter6Gen();
/// File path: assets/images/google_word/letter6/dimmed.png
AssetGenImage get dimmed =>
const AssetGenImage('assets/images/google_word/letter6/dimmed.png');
/// File path: assets/images/google_word/letter6/lit.png
AssetGenImage get lit =>
const AssetGenImage('assets/images/google_word/letter6/lit.png');
}
class $AssetsImagesKickerLeftGen {
const $AssetsImagesKickerLeftGen();
/// File path: assets/images/kicker/left/dimmed.png
AssetGenImage get dimmed =>
const AssetGenImage('assets/images/kicker/left/dimmed.png');
/// File path: assets/images/kicker/left/lit.png
AssetGenImage get lit =>
const AssetGenImage('assets/images/kicker/left/lit.png');
}
class $AssetsImagesKickerRightGen {
const $AssetsImagesKickerRightGen();
/// File path: assets/images/kicker/right/dimmed.png
AssetGenImage get dimmed =>
const AssetGenImage('assets/images/kicker/right/dimmed.png');
/// File path: assets/images/kicker/right/lit.png
AssetGenImage get lit =>
const AssetGenImage('assets/images/kicker/right/lit.png');
}
class $AssetsImagesMultiplierX2Gen { class $AssetsImagesMultiplierX2Gen {
const $AssetsImagesMultiplierX2Gen(); const $AssetsImagesMultiplierX2Gen();
@ -419,6 +521,10 @@ class $AssetsImagesSparkyComputerGen {
AssetGenImage get base => AssetGenImage get base =>
const AssetGenImage('assets/images/sparky/computer/base.png'); const AssetGenImage('assets/images/sparky/computer/base.png');
/// File path: assets/images/sparky/computer/glow.png
AssetGenImage get glow =>
const AssetGenImage('assets/images/sparky/computer/glow.png');
/// File path: assets/images/sparky/computer/top.png /// File path: assets/images/sparky/computer/top.png
AssetGenImage get top => AssetGenImage get top =>
const AssetGenImage('assets/images/sparky/computer/top.png'); const AssetGenImage('assets/images/sparky/computer/top.png');
@ -527,37 +633,37 @@ class $AssetsImagesDashBumperMainGen {
class $AssetsImagesSparkyBumperAGen { class $AssetsImagesSparkyBumperAGen {
const $AssetsImagesSparkyBumperAGen(); const $AssetsImagesSparkyBumperAGen();
/// File path: assets/images/sparky/bumper/a/active.png /// File path: assets/images/sparky/bumper/a/dimmed.png
AssetGenImage get active => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/sparky/bumper/a/active.png'); const AssetGenImage('assets/images/sparky/bumper/a/dimmed.png');
/// File path: assets/images/sparky/bumper/a/inactive.png /// File path: assets/images/sparky/bumper/a/lit.png
AssetGenImage get inactive => AssetGenImage get lit =>
const AssetGenImage('assets/images/sparky/bumper/a/inactive.png'); const AssetGenImage('assets/images/sparky/bumper/a/lit.png');
} }
class $AssetsImagesSparkyBumperBGen { class $AssetsImagesSparkyBumperBGen {
const $AssetsImagesSparkyBumperBGen(); const $AssetsImagesSparkyBumperBGen();
/// File path: assets/images/sparky/bumper/b/active.png /// File path: assets/images/sparky/bumper/b/dimmed.png
AssetGenImage get active => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/sparky/bumper/b/active.png'); const AssetGenImage('assets/images/sparky/bumper/b/dimmed.png');
/// File path: assets/images/sparky/bumper/b/inactive.png /// File path: assets/images/sparky/bumper/b/lit.png
AssetGenImage get inactive => AssetGenImage get lit =>
const AssetGenImage('assets/images/sparky/bumper/b/inactive.png'); const AssetGenImage('assets/images/sparky/bumper/b/lit.png');
} }
class $AssetsImagesSparkyBumperCGen { class $AssetsImagesSparkyBumperCGen {
const $AssetsImagesSparkyBumperCGen(); const $AssetsImagesSparkyBumperCGen();
/// File path: assets/images/sparky/bumper/c/active.png /// File path: assets/images/sparky/bumper/c/dimmed.png
AssetGenImage get active => AssetGenImage get dimmed =>
const AssetGenImage('assets/images/sparky/bumper/c/active.png'); const AssetGenImage('assets/images/sparky/bumper/c/dimmed.png');
/// File path: assets/images/sparky/bumper/c/inactive.png /// File path: assets/images/sparky/bumper/c/lit.png
AssetGenImage get inactive => AssetGenImage get lit =>
const AssetGenImage('assets/images/sparky/bumper/c/inactive.png'); const AssetGenImage('assets/images/sparky/bumper/c/lit.png');
} }
class Assets { class Assets {

@ -5,6 +5,7 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/android_bumper/behaviors/behaviors.dart'; import 'package:pinball_components/src/components/android_bumper/behaviors/behaviors.dart';
import 'package:pinball_components/src/components/bumping_behavior.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
export 'cubit/android_bumper_cubit.dart'; export 'cubit/android_bumper_cubit.dart';
@ -12,7 +13,7 @@ export 'cubit/android_bumper_cubit.dart';
/// {@template android_bumper} /// {@template android_bumper}
/// Bumper for area under the [AndroidSpaceship]. /// Bumper for area under the [AndroidSpaceship].
/// {@endtemplate} /// {@endtemplate}
class AndroidBumper extends BodyComponent with InitialPosition { class AndroidBumper extends BodyComponent with InitialPosition, ZIndex {
/// {@macro android_bumper} /// {@macro android_bumper}
AndroidBumper._({ AndroidBumper._({
required double majorRadius, required double majorRadius,
@ -25,7 +26,6 @@ class AndroidBumper extends BodyComponent with InitialPosition {
}) : _majorRadius = majorRadius, }) : _majorRadius = majorRadius,
_minorRadius = minorRadius, _minorRadius = minorRadius,
super( super(
priority: RenderPriority.androidBumper,
renderBody: false, renderBody: false,
children: [ children: [
AndroidBumperBallContactBehavior(), AndroidBumperBallContactBehavior(),
@ -38,7 +38,9 @@ class AndroidBumper extends BodyComponent with InitialPosition {
), ),
...?children, ...?children,
], ],
); ) {
zIndex = ZIndexes.androidBumper;
}
/// {@macro android_bumper} /// {@macro android_bumper}
AndroidBumper.a({ AndroidBumper.a({
@ -50,7 +52,10 @@ class AndroidBumper extends BodyComponent with InitialPosition {
dimmedAssetPath: Assets.images.android.bumper.a.dimmed.keyName, dimmedAssetPath: Assets.images.android.bumper.a.dimmed.keyName,
spritePosition: Vector2(0, -0.1), spritePosition: Vector2(0, -0.1),
bloc: AndroidBumperCubit(), bloc: AndroidBumperCubit(),
children: children, children: [
...?children,
BumpingBehavior(strength: 20),
],
); );
/// {@macro android_bumper} /// {@macro android_bumper}
@ -63,7 +68,10 @@ class AndroidBumper extends BodyComponent with InitialPosition {
dimmedAssetPath: Assets.images.android.bumper.b.dimmed.keyName, dimmedAssetPath: Assets.images.android.bumper.b.dimmed.keyName,
spritePosition: Vector2(0, -0.1), spritePosition: Vector2(0, -0.1),
bloc: AndroidBumperCubit(), bloc: AndroidBumperCubit(),
children: children, children: [
...?children,
BumpingBehavior(strength: 20),
],
); );
/// {@macro android_bumper} /// {@macro android_bumper}
@ -76,7 +84,10 @@ class AndroidBumper extends BodyComponent with InitialPosition {
dimmedAssetPath: Assets.images.android.bumper.cow.dimmed.keyName, dimmedAssetPath: Assets.images.android.bumper.cow.dimmed.keyName,
spritePosition: Vector2(0, -0.68), spritePosition: Vector2(0, -0.68),
bloc: AndroidBumperCubit(), bloc: AndroidBumperCubit(),
children: children, children: [
...?children,
BumpingBehavior(strength: 20),
],
); );
/// Creates an [AndroidBumper] without any children. /// Creates an [AndroidBumper] without any children.
@ -112,15 +123,11 @@ class AndroidBumper extends BodyComponent with InitialPosition {
majorRadius: _majorRadius, majorRadius: _majorRadius,
minorRadius: _minorRadius, minorRadius: _minorRadius,
)..rotate(1.29); )..rotate(1.29);
final fixtureDef = FixtureDef(
shape,
restitution: 4,
);
final bodyDef = BodyDef( final bodyDef = BodyDef(
position: initialPosition, position: initialPosition,
); );
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixtureFromShape(shape);
} }
} }

@ -5,25 +5,24 @@ import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/gen/assets.gen.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
class AndroidSpaceship extends Blueprint { class AndroidSpaceship extends Component {
AndroidSpaceship({required Vector2 position}) AndroidSpaceship({required Vector2 position})
: super( : super(
components: [ children: [
_SpaceshipSaucer()..initialPosition = position, _SpaceshipSaucer()..initialPosition = position,
_SpaceshipSaucerSpriteAnimationComponent()..position = position, _SpaceshipSaucerSpriteAnimationComponent()..position = position,
_LightBeamSpriteComponent()..position = position + Vector2(2.5, 5), _LightBeamSpriteComponent()..position = position + Vector2(2.5, 5),
_AndroidHead()..initialPosition = position + Vector2(0.5, 0.25), _AndroidHead()..initialPosition = position + Vector2(0.5, 0.25),
_SpaceshipHole( _SpaceshipHole(
outsideLayer: Layer.spaceshipExitRail, outsideLayer: Layer.spaceshipExitRail,
outsidePriority: RenderPriority.ballOnSpaceshipRail, outsidePriority: ZIndexes.ballOnSpaceshipRail,
)..initialPosition = position - Vector2(5.3, -5.4), )..initialPosition = position - Vector2(5.3, -5.4),
_SpaceshipHole( _SpaceshipHole(
outsideLayer: Layer.board, outsideLayer: Layer.board,
outsidePriority: RenderPriority.ballOnBoard, outsidePriority: ZIndexes.ballOnBoard,
)..initialPosition = position - Vector2(-7.5, -1.1), )..initialPosition = position - Vector2(-7.5, -1.1),
], ],
); );
@ -65,12 +64,13 @@ class _SpaceshipSaucerShape extends ChainShape {
} }
class _SpaceshipSaucerSpriteAnimationComponent extends SpriteAnimationComponent class _SpaceshipSaucerSpriteAnimationComponent extends SpriteAnimationComponent
with HasGameRef { with HasGameRef, ZIndex {
_SpaceshipSaucerSpriteAnimationComponent() _SpaceshipSaucerSpriteAnimationComponent()
: super( : super(
anchor: Anchor.center, anchor: Anchor.center,
priority: RenderPriority.spaceshipSaucer, ) {
); zIndex = ZIndexes.spaceshipSaucer;
}
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
@ -101,12 +101,14 @@ class _SpaceshipSaucerSpriteAnimationComponent extends SpriteAnimationComponent
} }
// TODO(allisonryan0002): add pulsing behavior. // TODO(allisonryan0002): add pulsing behavior.
class _LightBeamSpriteComponent extends SpriteComponent with HasGameRef { class _LightBeamSpriteComponent extends SpriteComponent
with HasGameRef, ZIndex {
_LightBeamSpriteComponent() _LightBeamSpriteComponent()
: super( : super(
anchor: Anchor.center, anchor: Anchor.center,
priority: RenderPriority.spaceshipLightBeam, ) {
); zIndex = ZIndexes.spaceshipLightBeam;
}
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
@ -121,14 +123,14 @@ class _LightBeamSpriteComponent extends SpriteComponent with HasGameRef {
} }
} }
class _AndroidHead extends BodyComponent with InitialPosition, Layered { class _AndroidHead extends BodyComponent with InitialPosition, Layered, ZIndex {
_AndroidHead() _AndroidHead()
: super( : super(
priority: RenderPriority.androidHead,
children: [_AndroidHeadSpriteAnimationComponent()], children: [_AndroidHeadSpriteAnimationComponent()],
renderBody: false, renderBody: false,
) { ) {
layer = Layer.spaceship; layer = Layer.spaceship;
zIndex = ZIndexes.androidHead;
} }
@override @override
@ -138,14 +140,9 @@ class _AndroidHead extends BodyComponent with InitialPosition, Layered {
majorRadius: 3.1, majorRadius: 3.1,
minorRadius: 2, minorRadius: 2,
)..rotate(1.4); )..rotate(1.4);
// TODO(allisonryan0002): use bumping behavior.
final fixtureDef = FixtureDef(
shape,
restitution: 0.1,
);
final bodyDef = BodyDef(position: initialPosition); final bodyDef = BodyDef(position: initialPosition);
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixtureFromShape(shape);
} }
} }
@ -191,8 +188,8 @@ class _SpaceshipHole extends LayerSensor {
insideLayer: Layer.spaceship, insideLayer: Layer.spaceship,
outsideLayer: outsideLayer, outsideLayer: outsideLayer,
orientation: LayerEntranceOrientation.down, orientation: LayerEntranceOrientation.down,
insidePriority: RenderPriority.ballOnSpaceship, insideZIndex: ZIndexes.ballOnSpaceship,
outsidePriority: outsidePriority, outsideZIndex: outsidePriority,
) { ) {
layer = Layer.spaceship; layer = Layer.spaceship;
} }

@ -6,12 +6,13 @@ import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template ball} /// {@template ball}
/// A solid, [BodyType.dynamic] sphere that rolls and bounces around. /// A solid, [BodyType.dynamic] sphere that rolls and bounces around.
/// {@endtemplate} /// {@endtemplate}
class Ball<T extends Forge2DGame> extends BodyComponent<T> class Ball<T extends Forge2DGame> extends BodyComponent<T>
with Layered, InitialPosition { with Layered, InitialPosition, ZIndex {
/// {@macro ball} /// {@macro ball}
Ball({ Ball({
required this.baseColor, required this.baseColor,
@ -67,7 +68,7 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
/// ///
/// If previously [stop]ped, the previous ball's velocity is not kept. /// If previously [stop]ped, the previous ball's velocity is not kept.
void resume() { void resume() {
body.gravityScale = Vector2(0, 1); body.gravityScale = Vector2(1, 1);
} }
/// Applies a boost and [_TurboChargeSpriteAnimationComponent] on this [Ball]. /// Applies a boost and [_TurboChargeSpriteAnimationComponent] on this [Ball].
@ -133,13 +134,14 @@ class _BallSpriteComponent extends SpriteComponent with HasGameRef {
} }
class _TurboChargeSpriteAnimationComponent extends SpriteAnimationComponent class _TurboChargeSpriteAnimationComponent extends SpriteAnimationComponent
with HasGameRef { with HasGameRef, ZIndex {
_TurboChargeSpriteAnimationComponent() _TurboChargeSpriteAnimationComponent()
: super( : super(
anchor: const Anchor(0.53, 0.72), anchor: const Anchor(0.53, 0.72),
priority: RenderPriority.turboChargeFlame,
removeOnFinish: true, removeOnFinish: true,
); ) {
zIndex = ZIndexes.turboChargeFlame;
}
late final Vector2 _textureSize; late final Vector2 _textureSize;

@ -2,14 +2,17 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
class BoardBackgroundSpriteComponent extends SpriteComponent with HasGameRef { class BoardBackgroundSpriteComponent extends SpriteComponent
with HasGameRef, ZIndex {
BoardBackgroundSpriteComponent() BoardBackgroundSpriteComponent()
: super( : super(
anchor: Anchor.center, anchor: Anchor.center,
priority: RenderPriority.boardBackground,
position: Vector2(0, -1), position: Vector2(0, -1),
); ) {
zIndex = ZIndexes.boardBackground;
}
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {

@ -4,13 +4,13 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
/// {@template boundaries} /// {@template boundaries}
/// A [Blueprint] which creates the [_BottomBoundary] and [_OuterBoundary]. /// Pinball machine walls.
///{@endtemplate boundaries} /// {@endtemplate}
class Boundaries extends Blueprint { class Boundaries extends Component {
/// {@macro boundaries} /// {@macro boundaries}
Boundaries() Boundaries()
: super( : super(
components: [ children: [
_BottomBoundary(), _BottomBoundary(),
_OuterBoundary(), _OuterBoundary(),
_OuterBottomBoundarySpriteComponent(), _OuterBottomBoundarySpriteComponent(),
@ -22,14 +22,15 @@ class Boundaries extends Blueprint {
/// Curved boundary at the bottom of the board where the [Ball] exits the field /// Curved boundary at the bottom of the board where the [Ball] exits the field
/// of play. /// of play.
/// {@endtemplate bottom_boundary} /// {@endtemplate bottom_boundary}
class _BottomBoundary extends BodyComponent with InitialPosition { class _BottomBoundary extends BodyComponent with InitialPosition, ZIndex {
/// {@macro bottom_boundary} /// {@macro bottom_boundary}
_BottomBoundary() _BottomBoundary()
: super( : super(
renderBody: false, renderBody: false,
priority: RenderPriority.bottomBoundary,
children: [_BottomBoundarySpriteComponent()], children: [_BottomBoundarySpriteComponent()],
); ) {
zIndex = ZIndexes.bottomBoundary;
}
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final bottomLeftCurve = BezierCurveShape( final bottomLeftCurve = BezierCurveShape(
@ -84,17 +85,20 @@ class _BottomBoundarySpriteComponent extends SpriteComponent with HasGameRef {
} }
/// {@template outer_boundary} /// {@template outer_boundary}
/// Boundary enclosing the top and left side of the board. The right side of the /// Boundary enclosing the top and left side of the board.
/// board is closed by the barrier the [LaunchRamp] creates. ///
/// The right side of the board is closed by the barrier the [LaunchRamp]
/// creates.
/// {@endtemplate outer_boundary} /// {@endtemplate outer_boundary}
class _OuterBoundary extends BodyComponent with InitialPosition { class _OuterBoundary extends BodyComponent with InitialPosition, ZIndex {
/// {@macro outer_boundary} /// {@macro outer_boundary}
_OuterBoundary() _OuterBoundary()
: super( : super(
renderBody: false, renderBody: false,
priority: RenderPriority.outerBoundary,
children: [_OuterBoundarySpriteComponent()], children: [_OuterBoundarySpriteComponent()],
); ) {
zIndex = ZIndexes.outerBoundary;
}
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final topWall = EdgeShape() final topWall = EdgeShape()
@ -189,13 +193,14 @@ class _OuterBoundarySpriteComponent extends SpriteComponent with HasGameRef {
} }
class _OuterBottomBoundarySpriteComponent extends SpriteComponent class _OuterBottomBoundarySpriteComponent extends SpriteComponent
with HasGameRef { with HasGameRef, ZIndex {
_OuterBottomBoundarySpriteComponent() _OuterBottomBoundarySpriteComponent()
: super( : super(
priority: RenderPriority.outerBottomBoundary,
anchor: Anchor.center, anchor: Anchor.center,
position: Vector2(0, 71), position: Vector2(0, 71),
); ) {
zIndex = ZIndexes.outerBottomBoundary;
}
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {

@ -1,202 +0,0 @@
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart' hide Timer;
import 'package:pinball_components/pinball_components.dart';
/// {@template chrome_dino}
/// Dino that swivels back and forth, opening its mouth to eat a [Ball].
///
/// Upon eating a [Ball], the dino rotates and spits the [Ball] out in a
/// different direction.
/// {@endtemplate}
class ChromeDino extends BodyComponent with InitialPosition {
/// {@macro chrome_dino}
ChromeDino()
: super(
priority: RenderPriority.dino,
renderBody: false,
);
/// The size of the dinosaur mouth.
static final size = Vector2(5.5, 5);
/// Anchors the [ChromeDino] to the [RevoluteJoint] that controls its arc
/// motion.
Future<_ChromeDinoJoint> _anchorToJoint() async {
// TODO(allisonryan0002): try moving to anchor after new body is defined.
final anchor = _ChromeDinoAnchor()
..initialPosition = initialPosition + Vector2(9, -4);
await add(anchor);
final jointDef = _ChromeDinoAnchorRevoluteJointDef(
chromeDino: this,
anchor: anchor,
);
final joint = _ChromeDinoJoint(jointDef);
world.createJoint(joint);
return joint;
}
@override
Future<void> onLoad() async {
await super.onLoad();
final joint = await _anchorToJoint();
const framesInAnimation = 98;
const animationFPS = 1 / 24;
await add(
TimerComponent(
period: (framesInAnimation / 2) * animationFPS,
onTick: joint._swivel,
repeat: true,
),
);
}
List<FixtureDef> _createFixtureDefs() {
final fixtureDefs = <FixtureDef>[];
// TODO(allisonryan0002): Update this shape to better match sprite.
final box = PolygonShape()
..setAsBox(
size.x / 2,
size.y / 2,
initialPosition + Vector2(-4, 2),
-_ChromeDinoJoint._halfSweepingAngle,
);
final fixtureDef = FixtureDef(box, density: 1);
fixtureDefs.add(fixtureDef);
return fixtureDefs;
}
@override
Body createBody() {
final bodyDef = BodyDef(
position: initialPosition,
type: BodyType.dynamic,
gravityScale: Vector2.zero(),
);
final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture);
return body;
}
}
class _ChromeDinoAnchor extends JointAnchor {
_ChromeDinoAnchor();
// TODO(allisonryan0002): if these aren't moved when fixing the rendering, see
// if the joint can be created in onMount to resolve render syncing.
@override
Future<void> onLoad() async {
await super.onLoad();
await addAll([
_ChromeDinoMouthSprite(),
_ChromeDinoHeadSprite(),
]);
}
}
/// {@template chrome_dino_anchor_revolute_joint_def}
/// Hinges a [ChromeDino] to a [_ChromeDinoAnchor].
/// {@endtemplate}
class _ChromeDinoAnchorRevoluteJointDef extends RevoluteJointDef {
/// {@macro chrome_dino_anchor_revolute_joint_def}
_ChromeDinoAnchorRevoluteJointDef({
required ChromeDino chromeDino,
required _ChromeDinoAnchor anchor,
}) {
initialize(
chromeDino.body,
anchor.body,
chromeDino.body.position + anchor.body.position,
);
enableLimit = true;
lowerAngle = -_ChromeDinoJoint._halfSweepingAngle;
upperAngle = _ChromeDinoJoint._halfSweepingAngle;
enableMotor = true;
maxMotorTorque = chromeDino.body.mass * 255;
motorSpeed = 2;
}
}
class _ChromeDinoJoint extends RevoluteJoint {
_ChromeDinoJoint(_ChromeDinoAnchorRevoluteJointDef def) : super(def);
static const _halfSweepingAngle = 0.1143;
/// Sweeps the [ChromeDino] up and down repeatedly.
void _swivel() {
setMotorSpeed(-motorSpeed);
}
}
class _ChromeDinoMouthSprite extends SpriteAnimationComponent with HasGameRef {
_ChromeDinoMouthSprite()
: super(
anchor: Anchor(Anchor.center.x + 0.47, Anchor.center.y - 0.29),
angle: _ChromeDinoJoint._halfSweepingAngle,
);
@override
Future<void> onLoad() async {
await super.onLoad();
final image = gameRef.images.fromCache(
Assets.images.dino.animatronic.mouth.keyName,
);
const amountPerRow = 11;
const amountPerColumn = 9;
final textureSize = Vector2(
image.width / amountPerRow,
image.height / amountPerColumn,
);
size = textureSize / 10;
final data = SpriteAnimationData.sequenced(
amount: (amountPerColumn * amountPerRow) - 1,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: textureSize,
);
animation = SpriteAnimation.fromFrameData(image, data)..currentIndex = 45;
}
}
class _ChromeDinoHeadSprite extends SpriteAnimationComponent with HasGameRef {
_ChromeDinoHeadSprite()
: super(
anchor: Anchor(Anchor.center.x + 0.47, Anchor.center.y - 0.29),
angle: _ChromeDinoJoint._halfSweepingAngle,
);
@override
Future<void> onLoad() async {
await super.onLoad();
final image = gameRef.images.fromCache(
Assets.images.dino.animatronic.head.keyName,
);
const amountPerRow = 11;
const amountPerColumn = 9;
final textureSize = Vector2(
image.width / amountPerRow,
image.height / amountPerColumn,
);
size = textureSize / 10;
final data = SpriteAnimationData.sequenced(
amount: (amountPerColumn * amountPerRow) - 1,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: textureSize,
);
animation = SpriteAnimation.fromFrameData(image, data)..currentIndex = 45;
}
}

@ -0,0 +1,4 @@
export 'chrome_dino_chomping_behavior.dart';
export 'chrome_dino_mouth_opening_behavior.dart';
export 'chrome_dino_spitting_behavior.dart';
export 'chrome_dino_swiveling_behavior.dart';

@ -0,0 +1,20 @@
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template chrome_dino_chomping_behavior}
/// Chomps a [Ball] after it has entered the [ChromeDino]'s mouth.
///
/// The chomped [Ball] is hidden in the mouth until it is spit out.
/// {@endtemplate}
class ChromeDinoChompingBehavior extends ContactBehavior<ChromeDino> {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
other.firstChild<SpriteComponent>()!.setOpacity(0);
parent.bloc.onChomp(other);
}
}

@ -0,0 +1,18 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template chrome_dino_mouth_opening_behavior}
/// Allows a [Ball] to enter the [ChromeDino] mouth when it is open.
/// {@endtemplate}
class ChromeDinoMouthOpeningBehavior extends ContactBehavior<ChromeDino> {
@override
void preSolve(Object other, Contact contact, Manifold oldManifold) {
super.preSolve(other, contact, oldManifold);
if (other is! Ball) return;
if (parent.bloc.state.isMouthOpen && parent.firstChild<Ball>() == null) {
contact.setEnabled(false);
}
}
}

@ -0,0 +1,44 @@
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template chrome_dino_spitting_behavior}
/// Spits the [Ball] from the [ChromeDino] the next time the mouth opens.
/// {@endtemplate}
class ChromeDinoSpittingBehavior extends Component
with ContactCallbacks, ParentIsA<ChromeDino> {
bool _waitingForSwivel = true;
void _onNewState(ChromeDinoState state) {
if (state.status == ChromeDinoStatus.chomping) {
if (state.isMouthOpen && !_waitingForSwivel) {
add(
TimerComponent(
period: 0.4,
onTick: _spit,
removeOnFinish: true,
),
);
_waitingForSwivel = true;
}
if (_waitingForSwivel && !state.isMouthOpen) {
_waitingForSwivel = false;
}
}
}
void _spit() {
parent.bloc.state.ball!
..firstChild<SpriteComponent>()!.setOpacity(1)
..body.linearVelocity = Vector2(-50, 0);
parent.bloc.onSpit();
}
@override
Future<void> onLoad() async {
await super.onLoad();
parent.bloc.stream.listen(_onNewState);
}
}

@ -0,0 +1,90 @@
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template chrome_dino_swivel_behavior}
/// Swivels the [ChromeDino] up and down periodically to match its animation
/// sequence.
/// {@endtemplate}
class ChromeDinoSwivelingBehavior extends TimerComponent
with ParentIsA<ChromeDino> {
/// {@macro chrome_dino_swivel_behavior}
ChromeDinoSwivelingBehavior()
: super(
period: 98 / 48,
repeat: true,
);
late final RevoluteJoint _joint;
@override
Future<void> onLoad() async {
final anchor = _ChromeDinoAnchor()
..initialPosition = parent.initialPosition + Vector2(9, -4);
await add(anchor);
final jointDef = _ChromeDinoAnchorRevoluteJointDef(
chromeDino: parent,
anchor: anchor,
);
_joint = RevoluteJoint(jointDef);
parent.world.createJoint(_joint);
}
@override
void update(double dt) {
super.update(dt);
final angle = _joint.jointAngle();
if (angle < _joint.upperLimit &&
angle > _joint.lowerLimit &&
parent.bloc.state.isMouthOpen) {
parent.bloc.onCloseMouth();
} else if ((angle >= _joint.upperLimit || angle <= _joint.lowerLimit) &&
!parent.bloc.state.isMouthOpen) {
parent.bloc.onOpenMouth();
}
}
@override
void onTick() {
super.onTick();
_joint.setMotorSpeed(-_joint.motorSpeed);
}
}
class _ChromeDinoAnchor extends JointAnchor
with ParentIsA<ChromeDinoSwivelingBehavior> {
@override
void onMount() {
super.onMount();
parent.parent.children
.whereType<SpriteAnimationComponent>()
.forEach((sprite) {
sprite.animation!.currentIndex = 45;
sprite.changeParent(this);
});
}
}
class _ChromeDinoAnchorRevoluteJointDef extends RevoluteJointDef {
_ChromeDinoAnchorRevoluteJointDef({
required ChromeDino chromeDino,
required _ChromeDinoAnchor anchor,
}) {
initialize(
chromeDino.body,
anchor.body,
chromeDino.body.position + anchor.body.position,
);
enableLimit = true;
lowerAngle = -ChromeDino.halfSweepingAngle;
upperAngle = ChromeDino.halfSweepingAngle;
enableMotor = true;
maxMotorTorque = chromeDino.body.mass * 255;
motorSpeed = 2;
}
}

@ -0,0 +1,207 @@
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart' hide Timer;
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/chrome_dino/behaviors/behaviors.dart';
import 'package:pinball_flame/pinball_flame.dart';
export 'cubit/chrome_dino_cubit.dart';
/// {@template chrome_dino}
/// Dino that swivels back and forth, opening its mouth to eat a [Ball].
///
/// Upon eating a [Ball], the dino rotates and spits the [Ball] out in the
/// opposite direction.
/// {@endtemplate}
class ChromeDino extends BodyComponent
with InitialPosition, ContactCallbacks, ZIndex {
/// {@macro chrome_dino}
ChromeDino({Iterable<Component>? children})
: bloc = ChromeDinoCubit(),
super(
children: [
_ChromeDinoMouthSprite(),
_ChromeDinoHeadSprite(),
ChromeDinoMouthOpeningBehavior()..applyTo(['mouth_opening']),
ChromeDinoSwivelingBehavior(),
ChromeDinoChompingBehavior()..applyTo(['inside_mouth']),
ChromeDinoSpittingBehavior(),
...?children,
],
renderBody: false,
) {
zIndex = ZIndexes.dino;
}
/// Creates a [ChromeDino] without any children.
///
/// This can be used for testing [ChromeDino]'s behaviors in isolation.
// TODO(alestiago): Refactor injecting bloc once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
@visibleForTesting
ChromeDino.test({
required this.bloc,
});
// TODO(alestiago): Consider refactoring once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
// ignore: public_member_api_docs
final ChromeDinoCubit bloc;
/// Angle to rotate the dino up or down from the starting horizontal position.
static const halfSweepingAngle = 0.1143;
@override
void onRemove() {
bloc.close();
super.onRemove();
}
List<FixtureDef> _createFixtureDefs() {
const mouthAngle = -(halfSweepingAngle + 0.28);
final size = Vector2(5.5, 6);
final topEdge = PolygonShape()
..setAsBox(
size.x / 2,
0.1,
initialPosition + Vector2(-4.2, -1.4),
mouthAngle,
);
final topEdgeFixtureDef = FixtureDef(topEdge, density: 100);
final backEdge = PolygonShape()
..setAsBox(
0.1,
size.y / 2,
initialPosition + Vector2(-1.3, 0.5),
-halfSweepingAngle,
);
final backEdgeFixtureDef = FixtureDef(backEdge, density: 100);
final bottomEdge = PolygonShape()
..setAsBox(
size.x / 2,
0.1,
initialPosition + Vector2(-3.5, 4.7),
mouthAngle,
);
final bottomEdgeFixtureDef = FixtureDef(
bottomEdge,
density: 100,
);
final mouthOpeningEdge = PolygonShape()
..setAsBox(
0.1,
size.y / 2.5,
initialPosition + Vector2(-6.4, 2.7),
-halfSweepingAngle,
);
final mouthOpeningEdgeFixtureDef = FixtureDef(
mouthOpeningEdge,
density: 0.1,
userData: 'mouth_opening',
);
final insideSensor = PolygonShape()
..setAsBox(
0.2,
0.2,
initialPosition + Vector2(-3.5, 1.5),
0,
);
final insideSensorFixtureDef = FixtureDef(
insideSensor,
isSensor: true,
userData: 'inside_mouth',
);
return [
topEdgeFixtureDef,
backEdgeFixtureDef,
bottomEdgeFixtureDef,
mouthOpeningEdgeFixtureDef,
insideSensorFixtureDef,
];
}
@override
Body createBody() {
final bodyDef = BodyDef(
position: initialPosition,
type: BodyType.dynamic,
gravityScale: Vector2.zero(),
);
final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture);
return body;
}
}
class _ChromeDinoMouthSprite extends SpriteAnimationComponent with HasGameRef {
_ChromeDinoMouthSprite()
: super(
anchor: Anchor(Anchor.center.x + 0.47, Anchor.center.y - 0.29),
angle: ChromeDino.halfSweepingAngle,
);
@override
Future<void> onLoad() async {
await super.onLoad();
final image = gameRef.images.fromCache(
Assets.images.dino.animatronic.mouth.keyName,
);
const amountPerRow = 11;
const amountPerColumn = 9;
final textureSize = Vector2(
image.width / amountPerRow,
image.height / amountPerColumn,
);
size = textureSize / 10;
final data = SpriteAnimationData.sequenced(
amount: (amountPerColumn * amountPerRow) - 1,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: textureSize,
);
animation = SpriteAnimation.fromFrameData(image, data);
}
}
class _ChromeDinoHeadSprite extends SpriteAnimationComponent with HasGameRef {
_ChromeDinoHeadSprite()
: super(
anchor: Anchor(Anchor.center.x + 0.47, Anchor.center.y - 0.29),
angle: ChromeDino.halfSweepingAngle,
);
@override
Future<void> onLoad() async {
await super.onLoad();
final image = gameRef.images.fromCache(
Assets.images.dino.animatronic.head.keyName,
);
const amountPerRow = 11;
const amountPerColumn = 9;
final textureSize = Vector2(
image.width / amountPerRow,
image.height / amountPerColumn,
);
size = textureSize / 10;
final data = SpriteAnimationData.sequenced(
amount: (amountPerColumn * amountPerRow) - 1,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: textureSize,
);
animation = SpriteAnimation.fromFrameData(image, data);
}
}

@ -0,0 +1,27 @@
// ignore_for_file: public_member_api_docs
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:pinball_components/pinball_components.dart';
part 'chrome_dino_state.dart';
class ChromeDinoCubit extends Cubit<ChromeDinoState> {
ChromeDinoCubit() : super(const ChromeDinoState.inital());
void onOpenMouth() {
emit(state.copyWith(isMouthOpen: true));
}
void onCloseMouth() {
emit(state.copyWith(isMouthOpen: false));
}
void onChomp(Ball ball) {
emit(state.copyWith(status: ChromeDinoStatus.chomping, ball: ball));
}
void onSpit() {
emit(state.copyWith(status: ChromeDinoStatus.idle));
}
}

@ -0,0 +1,46 @@
// ignore_for_file: public_member_api_docs
part of 'chrome_dino_cubit.dart';
enum ChromeDinoStatus {
idle,
chomping,
}
class ChromeDinoState extends Equatable {
const ChromeDinoState({
required this.status,
required this.isMouthOpen,
this.ball,
});
const ChromeDinoState.inital()
: this(
status: ChromeDinoStatus.idle,
isMouthOpen: false,
);
final ChromeDinoStatus status;
final bool isMouthOpen;
final Ball? ball;
ChromeDinoState copyWith({
ChromeDinoStatus? status,
bool? isMouthOpen,
Ball? ball,
}) {
final state = ChromeDinoState(
status: status ?? this.status,
isMouthOpen: isMouthOpen ?? this.isMouthOpen,
ball: ball ?? this.ball,
);
return state;
}
@override
List<Object?> get props => [
status,
isMouthOpen,
ball,
];
}

@ -8,7 +8,7 @@ export 'board_dimensions.dart';
export 'board_side.dart'; export 'board_side.dart';
export 'boundaries.dart'; export 'boundaries.dart';
export 'camera_zoom.dart'; export 'camera_zoom.dart';
export 'chrome_dino.dart'; export 'chrome_dino/chrome_dino.dart';
export 'dash_animatronic.dart'; export 'dash_animatronic.dart';
export 'dash_nest_bumper/dash_nest_bumper.dart'; export 'dash_nest_bumper/dash_nest_bumper.dart';
export 'dino_walls.dart'; export 'dino_walls.dart';
@ -17,15 +17,14 @@ export 'flipper.dart';
export 'google_letter/google_letter.dart'; export 'google_letter/google_letter.dart';
export 'initial_position.dart'; export 'initial_position.dart';
export 'joint_anchor.dart'; export 'joint_anchor.dart';
export 'kicker.dart'; export 'kicker/kicker.dart';
export 'launch_ramp.dart'; export 'launch_ramp.dart';
export 'layer.dart'; export 'layer.dart';
export 'layer_sensor.dart'; export 'layer_sensor.dart';
export 'multiplier/multiplier.dart'; export 'multiplier/multiplier.dart';
export 'plunger.dart'; export 'plunger.dart';
export 'render_priority.dart';
export 'rocket.dart'; export 'rocket.dart';
export 'score_text.dart'; export 'score_component.dart';
export 'shapes/shapes.dart'; export 'shapes/shapes.dart';
export 'signpost.dart'; export 'signpost.dart';
export 'slingshot.dart'; export 'slingshot.dart';
@ -34,3 +33,4 @@ export 'spaceship_ramp.dart';
export 'sparky_animatronic.dart'; export 'sparky_animatronic.dart';
export 'sparky_bumper/sparky_bumper.dart'; export 'sparky_bumper/sparky_bumper.dart';
export 'sparky_computer.dart'; export 'sparky_computer.dart';
export 'z_indexes.dart';

@ -4,6 +4,7 @@ import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/bumping_behavior.dart';
import 'package:pinball_components/src/components/dash_nest_bumper/behaviors/behaviors.dart'; import 'package:pinball_components/src/components/dash_nest_bumper/behaviors/behaviors.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
@ -47,8 +48,11 @@ class DashNestBumper extends BodyComponent with InitialPosition {
activeAssetPath: Assets.images.dash.bumper.main.active.keyName, activeAssetPath: Assets.images.dash.bumper.main.active.keyName,
inactiveAssetPath: Assets.images.dash.bumper.main.inactive.keyName, inactiveAssetPath: Assets.images.dash.bumper.main.inactive.keyName,
spritePosition: Vector2(0, -0.3), spritePosition: Vector2(0, -0.3),
children: children,
bloc: DashNestBumperCubit(), bloc: DashNestBumperCubit(),
children: [
...?children,
BumpingBehavior(strength: 20),
],
); );
/// {@macro dash_nest_bumper} /// {@macro dash_nest_bumper}
@ -60,8 +64,11 @@ class DashNestBumper extends BodyComponent with InitialPosition {
activeAssetPath: Assets.images.dash.bumper.a.active.keyName, activeAssetPath: Assets.images.dash.bumper.a.active.keyName,
inactiveAssetPath: Assets.images.dash.bumper.a.inactive.keyName, inactiveAssetPath: Assets.images.dash.bumper.a.inactive.keyName,
spritePosition: Vector2(0.35, -1.2), spritePosition: Vector2(0.35, -1.2),
children: children,
bloc: DashNestBumperCubit(), bloc: DashNestBumperCubit(),
children: [
...?children,
BumpingBehavior(strength: 20),
],
); );
/// {@macro dash_nest_bumper} /// {@macro dash_nest_bumper}
@ -73,8 +80,11 @@ class DashNestBumper extends BodyComponent with InitialPosition {
activeAssetPath: Assets.images.dash.bumper.b.active.keyName, activeAssetPath: Assets.images.dash.bumper.b.active.keyName,
inactiveAssetPath: Assets.images.dash.bumper.b.inactive.keyName, inactiveAssetPath: Assets.images.dash.bumper.b.inactive.keyName,
spritePosition: Vector2(0.35, -1.2), spritePosition: Vector2(0.35, -1.2),
children: children,
bloc: DashNestBumperCubit(), bloc: DashNestBumperCubit(),
children: [
...?children,
BumpingBehavior(strength: 20),
],
); );
/// Creates an [DashNestBumper] without any children. /// Creates an [DashNestBumper] without any children.
@ -108,13 +118,11 @@ class DashNestBumper extends BodyComponent with InitialPosition {
majorRadius: _majorRadius, majorRadius: _majorRadius,
minorRadius: _minorRadius, minorRadius: _minorRadius,
)..rotate(math.pi / 1.9); )..rotate(math.pi / 1.9);
final fixtureDef = FixtureDef(shape, restitution: 4);
final bodyDef = BodyDef( final bodyDef = BodyDef(
position: initialPosition, position: initialPosition,
userData: this,
); );
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixtureFromShape(shape);
} }
} }

@ -6,14 +6,14 @@ import 'package:pinball_components/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets; import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
/// {@template dinowalls} /// {@template dino_walls}
/// A [Blueprint] which creates walls for the [ChromeDino]. /// Walls near the [ChromeDino].
/// {@endtemplate} /// {@endtemplate}
class DinoWalls extends Blueprint { class DinoWalls extends Component {
/// {@macro dinowalls} /// {@macro dino_walls}
DinoWalls() DinoWalls()
: super( : super(
components: [ children: [
_DinoTopWall(), _DinoTopWall(),
_DinoBottomWall(), _DinoBottomWall(),
], ],
@ -23,14 +23,15 @@ class DinoWalls extends Blueprint {
/// {@template dino_top_wall} /// {@template dino_top_wall}
/// Wall segment located above [ChromeDino]. /// Wall segment located above [ChromeDino].
/// {@endtemplate} /// {@endtemplate}
class _DinoTopWall extends BodyComponent with InitialPosition { class _DinoTopWall extends BodyComponent with InitialPosition, ZIndex {
///{@macro dino_top_wall} ///{@macro dino_top_wall}
_DinoTopWall() _DinoTopWall()
: super( : super(
priority: RenderPriority.dinoTopWall,
children: [_DinoTopWallSpriteComponent()], children: [_DinoTopWallSpriteComponent()],
renderBody: false, renderBody: false,
); ) {
zIndex = ZIndexes.dinoTopWall;
}
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final topStraightShape = EdgeShape() final topStraightShape = EdgeShape()
@ -86,13 +87,7 @@ class _DinoTopWall extends BodyComponent with InitialPosition {
); );
final body = world.createBody(bodyDef); final body = world.createBody(bodyDef);
_createFixtureDefs().forEach( _createFixtureDefs().forEach(body.createFixture);
(fixture) => body.createFixture(
fixture
..restitution = 0.1
..friction = 0,
),
);
return body; return body;
} }
@ -116,14 +111,15 @@ class _DinoTopWallSpriteComponent extends SpriteComponent with HasGameRef {
/// {@template dino_bottom_wall} /// {@template dino_bottom_wall}
/// Wall segment located below [ChromeDino]. /// Wall segment located below [ChromeDino].
/// {@endtemplate} /// {@endtemplate}
class _DinoBottomWall extends BodyComponent with InitialPosition { class _DinoBottomWall extends BodyComponent with InitialPosition, ZIndex {
///{@macro dino_top_wall} ///{@macro dino_top_wall}
_DinoBottomWall() _DinoBottomWall()
: super( : super(
priority: RenderPriority.dinoBottomWall,
children: [_DinoBottomWallSpriteComponent()], children: [_DinoBottomWallSpriteComponent()],
renderBody: false, renderBody: false,
); ) {
zIndex = ZIndexes.dinoBottomWall;
}
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final topStraightShape = EdgeShape() final topStraightShape = EdgeShape()

@ -26,10 +26,8 @@ class FireEffect extends ParticleSystemComponent {
required this.burstPower, required this.burstPower,
required this.direction, required this.direction,
Vector2? position, Vector2? position,
int? priority,
}) : super( }) : super(
position: position, position: position,
priority: priority,
); );
/// A [double] value that will define how "strong" the burst of particles /// A [double] value that will define how "strong" the burst of particles

@ -24,7 +24,7 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
/// The speed required to move the [Flipper] to its highest position. /// The speed required to move the [Flipper] to its highest position.
/// ///
/// The higher the value, the faster the [Flipper] will move. /// The higher the value, the faster the [Flipper] will move.
static const double _speed = 60; static const double _speed = 90;
/// Whether the [Flipper] is on the left or right side of the board. /// Whether the [Flipper] is on the left or right side of the board.
/// ///

@ -5,13 +5,13 @@ import 'package:bloc/bloc.dart';
part 'google_letter_state.dart'; part 'google_letter_state.dart';
class GoogleLetterCubit extends Cubit<GoogleLetterState> { class GoogleLetterCubit extends Cubit<GoogleLetterState> {
GoogleLetterCubit() : super(GoogleLetterState.inactive); GoogleLetterCubit() : super(GoogleLetterState.dimmed);
void onBallContacted() { void onBallContacted() {
emit(GoogleLetterState.active); emit(GoogleLetterState.lit);
} }
void onReset() { void onReset() {
emit(GoogleLetterState.inactive); emit(GoogleLetterState.dimmed);
} }
} }

@ -1,10 +1,8 @@
// ignore_for_file: public_member_api_docs
part of 'google_letter_cubit.dart'; part of 'google_letter_cubit.dart';
/// Indicates the [GoogleLetterCubit]'s current state.
enum GoogleLetterState { enum GoogleLetterState {
/// A lit up letter. lit,
active, dimmed,
/// A dimmed letter.
inactive,
} }

@ -7,6 +7,33 @@ import 'package:pinball_flame/pinball_flame.dart';
export 'cubit/google_letter_cubit.dart'; export 'cubit/google_letter_cubit.dart';
final _spritePaths = <Map<GoogleLetterState, String>>[
{
GoogleLetterState.lit: Assets.images.googleWord.letter1.lit.keyName,
GoogleLetterState.dimmed: Assets.images.googleWord.letter1.dimmed.keyName,
},
{
GoogleLetterState.lit: Assets.images.googleWord.letter2.lit.keyName,
GoogleLetterState.dimmed: Assets.images.googleWord.letter2.dimmed.keyName,
},
{
GoogleLetterState.lit: Assets.images.googleWord.letter3.lit.keyName,
GoogleLetterState.dimmed: Assets.images.googleWord.letter3.dimmed.keyName,
},
{
GoogleLetterState.lit: Assets.images.googleWord.letter4.lit.keyName,
GoogleLetterState.dimmed: Assets.images.googleWord.letter4.dimmed.keyName,
},
{
GoogleLetterState.lit: Assets.images.googleWord.letter5.lit.keyName,
GoogleLetterState.dimmed: Assets.images.googleWord.letter5.dimmed.keyName,
},
{
GoogleLetterState.lit: Assets.images.googleWord.letter6.lit.keyName,
GoogleLetterState.dimmed: Assets.images.googleWord.letter6.dimmed.keyName,
},
];
/// {@template google_letter} /// {@template google_letter}
/// Circular sensor that represents a letter in "GOOGLE" for a given index. /// Circular sensor that represents a letter in "GOOGLE" for a given index.
/// {@endtemplate} /// {@endtemplate}
@ -15,13 +42,27 @@ class GoogleLetter extends BodyComponent with InitialPosition {
GoogleLetter( GoogleLetter(
int index, { int index, {
Iterable<Component>? children, Iterable<Component>? children,
}) : bloc = GoogleLetterCubit(), }) : this._(
super( index,
bloc: GoogleLetterCubit(),
children: children,
);
GoogleLetter._(
int index, {
required this.bloc,
Iterable<Component>? children,
}) : super(
children: [ children: [
_GoogleLetterSpriteGroupComponent(
litAssetPath: _spritePaths[index][GoogleLetterState.lit]!,
dimmedAssetPath: _spritePaths[index][GoogleLetterState.dimmed]!,
current: bloc.state,
),
GoogleLetterBallContactBehavior(), GoogleLetterBallContactBehavior(),
_GoogleLetterSprite(_GoogleLetterSprite.spritePaths[index]),
...?children, ...?children,
], ],
renderBody: false,
); );
/// Creates a [GoogleLetter] without any children. /// Creates a [GoogleLetter] without any children.
@ -61,33 +102,37 @@ class GoogleLetter extends BodyComponent with InitialPosition {
} }
} }
class _GoogleLetterSprite extends SpriteComponent class _GoogleLetterSpriteGroupComponent
extends SpriteGroupComponent<GoogleLetterState>
with HasGameRef, ParentIsA<GoogleLetter> { with HasGameRef, ParentIsA<GoogleLetter> {
_GoogleLetterSprite(String path) _GoogleLetterSpriteGroupComponent({
: _path = path, required String litAssetPath,
super(anchor: Anchor.center); required String dimmedAssetPath,
required GoogleLetterState current,
static final spritePaths = [ }) : _litAssetPath = litAssetPath,
Assets.images.googleWord.letter1.keyName, _dimmedAssetPath = dimmedAssetPath,
Assets.images.googleWord.letter2.keyName, super(
Assets.images.googleWord.letter3.keyName, anchor: Anchor.center,
Assets.images.googleWord.letter4.keyName, current: current,
Assets.images.googleWord.letter5.keyName, );
Assets.images.googleWord.letter6.keyName,
];
final String _path; final String _litAssetPath;
final String _dimmedAssetPath;
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
// TODO(alisonryan2002): Make SpriteGroupComponent. parent.bloc.stream.listen((state) => current = state);
// parent.bloc.stream.listen();
// TODO(alestiago): Used cached assets. final sprites = {
final sprite = await gameRef.loadSprite(_path); GoogleLetterState.lit: Sprite(
this.sprite = sprite; gameRef.images.fromCache(_litAssetPath),
// TODO(alestiago): Size correctly once the assets are provided. ),
size = sprite.originalSize / 5; GoogleLetterState.dimmed: Sprite(
gameRef.images.fromCache(_dimmedAssetPath),
),
};
this.sprites = sprites;
size = sprites[current]!.originalSize / 10;
} }
} }

@ -0,0 +1,2 @@
export 'kicker_ball_contact_behavior.dart';
export 'kicker_blinking_behavior.dart';

@ -0,0 +1,14 @@
// 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 KickerBallContactBehavior extends ContactBehavior<Kicker> {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
parent.bloc.onBallContacted();
}
}

@ -0,0 +1,37 @@
import 'package:flame/components.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template kicker_blinking_behavior}
/// Makes a [Kicker] blink back to [KickerState.lit] when [KickerState.dimmed].
/// {@endtemplate}
class KickerBlinkingBehavior extends TimerComponent with ParentIsA<Kicker> {
/// {@macro kicker_blinking_behavior}
KickerBlinkingBehavior() : super(period: 0.05);
void _onNewState(KickerState state) {
switch (state) {
case KickerState.lit:
break;
case KickerState.dimmed:
timer
..reset()
..start();
break;
}
}
@override
Future<void> onLoad() async {
await super.onLoad();
timer.stop();
parent.bloc.stream.listen(_onNewState);
}
@override
void onTick() {
super.onTick();
timer.stop();
parent.bloc.onBlinked();
}
}

@ -0,0 +1,17 @@
// ignore_for_file: public_member_api_docs
import 'package:bloc/bloc.dart';
part 'kicker_state.dart';
class KickerCubit extends Cubit<KickerState> {
KickerCubit() : super(KickerState.lit);
void onBallContacted() {
emit(KickerState.dimmed);
}
void onBlinked() {
emit(KickerState.lit);
}
}

@ -0,0 +1,8 @@
// ignore_for_file: public_member_api_docs
part of 'kicker_cubit.dart';
enum KickerState {
lit,
dimmed,
}

@ -2,9 +2,15 @@ import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:geometry/geometry.dart' as geometry show centroid; import 'package:geometry/geometry.dart' as geometry show centroid;
import 'package:pinball_components/gen/assets.gen.dart'; import 'package:pinball_components/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets; import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_components/src/components/bumping_behavior.dart';
import 'package:pinball_components/src/components/kicker/behaviors/behaviors.dart';
import 'package:pinball_flame/pinball_flame.dart';
export 'cubit/kicker_cubit.dart';
/// {@template kicker} /// {@template kicker}
/// Triangular [BodyType.static] body that propels the [Ball] towards the /// Triangular [BodyType.static] body that propels the [Ball] towards the
@ -17,42 +23,69 @@ class Kicker extends BodyComponent with InitialPosition {
Kicker({ Kicker({
required BoardSide side, required BoardSide side,
Iterable<Component>? children, Iterable<Component>? children,
}) : this._(
side: side,
bloc: KickerCubit(),
children: children,
);
Kicker._({
required BoardSide side,
required this.bloc,
Iterable<Component>? children,
}) : _side = side, }) : _side = side,
super( super(
children: [ children: [
_KickerSpriteComponent(side: side), BumpingBehavior(strength: 20)..applyTo(['bouncy_edge']),
KickerBallContactBehavior()..applyTo(['bouncy_edge']),
KickerBlinkingBehavior(),
_KickerSpriteGroupComponent(
side: side,
state: bloc.state,
),
...?children, ...?children,
], ],
renderBody: false, renderBody: false,
); );
/// The size of the [Kicker] body. /// Creates a [Kicker] without any children.
static final Vector2 size = Vector2(4.4, 15); ///
/// This can be used for testing [Kicker]'s behaviors in isolation.
// TODO(alestiago): Refactor injecting bloc once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
@visibleForTesting
Kicker.test({
required this.bloc,
required BoardSide side,
}) : _side = side;
// TODO(alestiago): Consider refactoring once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
// ignore: public_member_api_docs
final KickerCubit bloc;
@override
void onRemove() {
bloc.close();
super.onRemove();
}
/// Whether the [Kicker] is on the left or right side of the board. /// Whether the [Kicker] is on the left or right side of the board.
///
/// A [Kicker] with [BoardSide.left] propels the [Ball] to the right,
/// whereas a [Kicker] with [BoardSide.right] propels the [Ball] to the
/// left.
final BoardSide _side; final BoardSide _side;
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final fixturesDefs = <FixtureDef>[];
final direction = _side.direction; final direction = _side.direction;
const quarterPi = math.pi / 4; const quarterPi = math.pi / 4;
final size = Vector2(4.4, 15);
final upperCircle = CircleShape()..radius = 1.6; final upperCircle = CircleShape()..radius = 1.6;
upperCircle.position.setValues(0, upperCircle.radius / 2); upperCircle.position.setValues(0, upperCircle.radius / 2);
final upperCircleFixtureDef = FixtureDef(upperCircle);
fixturesDefs.add(upperCircleFixtureDef);
final lowerCircle = CircleShape()..radius = 1.6; final lowerCircle = CircleShape()..radius = 1.6;
lowerCircle.position.setValues( lowerCircle.position.setValues(
size.x * -direction, size.x * -direction,
size.y + 0.8, size.y + 0.8,
); );
final lowerCircleFixtureDef = FixtureDef(lowerCircle);
fixturesDefs.add(lowerCircleFixtureDef);
final wallFacingEdge = EdgeShape() final wallFacingEdge = EdgeShape()
..set( ..set(
@ -63,8 +96,6 @@ class Kicker extends BodyComponent with InitialPosition {
), ),
Vector2(2.5 * direction, size.y - 2), Vector2(2.5 * direction, size.y - 2),
); );
final wallFacingLineFixtureDef = FixtureDef(wallFacingEdge);
fixturesDefs.add(wallFacingLineFixtureDef);
final bottomEdge = EdgeShape() final bottomEdge = EdgeShape()
..set( ..set(
@ -75,8 +106,6 @@ class Kicker extends BodyComponent with InitialPosition {
lowerCircle.radius * math.sin(quarterPi), lowerCircle.radius * math.sin(quarterPi),
), ),
); );
final bottomLineFixtureDef = FixtureDef(bottomEdge);
fixturesDefs.add(bottomLineFixtureDef);
final bouncyEdge = EdgeShape() final bouncyEdge = EdgeShape()
..set( ..set(
@ -92,12 +121,13 @@ class Kicker extends BodyComponent with InitialPosition {
), ),
); );
final bouncyFixtureDef = FixtureDef( final fixturesDefs = [
bouncyEdge, FixtureDef(upperCircle),
// TODO(alestiago): Play with restitution value once game is bundled. FixtureDef(lowerCircle),
restitution: 10, FixtureDef(wallFacingEdge),
); FixtureDef(bottomEdge),
fixturesDefs.add(bouncyFixtureDef); FixtureDef(bouncyEdge, userData: 'bouncy_edge'),
];
// TODO(alestiago): Evaluate if there is value on centering the fixtures. // TODO(alestiago): Evaluate if there is value on centering the fixtures.
final centroid = geometry.centroid( final centroid = geometry.centroid(
@ -130,25 +160,46 @@ class Kicker extends BodyComponent with InitialPosition {
} }
} }
class _KickerSpriteComponent extends SpriteComponent with HasGameRef { class _KickerSpriteGroupComponent extends SpriteGroupComponent<KickerState>
_KickerSpriteComponent({required BoardSide side}) : _side = side; with HasGameRef, ParentIsA<Kicker> {
_KickerSpriteGroupComponent({
required BoardSide side,
required KickerState state,
}) : _side = side,
super(
anchor: Anchor.center,
position: Vector2(0.7 * -side.direction, -2.2),
current: state,
);
final BoardSide _side; final BoardSide _side;
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
// TODO(alestiago): Consider refactoring once the following is merged:
// TODO(alestiago): Used cached asset. // https://github.com/flame-engine/flame/pull/1538
final sprite = await gameRef.loadSprite( // ignore: public_member_api_docs
parent.bloc.stream.listen((state) => current = state);
final sprites = {
KickerState.lit: Sprite(
gameRef.images.fromCache(
(_side.isLeft) (_side.isLeft)
? Assets.images.kicker.left.keyName ? Assets.images.kicker.left.lit.keyName
: Assets.images.kicker.right.keyName, : Assets.images.kicker.right.lit.keyName,
); ),
this.sprite = sprite; ),
size = sprite.originalSize / 10; KickerState.dimmed: Sprite(
anchor = Anchor.center; gameRef.images.fromCache(
position = Vector2(0.7 * -_side.direction, -2.2); (_side.isLeft)
? Assets.images.kicker.left.dimmed.keyName
: Assets.images.kicker.right.dimmed.keyName,
),
),
};
this.sprites = sprites;
size = sprites[current]!.originalSize / 10;
} }
} }

@ -8,14 +8,13 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
/// {@template launch_ramp} /// {@template launch_ramp}
/// A [Blueprint] which creates the [_LaunchRampBase] and /// Ramp where the ball is launched from.
/// [_LaunchRampForegroundRailing].
/// {@endtemplate} /// {@endtemplate}
class LaunchRamp extends Blueprint { class LaunchRamp extends Component {
/// {@macro launch_ramp} /// {@macro launch_ramp}
LaunchRamp() LaunchRamp()
: super( : super(
components: [ children: [
_LaunchRampBase(), _LaunchRampBase(),
_LaunchRampForegroundRailing(), _LaunchRampForegroundRailing(),
_LaunchRampExit()..initialPosition = Vector2(0.6, -34), _LaunchRampExit()..initialPosition = Vector2(0.6, -34),
@ -24,20 +23,16 @@ class LaunchRamp extends Blueprint {
); );
} }
/// {@template launch_ramp_base} class _LaunchRampBase extends BodyComponent with Layered, ZIndex {
/// Ramp the [Ball] is launched from at the beginning of each ball life.
/// {@endtemplate}
class _LaunchRampBase extends BodyComponent with Layered {
/// {@macro launch_ramp_base}
_LaunchRampBase() _LaunchRampBase()
: super( : super(
priority: RenderPriority.launchRamp,
renderBody: false, renderBody: false,
children: [ children: [
_LaunchRampBackgroundRailingSpriteComponent(), _LaunchRampBackgroundRailingSpriteComponent(),
_LaunchRampBaseSpriteComponent(), _LaunchRampBaseSpriteComponent(),
], ],
) { ) {
zIndex = ZIndexes.launchRamp;
layer = Layer.launcher; layer = Layer.launcher;
} }
@ -140,18 +135,14 @@ class _LaunchRampBackgroundRailingSpriteComponent extends SpriteComponent
} }
} }
/// {@template launch_ramp_foreground_railing} class _LaunchRampForegroundRailing extends BodyComponent with ZIndex {
/// Foreground railing for the [_LaunchRampBase] to render in front of the
/// [Ball].
/// {@endtemplate}
class _LaunchRampForegroundRailing extends BodyComponent {
/// {@macro launch_ramp_foreground_railing}
_LaunchRampForegroundRailing() _LaunchRampForegroundRailing()
: super( : super(
priority: RenderPriority.launchRampForegroundRailing,
children: [_LaunchRampForegroundRailingSpriteComponent()], children: [_LaunchRampForegroundRailingSpriteComponent()],
renderBody: false, renderBody: false,
); ) {
zIndex = ZIndexes.launchRampForegroundRailing;
}
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[]; final fixturesDef = <FixtureDef>[];
@ -239,8 +230,8 @@ class _LaunchRampExit extends LayerSensor {
insideLayer: Layer.launcher, insideLayer: Layer.launcher,
outsideLayer: Layer.board, outsideLayer: Layer.board,
orientation: LayerEntranceOrientation.down, orientation: LayerEntranceOrientation.down,
insidePriority: RenderPriority.ballOnLaunchRamp, insideZIndex: ZIndexes.ballOnLaunchRamp,
outsidePriority: RenderPriority.ballOnBoard, outsideZIndex: ZIndexes.ballOnBoard,
) { ) {
layer = Layer.launcher; layer = Layer.launcher;
} }

@ -8,9 +8,6 @@ import 'package:flutter/material.dart';
/// [BodyComponent]s with compatible [Layer]s can collide with each other, /// [BodyComponent]s with compatible [Layer]s can collide with each other,
/// ignoring others. This compatibility depends on bit masking operation /// ignoring others. This compatibility depends on bit masking operation
/// between layers. For more information read: https://en.wikipedia.org/wiki/Mask_(computing). /// between layers. For more information read: https://en.wikipedia.org/wiki/Mask_(computing).
///
/// A parent [Layered] have priority against its children's layer. Them won't be
/// changed but will be ignored.
/// {@endtemplate} /// {@endtemplate}
mixin Layered<T extends Forge2DGame> on BodyComponent<T> { mixin Layered<T extends Forge2DGame> on BodyComponent<T> {
Layer _layer = Layer.all; Layer _layer = Layer.all;

@ -18,7 +18,7 @@ enum LayerEntranceOrientation {
/// [BodyComponent] located at the entrance and exit of a [Layer]. /// [BodyComponent] located at the entrance and exit of a [Layer].
/// ///
/// By default the base [layer] is set to [Layer.board] and the /// By default the base [layer] is set to [Layer.board] and the
/// [outsidePriority] is set to the lowest possible [Layer]. /// [_outsideZIndex] is set to [ZIndexes.ballOnBoard].
/// {@endtemplate} /// {@endtemplate}
abstract class LayerSensor extends BodyComponent abstract class LayerSensor extends BodyComponent
with InitialPosition, Layered, ContactCallbacks { with InitialPosition, Layered, ContactCallbacks {
@ -26,32 +26,21 @@ abstract class LayerSensor extends BodyComponent
LayerSensor({ LayerSensor({
required Layer insideLayer, required Layer insideLayer,
Layer? outsideLayer, Layer? outsideLayer,
required int insidePriority, required int insideZIndex,
int? outsidePriority, int? outsideZIndex,
required this.orientation, required this.orientation,
}) : _insideLayer = insideLayer, }) : _insideLayer = insideLayer,
_outsideLayer = outsideLayer ?? Layer.board, _outsideLayer = outsideLayer ?? Layer.board,
_insidePriority = insidePriority, _insideZIndex = insideZIndex,
_outsidePriority = outsidePriority ?? RenderPriority.ballOnBoard, _outsideZIndex = outsideZIndex ?? ZIndexes.ballOnBoard,
super(renderBody: false) { super(renderBody: false) {
layer = Layer.opening; layer = Layer.opening;
} }
final Layer _insideLayer; final Layer _insideLayer;
final Layer _outsideLayer; final Layer _outsideLayer;
final int _insidePriority; final int _insideZIndex;
final int _outsidePriority; final int _outsideZIndex;
/// Mask bits value for collisions on [Layer].
Layer get insideLayer => _insideLayer;
/// Mask bits value for collisions outside of [Layer].
Layer get outsideLayer => _outsideLayer;
/// Render priority for the [Ball] on [Layer].
int get insidePriority => _insidePriority;
/// Render priority for the [Ball] outside of [Layer].
int get outsidePriority => _outsidePriority;
/// The [Shape] of the [LayerSensor]. /// The [Shape] of the [LayerSensor].
Shape get shape; Shape get shape;
@ -80,7 +69,7 @@ abstract class LayerSensor extends BodyComponent
super.beginContact(other, contact); super.beginContact(other, contact);
if (other is! Ball) return; if (other is! Ball) return;
if (other.layer != insideLayer) { if (other.layer != _insideLayer) {
final isBallEnteringOpening = final isBallEnteringOpening =
(orientation == LayerEntranceOrientation.down && (orientation == LayerEntranceOrientation.down &&
other.body.linearVelocity.y < 0) || other.body.linearVelocity.y < 0) ||
@ -89,15 +78,13 @@ abstract class LayerSensor extends BodyComponent
if (isBallEnteringOpening) { if (isBallEnteringOpening) {
other other
..layer = insideLayer ..layer = _insideLayer
..priority = insidePriority ..zIndex = _insideZIndex;
..reorderChildren();
} }
} else { } else {
other other
..layer = outsideLayer ..layer = _outsideLayer
..priority = outsidePriority ..zIndex = _outsideZIndex;
..reorderChildren();
} }
} }
} }

@ -1,6 +1,7 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template plunger} /// {@template plunger}
/// [Plunger] serves as a spring, that shoots the ball on the right side of the /// [Plunger] serves as a spring, that shoots the ball on the right side of the
@ -8,16 +9,12 @@ 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, Layered { class Plunger extends BodyComponent with InitialPosition, Layered, ZIndex {
/// {@macro plunger} /// {@macro plunger}
Plunger({ Plunger({
required this.compressionDistance, required this.compressionDistance,
// TODO(ruimiguel): set to priority +1 over LaunchRamp once all priorities }) : super(renderBody: false) {
// are fixed. zIndex = ZIndexes.plunger;
}) : super(
priority: RenderPriority.plunger,
renderBody: false,
) {
layer = Layer.launcher; layer = Layer.launcher;
} }
@ -91,7 +88,7 @@ class Plunger extends BodyComponent with InitialPosition, Layered {
/// from its original [initialPosition]. /// from its original [initialPosition].
void release() { void release() {
_pullingDownTime = 0; _pullingDownTime = 0;
final velocity = (initialPosition.y - body.position.y) * 5; final velocity = (initialPosition.y - body.position.y) * 11;
body.linearVelocity = Vector2(0, velocity); body.linearVelocity = Vector2(0, velocity);
_spriteComponent.release(); _spriteComponent.release();
} }

@ -1,117 +0,0 @@
// ignore_for_file: public_member_api_docs
import 'package:pinball_components/pinball_components.dart';
/// {@template render_priority}
/// Priorities for the component rendering order in the pinball game.
/// {@endtemplate}
// TODO(allisonryan0002): find alternative to section comments.
abstract class RenderPriority {
static const _base = 0;
static const _above = 1;
static const _below = -1;
// Ball
/// Render priority for the [Ball] while it's on the board.
static const int ballOnBoard = _base;
/// Render priority for the [Ball] while it's on the [SpaceshipRamp].
static const int ballOnSpaceshipRamp =
_above + spaceshipRampBackgroundRailing;
/// Render priority for the [Ball] while it's on the [AndroidSpaceship].
static const int ballOnSpaceship = _above + spaceshipSaucer;
/// Render priority for the [Ball] while it's on the [SpaceshipRail].
static const int ballOnSpaceshipRail = _above + spaceshipRail;
/// Render priority for the [Ball] while it's on the [LaunchRamp].
static const int ballOnLaunchRamp = launchRamp;
// Background
// TODO(allisonryan0002): fix this magic priority. Could bump all priorities
// so there are no negatives.
static const int boardBackground = 3 * _below + _base;
// Boundaries
static const int bottomBoundary = _above + dinoBottomWall;
static const int outerBoundary = _above + boardBackground;
static const int outerBottomBoundary = _above + rocket;
// Bottom Group
static const int bottomGroup = _above + ballOnBoard;
// Launcher
static const int launchRamp = _above + outerBoundary;
static const int launchRampForegroundRailing = ballOnBoard;
static const int plunger = _above + launchRamp;
static const int rocket = _below + bottomBoundary;
// Dino Land
static const int dinoTopWall = _above + ballOnBoard;
static const int dino = _above + dinoTopWall;
static const int dinoBottomWall = _above + dino;
static const int slingshot = _above + dinoBottomWall;
// Flutter Forest
static const int flutterForest = _above + launchRampForegroundRailing;
// Sparky Fire Zone
static const int computerBase = _below + ballOnBoard;
static const int computerTop = _above + ballOnBoard;
static const int sparkyAnimatronic = _above + spaceshipRampForegroundRailing;
static const int sparkyBumper = _above + ballOnBoard;
static const int turboChargeFlame = _above + ballOnBoard;
// Android Acres
static const int spaceshipRail = _above + bottomGroup;
static const int spaceshipRailExit = _above + ballOnSpaceshipRail;
static const int spaceshipSaucer = _above + ballOnSpaceshipRail;
static const int spaceshipLightBeam = _below + spaceshipSaucer;
static const int androidHead = _above + spaceshipSaucer;
static const int spaceshipRamp = _above + ballOnBoard;
static const int spaceshipRampBackgroundRailing = _above + spaceshipRamp;
static const int spaceshipRampArrow = _above + spaceshipRamp;
static const int spaceshipRampForegroundRailing =
_above + ballOnSpaceshipRamp;
static const int spaceshipRampBoardOpening = _below + ballOnBoard;
static const int androidBumper = _above + ballOnBoard;
// Score Text
static const int scoreText = _above + spaceshipRampForegroundRailing;
// Debug information
static const int debugInfo = _above + scoreText;
}

@ -1,17 +1,16 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:pinball_components/gen/assets.gen.dart'; import 'package:pinball_components/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets; import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_flame/pinball_flame.dart';
/// {@template rocket_sprite_component} /// {@template rocket_sprite_component}
/// A [SpriteComponent] for the rocket over [Plunger]. /// A [SpriteComponent] for the rocket over [Plunger].
/// {@endtemplate} /// {@endtemplate}
class RocketSpriteComponent extends SpriteComponent with HasGameRef { class RocketSpriteComponent extends SpriteComponent with HasGameRef, ZIndex {
/// {@macro rocket_sprite_component} /// {@macro rocket_sprite_component}
RocketSpriteComponent() RocketSpriteComponent() : super(anchor: Anchor.center) {
: super( zIndex = ZIndexes.rocket;
priority: RenderPriority.rocket, }
anchor: Anchor.center,
);
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {

@ -0,0 +1,92 @@
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
enum Points {
fiveThousand,
twentyThousand,
twoHundredThousand,
oneMillion,
}
/// {@template score_component}
/// A [ScoreComponent] that spawns at a given [position] with a moving
/// animation.
/// {@endtemplate}
class ScoreComponent extends SpriteComponent with HasGameRef, ZIndex {
/// {@macro score_component}
ScoreComponent({
required this.points,
required Vector2 position,
}) : super(
position: position,
anchor: Anchor.center,
) {
zIndex = ZIndexes.score;
}
late final Effect _effect;
late Points points;
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(points.asset),
);
this.sprite = sprite;
size = sprite.originalSize / 55;
await add(
_effect = MoveEffect.by(
Vector2(0, -5),
EffectController(duration: 1),
),
);
}
@override
void update(double dt) {
super.update(dt);
if (_effect.controller.completed) {
removeFromParent();
}
}
}
extension PointsX on Points {
int get value {
switch (this) {
case Points.fiveThousand:
return 5000;
case Points.twentyThousand:
return 20000;
case Points.twoHundredThousand:
return 200000;
case Points.oneMillion:
return 1000000;
}
}
}
extension on Points {
String get asset {
switch (this) {
case Points.fiveThousand:
return Assets.images.score.fiveThousand.keyName;
case Points.twentyThousand:
return Assets.images.score.twentyThousand.keyName;
case Points.twoHundredThousand:
return Assets.images.score.twoHundredThousand.keyName;
case Points.oneMillion:
return Assets.images.score.oneMillion.keyName;
}
}
}

@ -1,55 +0,0 @@
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template score_text}
/// A [TextComponent] that spawns at a given [position] with a moving animation.
/// {@endtemplate}
class ScoreText extends TextComponent {
/// {@macro score_text}
ScoreText({
required String text,
required Vector2 position,
this.color = Colors.black,
}) : super(
text: text,
position: position,
anchor: Anchor.center,
priority: RenderPriority.scoreText,
);
late final Effect _effect;
/// The [text]'s [Color].
final Color color;
@override
Future<void> onLoad() async {
textRenderer = TextPaint(
style: TextStyle(
fontFamily: PinballFonts.pixeloidMono,
color: color,
fontSize: 4,
),
);
await add(
_effect = MoveEffect.by(
Vector2(0, -5),
EffectController(duration: 1),
),
);
}
@override
void update(double dt) {
super.update(dt);
if (_effect.controller.completed) {
removeFromParent();
}
}
}

@ -1,17 +1,17 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/bumping_behavior.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
/// {@template slingshots} /// {@template slingshots}
/// A [Blueprint] which creates the pair of [Slingshot]s on the right side of /// A collection of [Slingshot]s.
/// the board.
/// {@endtemplate} /// {@endtemplate}
class Slingshots extends Blueprint { class Slingshots extends Component with ZIndex {
/// {@macro slingshots} /// {@macro slingshots}
Slingshots() Slingshots()
: super( : super(
components: [ children: [
Slingshot( Slingshot(
length: 5.64, length: 5.64,
angle: -0.017, angle: -0.017,
@ -23,11 +23,13 @@ class Slingshots extends Blueprint {
spritePath: Assets.images.slingshot.lower.keyName, spritePath: Assets.images.slingshot.lower.keyName,
)..initialPosition = Vector2(24.7, 6.2), )..initialPosition = Vector2(24.7, 6.2),
], ],
); ) {
zIndex = ZIndexes.slingshots;
}
} }
/// {@template slingshot} /// {@template slingshot}
/// Elastic bumper that bounces the [Ball] off of its straight sides. /// Elastic bumper that bounces the [Ball] off of its sides.
/// {@endtemplate} /// {@endtemplate}
class Slingshot extends BodyComponent with InitialPosition { class Slingshot extends BodyComponent with InitialPosition {
/// {@macro slingshot} /// {@macro slingshot}
@ -38,8 +40,10 @@ class Slingshot extends BodyComponent with InitialPosition {
}) : _length = length, }) : _length = length,
_angle = angle, _angle = angle,
super( super(
priority: RenderPriority.slingshot, children: [
children: [_SlinghsotSpriteComponent(spritePath, angle: angle)], _SlinghsotSpriteComponent(spritePath, angle: angle),
BumpingBehavior(strength: 20),
],
renderBody: false, renderBody: false,
); );
@ -52,37 +56,27 @@ class Slingshot extends BodyComponent with InitialPosition {
final topCircleShape = CircleShape()..radius = circleRadius; final topCircleShape = CircleShape()..radius = circleRadius;
topCircleShape.position.setValues(0, -_length / 2); topCircleShape.position.setValues(0, -_length / 2);
final topCircleFixtureDef = FixtureDef(topCircleShape);
final bottomCircleShape = CircleShape()..radius = circleRadius; final bottomCircleShape = CircleShape()..radius = circleRadius;
bottomCircleShape.position.setValues(0, _length / 2); bottomCircleShape.position.setValues(0, _length / 2);
final bottomCircleFixtureDef = FixtureDef(bottomCircleShape);
final leftEdgeShape = EdgeShape() final leftEdgeShape = EdgeShape()
..set( ..set(
Vector2(circleRadius, _length / 2), Vector2(circleRadius, _length / 2),
Vector2(circleRadius, -_length / 2), Vector2(circleRadius, -_length / 2),
); );
final leftEdgeShapeFixtureDef = FixtureDef(
leftEdgeShape,
restitution: 5,
);
final rightEdgeShape = EdgeShape() final rightEdgeShape = EdgeShape()
..set( ..set(
Vector2(-circleRadius, _length / 2), Vector2(-circleRadius, _length / 2),
Vector2(-circleRadius, -_length / 2), Vector2(-circleRadius, -_length / 2),
); );
final rightEdgeShapeFixtureDef = FixtureDef(
rightEdgeShape,
restitution: 5,
);
return [ return [
topCircleFixtureDef, FixtureDef(topCircleShape),
bottomCircleFixtureDef, FixtureDef(bottomCircleShape),
leftEdgeShapeFixtureDef, FixtureDef(leftEdgeShape),
rightEdgeShapeFixtureDef, FixtureDef(rightEdgeShape),
]; ];
} }

@ -6,13 +6,13 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
/// {@template spaceship_rail} /// {@template spaceship_rail}
/// A [Blueprint] for the rail exiting the [AndroidSpaceship]. /// Rail exiting the [AndroidSpaceship].
/// {@endtemplate} /// {@endtemplate}
class SpaceshipRail extends Blueprint { class SpaceshipRail extends Component {
/// {@macro spaceship_rail} /// {@macro spaceship_rail}
SpaceshipRail() SpaceshipRail()
: super( : super(
components: [ children: [
_SpaceshipRail(), _SpaceshipRail(),
_SpaceshipRailExit(), _SpaceshipRailExit(),
_SpaceshipRailExitSpriteComponent() _SpaceshipRailExitSpriteComponent()
@ -20,14 +20,14 @@ class SpaceshipRail extends Blueprint {
); );
} }
class _SpaceshipRail extends BodyComponent with Layered { class _SpaceshipRail extends BodyComponent with Layered, ZIndex {
_SpaceshipRail() _SpaceshipRail()
: super( : super(
priority: RenderPriority.spaceshipRail,
children: [_SpaceshipRailSpriteComponent()], children: [_SpaceshipRailSpriteComponent()],
renderBody: false, renderBody: false,
) { ) {
layer = Layer.spaceshipExitRail; layer = Layer.spaceshipExitRail;
zIndex = ZIndexes.spaceshipRail;
} }
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
@ -125,13 +125,14 @@ class _SpaceshipRailSpriteComponent extends SpriteComponent with HasGameRef {
} }
class _SpaceshipRailExitSpriteComponent extends SpriteComponent class _SpaceshipRailExitSpriteComponent extends SpriteComponent
with HasGameRef { with HasGameRef, ZIndex {
_SpaceshipRailExitSpriteComponent() _SpaceshipRailExitSpriteComponent()
: super( : super(
anchor: Anchor.center, anchor: Anchor.center,
position: Vector2(-28, 19.4), position: Vector2(-28, 19.4),
priority: RenderPriority.spaceshipRailExit, ) {
); zIndex = ZIndexes.spaceshipRailExit;
}
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
@ -152,7 +153,7 @@ class _SpaceshipRailExit extends LayerSensor {
: super( : super(
orientation: LayerEntranceOrientation.down, orientation: LayerEntranceOrientation.down,
insideLayer: Layer.spaceshipExitRail, insideLayer: Layer.spaceshipExitRail,
insidePriority: RenderPriority.ballOnSpaceshipRail, insideZIndex: ZIndexes.ballOnSpaceshipRail,
) { ) {
layer = Layer.spaceshipExitRail; layer = Layer.spaceshipExitRail;
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save