fix: merge with main fixed

pull/359/head
RuiAlonso 3 years ago
commit 8e41a7db93

@ -17,15 +17,15 @@ class App extends StatelessWidget {
Key? key,
required AuthenticationRepository authenticationRepository,
required LeaderboardRepository leaderboardRepository,
required PinballAudio pinballAudio,
required PinballPlayer pinballPlayer,
}) : _authenticationRepository = authenticationRepository,
_leaderboardRepository = leaderboardRepository,
_pinballAudio = pinballAudio,
_pinballPlayer = pinballPlayer,
super(key: key);
final AuthenticationRepository _authenticationRepository;
final LeaderboardRepository _leaderboardRepository;
final PinballAudio _pinballAudio;
final PinballPlayer _pinballPlayer;
@override
Widget build(BuildContext context) {
@ -33,7 +33,7 @@ class App extends StatelessWidget {
providers: [
RepositoryProvider.value(value: _authenticationRepository),
RepositoryProvider.value(value: _leaderboardRepository),
RepositoryProvider.value(value: _pinballAudio),
RepositoryProvider.value(value: _pinballPlayer),
],
child: MultiBlocProvider(
providers: [

@ -0,0 +1,35 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
/// Spawns a new [Ball] into the game when all balls are lost and still
/// [GameStatus.playing].
class BallSpawningBehavior extends Component
with FlameBlocListenable<GameBloc, GameState>, HasGameRef {
@override
bool listenWhen(GameState? previousState, GameState newState) {
if (!newState.status.isPlaying) return false;
final startedGame = previousState?.status.isWaiting ?? true;
final lostRound =
(previousState?.rounds ?? newState.rounds + 1) > newState.rounds;
return startedGame || lostRound;
}
@override
void onNewState(GameState state) {
final plunger = gameRef.descendants().whereType<Plunger>().single;
final canvas = gameRef.descendants().whereType<ZCanvasComponent>().single;
final characterTheme = readProvider<CharacterTheme>();
final ball = ControlledBall.launch(characterTheme: characterTheme)
..initialPosition = Vector2(
plunger.body.position.x,
plunger.body.position.y - Ball.size.y,
);
canvas.add(ball);
}
}

@ -1,2 +1,4 @@
export 'bumper_noisy_behavior.dart';
export 'ball_spawning_behavior.dart';
export 'bumper_noise_behavior.dart';
export 'camera_focusing_behavior.dart';
export 'scoring_behavior.dart';

@ -1,14 +1,13 @@
// ignore_for_file: public_member_api_docs
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/pinball_game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_flame/pinball_flame.dart';
class BumperNoisyBehavior extends ContactBehavior with HasGameRef<PinballGame> {
class BumperNoiseBehavior extends ContactBehavior {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
gameRef.audio.bumper();
readProvider<PinballPlayer>().play(PinballAudio.bumper);
}
}

@ -0,0 +1,83 @@
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template focus_data}
/// Defines a [Camera] focus point.
/// {@endtemplate}
class FocusData {
/// {@template focus_data}
FocusData({
required this.zoom,
required this.position,
});
/// The amount of zoom.
final double zoom;
/// The position of the camera.
final Vector2 position;
}
/// Changes the game focus when the [GameBloc] status changes.
class CameraFocusingBehavior extends Component
with FlameBlocListenable<GameBloc, GameState>, HasGameRef {
late final Map<String, FocusData> _foci;
@override
bool listenWhen(GameState? previousState, GameState newState) {
return previousState?.status != newState.status;
}
@override
void onNewState(GameState state) {
switch (state.status) {
case GameStatus.waiting:
break;
case GameStatus.playing:
_zoom(_foci['game']!);
break;
case GameStatus.gameOver:
_zoom(_foci['backbox']!);
break;
}
}
@override
Future<void> onLoad() async {
await super.onLoad();
_foci = {
'game': FocusData(
zoom: gameRef.size.y / 16,
position: Vector2(0, -7.8),
),
'waiting': FocusData(
zoom: gameRef.size.y / 18,
position: Vector2(0, -112),
),
'backbox': FocusData(
zoom: gameRef.size.y / 10,
position: Vector2(0, -111),
),
};
_snap(_foci['waiting']!);
}
void _snap(FocusData data) {
gameRef.camera
..speed = 100
..followVector2(data.position)
..zoom = data.zoom;
}
void _zoom(FocusData data) {
final zoom = CameraZoom(value: data.zoom);
zoom.completed.then((_) {
gameRef.camera.moveTo(data.position);
});
add(zoom);
}
}

@ -2,6 +2,7 @@
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
@ -12,7 +13,8 @@ import 'package:pinball_flame/pinball_flame.dart';
///
/// The behavior removes itself after the duration.
/// {@endtemplate}
class ScoringBehavior extends Component with HasGameRef<PinballGame> {
class ScoringBehavior extends Component
with HasGameRef, FlameBlocReader<GameBloc, GameState> {
/// {@macto scoring_behavior}
ScoringBehavior({
required Points points,
@ -39,7 +41,8 @@ class ScoringBehavior extends Component with HasGameRef<PinballGame> {
@override
Future<void> onLoad() async {
gameRef.read<GameBloc>().add(Scored(points: _points.value));
await super.onLoad();
bloc.add(Scored(points: _points.value));
final canvas = gameRef.descendants().whereType<ZCanvasComponent>().single;
await canvas.add(
ScoreComponent(
@ -54,8 +57,7 @@ class ScoringBehavior extends Component with HasGameRef<PinballGame> {
/// {@template scoring_contact_behavior}
/// Adds points to the score when the [Ball] contacts the [parent].
/// {@endtemplate}
class ScoringContactBehavior extends ContactBehavior
with HasGameRef<PinballGame> {
class ScoringContactBehavior extends ContactBehavior {
/// {@macro scoring_contact_behavior}
ScoringContactBehavior({
required Points points,

@ -14,6 +14,16 @@ class GameBloc extends Bloc<GameEvent, GameState> {
on<MultiplierIncreased>(_onIncreasedMultiplier);
on<BonusActivated>(_onBonusActivated);
on<SparkyTurboChargeActivated>(_onSparkyTurboChargeActivated);
on<GameOver>(_onGameOver);
on<GameStarted>(_onGameStarted);
}
void _onGameStarted(GameStarted _, Emitter emit) {
emit(state.copyWith(status: GameStatus.playing));
}
void _onGameOver(GameOver _, Emitter emit) {
emit(state.copyWith(status: GameStatus.gameOver));
}
void _onRoundLost(RoundLost event, Emitter emit) {
@ -26,12 +36,13 @@ class GameBloc extends Bloc<GameEvent, GameState> {
roundScore: 0,
multiplier: 1,
rounds: roundsLeft,
status: roundsLeft == 0 ? GameStatus.gameOver : state.status,
),
);
}
void _onScored(Scored event, Emitter emit) {
if (!state.isGameOver) {
if (state.status.isPlaying) {
emit(
state.copyWith(roundScore: state.roundScore + event.points),
);
@ -39,7 +50,7 @@ class GameBloc extends Bloc<GameEvent, GameState> {
}
void _onIncreasedMultiplier(MultiplierIncreased event, Emitter emit) {
if (!state.isGameOver) {
if (state.status.isPlaying) {
emit(
state.copyWith(
multiplier: math.min(state.multiplier + 1, 6),

@ -59,3 +59,17 @@ class MultiplierIncreased extends GameEvent {
@override
List<Object?> get props => [];
}
class GameStarted extends GameEvent {
const GameStarted();
@override
List<Object?> get props => [];
}
class GameOver extends GameEvent {
const GameOver();
@override
List<Object?> get props => [];
}

@ -20,6 +20,18 @@ enum GameBonus {
androidSpaceship,
}
enum GameStatus {
waiting,
playing,
gameOver,
}
extension GameStatusX on GameStatus {
bool get isWaiting => this == GameStatus.waiting;
bool get isPlaying => this == GameStatus.playing;
bool get isGameOver => this == GameStatus.gameOver;
}
/// {@template game_state}
/// Represents the state of the pinball game.
/// {@endtemplate}
@ -31,13 +43,15 @@ class GameState extends Equatable {
required this.multiplier,
required this.rounds,
required this.bonusHistory,
required this.status,
}) : assert(totalScore >= 0, "TotalScore can't be negative"),
assert(roundScore >= 0, "Round score can't be negative"),
assert(multiplier > 0, 'Multiplier must be greater than zero'),
assert(rounds >= 0, "Number of rounds can't be negative");
const GameState.initial()
: totalScore = 0,
: status = GameStatus.waiting,
totalScore = 0,
roundScore = 0,
multiplier = 1,
rounds = 3,
@ -65,8 +79,7 @@ class GameState extends Equatable {
/// PinballGame.
final List<GameBonus> bonusHistory;
/// Determines when the game is over.
bool get isGameOver => rounds == 0;
final GameStatus status;
/// The score displayed at the game.
int get displayScore => roundScore + totalScore;
@ -78,6 +91,7 @@ class GameState extends Equatable {
int? balls,
int? rounds,
List<GameBonus>? bonusHistory,
GameStatus? status,
}) {
assert(
totalScore == null || totalScore >= this.totalScore,
@ -90,6 +104,7 @@ class GameState extends Equatable {
multiplier: multiplier ?? this.multiplier,
rounds: rounds ?? this.rounds,
bonusHistory: bonusHistory ?? this.bonusHistory,
status: status ?? this.status,
);
}
@ -100,5 +115,6 @@ class GameState extends Equatable {
multiplier,
rounds,
bonusHistory,
status,
];
}

@ -35,21 +35,21 @@ class AndroidAcres extends Component {
AndroidBumper.a(
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(-25, 1.3),
)..initialPosition = Vector2(-25.2, 1.5),
AndroidBumper.b(
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(-32.8, -9.2),
)..initialPosition = Vector2(-32.9, -9.3),
AndroidBumper.cow(
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(-20.5, -13.8),
)..initialPosition = Vector2(-20.7, -13),
AndroidSpaceshipBonusBehavior(),
],
);

@ -1,11 +1,12 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// Adds a [GameBonus.androidSpaceship] when [AndroidSpaceship] has a bonus.
class AndroidSpaceshipBonusBehavior extends Component
with HasGameRef<PinballGame>, ParentIsA<AndroidAcres> {
with ParentIsA<AndroidAcres>, FlameBlocReader<GameBloc, GameState> {
@override
void onMount() {
super.onMount();
@ -18,9 +19,7 @@ class AndroidSpaceshipBonusBehavior extends Component
final listenWhen = state == AndroidSpaceshipState.withBonus;
if (!listenWhen) return;
gameRef
.read<GameBloc>()
.add(const BonusActivated(GameBonus.androidSpaceship));
bloc.add(const BonusActivated(GameBonus.androidSpaceship));
androidSpaceship.bloc.onBonusAwarded();
});
}

@ -3,15 +3,13 @@ import 'dart:async';
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template ramp_bonus_behavior}
/// Increases the score when a [Ball] is shot 10 times into the [SpaceshipRamp].
/// {@endtemplate}
class RampBonusBehavior extends Component
with ParentIsA<SpaceshipRamp>, HasGameRef<PinballGame> {
class RampBonusBehavior extends Component with ParentIsA<SpaceshipRamp> {
/// {@macro ramp_bonus_behavior}
RampBonusBehavior({
required Points points,

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flutter/cupertino.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
@ -11,7 +12,7 @@ import 'package:pinball_flame/pinball_flame.dart';
/// Increases the score when a [Ball] is shot into the [SpaceshipRamp].
/// {@endtemplate}
class RampShotBehavior extends Component
with ParentIsA<SpaceshipRamp>, HasGameRef<PinballGame> {
with ParentIsA<SpaceshipRamp>, FlameBlocReader<GameBloc, GameState> {
/// {@macro ramp_shot_behavior}
RampShotBehavior({
required Points points,
@ -43,7 +44,7 @@ class RampShotBehavior extends Component
final achievedOneMillionPoints = state.hits % 10 == 0;
if (!achievedOneMillionPoints) {
gameRef.read<GameBloc>().add(const MultiplierIncreased());
bloc.add(const MultiplierIncreased());
parent.add(
ScoringBehavior(

@ -1,38 +1,89 @@
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/game/components/backbox/bloc/backbox_bloc.dart';
import 'package:pinball/game/components/backbox/displays/displays.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' hide Assets;
/// {@template backbox}
/// The [Backbox] of the pinball machine.
/// {@endtemplate}
class Backbox extends PositionComponent with HasGameRef, ZIndex {
class Backbox extends PositionComponent with ZIndex {
/// {@macro backbox}
Backbox()
: super(
position: Vector2(0, -87),
anchor: Anchor.bottomCenter,
children: [
_BackboxSpriteComponent(),
],
) {
Backbox({
required LeaderboardRepository leaderboardRepository,
}) : _bloc = BackboxBloc(leaderboardRepository: leaderboardRepository);
/// {@macro backbox}
@visibleForTesting
Backbox.test({
required BackboxBloc bloc,
}) : _bloc = bloc;
late final Component _display;
final BackboxBloc _bloc;
late StreamSubscription<BackboxState> _subscription;
@override
Future<void> onLoad() async {
position = Vector2(0, -87);
anchor = Anchor.bottomCenter;
zIndex = ZIndexes.backbox;
await add(_BackboxSpriteComponent());
await add(_display = Component());
_subscription = _bloc.stream.listen((state) {
_display.children.removeWhere((_) => true);
_build(state);
});
}
@override
void onRemove() {
super.onRemove();
_subscription.cancel();
}
void _build(BackboxState state) {
if (state is LoadingState) {
_display.add(LoadingDisplay());
} else if (state is InitialsFormState) {
_display.add(
InitialsInputDisplay(
score: state.score,
characterIconPath: state.character.leaderboardIcon.keyName,
onSubmit: (initials) {
_bloc.add(
PlayerInitialsSubmitted(
score: state.score,
initials: initials,
character: state.character,
),
);
},
),
);
} else if (state is InitialsSuccessState) {
_display.add(InitialsSubmissionSuccessDisplay());
} else if (state is InitialsFailureState) {
_display.add(InitialsSubmissionFailureDisplay());
}
}
/// Puts [InitialsInputDisplay] on the [Backbox].
Future<void> initialsInput({
void requestInitials({
required int score,
required String characterIconPath,
InitialsOnSubmit? onSubmit,
}) async {
removeAll(children.where((child) => child is! _BackboxSpriteComponent));
await add(
InitialsInputDisplay(
required CharacterTheme character,
}) {
_bloc.add(
PlayerInitialsRequested(
score: score,
characterIconPath: characterIconPath,
onSubmit: onSubmit,
character: character,
),
);
}

@ -0,0 +1,56 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/leaderboard/models/leader_board_entry.dart';
import 'package:pinball_theme/pinball_theme.dart';
part 'backbox_event.dart';
part 'backbox_state.dart';
/// {@template backbox_bloc}
/// Bloc which manages the Backbox display.
/// {@endtemplate}
class BackboxBloc extends Bloc<BackboxEvent, BackboxState> {
/// {@macro backbox_bloc}
BackboxBloc({
required LeaderboardRepository leaderboardRepository,
}) : _leaderboardRepository = leaderboardRepository,
super(LoadingState()) {
on<PlayerInitialsRequested>(_onPlayerInitialsRequested);
on<PlayerInitialsSubmitted>(_onPlayerInitialsSubmitted);
}
final LeaderboardRepository _leaderboardRepository;
void _onPlayerInitialsRequested(
PlayerInitialsRequested event,
Emitter<BackboxState> emit,
) {
emit(
InitialsFormState(
score: event.score,
character: event.character,
),
);
}
Future<void> _onPlayerInitialsSubmitted(
PlayerInitialsSubmitted event,
Emitter<BackboxState> emit,
) async {
try {
emit(LoadingState());
await _leaderboardRepository.addLeaderboardEntry(
LeaderboardEntryData(
playerInitials: event.initials,
score: event.score,
character: event.character.toType,
),
);
emit(InitialsSuccessState());
} catch (error, stackTrace) {
addError(error, stackTrace);
emit(InitialsFailureState());
}
}
}

@ -0,0 +1,53 @@
part of 'backbox_bloc.dart';
/// {@template backbox_event}
/// Base class for backbox events.
/// {@endtemplate}
abstract class BackboxEvent extends Equatable {
/// {@macro backbox_event}
const BackboxEvent();
}
/// {@template player_initials_requested}
/// Event that triggers the user initials display.
/// {@endtemplate}
class PlayerInitialsRequested extends BackboxEvent {
/// {@macro player_initials_requested}
const PlayerInitialsRequested({
required this.score,
required this.character,
});
/// Player's score.
final int score;
/// Player's character.
final CharacterTheme character;
@override
List<Object?> get props => [score, character];
}
/// {@template player_initials_submitted}
/// Event that submits the user score and initials.
/// {@endtemplate}
class PlayerInitialsSubmitted extends BackboxEvent {
/// {@macro player_initials_submitted}
const PlayerInitialsSubmitted({
required this.score,
required this.initials,
required this.character,
});
/// Player's score.
final int score;
/// Player's initials.
final String initials;
/// Player's character.
final CharacterTheme character;
@override
List<Object?> get props => [score, initials, character];
}

@ -0,0 +1,59 @@
part of 'backbox_bloc.dart';
/// {@template backbox_state}
/// The base state for all [BackboxState].
/// {@endtemplate backbox_state}
abstract class BackboxState extends Equatable {
/// {@macro backbox_state}
const BackboxState();
}
/// Loading state for the backbox.
class LoadingState extends BackboxState {
@override
List<Object?> get props => [];
}
/// State when the leaderboard was successfully loaded.
class LeaderboardSuccessState extends BackboxState {
@override
List<Object?> get props => [];
}
/// State when the leaderboard failed to load.
class LeaderboardFailureState extends BackboxState {
@override
List<Object?> get props => [];
}
/// {@template initials_form_state}
/// State when the user is inputting their initials.
/// {@endtemplate}
class InitialsFormState extends BackboxState {
/// {@macro initials_form_state}
const InitialsFormState({
required this.score,
required this.character,
}) : super();
/// Player's score.
final int score;
/// Player's character.
final CharacterTheme character;
@override
List<Object?> get props => [score, character];
}
/// State when the leaderboard was successfully loaded.
class InitialsSuccessState extends BackboxState {
@override
List<Object?> get props => [];
}
/// State when the initials submission failed.
class InitialsFailureState extends BackboxState {
@override
List<Object?> get props => [];
}

@ -1,2 +1,5 @@
export 'initials_input_display.dart';
export 'info_display.dart';
export 'initials_input_display.dart';
export 'initials_submission_failure_display.dart';
export 'initials_submission_success_display.dart';
export 'loading_display.dart';

@ -4,7 +4,7 @@ import 'dart:math';
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_ui/pinball_ui.dart';
@ -59,7 +59,7 @@ class InitialsInputDisplay extends Component with HasGameRef {
await add(
InitialsLetterPrompt(
position: Vector2(
11.4 + (2.3 * i),
10.8 + (2.5 * i),
-20,
),
hasFocus: i == 0,
@ -103,8 +103,7 @@ class InitialsInputDisplay extends Component with HasGameRef {
}
}
class _ScoreLabelTextComponent extends TextComponent
with HasGameRef<PinballGame> {
class _ScoreLabelTextComponent extends TextComponent {
_ScoreLabelTextComponent()
: super(
anchor: Anchor.centerLeft,
@ -119,7 +118,7 @@ class _ScoreLabelTextComponent extends TextComponent
@override
Future<void> onLoad() async {
await super.onLoad();
text = gameRef.l10n.score;
text = readProvider<AppLocalizations>().score;
}
}
@ -133,12 +132,11 @@ class _ScoreTextComponent extends TextComponent {
);
}
class _NameLabelTextComponent extends TextComponent
with HasGameRef<PinballGame> {
class _NameLabelTextComponent extends TextComponent {
_NameLabelTextComponent()
: super(
anchor: Anchor.center,
position: Vector2(11.4, -24),
position: Vector2(10.8, -24),
textRenderer: _bodyTextPaint.copyWith(
(style) => style.copyWith(
color: PinballColors.red,
@ -149,7 +147,7 @@ class _NameLabelTextComponent extends TextComponent
@override
Future<void> onLoad() async {
await super.onLoad();
text = gameRef.l10n.name;
text = readProvider<AppLocalizations>().name;
}
}
@ -158,7 +156,7 @@ class _CharacterIconSpriteComponent extends SpriteComponent with HasGameRef {
: _characterIconPath = characterIconPath,
super(
anchor: Anchor.center,
position: Vector2(8.4, -20),
position: Vector2(7.6, -20),
);
final String _characterIconPath;
@ -241,8 +239,9 @@ class InitialsLetterPrompt extends PositionComponent {
bool _cycle(bool up) {
if (_hasFocus) {
final newCharCode =
min(max(_charIndex + (up ? 1 : -1), 0), _alphabetLength);
var newCharCode = _charIndex + (up ? -1 : 1);
if (newCharCode < 0) newCharCode = _alphabetLength;
if (newCharCode > _alphabetLength) newCharCode = 0;
_input.text = String.fromCharCode(_alphabetCode + newCharCode);
_charIndex = newCharCode;
@ -299,8 +298,7 @@ class _InstructionsComponent extends PositionComponent with HasGameRef {
);
}
class _EnterInitialsTextComponent extends TextComponent
with HasGameRef<PinballGame> {
class _EnterInitialsTextComponent extends TextComponent {
_EnterInitialsTextComponent()
: super(
anchor: Anchor.center,
@ -311,11 +309,11 @@ class _EnterInitialsTextComponent extends TextComponent
@override
Future<void> onLoad() async {
await super.onLoad();
text = gameRef.l10n.enterInitials;
text = readProvider<AppLocalizations>().enterInitials;
}
}
class _ArrowsTextComponent extends TextComponent with HasGameRef<PinballGame> {
class _ArrowsTextComponent extends TextComponent {
_ArrowsTextComponent()
: super(
anchor: Anchor.center,
@ -330,12 +328,11 @@ class _ArrowsTextComponent extends TextComponent with HasGameRef<PinballGame> {
@override
Future<void> onLoad() async {
await super.onLoad();
text = gameRef.l10n.arrows;
text = readProvider<AppLocalizations>().arrows;
}
}
class _AndPressTextComponent extends TextComponent
with HasGameRef<PinballGame> {
class _AndPressTextComponent extends TextComponent {
_AndPressTextComponent()
: super(
anchor: Anchor.center,
@ -346,12 +343,11 @@ class _AndPressTextComponent extends TextComponent
@override
Future<void> onLoad() async {
await super.onLoad();
text = gameRef.l10n.andPress;
text = readProvider<AppLocalizations>().andPress;
}
}
class _EnterReturnTextComponent extends TextComponent
with HasGameRef<PinballGame> {
class _EnterReturnTextComponent extends TextComponent {
_EnterReturnTextComponent()
: super(
anchor: Anchor.center,
@ -366,12 +362,11 @@ class _EnterReturnTextComponent extends TextComponent
@override
Future<void> onLoad() async {
await super.onLoad();
text = gameRef.l10n.enterReturn;
text = readProvider<AppLocalizations>().enterReturn;
}
}
class _ToSubmitTextComponent extends TextComponent
with HasGameRef<PinballGame> {
class _ToSubmitTextComponent extends TextComponent {
_ToSubmitTextComponent()
: super(
anchor: Anchor.center,
@ -382,6 +377,6 @@ class _ToSubmitTextComponent extends TextComponent
@override
Future<void> onLoad() async {
await super.onLoad();
text = gameRef.l10n.toSubmit;
text = readProvider<AppLocalizations>().toSubmit;
}
}

@ -0,0 +1,27 @@
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_ui/pinball_ui.dart';
final _bodyTextPaint = TextPaint(
style: const TextStyle(
fontSize: 3,
color: PinballColors.white,
fontFamily: PinballFonts.pixeloidSans,
),
);
/// {@template initials_submission_failure_display}
/// [Backbox] display for when a failure occurs during initials submission.
/// {@endtemplate}
class InitialsSubmissionFailureDisplay extends TextComponent {
@override
Future<void> onLoad() async {
await super.onLoad();
position = Vector2(0, -10);
anchor = Anchor.center;
text = 'Failure!';
textRenderer = _bodyTextPaint;
}
}

@ -0,0 +1,27 @@
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_ui/pinball_ui.dart';
final _bodyTextPaint = TextPaint(
style: const TextStyle(
fontSize: 3,
color: PinballColors.white,
fontFamily: PinballFonts.pixeloidSans,
),
);
/// {@template initials_submission_success_display}
/// [Backbox] display for initials successfully submitted.
/// {@endtemplate}
class InitialsSubmissionSuccessDisplay extends TextComponent {
@override
Future<void> onLoad() async {
await super.onLoad();
position = Vector2(0, -10);
anchor = Anchor.center;
text = 'Success!';
textRenderer = _bodyTextPaint;
}
}

@ -0,0 +1,49 @@
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_ui/pinball_ui.dart';
final _bodyTextPaint = TextPaint(
style: const TextStyle(
fontSize: 3,
color: PinballColors.white,
fontFamily: PinballFonts.pixeloidSans,
),
);
/// {@template loading_display}
/// Display used to show the loading animation.
/// {@endtemplate}
class LoadingDisplay extends TextComponent {
/// {@template loading_display}
LoadingDisplay();
late final String _label;
@override
Future<void> onLoad() async {
await super.onLoad();
position = Vector2(0, -10);
anchor = Anchor.center;
text = _label = readProvider<AppLocalizations>().loading;
textRenderer = _bodyTextPaint;
await add(
TimerComponent(
period: 1,
repeat: true,
onTick: () {
final index = text.indexOf('.');
if (index != -1 && text.substring(index).length == 3) {
text = _label;
} else {
text = '$text.';
}
},
),
);
}
}

@ -39,14 +39,14 @@ class _BottomGroupSide extends Component {
@override
Future<void> onLoad() async {
final direction = _side.direction;
final centerXAdjustment = _side.isLeft ? 0 : -6.66;
final centerXAdjustment = _side.isLeft ? -0.45 : -6.8;
final flipper = ControlledFlipper(
side: _side,
)..initialPosition = Vector2((11.8 * direction) + centerXAdjustment, 43.6);
)..initialPosition = Vector2((11.6 * direction) + centerXAdjustment, 43.6);
final baseboard = Baseboard(side: _side)
..initialPosition = Vector2(
(25.58 * direction) + centerXAdjustment,
(25.38 * direction) + centerXAdjustment,
28.71,
);
final kicker = Kicker(
@ -56,7 +56,7 @@ class _BottomGroupSide extends Component {
..applyTo(['bouncy_edge']),
],
)..initialPosition = Vector2(
(22.64 * direction) + centerXAdjustment,
(22.44 * direction) + centerXAdjustment,
25.1,
);

@ -1,93 +0,0 @@
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// Adds helpers methods to Flame's [Camera].
extension CameraX on Camera {
/// Instantly apply the point of focus to the [Camera].
void snapToFocus(FocusData data) {
followVector2(data.position);
zoom = data.zoom;
}
/// Returns a [CameraZoom] that can be added to a [FlameGame].
CameraZoom focusToCameraZoom(FocusData data) {
final zoom = CameraZoom(value: data.zoom);
zoom.completed.then((_) {
moveTo(data.position);
});
return zoom;
}
}
/// {@template focus_data}
/// Model class that defines a focus point of the camera.
/// {@endtemplate}
class FocusData {
/// {@template focus_data}
FocusData({
required this.zoom,
required this.position,
});
/// The amount of zoom.
final double zoom;
/// The position of the camera.
final Vector2 position;
}
/// {@template camera_controller}
/// A [Component] that controls its game camera focus.
/// {@endtemplate}
class CameraController extends ComponentController<FlameGame> {
/// {@macro camera_controller}
CameraController(FlameGame component) : super(component) {
final gameZoom = component.size.y / 16;
final waitingBackboxZoom = component.size.y / 18;
final gameOverBackboxZoom = component.size.y / 10;
gameFocus = FocusData(
zoom: gameZoom,
position: Vector2(0, -7.8),
);
waitingBackboxFocus = FocusData(
zoom: waitingBackboxZoom,
position: Vector2(0, -112),
);
gameOverBackboxFocus = FocusData(
zoom: gameOverBackboxZoom,
position: Vector2(0, -111),
);
// Game starts with the camera focused on the [Backbox].
component.camera
..speed = 100
..snapToFocus(waitingBackboxFocus);
}
/// Holds the data for the game focus point.
late final FocusData gameFocus;
/// Holds the data for the waiting backbox focus point.
late final FocusData waitingBackboxFocus;
/// Holds the data for the game over backbox focus point.
late final FocusData gameOverBackboxFocus;
/// Move the camera focus to the game board.
void focusOnGame() {
component.add(component.camera.focusToCameraZoom(gameFocus));
}
/// Move the camera focus to the waiting backbox.
void focusOnWaitingBackbox() {
component.add(component.camera.focusToCameraZoom(waitingBackboxFocus));
}
/// Move the camera focus to the game over backbox.
void focusOnGameOverBackbox() {
component.add(component.camera.focusToCameraZoom(gameOverBackboxFocus));
}
}

@ -1,14 +1,13 @@
export 'android_acres/android_acres.dart';
export 'backbox/backbox.dart';
export 'bottom_group.dart';
export 'camera_controller.dart';
export 'controlled_ball.dart';
export 'controlled_flipper.dart';
export 'controlled_plunger.dart';
export 'dino_desert/dino_desert.dart';
export 'drain.dart';
export 'drain/drain.dart';
export 'flutter_forest/flutter_forest.dart';
export 'game_flow_controller.dart';
export 'game_bloc_status_listener.dart';
export 'google_word/google_word.dart';
export 'launcher.dart';
export 'multiballs/multiballs.dart';

@ -1,6 +1,7 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
@ -22,9 +23,7 @@ class ControlledBall extends Ball with Controls<BallController> {
zIndex = ZIndexes.ballOnLaunchRamp;
}
/// {@template bonus_ball}
/// {@macro controlled_ball}
/// {@endtemplate}
ControlledBall.bonus({
required CharacterTheme characterTheme,
}) : super(assetPath: characterTheme.ball.keyName) {
@ -43,20 +42,14 @@ class ControlledBall extends Ball with Controls<BallController> {
/// Controller attached to a [Ball] that handles its game related logic.
/// {@endtemplate}
class BallController extends ComponentController<Ball>
with HasGameRef<PinballGame> {
with FlameBlocReader<GameBloc, GameState> {
/// {@macro ball_controller}
BallController(Ball ball) : super(ball);
/// Event triggered when the ball is lost.
// TODO(alestiago): Refactor using behaviors.
void lost() {
component.shouldRemove = true;
}
/// Stops the [Ball] inside of the [SparkyComputer] while the turbo charge
/// sequence runs, then boosts the ball out of the computer.
Future<void> turboCharge() async {
gameRef.read<GameBloc>().add(const SparkyTurboChargeActivated());
bloc.add(const SparkyTurboChargeActivated());
component.stop();
// TODO(alestiago): Refactor this hard coded duration once the following is
@ -70,13 +63,4 @@ class BallController extends ComponentController<Ball>
BallTurboChargingBehavior(impulse: Vector2(40, 110)),
);
}
@override
void onRemove() {
super.onRemove();
final noBallsLeft = gameRef.descendants().whereType<Ball>().isEmpty;
if (noBallsLeft) {
gameRef.read<GameBloc>().add(const RoundLost());
}
}
}

@ -21,7 +21,7 @@ class ControlledFlipper extends Flipper with Controls<FlipperController> {
/// A [ComponentController] that controls a [Flipper]s movement.
/// {@endtemplate}
class FlipperController extends ComponentController<Flipper>
with KeyboardHandler, BlocComponent<GameBloc, GameState> {
with KeyboardHandler, FlameBlocReader<GameBloc, GameState> {
/// {@macro flipper_controller}
FlipperController(Flipper flipper)
: _keys = flipper.side.flipperKeys,
@ -37,7 +37,7 @@ class FlipperController extends ComponentController<Flipper>
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
if (state?.isGameOver ?? false) return true;
if (!bloc.state.status.isPlaying) return true;
if (!_keys.contains(event.logicalKey)) return true;
if (event is RawKeyDownEvent) {

@ -2,6 +2,7 @@ import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flutter/services.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
@ -14,13 +15,36 @@ class ControlledPlunger extends Plunger with Controls<PlungerController> {
: super(compressionDistance: compressionDistance) {
controller = PlungerController(this);
}
@override
void release() {
super.release();
add(PlungerNoiseBehavior());
}
}
/// A behavior attached to the plunger when it launches the ball which plays the
/// related sound effects.
class PlungerNoiseBehavior extends Component {
@override
Future<void> onLoad() async {
await super.onLoad();
readProvider<PinballPlayer>().play(PinballAudio.launcher);
}
@override
void update(double dt) {
super.update(dt);
removeFromParent();
}
}
/// {@template plunger_controller}
/// A [ComponentController] that controls a [Plunger]s movement.
/// {@endtemplate}
class PlungerController extends ComponentController<Plunger>
with KeyboardHandler, BlocComponent<GameBloc, GameState> {
with KeyboardHandler, FlameBlocReader<GameBloc, GameState> {
/// {@macro plunger_controller}
PlungerController(Plunger plunger) : super(plunger);
@ -38,7 +62,7 @@ class PlungerController extends ComponentController<Plunger>
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
if (state?.isGameOver ?? false) return true;
if (bloc.state.status.isGameOver) return true;
if (!_keys.contains(event.logicalKey)) return true;
if (event is RawKeyDownEvent) {

@ -1,11 +1,12 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// Adds a [GameBonus.dinoChomp] when a [Ball] is chomped by the [ChromeDino].
class ChromeDinoBonusBehavior extends Component
with HasGameRef<PinballGame>, ParentIsA<DinoDesert> {
with ParentIsA<DinoDesert>, FlameBlocReader<GameBloc, GameState> {
@override
void onMount() {
super.onMount();
@ -18,7 +19,7 @@ class ChromeDinoBonusBehavior extends Component
final listenWhen = state.status == ChromeDinoStatus.chomping;
if (!listenWhen) return;
gameRef.read<GameBloc>().add(const BonusActivated(GameBonus.dinoChomp));
bloc.add(const BonusActivated(GameBonus.dinoChomp));
});
}
}

@ -20,7 +20,7 @@ class DinoDesert extends Component {
ScoringContactBehavior(points: Points.twoHundredThousand)
..applyTo(['inside_mouth']),
],
)..initialPosition = Vector2(12.6, -6.9),
)..initialPosition = Vector2(12.2, -6.9),
_BarrierBehindDino(),
DinoWalls(),
Slingshots(),

@ -1,34 +0,0 @@
import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template drain}
/// Area located at the bottom of the board to detect when a [Ball] is lost.
/// {@endtemplate}
// TODO(allisonryan0002): move to components package when possible.
class Drain extends BodyComponent with ContactCallbacks {
/// {@macro drain}
Drain() : super(renderBody: false);
@override
Body createBody() {
final shape = EdgeShape()
..set(
BoardDimensions.bounds.bottomLeft.toVector2(),
BoardDimensions.bounds.bottomRight.toVector2(),
);
final fixtureDef = FixtureDef(shape, isSensor: true);
final bodyDef = BodyDef(userData: this);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
// TODO(allisonryan0002): move this to ball.dart when BallLost is removed.
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! ControlledBall) return;
other.controller.lost();
}
}

@ -0,0 +1 @@
export 'draining_behavior.dart';

@ -0,0 +1,25 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.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';
/// Handles removing a [Ball] from the game.
class DrainingBehavior extends ContactBehavior<Drain> with HasGameRef {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
other.removeFromParent();
final ballsLeft = gameRef.descendants().whereType<Ball>().length;
if (ballsLeft - 1 == 0) {
ancestors()
.whereType<FlameBlocProvider<GameBloc, GameState>>()
.first
.bloc
.add(const RoundLost());
}
}
}

@ -0,0 +1,36 @@
import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/foundation.dart';
import 'package:pinball/game/components/drain/behaviors/behaviors.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template drain}
/// Area located at the bottom of the board.
///
/// Its [DrainingBehavior] handles removing a [Ball] from the game.
/// {@endtemplate}
class Drain extends BodyComponent with ContactCallbacks {
/// {@macro drain}
Drain()
: super(
renderBody: false,
children: [DrainingBehavior()],
);
/// Creates a [Drain] without any children.
///
/// This can be used for testing a [Drain]'s behaviors in isolation.
@visibleForTesting
Drain.test();
@override
Body createBody() {
final shape = EdgeShape()
..set(
BoardDimensions.bounds.bottomLeft.toVector2(),
BoardDimensions.bounds.bottomRight.toVector2(),
);
final fixtureDef = FixtureDef(shape, isSensor: true);
return world.createBody(BodyDef())..createFixture(fixtureDef);
}
}

@ -1,7 +1,9 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
/// Bonus obtained at the [FlutterForest].
///
@ -9,7 +11,10 @@ import 'package:pinball_flame/pinball_flame.dart';
/// progresses. When the [Signpost] fully progresses, the [GameBonus.dashNest]
/// is awarded, and the [DashNestBumper.main] releases a new [Ball].
class FlutterForestBonusBehavior extends Component
with ParentIsA<FlutterForest>, HasGameRef<PinballGame> {
with
ParentIsA<FlutterForest>,
HasGameRef,
FlameBlocReader<GameBloc, GameState> {
@override
void onMount() {
super.onMount();
@ -35,12 +40,11 @@ class FlutterForestBonusBehavior extends Component
}
if (signpost.bloc.isFullyProgressed()) {
gameRef
.read<GameBloc>()
.add(const BonusActivated(GameBonus.dashNest));
bloc.add(const BonusActivated(GameBonus.dashNest));
canvas.add(
ControlledBall.bonus(characterTheme: gameRef.characterTheme)
..initialPosition = Vector2(29.5, -24.5),
ControlledBall.bonus(
characterTheme: readProvider<CharacterTheme>(),
)..initialPosition = Vector2(29.2, -24.5),
);
animatronic.playing = true;
signpost.bloc.onProgressed();

@ -19,27 +19,27 @@ class FlutterForest extends Component with ZIndex {
Signpost(
children: [
ScoringContactBehavior(points: Points.fiveThousand),
BumperNoisyBehavior(),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(8.35, -58.3),
)..initialPosition = Vector2(7.95, -58.35),
DashNestBumper.main(
children: [
ScoringContactBehavior(points: Points.twoHundredThousand),
BumperNoisyBehavior(),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(18.55, -59.35),
DashNestBumper.a(
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(8.95, -51.95),
DashNestBumper.b(
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(22.3, -46.75),
)..initialPosition = Vector2(21.8, -46.75),
DashAnimatronic()..position = Vector2(20, -66),
FlutterForestBonusBehavior(),
],

@ -0,0 +1,34 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
/// Listens to the [GameBloc] and updates the game accordingly.
class GameBlocStatusListener extends Component
with FlameBlocListenable<GameBloc, GameState>, HasGameRef {
@override
bool listenWhen(GameState? previousState, GameState newState) {
return previousState?.status != newState.status;
}
@override
void onNewState(GameState state) {
switch (state.status) {
case GameStatus.waiting:
break;
case GameStatus.playing:
readProvider<PinballPlayer>().play(PinballAudio.backgroundMusic);
gameRef.overlays.remove(PinballGame.playButtonOverlay);
break;
case GameStatus.gameOver:
readProvider<PinballPlayer>().play(PinballAudio.gameOverVoiceOver);
gameRef.descendants().whereType<Backbox>().first.requestInitials(
score: state.displayScore,
character: readProvider<CharacterTheme>(),
);
break;
}
}
}

@ -1,48 +0,0 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template game_flow_controller}
/// A [Component] that controls the game over and game restart logic
/// {@endtemplate}
class GameFlowController extends ComponentController<PinballGame>
with BlocComponent<GameBloc, GameState> {
/// {@macro game_flow_controller}
GameFlowController(PinballGame component) : super(component);
@override
bool listenWhen(GameState? previousState, GameState newState) {
return previousState?.isGameOver != newState.isGameOver;
}
@override
void onNewState(GameState state) {
print("START $state");
if (state.isGameOver) {
_initialsInput();
} else {
start();
}
}
/// Puts the game in the initials input state.
void _initialsInput() {
// TODO(erickzanardo): implement score submission and "navigate" to the
// next page
component.descendants().whereType<Backbox>().first.infoScreen(
score: state?.displayScore ?? 0,
characterIconPath: component.characterTheme.leaderboardIcon.keyName,
);
component.firstChild<CameraController>()!.focusOnGameOverBackbox();
}
/// Puts the game in the playing state.
void start() {
_initialsInput();
/*
component.audio.backgroundMusic();
component.firstChild<CameraController>()?.focusOnGame();
component.overlays.remove(PinballGame.playButtonOverlay);*/
}
}

@ -1,11 +1,13 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// Adds a [GameBonus.googleWord] when all [GoogleLetter]s are activated.
class GoogleWordBonusBehavior extends Component
with HasGameRef<PinballGame>, ParentIsA<GoogleWord> {
with ParentIsA<GoogleWord>, FlameBlocReader<GameBloc, GameState> {
@override
void onMount() {
super.onMount();
@ -20,10 +22,8 @@ class GoogleWordBonusBehavior extends Component
.every((letter) => letter.bloc.state == GoogleLetterState.lit);
if (achievedBonus) {
gameRef.audio.googleBonus();
gameRef
.read<GameBloc>()
.add(const BonusActivated(GameBonus.googleWord));
readProvider<PinballPlayer>().play(PinballAudio.google);
bloc.add(const BonusActivated(GameBonus.googleWord));
for (final letter in googleLetters) {
letter.bloc.onReset();
}

@ -14,8 +14,8 @@ class Launcher extends Component {
LaunchRamp(),
Flapper(),
ControlledPlunger(compressionDistance: 9.2)
..initialPosition = Vector2(41.2, 43.7),
RocketSpriteComponent()..position = Vector2(43, 62.3),
..initialPosition = Vector2(41, 43.7),
RocketSpriteComponent()..position = Vector2(42.8, 62.3),
],
);
}

@ -6,10 +6,7 @@ import 'package:pinball_flame/pinball_flame.dart';
/// Toggle each [Multiball] when there is a bonus ball.
class MultiballsBehavior extends Component
with
HasGameRef<PinballGame>,
ParentIsA<Multiballs>,
BlocComponent<GameBloc, GameState> {
with ParentIsA<Multiballs>, FlameBlocListenable<GameBloc, GameState> {
@override
bool listenWhen(GameState? previousState, GameState newState) {
final hasChanged = previousState?.bonusHistory != newState.bonusHistory;

@ -6,10 +6,7 @@ import 'package:pinball_flame/pinball_flame.dart';
/// Toggle each [Multiplier] when GameState.multiplier changes.
class MultipliersBehavior extends Component
with
HasGameRef<PinballGame>,
ParentIsA<Multipliers>,
BlocComponent<GameBloc, GameState> {
with ParentIsA<Multipliers>, FlameBlocListenable<GameBloc, GameState> {
@override
bool listenWhen(GameState? previousState, GameState newState) {
return previousState?.multiplier != newState.multiplier;

@ -14,23 +14,23 @@ class Multipliers extends Component with ZIndex {
: super(
children: [
Multiplier.x2(
position: Vector2(-19.5, -2),
position: Vector2(-19.6, -2),
angle: -15 * math.pi / 180,
),
Multiplier.x3(
position: Vector2(13, -9.4),
position: Vector2(12.8, -9.4),
angle: 15 * math.pi / 180,
),
Multiplier.x4(
position: Vector2(0, -21.2),
angle: 0,
position: Vector2(-0.3, -21.2),
angle: 3 * math.pi / 180,
),
Multiplier.x5(
position: Vector2(-8.5, -28),
position: Vector2(-8.9, -28),
angle: -3 * math.pi / 180,
),
Multiplier.x6(
position: Vector2(10, -30.7),
position: Vector2(9.8, -30.7),
angle: 8 * math.pi / 180,
),
MultipliersBehavior(),

@ -18,23 +18,23 @@ class SparkyScorch extends Component {
SparkyBumper.a(
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(-22.9, -41.65),
SparkyBumper.b(
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(-21.25, -57.9),
SparkyBumper.c(
children: [
ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(),
BumperNoiseBehavior(),
],
)..initialPosition = Vector2(-3.3, -52.55),
SparkyComputerSensor()..initialPosition = Vector2(-13, -49.9),
SparkyAnimatronic()..position = Vector2(-13.8, -58.2),
SparkyComputerSensor()..initialPosition = Vector2(-13.2, -49.9),
SparkyAnimatronic()..position = Vector2(-14, -58.2),
SparkyComputer(),
],
);

@ -7,6 +7,7 @@ import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
@ -16,18 +17,21 @@ import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
class PinballGame extends PinballForge2DGame
with
FlameBloc,
HasKeyboardHandlerComponents,
Controls<_GameBallsController>,
MultiTouchTapDetector {
with HasKeyboardHandlerComponents, MultiTouchTapDetector {
PinballGame({
required this.characterTheme,
required this.audio,
required this.l10n,
}) : super(gravity: Vector2(0, 30)) {
required CharacterTheme characterTheme,
required this.leaderboardRepository,
required GameBloc gameBloc,
required AppLocalizations l10n,
required PinballPlayer player,
}) : _gameBloc = gameBloc,
_player = player,
_characterTheme = characterTheme,
_l10n = l10n,
super(
gravity: Vector2(0, 30),
) {
images.prefix = '';
controller = _GameBallsController(this);
}
/// Identifier of the play button overlay
@ -36,26 +40,46 @@ class PinballGame extends PinballForge2DGame
@override
Color backgroundColor() => Colors.transparent;
final CharacterTheme characterTheme;
final CharacterTheme _characterTheme;
final PinballPlayer _player;
final PinballAudio audio;
final LeaderboardRepository leaderboardRepository;
final AppLocalizations l10n;
final AppLocalizations _l10n;
late final GameFlowController gameFlowController;
final GameBloc _gameBloc;
@override
Future<void> onLoad() async {
await add(gameFlowController = GameFlowController(this));
await add(CameraController(this));
final machine = [
await add(
FlameBlocProvider<GameBloc, GameState>.value(
value: _gameBloc,
children: [
MultiFlameProvider(
providers: [
FlameProvider<PinballPlayer>.value(_player),
FlameProvider<CharacterTheme>.value(_characterTheme),
FlameProvider<LeaderboardRepository>.value(leaderboardRepository),
FlameProvider<AppLocalizations>.value(_l10n),
],
children: [
GameBlocStatusListener(),
BallSpawningBehavior(),
CameraFocusingBehavior(),
CanvasComponent(
onSpritePainted: (paint) {
if (paint.filterQuality != FilterQuality.medium) {
paint.filterQuality = FilterQuality.medium;
}
},
children: [
ZCanvasComponent(
children: [
BoardBackgroundSpriteComponent(),
Boundaries(),
Backbox(),
];
final decals = [
GoogleWord(position: Vector2(-4.25, 1.8)),
Backbox(leaderboardRepository: leaderboardRepository),
GoogleWord(position: Vector2(-4.45, 1.8)),
Multipliers(),
Multiballs(),
SkillShot(
@ -63,27 +87,10 @@ class PinballGame extends PinballForge2DGame
ScoringContactBehavior(points: Points.oneMillion),
],
),
];
final characterAreas = [
AndroidAcres(),
DinoDesert(),
FlutterForest(),
SparkyScorch(),
];
await add(
CanvasComponent(
onSpritePainted: (paint) {
if (paint.filterQuality != FilterQuality.medium) {
paint.filterQuality = FilterQuality.medium;
}
},
children: [
ZCanvasComponent(
children: [
...machine,
...decals,
...characterAreas,
Drain(),
BottomGroup(),
Launcher(),
@ -91,6 +98,10 @@ class PinballGame extends PinballForge2DGame
),
],
),
],
),
],
),
);
await super.onLoad();
@ -144,57 +155,20 @@ class PinballGame extends PinballForge2DGame
}
}
class _GameBallsController extends ComponentController<PinballGame>
with BlocComponent<GameBloc, GameState> {
_GameBallsController(PinballGame game) : super(game);
@override
bool listenWhen(GameState? previousState, GameState newState) {
final noBallsLeft = component.descendants().whereType<Ball>().isEmpty;
final notGameOver = !newState.isGameOver;
return noBallsLeft && notGameOver;
}
@override
void onNewState(GameState state) {
super.onNewState(state);
spawnBall();
}
@override
Future<void> onLoad() async {
await super.onLoad();
spawnBall();
}
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.descendants().whereType<ZCanvasComponent>().single.add(ball);
});
}
}
class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
DebugPinballGame({
required CharacterTheme characterTheme,
required PinballAudio audio,
required LeaderboardRepository leaderboardRepository,
required AppLocalizations l10n,
required PinballPlayer player,
required GameBloc gameBloc,
}) : super(
characterTheme: characterTheme,
audio: audio,
player: player,
leaderboardRepository: leaderboardRepository,
l10n: l10n,
) {
controller = _GameBallsController(this);
}
gameBloc: gameBloc,
);
Vector2? lineStart;
Vector2? lineEnd;

@ -4,6 +4,7 @@ import 'package:flame/game.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/assets_manager/assets_manager.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
@ -36,35 +37,44 @@ class PinballGamePage extends StatelessWidget {
Widget build(BuildContext context) {
final characterTheme =
context.read<CharacterThemeCubit>().state.characterTheme;
final audio = context.read<PinballAudio>();
final pinballAudio = context.read<PinballAudio>();
final player = context.read<PinballPlayer>();
final leaderboardRepository = context.read<LeaderboardRepository>();
return BlocProvider(
create: (_) => GameBloc(),
child: Builder(
builder: (context) {
final gameBloc = context.read<GameBloc>();
final game = isDebugMode
? DebugPinballGame(
characterTheme: characterTheme,
audio: audio,
player: player,
leaderboardRepository: leaderboardRepository,
l10n: context.l10n,
gameBloc: gameBloc,
)
: PinballGame(
characterTheme: characterTheme,
audio: audio,
player: player,
leaderboardRepository: leaderboardRepository,
l10n: context.l10n,
gameBloc: gameBloc,
);
final loadables = [
...game.preLoadAssets(),
pinballAudio.load(),
...player.load(),
...BonusAnimation.loadAssets(),
...SelectedCharacter.loadAssets(),
];
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => GameBloc()),
BlocProvider(create: (_) => AssetsManagerCubit(loadables)..load()),
],
return BlocProvider(
create: (_) => AssetsManagerCubit(loadables)..load(),
child: PinballGameView(game: game),
);
},
),
);
}
}
@ -110,9 +120,9 @@ class PinballGameLoadedView extends StatelessWidget {
final gameWidgetWidth = MediaQuery.of(context).size.height * 9 / 16;
final screenWidth = MediaQuery.of(context).size.width;
final leftMargin = (screenWidth / 2) - (gameWidgetWidth / 1.8);
final clampedMargin = leftMargin > 0 ? leftMargin : 0.0;
return StartGameListener(
game: game,
child: Stack(
children: [
Positioned.fill(
@ -132,8 +142,8 @@ class PinballGameLoadedView extends StatelessWidget {
),
),
Positioned(
top: 16,
left: leftMargin,
top: 0,
left: clampedMargin,
child: Visibility(
visible: isPlaying,
child: const GameHud(),

@ -23,16 +23,18 @@ class _GameHudState extends State<GameHud> {
/// Ratio from sprite frame (width 500, height 144) w / h = ratio
static const _ratio = 3.47;
static const _width = 265.0;
@override
Widget build(BuildContext context) {
final isGameOver = context.select((GameBloc bloc) => bloc.state.isGameOver);
final isGameOver =
context.select((GameBloc bloc) => bloc.state.status.isGameOver);
final height = _calculateHeight(context);
return _ScoreViewDecoration(
child: SizedBox(
height: _width / _ratio,
width: _width,
height: height,
width: height * _ratio,
child: BlocListener<GameBloc, GameState>(
listenWhen: (previous, current) =>
previous.bonusHistory.length != current.bonusHistory.length,
@ -53,6 +55,17 @@ class _GameHudState extends State<GameHud> {
),
);
}
double _calculateHeight(BuildContext context) {
final height = MediaQuery.of(context).size.height * 0.09;
if (height > 90) {
return 90;
} else if (height < 60) {
return 60;
} else {
return height;
}
}
}
class _ScoreViewDecoration extends StatelessWidget {

@ -13,12 +13,13 @@ class ScoreView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isGameOver = context.select((GameBloc bloc) => bloc.state.isGameOver);
final isGameOver =
context.select((GameBloc bloc) => bloc.state.status.isGameOver);
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
vertical: 2,
),
child: AnimatedSwitcher(
duration: kThemeAnimationDuration,
@ -49,7 +50,8 @@ class _ScoreDisplay extends StatelessWidget {
Widget build(BuildContext context) {
final l10n = context.l10n;
return Column(
return FittedBox(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
@ -60,6 +62,7 @@ class _ScoreDisplay extends StatelessWidget {
const _ScoreText(),
const RoundCountDisplay(),
],
),
);
}
}

@ -3,8 +3,10 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/gen/gen.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_ui/pinball_ui.dart';
import 'package:platform_helper/platform_helper.dart';
@ -91,13 +93,16 @@ class _HowToPlayDialogState extends State<HowToPlayDialog> {
return WillPopScope(
onWillPop: () {
widget.onDismissCallback.call();
context.read<PinballPlayer>().play(PinballAudio.ioPinballVoiceOver);
return Future.value(true);
},
child: PinballDialog(
title: l10n.howToPlay,
subtitle: l10n.tipsForFlips,
child: FittedBox(
child: isMobile ? const _MobileBody() : const _DesktopBody(),
),
),
);
}
}
@ -109,8 +114,7 @@ class _MobileBody extends StatelessWidget {
Widget build(BuildContext context) {
final paddingWidth = MediaQuery.of(context).size.width * 0.15;
final paddingHeight = MediaQuery.of(context).size.height * 0.075;
return FittedBox(
child: Padding(
return Padding(
padding: EdgeInsets.symmetric(
horizontal: paddingWidth,
),
@ -121,7 +125,6 @@ class _MobileBody extends StatelessWidget {
const _MobileFlipperControls(),
],
),
),
);
}
}
@ -189,13 +192,15 @@ class _DesktopBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: const [
SizedBox(height: 16),
_DesktopLaunchControls(),
SizedBox(height: 16),
_DesktopFlipperControls(),
],
),
);
}
}

@ -1,3 +1,4 @@
import 'package:equatable/equatable.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball_theme/pinball_theme.dart';
@ -6,9 +7,9 @@ import 'package:pinball_theme/pinball_theme.dart';
/// player's initials, score, and chosen character.
///
/// {@endtemplate}
class LeaderboardEntry {
class LeaderboardEntry extends Equatable {
/// {@macro leaderboard_entry}
LeaderboardEntry({
const LeaderboardEntry({
required this.rank,
required this.playerInitials,
required this.score,
@ -26,6 +27,9 @@ class LeaderboardEntry {
/// [CharacterTheme] for [LeaderboardEntry].
final AssetGenImage character;
@override
List<Object?> get props => [rank, playerInitials, score, character];
}
/// Converts [LeaderboardEntryData] from repository to [LeaderboardEntry].

@ -11,7 +11,7 @@ void main() {
bootstrap((firestore, firebaseAuth) async {
final leaderboardRepository = LeaderboardRepository(firestore);
final authenticationRepository = AuthenticationRepository(firebaseAuth);
final pinballAudio = PinballAudio();
final pinballPlayer = PinballPlayer();
unawaited(
Firebase.initializeApp().then(
(_) => authenticationRepository.authenticateAnonymously(),
@ -20,7 +20,7 @@ void main() {
return App(
authenticationRepository: authenticationRepository,
leaderboardRepository: leaderboardRepository,
pinballAudio: pinballAudio,
pinballPlayer: pinballPlayer,
);
});
}

@ -11,7 +11,7 @@ void main() {
bootstrap((firestore, firebaseAuth) async {
final leaderboardRepository = LeaderboardRepository(firestore);
final authenticationRepository = AuthenticationRepository(firebaseAuth);
final pinballAudio = PinballAudio();
final pinballPlayer = PinballPlayer();
unawaited(
Firebase.initializeApp().then(
(_) => authenticationRepository.authenticateAnonymously(),
@ -20,7 +20,7 @@ void main() {
return App(
authenticationRepository: authenticationRepository,
leaderboardRepository: leaderboardRepository,
pinballAudio: pinballAudio,
pinballPlayer: pinballPlayer,
);
});
}

@ -11,7 +11,7 @@ void main() {
bootstrap((firestore, firebaseAuth) async {
final leaderboardRepository = LeaderboardRepository(firestore);
final authenticationRepository = AuthenticationRepository(firebaseAuth);
final pinballAudio = PinballAudio();
final pinballPlayer = PinballPlayer();
unawaited(
Firebase.initializeApp().then(
(_) => authenticationRepository.authenticateAnonymously(),
@ -20,7 +20,7 @@ void main() {
return App(
authenticationRepository: authenticationRepository,
leaderboardRepository: leaderboardRepository,
pinballAudio: pinballAudio,
pinballPlayer: pinballPlayer,
);
});
}

@ -4,7 +4,6 @@ import 'package:pinball/game/game.dart';
import 'package:pinball/how_to_play/how_to_play.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_ui/pinball_ui.dart';
/// {@template start_game_listener}
@ -18,13 +17,10 @@ class StartGameListener extends StatelessWidget {
const StartGameListener({
Key? key,
required Widget child,
required PinballGame game,
}) : _child = child,
_game = game,
super(key: key);
final Widget _child;
final PinballGame _game;
@override
Widget build(BuildContext context) {
@ -35,7 +31,7 @@ class StartGameListener extends StatelessWidget {
break;
case StartGameStatus.selectCharacter:
_onSelectCharacter(context);
_game.gameFlowController.start();
context.read<GameBloc>().add(const GameStarted());
break;
case StartGameStatus.howToPlay:
_onHowToPlay(context);
@ -57,14 +53,11 @@ class StartGameListener extends StatelessWidget {
}
void _onHowToPlay(BuildContext context) {
final audio = context.read<PinballAudio>();
_showPinballDialog(
context: context,
child: HowToPlayDialog(
onDismissCallback: () {
context.read<StartGameBloc>().add(const HowToPlayFinished());
audio.ioPinballVoiceOver();
},
),
);

@ -14,10 +14,13 @@ class $AssetsMusicGen {
class $AssetsSfxGen {
const $AssetsSfxGen();
String get afterLaunch => 'assets/sfx/after_launch.mp3';
String get bumperA => 'assets/sfx/bumper_a.mp3';
String get bumperB => 'assets/sfx/bumper_b.mp3';
String get gameOverVoiceOver => 'assets/sfx/game_over_voice_over.mp3';
String get google => 'assets/sfx/google.mp3';
String get ioPinballVoiceOver => 'assets/sfx/io_pinball_voice_over.mp3';
String get launcher => 'assets/sfx/launcher.mp3';
}
class Assets {

@ -3,10 +3,31 @@ import 'dart:math';
import 'package:audioplayers/audioplayers.dart';
import 'package:flame_audio/audio_pool.dart';
import 'package:flame_audio/flame_audio.dart';
import 'package:flutter/material.dart';
import 'package:pinball_audio/gen/assets.gen.dart';
/// Function that defines the contract of the creation
/// of an [AudioPool]
/// Sounds available for play
enum PinballAudio {
/// Google
google,
/// Bumper
bumper,
/// Background music
backgroundMusic,
/// IO Pinball voice over
ioPinballVoiceOver,
/// Game over
gameOverVoiceOver,
/// Launcher
launcher,
}
/// Defines the contract of the creation of an [AudioPool].
typedef CreateAudioPool = Future<AudioPool> Function(
String sound, {
bool? repeating,
@ -15,28 +36,109 @@ typedef CreateAudioPool = Future<AudioPool> Function(
String? prefix,
});
/// Function that defines the contract for playing a single
/// audio
/// Defines the contract for playing a single audio.
typedef PlaySingleAudio = Future<void> Function(String);
/// Function that defines the contract for looping a single
/// audio
/// Defines the contract for looping a single audio.
typedef LoopSingleAudio = Future<void> Function(String);
/// Function that defines the contract for pre fetching an
/// audio
/// Defines the contract for pre fetching an audio.
typedef PreCacheSingleAudio = Future<void> Function(String);
/// Function that defines the contract for configuring
/// an [AudioCache] instance
/// Defines the contract for configuring an [AudioCache] instance.
typedef ConfigureAudioCache = void Function(AudioCache);
/// {@template pinball_audio}
abstract class _Audio {
void play();
Future<void> load();
String prefixFile(String file) {
return 'packages/pinball_audio/$file';
}
}
class _SimplePlayAudio extends _Audio {
_SimplePlayAudio({
required this.preCacheSingleAudio,
required this.playSingleAudio,
required this.path,
});
final PreCacheSingleAudio preCacheSingleAudio;
final PlaySingleAudio playSingleAudio;
final String path;
@override
Future<void> load() => preCacheSingleAudio(prefixFile(path));
@override
void play() {
playSingleAudio(prefixFile(path));
}
}
class _LoopAudio extends _Audio {
_LoopAudio({
required this.preCacheSingleAudio,
required this.loopSingleAudio,
required this.path,
});
final PreCacheSingleAudio preCacheSingleAudio;
final LoopSingleAudio loopSingleAudio;
final String path;
@override
Future<void> load() => preCacheSingleAudio(prefixFile(path));
@override
void play() {
loopSingleAudio(prefixFile(path));
}
}
class _BumperAudio extends _Audio {
_BumperAudio({
required this.createAudioPool,
required this.seed,
});
final CreateAudioPool createAudioPool;
final Random seed;
late AudioPool bumperA;
late AudioPool bumperB;
@override
Future<void> load() async {
await Future.wait(
[
createAudioPool(
prefixFile(Assets.sfx.bumperA),
maxPlayers: 4,
prefix: '',
).then((pool) => bumperA = pool),
createAudioPool(
prefixFile(Assets.sfx.bumperB),
maxPlayers: 4,
prefix: '',
).then((pool) => bumperB = pool),
],
);
}
@override
void play() {
(seed.nextBool() ? bumperA : bumperB).start(volume: 0.6);
}
}
/// {@template pinball_player}
/// Sound manager for the pinball game
/// {@endtemplate}
class PinballAudio {
/// {@macro pinball_audio}
PinballAudio({
class PinballPlayer {
/// {@macro pinball_player}
PinballPlayer({
CreateAudioPool? createAudioPool,
PlaySingleAudio? playSingleAudio,
LoopSingleAudio? loopSingleAudio,
@ -52,7 +154,39 @@ class PinballAudio {
((AudioCache a) {
a.prefix = '';
}),
_seed = seed ?? Random();
_seed = seed ?? Random() {
audios = {
PinballAudio.google: _SimplePlayAudio(
preCacheSingleAudio: _preCacheSingleAudio,
playSingleAudio: _playSingleAudio,
path: Assets.sfx.google,
),
PinballAudio.launcher: _SimplePlayAudio(
preCacheSingleAudio: _preCacheSingleAudio,
playSingleAudio: _playSingleAudio,
path: Assets.sfx.launcher,
),
PinballAudio.ioPinballVoiceOver: _SimplePlayAudio(
preCacheSingleAudio: _preCacheSingleAudio,
playSingleAudio: _playSingleAudio,
path: Assets.sfx.ioPinballVoiceOver,
),
PinballAudio.gameOverVoiceOver: _SimplePlayAudio(
preCacheSingleAudio: _preCacheSingleAudio,
playSingleAudio: _playSingleAudio,
path: Assets.sfx.gameOverVoiceOver,
),
PinballAudio.bumper: _BumperAudio(
createAudioPool: _createAudioPool,
seed: _seed,
),
PinballAudio.backgroundMusic: _LoopAudio(
preCacheSingleAudio: _preCacheSingleAudio,
loopSingleAudio: _loopSingleAudio,
path: Assets.music.background,
),
};
}
final CreateAudioPool _createAudioPool;
@ -66,54 +200,24 @@ class PinballAudio {
final Random _seed;
late AudioPool _bumperAPool;
late AudioPool _bumperBPool;
/// Registered audios on the Player
@visibleForTesting
// ignore: library_private_types_in_public_api
late final Map<PinballAudio, _Audio> audios;
/// Loads the sounds effects into the memory
Future<void> load() async {
List<Future<void>> load() {
_configureAudioCache(FlameAudio.audioCache);
_bumperAPool = await _createAudioPool(
_prefixFile(Assets.sfx.bumperA),
maxPlayers: 4,
prefix: '',
);
_bumperBPool = await _createAudioPool(
_prefixFile(Assets.sfx.bumperB),
maxPlayers: 4,
prefix: '',
);
await Future.wait([
_preCacheSingleAudio(_prefixFile(Assets.sfx.google)),
_preCacheSingleAudio(_prefixFile(Assets.sfx.ioPinballVoiceOver)),
_preCacheSingleAudio(_prefixFile(Assets.music.background)),
]);
}
/// Plays a random bumper sfx.
void bumper() {
(_seed.nextBool() ? _bumperAPool : _bumperBPool).start(volume: 0.6);
}
/// Plays the google word bonus
void googleBonus() {
_playSingleAudio(_prefixFile(Assets.sfx.google));
}
/// Plays the I/O Pinball voice over audio.
void ioPinballVoiceOver() {
_playSingleAudio(_prefixFile(Assets.sfx.ioPinballVoiceOver));
return audios.values.map((a) => a.load()).toList();
}
/// Plays the background music
void backgroundMusic() {
_loopSingleAudio(_prefixFile(Assets.music.background));
}
String _prefixFile(String file) {
return 'packages/pinball_audio/$file';
/// Plays the received auido
void play(PinballAudio audio) {
assert(
audios.containsKey(audio),
'Tried to play unregistered audio $audio',
);
audios[audio]?.play();
}
}

@ -51,7 +51,7 @@ void main() {
late _MockLoopSingleAudio loopSingleAudio;
late _PreCacheSingleAudio preCacheSingleAudio;
late Random seed;
late PinballAudio audio;
late PinballPlayer player;
setUpAll(() {
registerFallbackValue(_MockAudioCache());
@ -81,7 +81,7 @@ void main() {
seed = _MockRandom();
audio = PinballAudio(
player = PinballPlayer(
configureAudioCache: configureAudioCache.onCall,
createAudioPool: createAudioPool.onCall,
playSingleAudio: playSingleAudio.onCall,
@ -92,12 +92,12 @@ void main() {
});
test('can be instantiated', () {
expect(PinballAudio(), isNotNull);
expect(PinballPlayer(), isNotNull);
});
group('load', () {
test('creates the bumpers pools', () async {
await audio.load();
await Future.wait(player.load());
verify(
() => createAudioPool.onCall(
@ -117,25 +117,25 @@ void main() {
});
test('configures the audio cache instance', () async {
await audio.load();
await Future.wait(player.load());
verify(() => configureAudioCache.onCall(FlameAudio.audioCache))
.called(1);
});
test('sets the correct prefix', () async {
audio = PinballAudio(
player = PinballPlayer(
createAudioPool: createAudioPool.onCall,
playSingleAudio: playSingleAudio.onCall,
preCacheSingleAudio: preCacheSingleAudio.onCall,
);
await audio.load();
await Future.wait(player.load());
expect(FlameAudio.audioCache.prefix, equals(''));
});
test('pre cache the assets', () async {
await audio.load();
await Future.wait(player.load());
verify(
() => preCacheSingleAudio
@ -146,6 +146,15 @@ void main() {
'packages/pinball_audio/assets/sfx/io_pinball_voice_over.mp3',
),
).called(1);
verify(
() => preCacheSingleAudio.onCall(
'packages/pinball_audio/assets/sfx/game_over_voice_over.mp3',
),
).called(1);
verify(
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/launcher.mp3'),
).called(1);
verify(
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/music/background.mp3'),
@ -184,8 +193,8 @@ void main() {
group('when seed is true', () {
test('plays the bumper A sound pool', () async {
when(seed.nextBool).thenReturn(true);
await audio.load();
audio.bumper();
await Future.wait(player.load());
player.play(PinballAudio.bumper);
verify(() => bumperAPool.start(volume: 0.6)).called(1);
});
@ -194,8 +203,8 @@ void main() {
group('when seed is false', () {
test('plays the bumper B sound pool', () async {
when(seed.nextBool).thenReturn(false);
await audio.load();
audio.bumper();
await Future.wait(player.load());
player.play(PinballAudio.bumper);
verify(() => bumperBPool.start(volume: 0.6)).called(1);
});
@ -204,8 +213,8 @@ void main() {
group('googleBonus', () {
test('plays the correct file', () async {
await audio.load();
audio.googleBonus();
await Future.wait(player.load());
player.play(PinballAudio.google);
verify(
() => playSingleAudio
@ -214,10 +223,22 @@ void main() {
});
});
group('launcher', () {
test('plays the correct file', () async {
await Future.wait(player.load());
player.play(PinballAudio.launcher);
verify(
() => playSingleAudio
.onCall('packages/pinball_audio/${Assets.sfx.launcher}'),
).called(1);
});
});
group('ioPinballVoiceOver', () {
test('plays the correct file', () async {
await audio.load();
audio.ioPinballVoiceOver();
await Future.wait(player.load());
player.play(PinballAudio.ioPinballVoiceOver);
verify(
() => playSingleAudio.onCall(
@ -227,10 +248,23 @@ void main() {
});
});
group('gameOverVoiceOver', () {
test('plays the correct file', () async {
await Future.wait(player.load());
player.play(PinballAudio.gameOverVoiceOver);
verify(
() => playSingleAudio.onCall(
'packages/pinball_audio/${Assets.sfx.gameOverVoiceOver}',
),
).called(1);
});
});
group('backgroundMusic', () {
test('plays the correct file', () async {
await audio.load();
audio.backgroundMusic();
await Future.wait(player.load());
player.play(PinballAudio.backgroundMusic);
verify(
() => loopSingleAudio
@ -238,5 +272,15 @@ void main() {
).called(1);
});
});
test(
'throws assertions error when playing an unregistered audio',
() async {
player.audios.remove(PinballAudio.google);
await Future.wait(player.load());
expect(() => player.play(PinballAudio.google), throwsAssertionError);
},
);
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 616 KiB

After

Width:  |  Height:  |  Size: 636 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 735 KiB

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 886 KiB

After

Width:  |  Height:  |  Size: 1012 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 374 KiB

@ -122,7 +122,7 @@ class _SpaceshipSaucerSpriteAnimationComponent extends SpriteAnimationComponent
SpriteAnimationData.sequenced(
amount: amountPerRow * amountPerColumn,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
stepTime: 1 / 12,
textureSize: textureSize,
),
);

@ -9,7 +9,7 @@ class BoardBackgroundSpriteComponent extends SpriteComponent
BoardBackgroundSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(0, -1),
position: Vector2(-0.2, 0.1),
) {
zIndex = ZIndexes.boardBackground;
}

@ -68,7 +68,7 @@ class _BottomBoundarySpriteComponent extends SpriteComponent with HasGameRef {
_BottomBoundarySpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(-5, 55.6),
position: Vector2(-5.2, 55.6),
);
@override

@ -60,10 +60,10 @@ class DashNestBumper extends BodyComponent with InitialPosition {
Iterable<Component>? children,
}) : this._(
majorRadius: 3,
minorRadius: 2.5,
minorRadius: 2.2,
activeAssetPath: Assets.images.dash.bumper.a.active.keyName,
inactiveAssetPath: Assets.images.dash.bumper.a.inactive.keyName,
spritePosition: Vector2(0.35, -1.2),
spritePosition: Vector2(0.3, -1.3),
bloc: DashNestBumperCubit(),
children: [
...?children,
@ -75,11 +75,11 @@ class DashNestBumper extends BodyComponent with InitialPosition {
DashNestBumper.b({
Iterable<Component>? children,
}) : this._(
majorRadius: 3,
minorRadius: 2.5,
majorRadius: 3.1,
minorRadius: 2.2,
activeAssetPath: Assets.images.dash.bumper.b.active.keyName,
inactiveAssetPath: Assets.images.dash.bumper.b.inactive.keyName,
spritePosition: Vector2(0.35, -1.2),
spritePosition: Vector2(0.4, -1.2),
bloc: DashNestBumperCubit(),
children: [
...?children,

@ -37,46 +37,46 @@ class _DinoTopWall extends BodyComponent with InitialPosition {
List<FixtureDef> _createFixtureDefs() {
final topEdgeShape = EdgeShape()
..set(
Vector2(29.25, -35.27),
Vector2(28.4, -34.77),
Vector2(29.05, -35.27),
Vector2(28.2, -34.77),
);
final topCurveShape = BezierCurveShape(
controlPoints: [
topEdgeShape.vertex2,
Vector2(21.35, -28.72),
Vector2(23.45, -24.62),
Vector2(21.15, -28.72),
Vector2(23.25, -24.62),
],
);
final tunnelTopEdgeShape = EdgeShape()
..set(
topCurveShape.vertices.last,
Vector2(30.35, -27.32),
Vector2(30.15, -27.32),
);
final tunnelBottomEdgeShape = EdgeShape()
..set(
Vector2(30.75, -23.17),
Vector2(25.45, -21.22),
Vector2(30.55, -23.17),
Vector2(25.25, -21.22),
);
final middleEdgeShape = EdgeShape()
..set(
tunnelBottomEdgeShape.vertex2,
Vector2(27.45, -19.32),
Vector2(27.25, -19.32),
);
final bottomEdgeShape = EdgeShape()
..set(
middleEdgeShape.vertex2,
Vector2(24.65, -15.02),
Vector2(24.45, -15.02),
);
final undersideEdgeShape = EdgeShape()
..set(
bottomEdgeShape.vertex2,
Vector2(31.75, -13.77),
Vector2(31.55, -13.77),
);
return [
@ -108,7 +108,7 @@ class _DinoTopWallSpriteComponent extends SpriteComponent
with HasGameRef, ZIndex {
_DinoTopWallSpriteComponent()
: super(
position: Vector2(22.75, -38.07),
position: Vector2(22.55, -38.07),
) {
zIndex = ZIndexes.dinoTopWall;
}
@ -129,7 +129,7 @@ class _DinoTopWallSpriteComponent extends SpriteComponent
class _DinoTopWallTunnelSpriteComponent extends SpriteComponent
with HasGameRef, ZIndex {
_DinoTopWallTunnelSpriteComponent()
: super(position: Vector2(23.31, -26.01)) {
: super(position: Vector2(23.11, -26.01)) {
zIndex = ZIndexes.dinoTopWallTunnel;
}
@ -162,28 +162,28 @@ class _DinoBottomWall extends BodyComponent with InitialPosition, ZIndex {
List<FixtureDef> _createFixtureDefs() {
final topEdgeShape = EdgeShape()
..set(
Vector2(32.4, -8.8),
Vector2(25, -7.7),
Vector2(32.2, -8.8),
Vector2(24.8, -7.7),
);
final topLeftCurveShape = BezierCurveShape(
controlPoints: [
topEdgeShape.vertex2,
Vector2(21.8, -7),
Vector2(29.8, 13.8),
Vector2(21.6, -7),
Vector2(29.6, 13.8),
],
);
final bottomLeftEdgeShape = EdgeShape()
..set(
topLeftCurveShape.vertices.last,
Vector2(31.9, 44.1),
Vector2(31.7, 44.1),
);
final bottomEdgeShape = EdgeShape()
..set(
bottomLeftEdgeShape.vertex2,
Vector2(37.8, 44.1),
Vector2(37.6, 44.1),
);
return [
@ -219,6 +219,6 @@ class _DinoBottomWallSpriteComponent extends SpriteComponent with HasGameRef {
);
this.sprite = sprite;
size = sprite.originalSize / 10;
position = Vector2(23.8, -9.5);
position = Vector2(23.6, -9.5);
}
}

@ -18,9 +18,9 @@ class Flapper extends Component {
children: [
FlapperSpinningBehavior(),
],
)..initialPosition = Vector2(4, -69.3),
)..initialPosition = Vector2(3.8, -69.3),
_FlapperStructure(),
_FlapperExit()..initialPosition = Vector2(-0.6, -33.8),
_FlapperExit()..initialPosition = Vector2(-0.8, -33.8),
_BackSupportSpriteComponent(),
_FrontSupportSpriteComponent(),
FlapSpriteAnimationComponent(),
@ -73,14 +73,14 @@ class _FlapperStructure extends BodyComponent with Layered {
List<FixtureDef> _createFixtureDefs() {
final leftEdgeShape = EdgeShape()
..set(
Vector2(1.9, -69.3),
Vector2(1.9, -66),
Vector2(1.7, -69.3),
Vector2(1.7, -66),
);
final bottomEdgeShape = EdgeShape()
..set(
leftEdgeShape.vertex2,
Vector2(3.9, -66),
Vector2(3.7, -66),
);
return [
@ -130,7 +130,7 @@ class FlapSpriteAnimationComponent extends SpriteAnimationComponent
FlapSpriteAnimationComponent()
: super(
anchor: Anchor.center,
position: Vector2(2.8, -70.7),
position: Vector2(2.6, -70.7),
playing: false,
) {
zIndex = ZIndexes.flapper;
@ -173,7 +173,7 @@ class _BackSupportSpriteComponent extends SpriteComponent
_BackSupportSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(2.95, -70.6),
position: Vector2(2.75, -70.6),
) {
zIndex = ZIndexes.flapperBack;
}
@ -196,7 +196,7 @@ class _FrontSupportSpriteComponent extends SpriteComponent
_FrontSupportSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(2.9, -67.6),
position: Vector2(2.7, -67.7),
) {
zIndex = ZIndexes.flapperFront;
}

@ -40,22 +40,22 @@ class _LaunchRampBase extends BodyComponent with Layered, ZIndex {
final rightStraightShape = EdgeShape()
..set(
Vector2(31.4, -61.4),
Vector2(46.5, 68.4),
Vector2(31, -61.4),
Vector2(46.1, 68.4),
);
final rightStraightFixtureDef = FixtureDef(rightStraightShape);
fixturesDef.add(rightStraightFixtureDef);
final leftStraightShape = EdgeShape()
..set(
Vector2(27.8, -61.4),
Vector2(41.5, 68.4),
Vector2(27.4, -61.4),
Vector2(41.1, 68.4),
);
final leftStraightFixtureDef = FixtureDef(leftStraightShape);
fixturesDef.add(leftStraightFixtureDef);
final topCurveShape = ArcShape(
center: Vector2(20.5, -61.1),
center: Vector2(20.1, -61.1),
arcRadius: 11,
angle: 1.6,
rotation: 0.1,
@ -64,7 +64,7 @@ class _LaunchRampBase extends BodyComponent with Layered, ZIndex {
fixturesDef.add(topCurveFixtureDef);
final bottomCurveShape = ArcShape(
center: Vector2(19.3, -60.3),
center: Vector2(18.9, -60.3),
arcRadius: 8.5,
angle: 1.48,
rotation: 0.1,
@ -74,16 +74,16 @@ class _LaunchRampBase extends BodyComponent with Layered, ZIndex {
final topStraightShape = EdgeShape()
..set(
Vector2(3.7, -70.1),
Vector2(19.1, -72.1),
Vector2(3.3, -70.1),
Vector2(18.7, -72.1),
);
final topStraightFixtureDef = FixtureDef(topStraightShape);
fixturesDef.add(topStraightFixtureDef);
final bottomStraightShape = EdgeShape()
..set(
Vector2(3.7, -66.9),
Vector2(19.1, -68.8),
Vector2(3.3, -66.9),
Vector2(18.7, -68.8),
);
final bottomStraightFixtureDef = FixtureDef(bottomStraightShape);
fixturesDef.add(bottomStraightFixtureDef);
@ -113,7 +113,7 @@ class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef {
this.sprite = sprite;
size = sprite.originalSize / 10;
anchor = Anchor.center;
position = Vector2(25.65, 0.7);
position = Vector2(25.25, 0.7);
}
}
@ -131,7 +131,7 @@ class _LaunchRampBackgroundRailingSpriteComponent extends SpriteComponent
this.sprite = sprite;
size = sprite.originalSize / 10;
anchor = Anchor.center;
position = Vector2(25.6, -1.3);
position = Vector2(25.2, -1.3);
}
}
@ -149,14 +149,14 @@ class _LaunchRampForegroundRailing extends BodyComponent with ZIndex {
final rightStraightShape = EdgeShape()
..set(
Vector2(27.6, -57.9),
Vector2(38.1, 42.6),
Vector2(27.2, -57.9),
Vector2(37.7, 42.6),
);
final rightStraightFixtureDef = FixtureDef(rightStraightShape);
fixturesDef.add(rightStraightFixtureDef);
final curveShape = ArcShape(
center: Vector2(20.1, -59.3),
center: Vector2(19.7, -59.3),
arcRadius: 7.5,
angle: 1.8,
rotation: -0.13,
@ -166,8 +166,8 @@ class _LaunchRampForegroundRailing extends BodyComponent with ZIndex {
final topStraightShape = EdgeShape()
..set(
Vector2(3.7, -66.8),
Vector2(19.7, -66.8),
Vector2(3.3, -66.8),
Vector2(19.3, -66.8),
);
final topStraightFixtureDef = FixtureDef(topStraightShape);
fixturesDef.add(topStraightFixtureDef);
@ -198,6 +198,6 @@ class _LaunchRampForegroundRailingSpriteComponent extends SpriteComponent
this.sprite = sprite;
size = sprite.originalSize / 10;
anchor = Anchor.center;
position = Vector2(22.8, 0.5);
position = Vector2(22.4, 0.5);
}
}

@ -36,8 +36,8 @@ class Multiball extends Component {
Multiball.a({
Iterable<Component>? children,
}) : this._(
position: Vector2(-23, 7.5),
rotation: -24 * math.pi / 180,
position: Vector2(-23.3, 7.5),
rotation: -27 * math.pi / 180,
bloc: MultiballCubit(),
children: children,
);
@ -46,8 +46,8 @@ class Multiball extends Component {
Multiball.b({
Iterable<Component>? children,
}) : this._(
position: Vector2(-7.2, -6.2),
rotation: -5 * math.pi / 180,
position: Vector2(-7.65, -6.2),
rotation: -2 * math.pi / 180,
bloc: MultiballCubit(),
children: children,
);
@ -56,8 +56,8 @@ class Multiball extends Component {
Multiball.c({
Iterable<Component>? children,
}) : this._(
position: Vector2(-0.7, -9.3),
rotation: 2.7 * math.pi / 180,
position: Vector2(-1.1, -9.3),
rotation: 6 * math.pi / 180,
bloc: MultiballCubit(),
children: children,
);
@ -66,8 +66,8 @@ class Multiball extends Component {
Multiball.d({
Iterable<Component>? children,
}) : this._(
position: Vector2(15, 7),
rotation: 24 * math.pi / 180,
position: Vector2(14.8, 7),
rotation: 27 * math.pi / 180,
bloc: MultiballCubit(),
children: children,
);

@ -1,5 +1,6 @@
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_flame/pinball_flame.dart';
@ -13,16 +14,23 @@ class Plunger extends BodyComponent with InitialPosition, Layered, ZIndex {
/// {@macro plunger}
Plunger({
required this.compressionDistance,
}) : super(renderBody: false) {
}) : super(
renderBody: false,
children: [_PlungerSpriteAnimationGroupComponent()],
) {
zIndex = ZIndexes.plunger;
layer = Layer.launcher;
}
/// Creates a [Plunger] without any children.
///
/// This can be used for testing [Plunger]'s behaviors in isolation.
@visibleForTesting
Plunger.test({required this.compressionDistance});
/// Distance the plunger can lower.
final double compressionDistance;
late final _PlungerSpriteAnimationGroupComponent _spriteComponent;
List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[];
@ -78,8 +86,10 @@ class Plunger extends BodyComponent with InitialPosition, Layered, ZIndex {
/// Set a constant downward velocity on the [Plunger].
void pull() {
final sprite = firstChild<_PlungerSpriteAnimationGroupComponent>()!;
body.linearVelocity = Vector2(0, 7);
_spriteComponent.pull();
sprite.pull();
}
/// Set an upward velocity on the [Plunger].
@ -87,17 +97,19 @@ class Plunger extends BodyComponent with InitialPosition, Layered, ZIndex {
/// The velocity's magnitude depends on how far the [Plunger] has been pulled
/// from its original [initialPosition].
void release() {
final sprite = firstChild<_PlungerSpriteAnimationGroupComponent>()!;
_pullingDownTime = 0;
final velocity = (initialPosition.y - body.position.y) * 11;
body.linearVelocity = Vector2(0, velocity);
_spriteComponent.release();
sprite.release();
}
@override
void update(double dt) {
// Ensure that we only pull or release when the time is greater than zero.
if (_pullingDownTime > 0) {
_pullingDownTime -= dt;
_pullingDownTime -= PinballForge2DGame.clampDt(dt);
if (_pullingDownTime <= 0) {
release();
} else {
@ -127,9 +139,6 @@ class Plunger extends BodyComponent with InitialPosition, Layered, ZIndex {
Future<void> onLoad() async {
await super.onLoad();
await _anchorToJoint();
_spriteComponent = _PlungerSpriteAnimationGroupComponent();
await add(_spriteComponent);
}
}

@ -13,15 +13,13 @@ class Slingshots extends Component with ZIndex {
: super(
children: [
Slingshot(
length: 5.64,
angle: -0.017,
spritePath: Assets.images.slingshot.upper.keyName,
)..initialPosition = Vector2(22.3, -1.58),
)..initialPosition = Vector2(22.7, -0.3),
Slingshot(
length: 3.46,
angle: -0.468,
spritePath: Assets.images.slingshot.lower.keyName,
)..initialPosition = Vector2(24.7, 6.2),
)..initialPosition = Vector2(24.6, 6.1),
],
) {
zIndex = ZIndexes.slingshots;
@ -34,11 +32,9 @@ class Slingshots extends Component with ZIndex {
class Slingshot extends BodyComponent with InitialPosition {
/// {@macro slingshot}
Slingshot({
required double length,
required double angle,
required String spritePath,
}) : _length = length,
_angle = angle,
}) : _angle = angle,
super(
children: [
_SlinghsotSpriteComponent(spritePath, angle: angle),
@ -47,29 +43,28 @@ class Slingshot extends BodyComponent with InitialPosition {
renderBody: false,
);
final double _length;
final double _angle;
List<FixtureDef> _createFixtureDefs() {
const length = 3.46;
const circleRadius = 1.55;
final topCircleShape = CircleShape()..radius = circleRadius;
topCircleShape.position.setValues(0, -_length / 2);
topCircleShape.position.setValues(0, -length / 2);
final bottomCircleShape = CircleShape()..radius = circleRadius;
bottomCircleShape.position.setValues(0, _length / 2);
bottomCircleShape.position.setValues(0, length / 2);
final leftEdgeShape = EdgeShape()
..set(
Vector2(circleRadius, _length / 2),
Vector2(circleRadius, -_length / 2),
Vector2(circleRadius, length / 2),
Vector2(circleRadius, -length / 2),
);
final rightEdgeShape = EdgeShape()
..set(
Vector2(-circleRadius, _length / 2),
Vector2(-circleRadius, -_length / 2),
Vector2(-circleRadius, length / 2),
Vector2(-circleRadius, -length / 2),
);
return [

@ -32,18 +32,18 @@ class _ComputerBase extends BodyComponent with InitialPosition, ZIndex {
List<FixtureDef> _createFixtureDefs() {
final leftEdge = EdgeShape()
..set(
Vector2(-14.9, -46),
Vector2(-15.3, -49.6),
Vector2(-15.3, -45.9),
Vector2(-15.7, -49.5),
);
final topEdge = EdgeShape()
..set(
Vector2(-15.3, -49.6),
Vector2(-10.7, -50.6),
leftEdge.vertex2,
Vector2(-11.1, -50.5),
);
final rightEdge = EdgeShape()
..set(
Vector2(-10.7, -50.6),
Vector2(-9, -47.2),
topEdge.vertex2,
Vector2(-9.4, -47.1),
);
return [
@ -67,7 +67,7 @@ class _ComputerBaseSpriteComponent extends SpriteComponent with HasGameRef {
_ComputerBaseSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(-12.1, -48.15),
position: Vector2(-12.44, -48.15),
);
@override
@ -89,7 +89,7 @@ class _ComputerTopSpriteComponent extends SpriteComponent
_ComputerTopSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(-12.52, -49.37),
position: Vector2(-12.86, -49.37),
) {
zIndex = ZIndexes.computerTop;
}
@ -113,7 +113,7 @@ class _ComputerGlowSpriteComponent extends SpriteComponent
_ComputerGlowSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(7.4, 10),
position: Vector2(4, 11),
) {
zIndex = ZIndexes.computerGlow;
}

@ -33,7 +33,7 @@ abstract class ZIndexes {
static const outerBoundary = _above + boardBackground;
static const outerBottomBoundary = _above + rocket;
static const outerBottomBoundary = _above + bottomBoundary;
// Bottom Group
@ -77,7 +77,7 @@ abstract class ZIndexes {
static const computerTop = _above + ballOnBoard;
static const computerGlow = _above + ballOnBoard;
static const computerGlow = _above + computerTop;
static const sparkyAnimatronic = _above + spaceshipRampForegroundRailing;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

After

Width:  |  Height:  |  Size: 254 KiB

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

Loading…
Cancel
Save