Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 200 KiB |
Before Width: | Height: | Size: 2.2 MiB |
@ -1,8 +1 @@
|
||||
// Copyright (c) 2021, Very Good Ventures
|
||||
// https://verygood.ventures
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
export 'view/app.dart';
|
||||
|
@ -0,0 +1,2 @@
|
||||
export 'cubit/assets_manager_cubit.dart';
|
||||
export 'views/views.dart';
|
@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:pinball/assets_manager/assets_manager.dart';
|
||||
import 'package:pinball/l10n/l10n.dart';
|
||||
import 'package:pinball_ui/pinball_ui.dart';
|
||||
|
||||
/// {@template assets_loading_page}
|
||||
/// Widget used to indicate the loading progress of the different assets used
|
||||
/// in the game
|
||||
/// {@endtemplate}
|
||||
class AssetsLoadingPage extends StatelessWidget {
|
||||
/// {@macro assets_loading_page}
|
||||
const AssetsLoadingPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final headline1 = Theme.of(context).textTheme.headline1;
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
l10n.ioPinball,
|
||||
style: headline1!.copyWith(fontSize: 80),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
AnimatedEllipsisText(
|
||||
l10n.loading,
|
||||
style: headline1,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
FractionallySizedBox(
|
||||
widthFactor: 0.8,
|
||||
child: BlocBuilder<AssetsManagerCubit, AssetsManagerState>(
|
||||
builder: (context, state) {
|
||||
return PinballLoadingIndicator(value: state.progress);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export 'assets_loading_page.dart';
|
@ -1,39 +1,50 @@
|
||||
// ignore_for_file: avoid_renaming_method_parameters
|
||||
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball/game/components/android_acres/behaviors/behaviors.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)),
|
||||
AndroidAnimatronic(
|
||||
children: [
|
||||
ScoringBehavior(points: Points.twoHundredThousand),
|
||||
],
|
||||
)..initialPosition = Vector2(-26, -28.25),
|
||||
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(),
|
||||
AndroidSpaceshipBonusBehavior(),
|
||||
],
|
||||
);
|
||||
|
||||
/// Creates [AndroidAcres] without any children.
|
||||
///
|
||||
/// This can be used for testing [AndroidAcres]'s behaviors in isolation.
|
||||
@visibleForTesting
|
||||
AndroidAcres.test();
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
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';
|
||||
|
||||
/// Adds a [GameBonus.androidSpaceship] when [AndroidSpaceship] has a bonus.
|
||||
class AndroidSpaceshipBonusBehavior extends Component
|
||||
with HasGameRef<PinballGame>, ParentIsA<AndroidAcres> {
|
||||
@override
|
||||
void onMount() {
|
||||
super.onMount();
|
||||
final androidSpaceship = parent.firstChild<AndroidSpaceship>()!;
|
||||
|
||||
// TODO(alestiago): Refactor subscription management once the following is
|
||||
// merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
androidSpaceship.bloc.stream.listen((state) {
|
||||
final listenWhen = state == AndroidSpaceshipState.withBonus;
|
||||
if (!listenWhen) return;
|
||||
|
||||
gameRef
|
||||
.read<GameBloc>()
|
||||
.add(const BonusActivated(GameBonus.androidSpaceship));
|
||||
androidSpaceship.bloc.onBonusAwarded();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export 'android_spaceship_bonus_behavior.dart';
|
@ -1,16 +1,17 @@
|
||||
export 'android_acres.dart';
|
||||
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.dart';
|
||||
export 'dino_desert/dino_desert.dart';
|
||||
export 'drain.dart';
|
||||
export 'flutter_forest/flutter_forest.dart';
|
||||
export 'game_flow_controller.dart';
|
||||
export 'google_word/google_word.dart';
|
||||
export 'launcher.dart';
|
||||
export 'multiballs/multiballs.dart';
|
||||
export 'multipliers/multipliers.dart';
|
||||
export 'scoring_behavior.dart';
|
||||
export 'sparky_fire_zone.dart';
|
||||
export 'sparky_scorch.dart';
|
||||
|
@ -1,23 +0,0 @@
|
||||
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 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 {
|
||||
/// {@macro dino_desert}
|
||||
DinoDesert()
|
||||
: super(
|
||||
components: [
|
||||
ChromeDino()..initialPosition = Vector2(12.3, -6.9),
|
||||
],
|
||||
blueprints: [
|
||||
DinoWalls(),
|
||||
],
|
||||
);
|
||||
}
|
@ -0,0 +1 @@
|
||||
export 'chrome_dino_bonus_behavior.dart';
|
@ -0,0 +1,24 @@
|
||||
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';
|
||||
|
||||
/// Adds a [GameBonus.dinoChomp] when a [Ball] is chomped by the [ChromeDino].
|
||||
class ChromeDinoBonusBehavior extends Component
|
||||
with HasGameRef<PinballGame>, ParentIsA<DinoDesert> {
|
||||
@override
|
||||
void onMount() {
|
||||
super.onMount();
|
||||
final chromeDino = parent.firstChild<ChromeDino>()!;
|
||||
|
||||
// TODO(alestiago): Refactor subscription management once the following is
|
||||
// merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
chromeDino.bloc.stream.listen((state) {
|
||||
final listenWhen = state.status == ChromeDinoStatus.chomping;
|
||||
if (!listenWhen) return;
|
||||
|
||||
gameRef.read<GameBloc>().add(const BonusActivated(GameBonus.dinoChomp));
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball/game/components/dino_desert/behaviors/behaviors.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template dino_desert}
|
||||
/// Area located next to the [Launcher] containing the [ChromeDino],
|
||||
/// [DinoWalls], and the [Slingshots].
|
||||
/// {@endtemplate}
|
||||
class DinoDesert extends Component {
|
||||
/// {@macro dino_desert}
|
||||
DinoDesert()
|
||||
: super(
|
||||
children: [
|
||||
ChromeDino(
|
||||
children: [
|
||||
ScoringBehavior(points: Points.twoHundredThousand)
|
||||
..applyTo(['inside_mouth']),
|
||||
],
|
||||
)..initialPosition = Vector2(12.6, -6.9),
|
||||
_BarrierBehindDino(),
|
||||
DinoWalls(),
|
||||
Slingshots(),
|
||||
ChromeDinoBonusBehavior(),
|
||||
],
|
||||
);
|
||||
|
||||
/// Creates [DinoDesert] without any children.
|
||||
///
|
||||
/// This can be used for testing [DinoDesert]'s behaviors in isolation.
|
||||
@visibleForTesting
|
||||
DinoDesert.test();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -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()],
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
export 'multiballs_behavior.dart';
|
@ -0,0 +1,28 @@
|
||||
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';
|
||||
|
||||
/// Toggle each [Multiball] when there is a bonus ball.
|
||||
class MultiballsBehavior extends Component
|
||||
with
|
||||
HasGameRef<PinballGame>,
|
||||
ParentIsA<Multiballs>,
|
||||
BlocComponent<GameBloc, GameState> {
|
||||
@override
|
||||
bool listenWhen(GameState? previousState, GameState newState) {
|
||||
final hasChanged = previousState?.bonusHistory != newState.bonusHistory;
|
||||
final lastBonusIsMultiball = newState.bonusHistory.isNotEmpty &&
|
||||
newState.bonusHistory.last == GameBonus.dashNest;
|
||||
|
||||
return hasChanged && lastBonusIsMultiball;
|
||||
}
|
||||
|
||||
@override
|
||||
void onNewState(GameState state) {
|
||||
parent.children.whereType<Multiball>().forEach((multiball) {
|
||||
multiball.bloc.onAnimate();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball/game/components/multiballs/behaviors/behaviors.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// {@template multiballs_component}
|
||||
/// A [SpriteGroupComponent] for the multiball over the board.
|
||||
/// {@endtemplate}
|
||||
class Multiballs extends Component with ZIndex {
|
||||
/// {@macro multiballs_component}
|
||||
Multiballs()
|
||||
: super(
|
||||
children: [
|
||||
Multiball.a(),
|
||||
Multiball.b(),
|
||||
Multiball.c(),
|
||||
Multiball.d(),
|
||||
MultiballsBehavior(),
|
||||
],
|
||||
) {
|
||||
zIndex = ZIndexes.decal;
|
||||
}
|
||||
|
||||
/// Creates a [Multiballs] without any children.
|
||||
///
|
||||
/// This can be used for testing [Multiballs]'s behaviors in isolation.
|
||||
@visibleForTesting
|
||||
Multiballs.test();
|
||||
}
|
@ -0,0 +1 @@
|
||||
export 'widgets/widgets.dart';
|
@ -0,0 +1,102 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/flame.dart';
|
||||
import 'package:flame/sprite.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
import 'package:pinball_theme/pinball_theme.dart';
|
||||
|
||||
/// {@template selected_character}
|
||||
/// Shows an animated version of the character currently selected.
|
||||
/// {@endtemplate}
|
||||
class SelectedCharacter extends StatefulWidget {
|
||||
/// {@macro selected_character}
|
||||
const SelectedCharacter({
|
||||
Key? key,
|
||||
required this.currentCharacter,
|
||||
}) : super(key: key);
|
||||
|
||||
/// The character that is selected at the moment.
|
||||
final CharacterTheme currentCharacter;
|
||||
|
||||
@override
|
||||
State<SelectedCharacter> createState() => _SelectedCharacterState();
|
||||
|
||||
/// Returns a list of assets to be loaded.
|
||||
static List<Future> loadAssets() {
|
||||
return [
|
||||
Flame.images.load(const DashTheme().animation.keyName),
|
||||
Flame.images.load(const AndroidTheme().animation.keyName),
|
||||
Flame.images.load(const DinoTheme().animation.keyName),
|
||||
Flame.images.load(const SparkyTheme().animation.keyName),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class _SelectedCharacterState extends State<SelectedCharacter>
|
||||
with TickerProviderStateMixin {
|
||||
SpriteAnimationController? _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_setupCharacterAnimation();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant SelectedCharacter oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_setupCharacterAnimation();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
widget.currentCharacter.name,
|
||||
style: Theme.of(context).textTheme.headline2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Expanded(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SizedBox(
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
child: SpriteAnimationWidget(
|
||||
controller: _controller!,
|
||||
anchor: Anchor.center,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _setupCharacterAnimation() {
|
||||
final spriteSheet = SpriteSheet.fromColumnsAndRows(
|
||||
image: Flame.images.fromCache(widget.currentCharacter.animation.keyName),
|
||||
columns: 12,
|
||||
rows: 6,
|
||||
);
|
||||
final animation = spriteSheet.createAnimation(
|
||||
row: 0,
|
||||
stepTime: 1 / 24,
|
||||
to: spriteSheet.rows * spriteSheet.columns,
|
||||
);
|
||||
if (_controller != null) _controller?.dispose();
|
||||
_controller = SpriteAnimationController(vsync: this, animation: animation)
|
||||
..forward()
|
||||
..repeat();
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
export 'character_selection_page.dart';
|
||||
export 'selected_character.dart';
|
||||
|
@ -1 +0,0 @@
|
||||
export 'mocks.dart';
|
@ -1,34 +0,0 @@
|
||||
// ignore_for_file: one_member_abstracts
|
||||
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:flame_audio/audio_pool.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
abstract class _CreateAudioPoolStub {
|
||||
Future<AudioPool> onCall(
|
||||
String sound, {
|
||||
bool? repeating,
|
||||
int? maxPlayers,
|
||||
int? minPlayers,
|
||||
String? prefix,
|
||||
});
|
||||
}
|
||||
|
||||
class CreateAudioPoolStub extends Mock implements _CreateAudioPoolStub {}
|
||||
|
||||
abstract class _ConfigureAudioCacheStub {
|
||||
void onCall(AudioCache cache);
|
||||
}
|
||||
|
||||
class ConfigureAudioCacheStub extends Mock implements _ConfigureAudioCacheStub {
|
||||
}
|
||||
|
||||
abstract class _PlaySingleAudioStub {
|
||||
Future<void> onCall(String url);
|
||||
}
|
||||
|
||||
class PlaySingleAudioStub extends Mock implements _PlaySingleAudioStub {}
|
||||
|
||||
class MockAudioPool extends Mock implements AudioPool {}
|
||||
|
||||
class MockAudioCache extends Mock implements AudioCache {}
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 588 B |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 371 B |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 371 B |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 371 B |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 371 B |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 313 B |
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 343 B |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 16 KiB |