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
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame/components.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template android_acres}
/// Area positioned on the left side of the board containing the
/// [AndroidSpaceship], [SpaceshipRamp], [SpaceshipRail], and [AndroidBumper]s.
/// {@endtemplate}
class AndroidAcres extends Blueprint {
class AndroidAcres extends Component {
/// {@macro android_acres}
AndroidAcres()
: super(
components: [
children: [
SpaceshipRamp(),
SpaceshipRail(),
AndroidSpaceship(position: Vector2(-26.5, -28.5)),
AndroidBumper.a(
children: [
ScoringBehavior(points: 20000),
ScoringBehavior(points: Points.twentyThousand),
],
)..initialPosition = Vector2(-25, 1.3),
AndroidBumper.b(
children: [
ScoringBehavior(points: 20000),
ScoringBehavior(points: Points.twentyThousand),
],
)..initialPosition = Vector2(-32.8, -9.2),
AndroidBumper.cow(
children: [
ScoringBehavior(points: 20),
ScoringBehavior(points: Points.twentyThousand),
],
)..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:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template bottom_group}
/// 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.
/// {@endtemplate}
// TODO(allisonryan0002): Consider renaming.
class BottomGroup extends Component {
class BottomGroup extends Component with ZIndex {
/// {@macro bottom_group}
BottomGroup()
: super(
@ -16,8 +17,9 @@ class BottomGroup extends Component {
_BottomGroupSide(side: BoardSide.right),
_BottomGroupSide(side: BoardSide.left),
],
priority: RenderPriority.bottomGroup,
);
) {
zIndex = ZIndexes.bottomGroup;
}
}
/// {@template bottom_group_side}
@ -36,7 +38,7 @@ class _BottomGroupSide extends Component {
@override
Future<void> onLoad() async {
final direction = _side.direction;
final centerXAdjustment = _side.isLeft ? 0 : -6.5;
final centerXAdjustment = _side.isLeft ? 0 : -6.66;
final flipper = ControlledFlipper(
side: _side,
@ -44,16 +46,16 @@ class _BottomGroupSide extends Component {
final baseboard = Baseboard(side: _side)
..initialPosition = Vector2(
(25.58 * direction) + centerXAdjustment,
28.69,
28.71,
);
final kicker = Kicker(
side: _side,
children: [
ScoringBehavior(points: 5000),
ScoringBehavior(points: Points.fiveThousand)..applyTo(['bouncy_edge']),
],
)..initialPosition = Vector2(
(22.4 * direction) + centerXAdjustment,
25,
(22.64 * direction) + centerXAdjustment,
25.1,
);
await addAll([flipper, baseboard, kicker]);

@ -12,4 +12,4 @@ export 'google_word/google_word.dart';
export 'launcher.dart';
export 'multipliers/multipliers.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,
}) : super(baseColor: characterTheme.ballColor) {
controller = BallController(this);
priority = RenderPriority.ballOnLaunchRamp;
layer = Layer.launcher;
zIndex = ZIndexes.ballOnLaunchRamp;
}
/// {@template bonus_ball}
@ -30,13 +30,13 @@ class ControlledBall extends Ball with Controls<BallController> {
required CharacterTheme characterTheme,
}) : super(baseColor: characterTheme.ballColor) {
controller = BallController(this);
priority = RenderPriority.ballOnBoard;
zIndex = ZIndexes.ballOnBoard;
}
/// [Ball] used in [DebugPinballGame].
ControlledBall.debug() : super(baseColor: const Color(0xFFFF0000)) {
controller = BallController(this);
priority = RenderPriority.ballOnBoard;
zIndex = ZIndexes.ballOnBoard;
}
}

@ -1,23 +1,39 @@
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template dino_desert}
/// Area located next to the [Launcher] containing the [ChromeDino] and
/// [DinoWalls].
/// {@endtemplate}
// TODO(allisonryan0002): use a controller to initiate dino bonus when dino is
// fully implemented.
class DinoDesert extends Blueprint {
class DinoDesert extends Component {
/// {@macro dino_desert}
DinoDesert()
: super(
components: [
ChromeDino()..initialPosition = Vector2(12.3, -6.9),
],
blueprints: [
children: [
ChromeDino(
children: [
ScoringBehavior(points: Points.twoHundredThousand)
..applyTo(['inside_mouth']),
],
)..initialPosition = Vector2(12.6, -6.9),
_BarrierBehindDino(),
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,10 +25,10 @@ class FlutterForestBonusBehavior extends Component
gameRef
.read<GameBloc>()
.add(const BonusActivated(GameBonus.dashNest));
gameRef.add(
ControlledBall.bonus(characterTheme: gameRef.characterTheme)
..initialPosition = Vector2(17.2, -52.7),
);
gameRef.firstChild<ZCanvasComponent>()!.add(
ControlledBall.bonus(characterTheme: gameRef.characterTheme)
..initialPosition = Vector2(17.2, -52.7),
);
parent.firstChild<DashAnimatronic>()?.playing = true;
for (final bumper in bumpers) {

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

@ -17,7 +17,7 @@ class GoogleWordBonusBehavior extends Component
// https://github.com/flame-engine/flame/pull/1538
letter.bloc.stream.listen((_) {
final achievedBonus = googleLetters
.every((letter) => letter.bloc.state == GoogleLetterState.active);
.every((letter) => letter.bloc.state == GoogleLetterState.lit);
if (achievedBonus) {
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/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template google_word}
/// Loads all [GoogleLetter]s to compose a [GoogleWord].
/// {@endtemplate}
class GoogleWord extends Component {
class GoogleWord extends Component with ZIndex {
/// {@macro google_word}
GoogleWord({
required Vector2 position,
@ -15,31 +16,33 @@ class GoogleWord extends Component {
children: [
GoogleLetter(
0,
children: [ScoringBehavior(points: 5000)],
)..initialPosition = position + Vector2(-12.92, 1.82),
children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(-13.1, 1.72),
GoogleLetter(
1,
children: [ScoringBehavior(points: 5000)],
)..initialPosition = position + Vector2(-8.33, -0.65),
children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(-8.33, -0.75),
GoogleLetter(
2,
children: [ScoringBehavior(points: 5000)],
)..initialPosition = position + Vector2(-2.88, -1.75),
children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(-2.88, -1.85),
GoogleLetter(
3,
children: [ScoringBehavior(points: 5000)],
)..initialPosition = position + Vector2(2.88, -1.75),
children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(2.88, -1.85),
GoogleLetter(
4,
children: [ScoringBehavior(points: 5000)],
)..initialPosition = position + Vector2(8.33, -0.65),
children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(8.33, -0.75),
GoogleLetter(
5,
children: [ScoringBehavior(points: 5000)],
)..initialPosition = position + Vector2(12.92, 1.82),
children: [ScoringBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(13.1, 1.72),
GoogleWordBonusBehavior(),
],
);
) {
zIndex = ZIndexes.decal;
}
/// 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_components/pinball_components.dart' hide Assets;
import 'package:pinball_flame/pinball_flame.dart';
/// {@template launcher}
/// A [Blueprint] which creates the [Plunger], [RocketSpriteComponent] and
/// [LaunchRamp].
/// Channel on the right side of the board containing the [LaunchRamp],
/// [Plunger], and [RocketSpriteComponent].
/// {@endtemplate}
class Launcher extends Blueprint {
class Launcher extends Component {
/// {@macro launcher}
Launcher()
: super(
components: [
ControlledPlunger(compressionDistance: 10.5)
..initialPosition = Vector2(41.1, 43),
children: [
LaunchRamp(),
ControlledPlunger(compressionDistance: 9.2)
..initialPosition = Vector2(41.2, 43.7),
RocketSpriteComponent()..position = Vector2(43, 62.3),
],
blueprints: [LaunchRamp()],
);
}

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

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

@ -1,40 +1,36 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template sparky_fire_zone}
/// Area positioned at the top left of the board where the [Ball]
/// can bounce off [SparkyBumper]s.
///
/// When a [Ball] hits [SparkyBumper]s, the bumper animates.
/// {@template sparky_scorch}
/// Area positioned at the top left of the board containing the
/// [SparkyComputer], [SparkyAnimatronic], and [SparkyBumper]s.
/// {@endtemplate}
class SparkyFireZone extends Blueprint {
/// {@macro sparky_fire_zone}
SparkyFireZone()
class SparkyScorch extends Component {
/// {@macro sparky_scorch}
SparkyScorch()
: super(
components: [
children: [
SparkyBumper.a(
children: [
ScoringBehavior(points: 20000),
ScoringBehavior(points: Points.twentyThousand),
],
)..initialPosition = Vector2(-22.9, -41.65),
SparkyBumper.b(
children: [
ScoringBehavior(points: 20000),
ScoringBehavior(points: Points.twentyThousand),
],
)..initialPosition = Vector2(-21.25, -57.9),
SparkyBumper.c(
children: [
ScoringBehavior(points: 20000),
ScoringBehavior(points: Points.twentyThousand),
],
)..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),
],
blueprints: [
SparkyComputer(),
],
);
@ -51,13 +47,19 @@ class SparkyComputerSensor extends BodyComponent
: super(
renderBody: false,
children: [
ScoringBehavior(points: 200000),
ScoringBehavior(points: Points.twentyThousand),
],
);
@override
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 bodyDef = BodyDef(
position: initialPosition,

@ -24,8 +24,10 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.flipper.right.keyName),
images.load(components.Assets.images.baseboard.left.keyName),
images.load(components.Assets.images.baseboard.right.keyName),
images.load(components.Assets.images.kicker.left.keyName),
images.load(components.Assets.images.kicker.right.keyName),
images.load(components.Assets.images.kicker.left.lit.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.lower.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.sparky.computer.top.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.bumper.a.inactive.keyName),
images.load(components.Assets.images.sparky.bumper.a.active.keyName),
images.load(components.Assets.images.sparky.bumper.b.active.keyName),
images.load(components.Assets.images.sparky.bumper.b.inactive.keyName),
images.load(components.Assets.images.sparky.bumper.c.active.keyName),
images.load(components.Assets.images.sparky.bumper.c.inactive.keyName),
images.load(components.Assets.images.sparky.bumper.a.lit.keyName),
images.load(components.Assets.images.sparky.bumper.a.dimmed.keyName),
images.load(components.Assets.images.sparky.bumper.b.lit.keyName),
images.load(components.Assets.images.sparky.bumper.b.dimmed.keyName),
images.load(components.Assets.images.sparky.bumper.c.lit.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.backboardGameOver.keyName),
images.load(components.Assets.images.googleWord.letter1.keyName),
images.load(components.Assets.images.googleWord.letter2.keyName),
images.load(components.Assets.images.googleWord.letter3.keyName),
images.load(components.Assets.images.googleWord.letter4.keyName),
images.load(components.Assets.images.googleWord.letter5.keyName),
images.load(components.Assets.images.googleWord.letter6.keyName),
images.load(components.Assets.images.googleWord.letter1.lit.keyName),
images.load(components.Assets.images.googleWord.letter1.dimmed.keyName),
images.load(components.Assets.images.googleWord.letter2.lit.keyName),
images.load(components.Assets.images.googleWord.letter2.dimmed.keyName),
images.load(components.Assets.images.googleWord.letter3.lit.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.multiplier.x2.lit.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.x6.lit.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(sparkyTheme.leaderboardIcon.keyName),
images.load(androidTheme.leaderboardIcon.keyName),

@ -23,7 +23,7 @@ class PinballGame extends Forge2DGame
PinballGame({
required this.characterTheme,
required this.audio,
}) {
}) : super(gravity: Vector2(0, 30)) {
images.prefix = '';
controller = _GameBallsController(this);
}
@ -42,32 +42,38 @@ class PinballGame extends Forge2DGame
@override
Future<void> onLoad() async {
unawaited(add(gameFlowController = GameFlowController(this)));
unawaited(add(CameraController(this)));
unawaited(add(Backboard.waiting(position: Vector2(0, -88))));
await add(BoardBackgroundSpriteComponent());
await add(Drain());
await add(BottomGroup());
unawaited(addFromBlueprint(Boundaries()));
final launcher = Launcher();
unawaited(addFromBlueprint(launcher));
await add(Multipliers());
await add(FlutterForest());
await addFromBlueprint(SparkyFireZone());
await addFromBlueprint(AndroidAcres());
await addFromBlueprint(DinoDesert());
unawaited(addFromBlueprint(Slingshots()));
await add(gameFlowController = GameFlowController(this));
await add(CameraController(this));
final machine = [
BoardBackgroundSpriteComponent(),
Boundaries(),
Backboard.waiting(position: Vector2(0, -88)),
];
final decals = [
GoogleWord(position: Vector2(-4.25, 1.8)),
Multipliers(),
];
final characterAreas = [
AndroidAcres(),
DinoDesert(),
FlutterForest(),
SparkyScorch(),
];
await add(
GoogleWord(
position: Vector2(
BoardDimensions.bounds.center.dx - 4.1,
BoardDimensions.bounds.center.dy + 1.8,
),
ZCanvasComponent(
children: [
...machine,
...decals,
...characterAreas,
Drain(),
BottomGroup(),
Launcher(),
],
),
);
controller.attachTo(launcher.components.whereType<Plunger>().single);
await super.onLoad();
}
@ -76,12 +82,12 @@ class PinballGame extends Forge2DGame
@override
void onTapDown(TapDownInfo info) {
if (info.raw.kind == PointerDeviceKind.touch) {
final rocket = children.whereType<RocketSpriteComponent>().first;
final rocket = descendants().whereType<RocketSpriteComponent>().first;
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.
if (bounds.contains(info.eventPosition.game.toOffset())) {
children.whereType<Plunger>().first.pullFor(2);
descendants().whereType<Plunger>().single.pullFor(2);
} else {
final leftSide = info.eventPosition.widget.x < canvasSize.x / 2;
focusedBoardSide = leftSide ? BoardSide.left : BoardSide.right;
@ -122,8 +128,6 @@ class _GameBallsController extends ComponentController<PinballGame>
with BlocComponent<GameBloc, GameState> {
_GameBallsController(PinballGame game) : super(game);
late final Plunger _plunger;
@override
bool listenWhen(GameState? previousState, GameState newState) {
final noBallsLeft = component.descendants().whereType<Ball>().isEmpty;
@ -135,30 +139,27 @@ class _GameBallsController extends ComponentController<PinballGame>
@override
void onNewState(GameState state) {
super.onNewState(state);
_spawnBall();
spawnBall();
}
@override
Future<void> onLoad() async {
await super.onLoad();
_spawnBall();
spawnBall();
}
void _spawnBall() {
final ball = ControlledBall.launch(
characterTheme: component.characterTheme,
)..initialPosition = Vector2(
_plunger.body.position.x,
_plunger.body.position.y - Ball.size.y,
);
component.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;
void spawnBall() {
// TODO(alestiago): Refactor with behavioural pattern.
component.ready().whenComplete(() {
final plunger = parent!.descendants().whereType<Plunger>().single;
final ball = ControlledBall.launch(
characterTheme: component.characterTheme,
)..initialPosition = Vector2(
plunger.body.position.x,
plunger.body.position.y - Ball.size.y,
);
component.firstChild<ZCanvasComponent>()?.add(ball);
});
}
}
@ -170,7 +171,7 @@ class DebugPinballGame extends PinballGame with FPSCounter {
characterTheme: characterTheme,
audio: audio,
) {
controller = _DebugGameBallsController(this);
controller = _GameBallsController(this);
}
@override
@ -179,42 +180,21 @@ class DebugPinballGame extends PinballGame with FPSCounter {
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
void onTapUp(TapUpInfo info) {
super.onTapUp(info);
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.
// coverage:ignore-start
class _DebugInformation extends Component with HasGameRef<DebugPinballGame> {
_DebugInformation() : super(priority: RenderPriority.debugInfo);
@override
PositionType get positionType => PositionType.widget;

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

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

@ -46,19 +46,19 @@
},
"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": {
"description": "Text displayed on space control button"
},
"characterSelectionTitle": "Choose your character!",
"characterSelectionTitle": "Select a Character",
"@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": {
"description": "Text displayed on the selecting character dialog at game beginning"
"description": "Subtitle text displayed on the character selection dialog"
},
"gameOver": "Game Over",
"@gameOver": {

@ -10,6 +10,14 @@ class CharacterThemeState extends Equatable {
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
List<Object> get props => [characterTheme];
}

@ -1,139 +1,160 @@
// ignore_for_file: public_member_api_docs
import 'package:flutter/material.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/select_character/cubit/character_theme_cubit.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_ui/pinball_ui.dart';
/// Inflates [CharacterSelectionDialog] using [showDialog].
Future<void> showCharacterSelectionDialog(BuildContext context) {
return showDialog<void>(
context: context,
barrierDismissible: false,
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);
static Route route() {
return MaterialPageRoute<void>(
builder: (_) => const CharacterSelectionDialog(),
);
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CharacterThemeCubit(),
child: const CharacterSelectionView(),
final l10n = context.l10n;
return PinballDialog(
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 {
const CharacterSelectionView({Key? key}) : super(key: key);
class _SelectCharacterButton extends StatelessWidget {
const _SelectCharacterButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return PinballButton(
onTap: () async {
Navigator.of(context).pop();
await showHowToPlayDialog(context);
},
text: l10n.select,
);
}
}
return PixelatedDecoration(
header: Text(
l10n.characterSelectionTitle,
style: Theme.of(context).textTheme.headline3,
),
body: SingleChildScrollView(
child: Column(
class _CharacterGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<CharacterThemeCubit, CharacterThemeState>(
builder: (context, state) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const _CharacterSelectionGridView(),
const SizedBox(height: 20),
TextButton(
onPressed: () {
Navigator.of(context).pop();
// TODO(arturplaczek): remove after merge StarBlocListener
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(),
),
),
);
},
child: Text(l10n.start),
Column(
children: [
_Character(
key: const Key('sparky_character_selection'),
character: const SparkyTheme(),
isSelected: state.isSparkySelected,
),
const SizedBox(height: 6),
_Character(
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 {
const _CharacterSelectionGridView({Key? key}) : super(key: key);
class _CharacterPreview extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(20),
child: GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
mainAxisSpacing: 20,
crossAxisSpacing: 20,
children: const [
CharacterImageButton(
DashTheme(),
key: Key('characterSelectionPage_dashButton'),
),
CharacterImageButton(
SparkyTheme(),
key: Key('characterSelectionPage_sparkyButton'),
),
CharacterImageButton(
AndroidTheme(),
key: Key('characterSelectionPage_androidButton'),
),
CharacterImageButton(
DinoTheme(),
key: Key('characterSelectionPage_dinoButton'),
),
],
),
return BlocBuilder<CharacterThemeCubit, CharacterThemeState>(
builder: (context, state) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
state.characterTheme.name,
style: Theme.of(context).textTheme.headline2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
Expanded(child: state.characterTheme.icon.image()),
],
);
},
);
}
}
// TODO(allisonryan0002): remove visibility when adding final UI.
@visibleForTesting
class CharacterImageButton extends StatelessWidget {
const CharacterImageButton(
this.characterTheme, {
class _Character extends StatelessWidget {
const _Character({
Key? key,
required this.character,
required this.isSelected,
}) : super(key: key);
final CharacterTheme characterTheme;
final CharacterTheme character;
final bool isSelected;
@override
Widget build(BuildContext context) {
final currentCharacterTheme =
context.select<CharacterThemeCubit, CharacterTheme>(
(cubit) => cubit.state.characterTheme,
);
return GestureDetector(
onTap: () =>
context.read<CharacterThemeCubit>().characterSelected(characterTheme),
child: DecoratedBox(
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(),
return Expanded(
child: Opacity(
opacity: isSelected ? 1 : 0.3,
child: InkWell(
onTap: () =>
context.read<CharacterThemeCubit>().characterSelected(character),
child: character.icon.image(fit: BoxFit.contain),
),
),
);

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

@ -5,6 +5,7 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.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/bumping_behavior.dart';
import 'package:pinball_flame/pinball_flame.dart';
export 'cubit/android_bumper_cubit.dart';
@ -12,7 +13,7 @@ export 'cubit/android_bumper_cubit.dart';
/// {@template android_bumper}
/// Bumper for area under the [AndroidSpaceship].
/// {@endtemplate}
class AndroidBumper extends BodyComponent with InitialPosition {
class AndroidBumper extends BodyComponent with InitialPosition, ZIndex {
/// {@macro android_bumper}
AndroidBumper._({
required double majorRadius,
@ -25,7 +26,6 @@ class AndroidBumper extends BodyComponent with InitialPosition {
}) : _majorRadius = majorRadius,
_minorRadius = minorRadius,
super(
priority: RenderPriority.androidBumper,
renderBody: false,
children: [
AndroidBumperBallContactBehavior(),
@ -38,7 +38,9 @@ class AndroidBumper extends BodyComponent with InitialPosition {
),
...?children,
],
);
) {
zIndex = ZIndexes.androidBumper;
}
/// {@macro android_bumper}
AndroidBumper.a({
@ -50,7 +52,10 @@ class AndroidBumper extends BodyComponent with InitialPosition {
dimmedAssetPath: Assets.images.android.bumper.a.dimmed.keyName,
spritePosition: Vector2(0, -0.1),
bloc: AndroidBumperCubit(),
children: children,
children: [
...?children,
BumpingBehavior(strength: 20),
],
);
/// {@macro android_bumper}
@ -63,7 +68,10 @@ class AndroidBumper extends BodyComponent with InitialPosition {
dimmedAssetPath: Assets.images.android.bumper.b.dimmed.keyName,
spritePosition: Vector2(0, -0.1),
bloc: AndroidBumperCubit(),
children: children,
children: [
...?children,
BumpingBehavior(strength: 20),
],
);
/// {@macro android_bumper}
@ -76,7 +84,10 @@ class AndroidBumper extends BodyComponent with InitialPosition {
dimmedAssetPath: Assets.images.android.bumper.cow.dimmed.keyName,
spritePosition: Vector2(0, -0.68),
bloc: AndroidBumperCubit(),
children: children,
children: [
...?children,
BumpingBehavior(strength: 20),
],
);
/// Creates an [AndroidBumper] without any children.
@ -112,15 +123,11 @@ class AndroidBumper extends BodyComponent with InitialPosition {
majorRadius: _majorRadius,
minorRadius: _minorRadius,
)..rotate(1.29);
final fixtureDef = FixtureDef(
shape,
restitution: 4,
);
final bodyDef = BodyDef(
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_forge2d/flame_forge2d.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';
import 'package:pinball_flame/pinball_flame.dart';
class AndroidSpaceship extends Blueprint {
class AndroidSpaceship extends Component {
AndroidSpaceship({required Vector2 position})
: super(
components: [
children: [
_SpaceshipSaucer()..initialPosition = position,
_SpaceshipSaucerSpriteAnimationComponent()..position = position,
_LightBeamSpriteComponent()..position = position + Vector2(2.5, 5),
_AndroidHead()..initialPosition = position + Vector2(0.5, 0.25),
_SpaceshipHole(
outsideLayer: Layer.spaceshipExitRail,
outsidePriority: RenderPriority.ballOnSpaceshipRail,
outsidePriority: ZIndexes.ballOnSpaceshipRail,
)..initialPosition = position - Vector2(5.3, -5.4),
_SpaceshipHole(
outsideLayer: Layer.board,
outsidePriority: RenderPriority.ballOnBoard,
outsidePriority: ZIndexes.ballOnBoard,
)..initialPosition = position - Vector2(-7.5, -1.1),
],
);
@ -65,12 +64,13 @@ class _SpaceshipSaucerShape extends ChainShape {
}
class _SpaceshipSaucerSpriteAnimationComponent extends SpriteAnimationComponent
with HasGameRef {
with HasGameRef, ZIndex {
_SpaceshipSaucerSpriteAnimationComponent()
: super(
anchor: Anchor.center,
priority: RenderPriority.spaceshipSaucer,
);
) {
zIndex = ZIndexes.spaceshipSaucer;
}
@override
Future<void> onLoad() async {
@ -101,12 +101,14 @@ class _SpaceshipSaucerSpriteAnimationComponent extends SpriteAnimationComponent
}
// TODO(allisonryan0002): add pulsing behavior.
class _LightBeamSpriteComponent extends SpriteComponent with HasGameRef {
class _LightBeamSpriteComponent extends SpriteComponent
with HasGameRef, ZIndex {
_LightBeamSpriteComponent()
: super(
anchor: Anchor.center,
priority: RenderPriority.spaceshipLightBeam,
);
) {
zIndex = ZIndexes.spaceshipLightBeam;
}
@override
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()
: super(
priority: RenderPriority.androidHead,
children: [_AndroidHeadSpriteAnimationComponent()],
renderBody: false,
) {
layer = Layer.spaceship;
zIndex = ZIndexes.androidHead;
}
@override
@ -138,14 +140,9 @@ class _AndroidHead extends BodyComponent with InitialPosition, Layered {
majorRadius: 3.1,
minorRadius: 2,
)..rotate(1.4);
// TODO(allisonryan0002): use bumping behavior.
final fixtureDef = FixtureDef(
shape,
restitution: 0.1,
);
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,
outsideLayer: outsideLayer,
orientation: LayerEntranceOrientation.down,
insidePriority: RenderPriority.ballOnSpaceship,
outsidePriority: outsidePriority,
insideZIndex: ZIndexes.ballOnSpaceship,
outsideZIndex: outsidePriority,
) {
layer = Layer.spaceship;
}

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

@ -2,14 +2,17 @@
import 'package:flame/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()
: super(
anchor: Anchor.center,
priority: RenderPriority.boardBackground,
position: Vector2(0, -1),
);
) {
zIndex = ZIndexes.boardBackground;
}
@override
Future<void> onLoad() async {

@ -4,13 +4,13 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template boundaries}
/// A [Blueprint] which creates the [_BottomBoundary] and [_OuterBoundary].
///{@endtemplate boundaries}
class Boundaries extends Blueprint {
/// Pinball machine walls.
/// {@endtemplate}
class Boundaries extends Component {
/// {@macro boundaries}
Boundaries()
: super(
components: [
children: [
_BottomBoundary(),
_OuterBoundary(),
_OuterBottomBoundarySpriteComponent(),
@ -22,14 +22,15 @@ class Boundaries extends Blueprint {
/// Curved boundary at the bottom of the board where the [Ball] exits the field
/// of play.
/// {@endtemplate bottom_boundary}
class _BottomBoundary extends BodyComponent with InitialPosition {
class _BottomBoundary extends BodyComponent with InitialPosition, ZIndex {
/// {@macro bottom_boundary}
_BottomBoundary()
: super(
renderBody: false,
priority: RenderPriority.bottomBoundary,
children: [_BottomBoundarySpriteComponent()],
);
) {
zIndex = ZIndexes.bottomBoundary;
}
List<FixtureDef> _createFixtureDefs() {
final bottomLeftCurve = BezierCurveShape(
@ -84,17 +85,20 @@ class _BottomBoundarySpriteComponent extends SpriteComponent with HasGameRef {
}
/// {@template outer_boundary}
/// Boundary enclosing the top and left side of the board. The right side of the
/// board is closed by the barrier the [LaunchRamp] creates.
/// Boundary enclosing the top and left side of the board.
///
/// The right side of the board is closed by the barrier the [LaunchRamp]
/// creates.
/// {@endtemplate outer_boundary}
class _OuterBoundary extends BodyComponent with InitialPosition {
class _OuterBoundary extends BodyComponent with InitialPosition, ZIndex {
/// {@macro outer_boundary}
_OuterBoundary()
: super(
renderBody: false,
priority: RenderPriority.outerBoundary,
children: [_OuterBoundarySpriteComponent()],
);
) {
zIndex = ZIndexes.outerBoundary;
}
List<FixtureDef> _createFixtureDefs() {
final topWall = EdgeShape()
@ -189,13 +193,14 @@ class _OuterBoundarySpriteComponent extends SpriteComponent with HasGameRef {
}
class _OuterBottomBoundarySpriteComponent extends SpriteComponent
with HasGameRef {
with HasGameRef, ZIndex {
_OuterBottomBoundarySpriteComponent()
: super(
priority: RenderPriority.outerBottomBoundary,
anchor: Anchor.center,
position: Vector2(0, 71),
);
) {
zIndex = ZIndexes.outerBottomBoundary;
}
@override
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 'boundaries.dart';
export 'camera_zoom.dart';
export 'chrome_dino.dart';
export 'chrome_dino/chrome_dino.dart';
export 'dash_animatronic.dart';
export 'dash_nest_bumper/dash_nest_bumper.dart';
export 'dino_walls.dart';
@ -17,15 +17,14 @@ export 'flipper.dart';
export 'google_letter/google_letter.dart';
export 'initial_position.dart';
export 'joint_anchor.dart';
export 'kicker.dart';
export 'kicker/kicker.dart';
export 'launch_ramp.dart';
export 'layer.dart';
export 'layer_sensor.dart';
export 'multiplier/multiplier.dart';
export 'plunger.dart';
export 'render_priority.dart';
export 'rocket.dart';
export 'score_text.dart';
export 'score_component.dart';
export 'shapes/shapes.dart';
export 'signpost.dart';
export 'slingshot.dart';
@ -34,3 +33,4 @@ export 'spaceship_ramp.dart';
export 'sparky_animatronic.dart';
export 'sparky_bumper/sparky_bumper.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:flutter/material.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_flame/pinball_flame.dart';
@ -47,8 +48,11 @@ class DashNestBumper extends BodyComponent with InitialPosition {
activeAssetPath: Assets.images.dash.bumper.main.active.keyName,
inactiveAssetPath: Assets.images.dash.bumper.main.inactive.keyName,
spritePosition: Vector2(0, -0.3),
children: children,
bloc: DashNestBumperCubit(),
children: [
...?children,
BumpingBehavior(strength: 20),
],
);
/// {@macro dash_nest_bumper}
@ -60,8 +64,11 @@ class DashNestBumper extends BodyComponent with InitialPosition {
activeAssetPath: Assets.images.dash.bumper.a.active.keyName,
inactiveAssetPath: Assets.images.dash.bumper.a.inactive.keyName,
spritePosition: Vector2(0.35, -1.2),
children: children,
bloc: DashNestBumperCubit(),
children: [
...?children,
BumpingBehavior(strength: 20),
],
);
/// {@macro dash_nest_bumper}
@ -73,8 +80,11 @@ class DashNestBumper extends BodyComponent with InitialPosition {
activeAssetPath: Assets.images.dash.bumper.b.active.keyName,
inactiveAssetPath: Assets.images.dash.bumper.b.inactive.keyName,
spritePosition: Vector2(0.35, -1.2),
children: children,
bloc: DashNestBumperCubit(),
children: [
...?children,
BumpingBehavior(strength: 20),
],
);
/// Creates an [DashNestBumper] without any children.
@ -108,13 +118,11 @@ class DashNestBumper extends BodyComponent with InitialPosition {
majorRadius: _majorRadius,
minorRadius: _minorRadius,
)..rotate(math.pi / 1.9);
final fixtureDef = FixtureDef(shape, restitution: 4);
final bodyDef = BodyDef(
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_flame/pinball_flame.dart';
/// {@template dinowalls}
/// A [Blueprint] which creates walls for the [ChromeDino].
/// {@template dino_walls}
/// Walls near the [ChromeDino].
/// {@endtemplate}
class DinoWalls extends Blueprint {
/// {@macro dinowalls}
class DinoWalls extends Component {
/// {@macro dino_walls}
DinoWalls()
: super(
components: [
children: [
_DinoTopWall(),
_DinoBottomWall(),
],
@ -23,14 +23,15 @@ class DinoWalls extends Blueprint {
/// {@template dino_top_wall}
/// Wall segment located above [ChromeDino].
/// {@endtemplate}
class _DinoTopWall extends BodyComponent with InitialPosition {
class _DinoTopWall extends BodyComponent with InitialPosition, ZIndex {
///{@macro dino_top_wall}
_DinoTopWall()
: super(
priority: RenderPriority.dinoTopWall,
children: [_DinoTopWallSpriteComponent()],
renderBody: false,
);
) {
zIndex = ZIndexes.dinoTopWall;
}
List<FixtureDef> _createFixtureDefs() {
final topStraightShape = EdgeShape()
@ -86,13 +87,7 @@ class _DinoTopWall extends BodyComponent with InitialPosition {
);
final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(
(fixture) => body.createFixture(
fixture
..restitution = 0.1
..friction = 0,
),
);
_createFixtureDefs().forEach(body.createFixture);
return body;
}
@ -116,14 +111,15 @@ class _DinoTopWallSpriteComponent extends SpriteComponent with HasGameRef {
/// {@template dino_bottom_wall}
/// Wall segment located below [ChromeDino].
/// {@endtemplate}
class _DinoBottomWall extends BodyComponent with InitialPosition {
class _DinoBottomWall extends BodyComponent with InitialPosition, ZIndex {
///{@macro dino_top_wall}
_DinoBottomWall()
: super(
priority: RenderPriority.dinoBottomWall,
children: [_DinoBottomWallSpriteComponent()],
renderBody: false,
);
) {
zIndex = ZIndexes.dinoBottomWall;
}
List<FixtureDef> _createFixtureDefs() {
final topStraightShape = EdgeShape()

@ -26,10 +26,8 @@ class FireEffect extends ParticleSystemComponent {
required this.burstPower,
required this.direction,
Vector2? position,
int? priority,
}) : super(
position: position,
priority: priority,
);
/// 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 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.
///

@ -5,13 +5,13 @@ import 'package:bloc/bloc.dart';
part 'google_letter_state.dart';
class GoogleLetterCubit extends Cubit<GoogleLetterState> {
GoogleLetterCubit() : super(GoogleLetterState.inactive);
GoogleLetterCubit() : super(GoogleLetterState.dimmed);
void onBallContacted() {
emit(GoogleLetterState.active);
emit(GoogleLetterState.lit);
}
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';
/// Indicates the [GoogleLetterCubit]'s current state.
enum GoogleLetterState {
/// A lit up letter.
active,
/// A dimmed letter.
inactive,
lit,
dimmed,
}

@ -7,6 +7,33 @@ import 'package:pinball_flame/pinball_flame.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}
/// Circular sensor that represents a letter in "GOOGLE" for a given index.
/// {@endtemplate}
@ -15,13 +42,27 @@ class GoogleLetter extends BodyComponent with InitialPosition {
GoogleLetter(
int index, {
Iterable<Component>? children,
}) : bloc = GoogleLetterCubit(),
super(
}) : this._(
index,
bloc: GoogleLetterCubit(),
children: children,
);
GoogleLetter._(
int index, {
required this.bloc,
Iterable<Component>? children,
}) : super(
children: [
_GoogleLetterSpriteGroupComponent(
litAssetPath: _spritePaths[index][GoogleLetterState.lit]!,
dimmedAssetPath: _spritePaths[index][GoogleLetterState.dimmed]!,
current: bloc.state,
),
GoogleLetterBallContactBehavior(),
_GoogleLetterSprite(_GoogleLetterSprite.spritePaths[index]),
...?children,
],
renderBody: false,
);
/// 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> {
_GoogleLetterSprite(String path)
: _path = path,
super(anchor: Anchor.center);
static final spritePaths = [
Assets.images.googleWord.letter1.keyName,
Assets.images.googleWord.letter2.keyName,
Assets.images.googleWord.letter3.keyName,
Assets.images.googleWord.letter4.keyName,
Assets.images.googleWord.letter5.keyName,
Assets.images.googleWord.letter6.keyName,
];
_GoogleLetterSpriteGroupComponent({
required String litAssetPath,
required String dimmedAssetPath,
required GoogleLetterState current,
}) : _litAssetPath = litAssetPath,
_dimmedAssetPath = dimmedAssetPath,
super(
anchor: Anchor.center,
current: current,
);
final String _path;
final String _litAssetPath;
final String _dimmedAssetPath;
@override
Future<void> onLoad() async {
await super.onLoad();
// TODO(alisonryan2002): Make SpriteGroupComponent.
// parent.bloc.stream.listen();
parent.bloc.stream.listen((state) => current = state);
// TODO(alestiago): Used cached assets.
final sprite = await gameRef.loadSprite(_path);
this.sprite = sprite;
// TODO(alestiago): Size correctly once the assets are provided.
size = sprite.originalSize / 5;
final sprites = {
GoogleLetterState.lit: Sprite(
gameRef.images.fromCache(_litAssetPath),
),
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_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:geometry/geometry.dart' as geometry show centroid;
import 'package:pinball_components/gen/assets.gen.dart';
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}
/// Triangular [BodyType.static] body that propels the [Ball] towards the
@ -17,42 +23,69 @@ class Kicker extends BodyComponent with InitialPosition {
Kicker({
required BoardSide side,
Iterable<Component>? children,
}) : this._(
side: side,
bloc: KickerCubit(),
children: children,
);
Kicker._({
required BoardSide side,
required this.bloc,
Iterable<Component>? children,
}) : _side = side,
super(
children: [
_KickerSpriteComponent(side: side),
BumpingBehavior(strength: 20)..applyTo(['bouncy_edge']),
KickerBallContactBehavior()..applyTo(['bouncy_edge']),
KickerBlinkingBehavior(),
_KickerSpriteGroupComponent(
side: side,
state: bloc.state,
),
...?children,
],
renderBody: false,
);
/// The size of the [Kicker] body.
static final Vector2 size = Vector2(4.4, 15);
/// Creates a [Kicker] without any children.
///
/// 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.
///
/// 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;
List<FixtureDef> _createFixtureDefs() {
final fixturesDefs = <FixtureDef>[];
final direction = _side.direction;
const quarterPi = math.pi / 4;
final size = Vector2(4.4, 15);
final upperCircle = CircleShape()..radius = 1.6;
upperCircle.position.setValues(0, upperCircle.radius / 2);
final upperCircleFixtureDef = FixtureDef(upperCircle);
fixturesDefs.add(upperCircleFixtureDef);
final lowerCircle = CircleShape()..radius = 1.6;
lowerCircle.position.setValues(
size.x * -direction,
size.y + 0.8,
);
final lowerCircleFixtureDef = FixtureDef(lowerCircle);
fixturesDefs.add(lowerCircleFixtureDef);
final wallFacingEdge = EdgeShape()
..set(
@ -63,8 +96,6 @@ class Kicker extends BodyComponent with InitialPosition {
),
Vector2(2.5 * direction, size.y - 2),
);
final wallFacingLineFixtureDef = FixtureDef(wallFacingEdge);
fixturesDefs.add(wallFacingLineFixtureDef);
final bottomEdge = EdgeShape()
..set(
@ -75,8 +106,6 @@ class Kicker extends BodyComponent with InitialPosition {
lowerCircle.radius * math.sin(quarterPi),
),
);
final bottomLineFixtureDef = FixtureDef(bottomEdge);
fixturesDefs.add(bottomLineFixtureDef);
final bouncyEdge = EdgeShape()
..set(
@ -92,12 +121,13 @@ class Kicker extends BodyComponent with InitialPosition {
),
);
final bouncyFixtureDef = FixtureDef(
bouncyEdge,
// TODO(alestiago): Play with restitution value once game is bundled.
restitution: 10,
);
fixturesDefs.add(bouncyFixtureDef);
final fixturesDefs = [
FixtureDef(upperCircle),
FixtureDef(lowerCircle),
FixtureDef(wallFacingEdge),
FixtureDef(bottomEdge),
FixtureDef(bouncyEdge, userData: 'bouncy_edge'),
];
// TODO(alestiago): Evaluate if there is value on centering the fixtures.
final centroid = geometry.centroid(
@ -130,25 +160,46 @@ class Kicker extends BodyComponent with InitialPosition {
}
}
class _KickerSpriteComponent extends SpriteComponent with HasGameRef {
_KickerSpriteComponent({required BoardSide side}) : _side = side;
class _KickerSpriteGroupComponent extends SpriteGroupComponent<KickerState>
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;
@override
Future<void> onLoad() async {
await super.onLoad();
// TODO(alestiago): Used cached asset.
final sprite = await gameRef.loadSprite(
(_side.isLeft)
? Assets.images.kicker.left.keyName
: Assets.images.kicker.right.keyName,
);
this.sprite = sprite;
size = sprite.originalSize / 10;
anchor = Anchor.center;
position = Vector2(0.7 * -_side.direction, -2.2);
// TODO(alestiago): Consider refactoring once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
// ignore: public_member_api_docs
parent.bloc.stream.listen((state) => current = state);
final sprites = {
KickerState.lit: Sprite(
gameRef.images.fromCache(
(_side.isLeft)
? Assets.images.kicker.left.lit.keyName
: Assets.images.kicker.right.lit.keyName,
),
),
KickerState.dimmed: Sprite(
gameRef.images.fromCache(
(_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';
/// {@template launch_ramp}
/// A [Blueprint] which creates the [_LaunchRampBase] and
/// [_LaunchRampForegroundRailing].
/// Ramp where the ball is launched from.
/// {@endtemplate}
class LaunchRamp extends Blueprint {
class LaunchRamp extends Component {
/// {@macro launch_ramp}
LaunchRamp()
: super(
components: [
children: [
_LaunchRampBase(),
_LaunchRampForegroundRailing(),
_LaunchRampExit()..initialPosition = Vector2(0.6, -34),
@ -24,20 +23,16 @@ class LaunchRamp extends Blueprint {
);
}
/// {@template launch_ramp_base}
/// Ramp the [Ball] is launched from at the beginning of each ball life.
/// {@endtemplate}
class _LaunchRampBase extends BodyComponent with Layered {
/// {@macro launch_ramp_base}
class _LaunchRampBase extends BodyComponent with Layered, ZIndex {
_LaunchRampBase()
: super(
priority: RenderPriority.launchRamp,
renderBody: false,
children: [
_LaunchRampBackgroundRailingSpriteComponent(),
_LaunchRampBaseSpriteComponent(),
],
) {
zIndex = ZIndexes.launchRamp;
layer = Layer.launcher;
}
@ -140,18 +135,14 @@ class _LaunchRampBackgroundRailingSpriteComponent extends SpriteComponent
}
}
/// {@template launch_ramp_foreground_railing}
/// Foreground railing for the [_LaunchRampBase] to render in front of the
/// [Ball].
/// {@endtemplate}
class _LaunchRampForegroundRailing extends BodyComponent {
/// {@macro launch_ramp_foreground_railing}
class _LaunchRampForegroundRailing extends BodyComponent with ZIndex {
_LaunchRampForegroundRailing()
: super(
priority: RenderPriority.launchRampForegroundRailing,
children: [_LaunchRampForegroundRailingSpriteComponent()],
renderBody: false,
);
) {
zIndex = ZIndexes.launchRampForegroundRailing;
}
List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[];
@ -239,8 +230,8 @@ class _LaunchRampExit extends LayerSensor {
insideLayer: Layer.launcher,
outsideLayer: Layer.board,
orientation: LayerEntranceOrientation.down,
insidePriority: RenderPriority.ballOnLaunchRamp,
outsidePriority: RenderPriority.ballOnBoard,
insideZIndex: ZIndexes.ballOnLaunchRamp,
outsideZIndex: ZIndexes.ballOnBoard,
) {
layer = Layer.launcher;
}

@ -8,9 +8,6 @@ import 'package:flutter/material.dart';
/// [BodyComponent]s with compatible [Layer]s can collide with each other,
/// ignoring others. This compatibility depends on bit masking operation
/// 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}
mixin Layered<T extends Forge2DGame> on BodyComponent<T> {
Layer _layer = Layer.all;

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

@ -1,6 +1,7 @@
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 plunger}
/// [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].
/// {@endtemplate}
class Plunger extends BodyComponent with InitialPosition, Layered {
class Plunger extends BodyComponent with InitialPosition, Layered, ZIndex {
/// {@macro plunger}
Plunger({
required this.compressionDistance,
// TODO(ruimiguel): set to priority +1 over LaunchRamp once all priorities
// are fixed.
}) : super(
priority: RenderPriority.plunger,
renderBody: false,
) {
}) : super(renderBody: false) {
zIndex = ZIndexes.plunger;
layer = Layer.launcher;
}
@ -91,7 +88,7 @@ class Plunger extends BodyComponent with InitialPosition, Layered {
/// from its original [initialPosition].
void release() {
_pullingDownTime = 0;
final velocity = (initialPosition.y - body.position.y) * 5;
final velocity = (initialPosition.y - body.position.y) * 11;
body.linearVelocity = Vector2(0, velocity);
_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:pinball_components/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_flame/pinball_flame.dart';
/// {@template rocket_sprite_component}
/// A [SpriteComponent] for the rocket over [Plunger].
/// {@endtemplate}
class RocketSpriteComponent extends SpriteComponent with HasGameRef {
class RocketSpriteComponent extends SpriteComponent with HasGameRef, ZIndex {
/// {@macro rocket_sprite_component}
RocketSpriteComponent()
: super(
priority: RenderPriority.rocket,
anchor: Anchor.center,
);
RocketSpriteComponent() : super(anchor: Anchor.center) {
zIndex = ZIndexes.rocket;
}
@override
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_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/bumping_behavior.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template slingshots}
/// A [Blueprint] which creates the pair of [Slingshot]s on the right side of
/// the board.
/// A collection of [Slingshot]s.
/// {@endtemplate}
class Slingshots extends Blueprint {
class Slingshots extends Component with ZIndex {
/// {@macro slingshots}
Slingshots()
: super(
components: [
children: [
Slingshot(
length: 5.64,
angle: -0.017,
@ -23,11 +23,13 @@ class Slingshots extends Blueprint {
spritePath: Assets.images.slingshot.lower.keyName,
)..initialPosition = Vector2(24.7, 6.2),
],
);
) {
zIndex = ZIndexes.slingshots;
}
}
/// {@template slingshot}
/// Elastic bumper that bounces the [Ball] off of its straight sides.
/// Elastic bumper that bounces the [Ball] off of its sides.
/// {@endtemplate}
class Slingshot extends BodyComponent with InitialPosition {
/// {@macro slingshot}
@ -38,8 +40,10 @@ class Slingshot extends BodyComponent with InitialPosition {
}) : _length = length,
_angle = angle,
super(
priority: RenderPriority.slingshot,
children: [_SlinghsotSpriteComponent(spritePath, angle: angle)],
children: [
_SlinghsotSpriteComponent(spritePath, angle: angle),
BumpingBehavior(strength: 20),
],
renderBody: false,
);
@ -52,37 +56,27 @@ class Slingshot extends BodyComponent with InitialPosition {
final topCircleShape = CircleShape()..radius = circleRadius;
topCircleShape.position.setValues(0, -_length / 2);
final topCircleFixtureDef = FixtureDef(topCircleShape);
final bottomCircleShape = CircleShape()..radius = circleRadius;
bottomCircleShape.position.setValues(0, _length / 2);
final bottomCircleFixtureDef = FixtureDef(bottomCircleShape);
final leftEdgeShape = EdgeShape()
..set(
Vector2(circleRadius, _length / 2),
Vector2(circleRadius, -_length / 2),
);
final leftEdgeShapeFixtureDef = FixtureDef(
leftEdgeShape,
restitution: 5,
);
final rightEdgeShape = EdgeShape()
..set(
Vector2(-circleRadius, _length / 2),
Vector2(-circleRadius, -_length / 2),
);
final rightEdgeShapeFixtureDef = FixtureDef(
rightEdgeShape,
restitution: 5,
);
return [
topCircleFixtureDef,
bottomCircleFixtureDef,
leftEdgeShapeFixtureDef,
rightEdgeShapeFixtureDef,
FixtureDef(topCircleShape),
FixtureDef(bottomCircleShape),
FixtureDef(leftEdgeShape),
FixtureDef(rightEdgeShape),
];
}

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

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

Loading…
Cancel
Save