@ -0,0 +1,24 @@
|
||||
name: pinball_flame
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "packages/pinball_flame/**"
|
||||
- ".github/workflows/pinball_flame.yaml"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- "packages/pinball_flame/**"
|
||||
- ".github/workflows/pinball_flame.yaml"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
|
||||
with:
|
||||
working_directory: packages/pinball_flame
|
||||
coverage_excludes: "lib/gen/*.dart"
|
||||
test_optimization: false
|
@ -0,0 +1,18 @@
|
||||
name: share_repository
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "packages/share_repository/**"
|
||||
- ".github/workflows/share_repository.yaml"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- "packages/share_repository/**"
|
||||
- ".github/workflows/share_repository.yaml"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
|
||||
with:
|
||||
working_directory: packages/share_repository
|
After Width: | Height: | Size: 306 KiB |
After Width: | Height: | Size: 422 KiB |
After Width: | Height: | Size: 171 KiB |
After Width: | Height: | Size: 222 KiB |
After Width: | Height: | Size: 298 KiB |
Before Width: | Height: | Size: 9.5 MiB After Width: | Height: | Size: 2.2 MiB |
Before Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 11 KiB |
@ -1 +0,0 @@
|
||||
export 'component_controller.dart';
|
@ -0,0 +1,29 @@
|
||||
// ignore_for_file: avoid_renaming_method_parameters
|
||||
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// {@template alien_zone}
|
||||
/// Area positioned below [Spaceship] where the [Ball]
|
||||
/// can bounce off [AlienBumper]s.
|
||||
/// {@endtemplate}
|
||||
class AlienZone extends Blueprint {
|
||||
/// {@macro alien_zone}
|
||||
AlienZone()
|
||||
: super(
|
||||
components: [
|
||||
AlienBumper.a(
|
||||
children: [
|
||||
ScoringBehavior(points: 20),
|
||||
],
|
||||
)..initialPosition = Vector2(-32.52, -9.1),
|
||||
AlienBumper.b(
|
||||
children: [
|
||||
ScoringBehavior(points: 20),
|
||||
],
|
||||
)..initialPosition = Vector2(-22.89, -17.35),
|
||||
],
|
||||
);
|
||||
}
|
@ -1,208 +0,0 @@
|
||||
// ignore_for_file: avoid_renaming_method_parameters
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
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:flutter/material.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template bonus_word}
|
||||
/// Loads all [BonusLetter]s to compose a [BonusWord].
|
||||
/// {@endtemplate}
|
||||
class BonusWord extends Component
|
||||
with BlocComponent<GameBloc, GameState>, HasGameRef<PinballGame> {
|
||||
/// {@macro bonus_word}
|
||||
BonusWord({required Vector2 position}) : _position = position;
|
||||
|
||||
final Vector2 _position;
|
||||
|
||||
@override
|
||||
bool listenWhen(GameState? previousState, GameState newState) {
|
||||
return (previousState?.bonusHistory.length ?? 0) <
|
||||
newState.bonusHistory.length &&
|
||||
newState.bonusHistory.last == GameBonus.word;
|
||||
}
|
||||
|
||||
@override
|
||||
void onNewState(GameState state) {
|
||||
if (state.bonusHistory.last == GameBonus.word) {
|
||||
gameRef.audio.googleBonus();
|
||||
|
||||
final letters = children.whereType<BonusLetter>().toList();
|
||||
|
||||
for (var i = 0; i < letters.length; i++) {
|
||||
final letter = letters[i];
|
||||
letter
|
||||
..isEnabled = false
|
||||
..add(
|
||||
SequenceEffect(
|
||||
[
|
||||
ColorEffect(
|
||||
i.isOdd
|
||||
? BonusLetter._activeColor
|
||||
: BonusLetter._disableColor,
|
||||
const Offset(0, 1),
|
||||
EffectController(duration: 0.25),
|
||||
),
|
||||
ColorEffect(
|
||||
i.isOdd
|
||||
? BonusLetter._disableColor
|
||||
: BonusLetter._activeColor,
|
||||
const Offset(0, 1),
|
||||
EffectController(duration: 0.25),
|
||||
),
|
||||
],
|
||||
repeatCount: 4,
|
||||
)..onFinishCallback = () {
|
||||
letter
|
||||
..isEnabled = true
|
||||
..add(
|
||||
ColorEffect(
|
||||
BonusLetter._disableColor,
|
||||
const Offset(0, 1),
|
||||
EffectController(duration: 0.25),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
final offsets = [
|
||||
Vector2(-12.92, -1.82),
|
||||
Vector2(-8.33, 0.65),
|
||||
Vector2(-2.88, 1.75),
|
||||
];
|
||||
offsets.addAll(
|
||||
offsets.reversed
|
||||
.map(
|
||||
(offset) => Vector2(-offset.x, offset.y),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
assert(offsets.length == GameBloc.bonusWord.length, 'Invalid positions');
|
||||
|
||||
final letters = <BonusLetter>[];
|
||||
for (var i = 0; i < GameBloc.bonusWord.length; i++) {
|
||||
letters.add(
|
||||
BonusLetter(
|
||||
letter: GameBloc.bonusWord[i],
|
||||
index: i,
|
||||
)..initialPosition = _position + offsets[i],
|
||||
);
|
||||
}
|
||||
|
||||
await addAll(letters);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template bonus_letter}
|
||||
/// [BodyType.static] sensor component, part of a word bonus,
|
||||
/// which will activate its letter after contact with a [Ball].
|
||||
/// {@endtemplate}
|
||||
class BonusLetter extends BodyComponent<PinballGame>
|
||||
with BlocComponent<GameBloc, GameState>, InitialPosition {
|
||||
/// {@macro bonus_letter}
|
||||
BonusLetter({
|
||||
required String letter,
|
||||
required int index,
|
||||
}) : _letter = letter,
|
||||
_index = index {
|
||||
paint = Paint()..color = _disableColor;
|
||||
}
|
||||
|
||||
/// The size of the [BonusLetter].
|
||||
static final size = Vector2.all(3.7);
|
||||
|
||||
static const _activeColor = Colors.green;
|
||||
static const _disableColor = Colors.red;
|
||||
|
||||
final String _letter;
|
||||
final int _index;
|
||||
|
||||
/// Indicates if a [BonusLetter] can be activated on [Ball] contact.
|
||||
///
|
||||
/// It is disabled whilst animating and enabled again once the animation
|
||||
/// completes. The animation is triggered when [GameBonus.word] is
|
||||
/// awarded.
|
||||
bool isEnabled = true;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
await add(
|
||||
TextComponent(
|
||||
position: Vector2(-1, -1),
|
||||
text: _letter,
|
||||
textRenderer: TextPaint(
|
||||
style: const TextStyle(fontSize: 2, color: Colors.white),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = CircleShape()..radius = size.x / 2;
|
||||
|
||||
final fixtureDef = FixtureDef(shape)..isSensor = true;
|
||||
|
||||
final bodyDef = BodyDef()
|
||||
..position = initialPosition
|
||||
..userData = this
|
||||
..type = BodyType.static;
|
||||
|
||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||
}
|
||||
|
||||
@override
|
||||
bool listenWhen(GameState? previousState, GameState newState) {
|
||||
final wasActive = previousState?.isLetterActivated(_index) ?? false;
|
||||
final isActive = newState.isLetterActivated(_index);
|
||||
|
||||
return wasActive != isActive;
|
||||
}
|
||||
|
||||
@override
|
||||
void onNewState(GameState state) {
|
||||
final isActive = state.isLetterActivated(_index);
|
||||
|
||||
add(
|
||||
ColorEffect(
|
||||
isActive ? _activeColor : _disableColor,
|
||||
const Offset(0, 1),
|
||||
EffectController(duration: 0.25),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Activates this [BonusLetter], if it's not already activated.
|
||||
void activate() {
|
||||
final isActive = state?.isLetterActivated(_index) ?? false;
|
||||
if (!isActive) {
|
||||
gameRef.read<GameBloc>().add(BonusLetterActivated(_index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Triggers [BonusLetter.activate] method when a [BonusLetter] and a [Ball]
|
||||
/// come in contact.
|
||||
class BonusLetterBallContactCallback
|
||||
extends ContactCallback<Ball, BonusLetter> {
|
||||
@override
|
||||
void begin(Ball ball, BonusLetter bonusLetter, Contact contact) {
|
||||
if (bonusLetter.isEnabled) {
|
||||
bonusLetter.activate();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
export 'alien_zone.dart';
|
||||
export 'board.dart';
|
||||
export 'bonus_word.dart';
|
||||
export 'camera_controller.dart';
|
||||
export 'controlled_ball.dart';
|
||||
export 'controlled_flipper.dart';
|
||||
export 'controlled_sparky_computer.dart';
|
||||
export 'flutter_forest.dart';
|
||||
export 'controlled_plunger.dart';
|
||||
export 'flutter_forest/flutter_forest.dart';
|
||||
export 'game_flow_controller.dart';
|
||||
export 'plunger.dart';
|
||||
export 'score_effect_controller.dart';
|
||||
export 'score_points.dart';
|
||||
export 'google_word/google_word.dart';
|
||||
export 'launcher.dart';
|
||||
export 'scoring_behavior.dart';
|
||||
export 'sparky_fire_zone.dart';
|
||||
export 'wall.dart';
|
||||
|
@ -0,0 +1,52 @@
|
||||
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_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// {@template controlled_plunger}
|
||||
/// A [Plunger] with a [PlungerController] attached.
|
||||
/// {@endtemplate}
|
||||
class ControlledPlunger extends Plunger with Controls<PlungerController> {
|
||||
/// {@macro controlled_plunger}
|
||||
ControlledPlunger({required double compressionDistance})
|
||||
: super(compressionDistance: compressionDistance) {
|
||||
controller = PlungerController(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template plunger_controller}
|
||||
/// A [ComponentController] that controls a [Plunger]s movement.
|
||||
/// {@endtemplate}
|
||||
class PlungerController extends ComponentController<Plunger>
|
||||
with KeyboardHandler, BlocComponent<GameBloc, GameState> {
|
||||
/// {@macro plunger_controller}
|
||||
PlungerController(Plunger plunger) : super(plunger);
|
||||
|
||||
/// The [LogicalKeyboardKey]s that will control the [Flipper].
|
||||
///
|
||||
/// [onKeyEvent] method listens to when one of these keys is pressed.
|
||||
static const List<LogicalKeyboardKey> _keys = [
|
||||
LogicalKeyboardKey.arrowDown,
|
||||
LogicalKeyboardKey.space,
|
||||
LogicalKeyboardKey.keyS,
|
||||
];
|
||||
|
||||
@override
|
||||
bool onKeyEvent(
|
||||
RawKeyEvent event,
|
||||
Set<LogicalKeyboardKey> keysPressed,
|
||||
) {
|
||||
if (state?.isGameOver ?? false) return true;
|
||||
if (!_keys.contains(event.logicalKey)) return true;
|
||||
|
||||
if (event is RawKeyDownEvent) {
|
||||
component.pull();
|
||||
} else if (event is RawKeyUpEvent) {
|
||||
component.release();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
// ignore_for_file: avoid_renaming_method_parameters
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball/flame/flame.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template controlled_sparky_computer}
|
||||
/// [SparkyComputer] with a [SparkyComputerController] attached.
|
||||
/// {@endtemplate}
|
||||
class ControlledSparkyComputer extends SparkyComputer
|
||||
with Controls<SparkyComputerController>, HasGameRef<PinballGame> {
|
||||
/// {@macro controlled_sparky_computer}
|
||||
ControlledSparkyComputer() {
|
||||
controller = SparkyComputerController(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void build(Forge2DGame _) {
|
||||
addContactCallback(SparkyTurboChargeSensorBallContactCallback());
|
||||
final sparkyTurboChargeSensor = SparkyTurboChargeSensor()
|
||||
..initialPosition = Vector2(-13, 49.8);
|
||||
add(sparkyTurboChargeSensor);
|
||||
super.build(_);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template sparky_computer_controller}
|
||||
/// Controller attached to a [SparkyComputer] that handles its game related
|
||||
/// logic.
|
||||
/// {@endtemplate}
|
||||
// TODO(allisonryan0002): listen for turbo charge game bonus and animate Sparky.
|
||||
class SparkyComputerController
|
||||
extends ComponentController<ControlledSparkyComputer> {
|
||||
/// {@macro sparky_computer_controller}
|
||||
SparkyComputerController(ControlledSparkyComputer controlledComputer)
|
||||
: super(controlledComputer);
|
||||
}
|
||||
|
||||
/// {@template sparky_turbo_charge_sensor}
|
||||
/// Small sensor body used to detect when a ball has entered the
|
||||
/// [SparkyComputer] with the [SparkyTurboChargeSensorBallContactCallback].
|
||||
/// {@endtemplate}
|
||||
@visibleForTesting
|
||||
class SparkyTurboChargeSensor extends BodyComponent with InitialPosition {
|
||||
/// {@macro sparky_turbo_charge_sensor}
|
||||
SparkyTurboChargeSensor() {
|
||||
renderBody = false;
|
||||
}
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = CircleShape()..radius = 0.1;
|
||||
|
||||
final fixtureDef = FixtureDef(shape)..isSensor = true;
|
||||
|
||||
final bodyDef = BodyDef()
|
||||
..position = initialPosition
|
||||
..userData = this;
|
||||
|
||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template sparky_turbo_charge_sensor_ball_contact_callback}
|
||||
/// Turbo charges the [Ball] on contact with [SparkyTurboChargeSensor].
|
||||
/// {@endtemplate}
|
||||
@visibleForTesting
|
||||
class SparkyTurboChargeSensorBallContactCallback
|
||||
extends ContactCallback<SparkyTurboChargeSensor, ControlledBall> {
|
||||
/// {@macro sparky_turbo_charge_sensor_ball_contact_callback}
|
||||
SparkyTurboChargeSensorBallContactCallback();
|
||||
|
||||
@override
|
||||
void begin(
|
||||
SparkyTurboChargeSensor sparkyTurboChargeSensor,
|
||||
ControlledBall ball,
|
||||
_,
|
||||
) {
|
||||
ball.controller.turboCharge();
|
||||
}
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
// ignore_for_file: avoid_renaming_method_parameters
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_bloc/flame_bloc.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball/flame/flame.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template flutter_forest}
|
||||
/// Area positioned at the top right of the [Board] where the [Ball]
|
||||
/// can bounce off [DashNestBumper]s.
|
||||
///
|
||||
/// When all [DashNestBumper]s are hit at least once, the [GameBonus.dashNest]
|
||||
/// is awarded, and the [BigDashNestBumper] releases a new [Ball].
|
||||
/// {@endtemplate}
|
||||
// TODO(alestiago): Make a [Blueprint] once [Blueprint] inherits from
|
||||
// [Component].
|
||||
class FlutterForest extends Component with Controls<_FlutterForestController> {
|
||||
/// {@macro flutter_forest}
|
||||
FlutterForest() {
|
||||
controller = _FlutterForestController(this);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
final signPost = FlutterSignPost()..initialPosition = Vector2(8.35, 58.3);
|
||||
|
||||
final bigNest = _ControlledBigDashNestBumper(
|
||||
id: 'big_nest_bumper',
|
||||
)..initialPosition = Vector2(18.55, 59.35);
|
||||
final smallLeftNest = _ControlledSmallDashNestBumper.a(
|
||||
id: 'small_nest_bumper_a',
|
||||
)..initialPosition = Vector2(8.95, 51.95);
|
||||
final smallRightNest = _ControlledSmallDashNestBumper.b(
|
||||
id: 'small_nest_bumper_b',
|
||||
)..initialPosition = Vector2(23.3, 46.75);
|
||||
|
||||
await addAll([
|
||||
signPost,
|
||||
smallLeftNest,
|
||||
smallRightNest,
|
||||
bigNest,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class _FlutterForestController extends ComponentController<FlutterForest>
|
||||
with BlocComponent<GameBloc, GameState>, HasGameRef<PinballGame> {
|
||||
_FlutterForestController(FlutterForest flutterForest) : super(flutterForest);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
gameRef.addContactCallback(_ControlledDashNestBumperBallContactCallback());
|
||||
}
|
||||
|
||||
@override
|
||||
bool listenWhen(GameState? previousState, GameState newState) {
|
||||
return (previousState?.bonusHistory.length ?? 0) <
|
||||
newState.bonusHistory.length &&
|
||||
newState.bonusHistory.last == GameBonus.dashNest;
|
||||
}
|
||||
|
||||
@override
|
||||
void onNewState(GameState state) {
|
||||
super.onNewState(state);
|
||||
|
||||
gameRef.add(
|
||||
ControlledBall.bonus(theme: gameRef.theme)
|
||||
..initialPosition = Vector2(17.2, 52.7),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ControlledBigDashNestBumper extends BigDashNestBumper
|
||||
with Controls<DashNestBumperController>, ScorePoints {
|
||||
_ControlledBigDashNestBumper({required String id}) : super() {
|
||||
controller = DashNestBumperController(this, id: id);
|
||||
}
|
||||
|
||||
@override
|
||||
int get points => 20;
|
||||
}
|
||||
|
||||
class _ControlledSmallDashNestBumper extends SmallDashNestBumper
|
||||
with Controls<DashNestBumperController>, ScorePoints {
|
||||
_ControlledSmallDashNestBumper.a({required String id}) : super.a() {
|
||||
controller = DashNestBumperController(this, id: id);
|
||||
}
|
||||
|
||||
_ControlledSmallDashNestBumper.b({required String id}) : super.b() {
|
||||
controller = DashNestBumperController(this, id: id);
|
||||
}
|
||||
|
||||
@override
|
||||
int get points => 10;
|
||||
}
|
||||
|
||||
/// {@template dash_nest_bumper_controller}
|
||||
/// Controls a [DashNestBumper].
|
||||
/// {@endtemplate}
|
||||
@visibleForTesting
|
||||
class DashNestBumperController extends ComponentController<DashNestBumper>
|
||||
with BlocComponent<GameBloc, GameState>, HasGameRef<PinballGame> {
|
||||
/// {@macro dash_nest_bumper_controller}
|
||||
DashNestBumperController(
|
||||
DashNestBumper dashNestBumper, {
|
||||
required this.id,
|
||||
}) : super(dashNestBumper);
|
||||
|
||||
/// Unique identifier for the controlled [DashNestBumper].
|
||||
///
|
||||
/// Used to identify [DashNestBumper]s in [GameState.activatedDashNests].
|
||||
final String id;
|
||||
|
||||
@override
|
||||
bool listenWhen(GameState? previousState, GameState newState) {
|
||||
final wasActive = previousState?.activatedDashNests.contains(id) ?? false;
|
||||
final isActive = newState.activatedDashNests.contains(id);
|
||||
|
||||
return wasActive != isActive;
|
||||
}
|
||||
|
||||
@override
|
||||
void onNewState(GameState state) {
|
||||
super.onNewState(state);
|
||||
|
||||
if (state.activatedDashNests.contains(id)) {
|
||||
component.activate();
|
||||
} else {
|
||||
component.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers when a [DashNestBumper] is hit by a [Ball].
|
||||
///
|
||||
/// Triggered by [_ControlledDashNestBumperBallContactCallback].
|
||||
void hit() {
|
||||
gameRef.read<GameBloc>().add(DashNestActivated(id));
|
||||
}
|
||||
}
|
||||
|
||||
/// Listens when a [Ball] bounces bounces against a [DashNestBumper].
|
||||
class _ControlledDashNestBumperBallContactCallback
|
||||
extends ContactCallback<Controls<DashNestBumperController>, Ball> {
|
||||
@override
|
||||
void begin(
|
||||
Controls<DashNestBumperController> controlledDashNestBumper,
|
||||
Ball _,
|
||||
Contact __,
|
||||
) {
|
||||
controlledDashNestBumper.controller.hit();
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export 'flutter_forest_bonus_behavior.dart';
|
@ -0,0 +1,41 @@
|
||||
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';
|
||||
|
||||
/// When all [DashNestBumper]s are hit at least once, the [GameBonus.dashNest]
|
||||
/// is awarded, and the [DashNestBumper.main] releases a new [Ball].
|
||||
class FlutterForestBonusBehavior extends Component
|
||||
with ParentIsA<FlutterForest>, HasGameRef<PinballGame> {
|
||||
@override
|
||||
void onMount() {
|
||||
super.onMount();
|
||||
|
||||
final bumpers = parent.children.whereType<DashNestBumper>();
|
||||
for (final bumper in bumpers) {
|
||||
// TODO(alestiago): Refactor subscription management once the following is
|
||||
// merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
bumper.bloc.stream.listen((state) {
|
||||
final achievedBonus = bumpers.every(
|
||||
(bumper) => bumper.bloc.state == DashNestBumperState.active,
|
||||
);
|
||||
|
||||
if (achievedBonus) {
|
||||
gameRef
|
||||
.read<GameBloc>()
|
||||
.add(const BonusActivated(GameBonus.dashNest));
|
||||
gameRef.add(
|
||||
ControlledBall.bonus(characterTheme: gameRef.characterTheme)
|
||||
..initialPosition = Vector2(17.2, -52.7),
|
||||
);
|
||||
parent.firstChild<DashAnimatronic>()?.playing = true;
|
||||
|
||||
for (final bumper in bumpers) {
|
||||
bumper.bloc.onReset();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
// ignore_for_file: avoid_renaming_method_parameters
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball/game/components/flutter_forest/behaviors/behaviors.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template flutter_forest}
|
||||
/// Area positioned at the top right of the [Board] where the [Ball] can bounce
|
||||
/// off [DashNestBumper]s.
|
||||
/// {@endtemplate}
|
||||
class FlutterForest extends Component {
|
||||
/// {@macro flutter_forest}
|
||||
FlutterForest()
|
||||
: super(
|
||||
priority: RenderPriority.flutterForest,
|
||||
children: [
|
||||
Signpost(
|
||||
children: [
|
||||
ScoringBehavior(points: 20),
|
||||
],
|
||||
)..initialPosition = Vector2(8.35, -58.3),
|
||||
DashNestBumper.main(
|
||||
children: [
|
||||
ScoringBehavior(points: 20),
|
||||
],
|
||||
)..initialPosition = Vector2(18.55, -59.35),
|
||||
DashNestBumper.a(
|
||||
children: [
|
||||
ScoringBehavior(points: 20),
|
||||
],
|
||||
)..initialPosition = Vector2(8.95, -51.95),
|
||||
DashNestBumper.b(
|
||||
children: [
|
||||
ScoringBehavior(points: 20),
|
||||
],
|
||||
)..initialPosition = Vector2(23.3, -46.75),
|
||||
DashAnimatronic()..position = Vector2(20, -66),
|
||||
FlutterForestBonusBehavior(),
|
||||
],
|
||||
);
|
||||
|
||||
/// Creates a [FlutterForest] without any children.
|
||||
///
|
||||
/// This can be used for testing [FlutterForest]'s behaviors in isolation.
|
||||
@visibleForTesting
|
||||
FlutterForest.test();
|
||||
}
|
@ -0,0 +1 @@
|
||||
export 'google_word_bonus_behavior.dart';
|
@ -0,0 +1,34 @@
|
||||
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.googleWord] when all [GoogleLetter]s are activated.
|
||||
class GoogleWordBonusBehavior extends Component
|
||||
with HasGameRef<PinballGame>, ParentIsA<GoogleWord> {
|
||||
@override
|
||||
void onMount() {
|
||||
super.onMount();
|
||||
|
||||
final googleLetters = parent.children.whereType<GoogleLetter>();
|
||||
for (final letter in googleLetters) {
|
||||
// TODO(alestiago): Refactor subscription management once the following is
|
||||
// merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
letter.bloc.stream.listen((_) {
|
||||
final achievedBonus = googleLetters
|
||||
.every((letter) => letter.bloc.state == GoogleLetterState.active);
|
||||
|
||||
if (achievedBonus) {
|
||||
gameRef.audio.googleBonus();
|
||||
gameRef
|
||||
.read<GameBloc>()
|
||||
.add(const BonusActivated(GameBonus.googleWord));
|
||||
for (final letter in googleLetters) {
|
||||
letter.bloc.onReset();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball/game/components/google_word/behaviors/behaviors.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template google_word}
|
||||
/// Loads all [GoogleLetter]s to compose a [GoogleWord].
|
||||
/// {@endtemplate}
|
||||
class GoogleWord extends Component {
|
||||
/// {@macro google_word}
|
||||
GoogleWord({
|
||||
required Vector2 position,
|
||||
}) : super(
|
||||
children: [
|
||||
GoogleLetter(0)..initialPosition = position + Vector2(-12.92, 1.82),
|
||||
GoogleLetter(1)..initialPosition = position + Vector2(-8.33, -0.65),
|
||||
GoogleLetter(2)..initialPosition = position + Vector2(-2.88, -1.75),
|
||||
GoogleLetter(3)..initialPosition = position + Vector2(2.88, -1.75),
|
||||
GoogleLetter(4)..initialPosition = position + Vector2(8.33, -0.65),
|
||||
GoogleLetter(5)..initialPosition = position + Vector2(12.92, 1.82),
|
||||
GoogleWordBonusBehavior(),
|
||||
],
|
||||
);
|
||||
|
||||
/// Creates a [GoogleWord] without any children.
|
||||
///
|
||||
/// This can be used for testing [GoogleWord]'s behaviors in isolation.
|
||||
@visibleForTesting
|
||||
GoogleWord.test();
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import 'package:flame_forge2d/flame_forge2d.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].
|
||||
/// {@endtemplate}
|
||||
class Launcher extends Blueprint {
|
||||
/// {@macro launcher}
|
||||
Launcher()
|
||||
: super(
|
||||
components: [
|
||||
ControlledPlunger(compressionDistance: 14)
|
||||
..initialPosition = Vector2(40.7, 38),
|
||||
RocketSpriteComponent()..position = Vector2(43, 62),
|
||||
],
|
||||
blueprints: [LaunchRamp()],
|
||||
);
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:pinball/gen/assets.gen.dart';
|
||||
import 'package:pinball_components/pinball_components.dart' hide Assets;
|
||||
|
||||
/// {@template plunger}
|
||||
/// [Plunger] serves as a spring, that shoots the ball on the right side of the
|
||||
/// playfield.
|
||||
///
|
||||
/// [Plunger] ignores gravity so the player controls its downward [_pull].
|
||||
/// {@endtemplate}
|
||||
class Plunger extends BodyComponent with KeyboardHandler, InitialPosition {
|
||||
/// {@macro plunger}
|
||||
Plunger({
|
||||
required this.compressionDistance,
|
||||
// TODO(ruimiguel): set to priority +1 over LaunchRamp once all priorities
|
||||
// are fixed.
|
||||
}) : super(priority: 0);
|
||||
|
||||
/// Distance the plunger can lower.
|
||||
final double compressionDistance;
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = PolygonShape()
|
||||
..setAsBox(
|
||||
1.35,
|
||||
0.5,
|
||||
Vector2.zero(),
|
||||
BoardDimensions.perspectiveAngle,
|
||||
);
|
||||
|
||||
final fixtureDef = FixtureDef(shape)..density = 80;
|
||||
|
||||
final bodyDef = BodyDef()
|
||||
..position = initialPosition
|
||||
..userData = this
|
||||
..type = BodyType.dynamic
|
||||
..gravityScale = 0;
|
||||
|
||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||
}
|
||||
|
||||
/// Set a constant downward velocity on the [Plunger].
|
||||
void _pull() {
|
||||
body.linearVelocity = Vector2(0, -7);
|
||||
}
|
||||
|
||||
/// Set an upward velocity on the [Plunger].
|
||||
///
|
||||
/// The velocity's magnitude depends on how far the [Plunger] has been pulled
|
||||
/// from its original [initialPosition].
|
||||
void _release() {
|
||||
final velocity = (initialPosition.y - body.position.y) * 5;
|
||||
body.linearVelocity = Vector2(0, velocity);
|
||||
}
|
||||
|
||||
@override
|
||||
bool onKeyEvent(
|
||||
RawKeyEvent event,
|
||||
Set<LogicalKeyboardKey> keysPressed,
|
||||
) {
|
||||
final keys = [
|
||||
LogicalKeyboardKey.space,
|
||||
LogicalKeyboardKey.arrowDown,
|
||||
LogicalKeyboardKey.keyS,
|
||||
];
|
||||
if (!keys.contains(event.logicalKey)) return true;
|
||||
|
||||
if (event is RawKeyDownEvent) {
|
||||
_pull();
|
||||
} else if (event is RawKeyUpEvent) {
|
||||
_release();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Anchors the [Plunger] to the [PrismaticJoint] that controls its vertical
|
||||
/// motion.
|
||||
Future<void> _anchorToJoint() async {
|
||||
final anchor = PlungerAnchor(plunger: this);
|
||||
await add(anchor);
|
||||
|
||||
final jointDef = PlungerAnchorPrismaticJointDef(
|
||||
plunger: this,
|
||||
anchor: anchor,
|
||||
);
|
||||
|
||||
world.createJoint(
|
||||
PrismaticJoint(jointDef)..setLimits(-compressionDistance, 0),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
await _anchorToJoint();
|
||||
|
||||
renderBody = false;
|
||||
|
||||
await _loadSprite();
|
||||
}
|
||||
|
||||
Future<void> _loadSprite() async {
|
||||
final sprite = await gameRef.loadSprite(
|
||||
Assets.images.components.plunger.path,
|
||||
);
|
||||
|
||||
await add(
|
||||
SpriteComponent(
|
||||
sprite: sprite,
|
||||
size: Vector2(5.5, 40),
|
||||
anchor: Anchor.center,
|
||||
position: Vector2(2, 19),
|
||||
angle: -0.033,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template plunger_anchor}
|
||||
/// [JointAnchor] positioned below a [Plunger].
|
||||
/// {@endtemplate}
|
||||
class PlungerAnchor extends JointAnchor {
|
||||
/// {@macro plunger_anchor}
|
||||
PlungerAnchor({
|
||||
required Plunger plunger,
|
||||
}) {
|
||||
initialPosition = Vector2(
|
||||
0,
|
||||
-plunger.compressionDistance,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final bodyDef = BodyDef()
|
||||
..position = initialPosition
|
||||
..type = BodyType.static;
|
||||
return world.createBody(bodyDef);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template plunger_anchor_prismatic_joint_def}
|
||||
/// [PrismaticJointDef] between a [Plunger] and an [JointAnchor] with motion on
|
||||
/// the vertical axis.
|
||||
///
|
||||
/// The [Plunger] is constrained vertically between its starting position and
|
||||
/// the [JointAnchor]. The [JointAnchor] must be below the [Plunger].
|
||||
/// {@endtemplate}
|
||||
class PlungerAnchorPrismaticJointDef extends PrismaticJointDef {
|
||||
/// {@macro plunger_anchor_prismatic_joint_def}
|
||||
PlungerAnchorPrismaticJointDef({
|
||||
required Plunger plunger,
|
||||
required PlungerAnchor anchor,
|
||||
}) {
|
||||
initialize(
|
||||
plunger.body,
|
||||
anchor.body,
|
||||
plunger.body.position + anchor.body.position,
|
||||
Vector2(18.6, BoardDimensions.bounds.height),
|
||||
);
|
||||
enableLimit = true;
|
||||
lowerTranslation = double.negativeInfinity;
|
||||
enableMotor = true;
|
||||
motorSpeed = 1000;
|
||||
maxMotorForce = motorSpeed;
|
||||
collideConnected = true;
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_bloc/flame_bloc.dart';
|
||||
import 'package:pinball/flame/flame.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template score_effect_controller}
|
||||
/// A [ComponentController] responsible for adding [ScoreText]s
|
||||
/// on the game screen when the user earns points.
|
||||
/// {@endtemplate}
|
||||
class ScoreEffectController extends ComponentController<PinballGame>
|
||||
with BlocComponent<GameBloc, GameState> {
|
||||
/// {@macro score_effect_controller}
|
||||
ScoreEffectController(PinballGame component) : super(component);
|
||||
|
||||
int _lastScore = 0;
|
||||
final _random = Random();
|
||||
|
||||
double _noise() {
|
||||
return _random.nextDouble() * 5 * (_random.nextBool() ? -1 : 1);
|
||||
}
|
||||
|
||||
@override
|
||||
bool listenWhen(GameState? previousState, GameState newState) {
|
||||
return previousState?.score != newState.score;
|
||||
}
|
||||
|
||||
@override
|
||||
void onNewState(GameState state) {
|
||||
final newScore = state.score - _lastScore;
|
||||
_lastScore = state.score;
|
||||
|
||||
component.add(
|
||||
ScoreText(
|
||||
text: newScore.toString(),
|
||||
position: Vector2(
|
||||
_noise(),
|
||||
_noise() + (-BoardDimensions.bounds.topCenter.dy + 10),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
// ignore_for_file: avoid_renaming_method_parameters
|
||||
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template score_points}
|
||||
/// Specifies the amount of points received on [Ball] collision.
|
||||
/// {@endtemplate}
|
||||
mixin ScorePoints<T extends Forge2DGame> on BodyComponent<T> {
|
||||
/// {@macro score_points}
|
||||
int get points;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
body.userData = this;
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template ball_score_points_callbacks}
|
||||
/// Adds points to the score when a [Ball] collides with a [BodyComponent] that
|
||||
/// implements [ScorePoints].
|
||||
/// {@endtemplate}
|
||||
class BallScorePointsCallback extends ContactCallback<Ball, ScorePoints> {
|
||||
/// {@macro ball_score_points_callbacks}
|
||||
BallScorePointsCallback(PinballGame game) : _gameRef = game;
|
||||
|
||||
final PinballGame _gameRef;
|
||||
|
||||
@override
|
||||
void begin(
|
||||
Ball _,
|
||||
ScorePoints scorePoints,
|
||||
Contact __,
|
||||
) {
|
||||
_gameRef.read<GameBloc>().add(
|
||||
Scored(points: scorePoints.points),
|
||||
);
|
||||
|
||||
_gameRef.audio.score();
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// ignore_for_file: avoid_renaming_method_parameters
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// {@template scoring_behavior}
|
||||
/// Adds points to the score when the ball contacts the [parent].
|
||||
/// {@endtemplate}
|
||||
class ScoringBehavior extends ContactBehavior with HasGameRef<PinballGame> {
|
||||
/// {@macro scoring_behavior}
|
||||
ScoringBehavior({
|
||||
required int points,
|
||||
}) : _points = points;
|
||||
|
||||
final int _points;
|
||||
|
||||
@override
|
||||
void beginContact(Object other, Contact contact) {
|
||||
super.beginContact(other, contact);
|
||||
if (other is! Ball) return;
|
||||
|
||||
gameRef.read<GameBloc>().add(Scored(points: _points));
|
||||
gameRef.audio.score();
|
||||
gameRef.add(
|
||||
ScoreText(
|
||||
text: _points.toString(),
|
||||
position: other.body.position,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,103 +1,71 @@
|
||||
// ignore_for_file: avoid_renaming_method_parameters
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball/flame/flame.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// {@template sparky_fire_zone}
|
||||
/// Area positioned at the top left of the [Board] where the [Ball]
|
||||
/// can bounce off [SparkyBumper]s.
|
||||
///
|
||||
/// When a [Ball] hits [SparkyBumper]s, they toggle between activated and
|
||||
/// deactivated states.
|
||||
/// When a [Ball] hits [SparkyBumper]s, the bumper animates.
|
||||
/// {@endtemplate}
|
||||
class SparkyFireZone extends Component with HasGameRef<PinballGame> {
|
||||
class SparkyFireZone extends Blueprint {
|
||||
/// {@macro sparky_fire_zone}
|
||||
SparkyFireZone();
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
gameRef.addContactCallback(_ControlledSparkyBumperBallContactCallback());
|
||||
|
||||
final lowerLeftBumper = ControlledSparkyBumper.a()
|
||||
..initialPosition = Vector2(-23.15, 41.65);
|
||||
final upperLeftBumper = ControlledSparkyBumper.b()
|
||||
..initialPosition = Vector2(-21.25, 58.15);
|
||||
final rightBumper = ControlledSparkyBumper.c()
|
||||
..initialPosition = Vector2(-3.56, 53.051);
|
||||
|
||||
await addAll([
|
||||
lowerLeftBumper,
|
||||
upperLeftBumper,
|
||||
rightBumper,
|
||||
]);
|
||||
}
|
||||
SparkyFireZone()
|
||||
: super(
|
||||
components: [
|
||||
SparkyBumper.a(
|
||||
children: [
|
||||
ScoringBehavior(points: 20),
|
||||
],
|
||||
)..initialPosition = Vector2(-22.9, -41.65),
|
||||
SparkyBumper.b(
|
||||
children: [
|
||||
ScoringBehavior(points: 20),
|
||||
],
|
||||
)..initialPosition = Vector2(-21.25, -57.9),
|
||||
SparkyBumper.c(
|
||||
children: [
|
||||
ScoringBehavior(points: 20),
|
||||
],
|
||||
)..initialPosition = Vector2(-3.3, -52.55),
|
||||
SparkyComputerSensor()..initialPosition = Vector2(-13, -49.8),
|
||||
SparkyAnimatronic()..position = Vector2(-13.8, -58.2),
|
||||
],
|
||||
blueprints: [
|
||||
SparkyComputer(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// {@template controlled_sparky_bumper}
|
||||
/// [SparkyBumper] with [_SparkyBumperController] attached.
|
||||
/// {@template sparky_computer_sensor}
|
||||
/// Small sensor body used to detect when a ball has entered the
|
||||
/// [SparkyComputer].
|
||||
/// {@endtemplate}
|
||||
@visibleForTesting
|
||||
class ControlledSparkyBumper extends SparkyBumper
|
||||
with Controls<_SparkyBumperController>, ScorePoints {
|
||||
///{@macro controlled_sparky_bumper}
|
||||
ControlledSparkyBumper.a() : super.a() {
|
||||
controller = _SparkyBumperController(this);
|
||||
}
|
||||
|
||||
///{@macro controlled_sparky_bumper}
|
||||
ControlledSparkyBumper.b() : super.b() {
|
||||
controller = _SparkyBumperController(this);
|
||||
}
|
||||
|
||||
///{@macro controlled_sparky_bumper}
|
||||
ControlledSparkyBumper.c() : super.c() {
|
||||
controller = _SparkyBumperController(this);
|
||||
}
|
||||
class SparkyComputerSensor extends BodyComponent
|
||||
with InitialPosition, ContactCallbacks {
|
||||
/// {@macro sparky_computer_sensor}
|
||||
SparkyComputerSensor() : super(renderBody: false);
|
||||
|
||||
@override
|
||||
int get points => 20;
|
||||
}
|
||||
|
||||
/// {@template sparky_bumper_controller}
|
||||
/// Controls a [SparkyBumper].
|
||||
/// {@endtemplate}
|
||||
class _SparkyBumperController extends ComponentController<SparkyBumper>
|
||||
with HasGameRef<PinballGame> {
|
||||
/// {@macro sparky_bumper_controller}
|
||||
_SparkyBumperController(ControlledSparkyBumper controlledSparkyBumper)
|
||||
: super(controlledSparkyBumper);
|
||||
|
||||
/// Flag for activated state of the [SparkyBumper].
|
||||
///
|
||||
/// Used to toggle [SparkyBumper]s' state between activated and deactivated.
|
||||
bool isActivated = false;
|
||||
|
||||
/// Registers when a [SparkyBumper] is hit by a [Ball].
|
||||
void hit() {
|
||||
if (isActivated) {
|
||||
component.deactivate();
|
||||
} else {
|
||||
component.activate();
|
||||
}
|
||||
isActivated = !isActivated;
|
||||
}
|
||||
Body createBody() {
|
||||
final shape = CircleShape()..radius = 0.1;
|
||||
final fixtureDef = FixtureDef(shape, isSensor: true);
|
||||
final bodyDef = BodyDef(
|
||||
position: initialPosition,
|
||||
userData: this,
|
||||
);
|
||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||
}
|
||||
|
||||
/// Listens when a [Ball] bounces bounces against a [SparkyBumper].
|
||||
class _ControlledSparkyBumperBallContactCallback
|
||||
extends ContactCallback<Controls<_SparkyBumperController>, Ball> {
|
||||
@override
|
||||
void begin(
|
||||
Controls<_SparkyBumperController> controlledSparkyBumper,
|
||||
Ball _,
|
||||
Contact __,
|
||||
) {
|
||||
controlledSparkyBumper.controller.hit();
|
||||
void beginContact(Object other, Contact contact) {
|
||||
super.beginContact(other, contact);
|
||||
if (other is! ControlledBall) return;
|
||||
|
||||
other.controller.turboCharge();
|
||||
gameRef.firstChild<SparkyAnimatronic>()?.playing = true;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,156 @@
|
||||
import 'package:flame/flame.dart';
|
||||
import 'package:flame/sprite.dart';
|
||||
import 'package:flutter/material.dart' hide Image;
|
||||
import 'package:pinball/gen/assets.gen.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// {@template bonus_animation}
|
||||
/// [Widget] that displays bonus animations.
|
||||
/// {@endtemplate}
|
||||
class BonusAnimation extends StatefulWidget {
|
||||
/// {@macro bonus_animation}
|
||||
const BonusAnimation._(
|
||||
String imagePath, {
|
||||
VoidCallback? onCompleted,
|
||||
Key? key,
|
||||
}) : _imagePath = imagePath,
|
||||
_onCompleted = onCompleted,
|
||||
super(key: key);
|
||||
|
||||
/// [Widget] that displays the dash nest animation.
|
||||
BonusAnimation.dashNest({
|
||||
Key? key,
|
||||
VoidCallback? onCompleted,
|
||||
}) : this._(
|
||||
Assets.images.bonusAnimation.dashNest.keyName,
|
||||
onCompleted: onCompleted,
|
||||
key: key,
|
||||
);
|
||||
|
||||
/// [Widget] that displays the sparky turbo charge animation.
|
||||
BonusAnimation.sparkyTurboCharge({
|
||||
Key? key,
|
||||
VoidCallback? onCompleted,
|
||||
}) : this._(
|
||||
Assets.images.bonusAnimation.sparkyTurboCharge.keyName,
|
||||
onCompleted: onCompleted,
|
||||
key: key,
|
||||
);
|
||||
|
||||
/// [Widget] that displays the dino chomp animation.
|
||||
BonusAnimation.dinoChomp({
|
||||
Key? key,
|
||||
VoidCallback? onCompleted,
|
||||
}) : this._(
|
||||
Assets.images.bonusAnimation.dinoChomp.keyName,
|
||||
onCompleted: onCompleted,
|
||||
key: key,
|
||||
);
|
||||
|
||||
/// [Widget] that displays the android spaceship animation.
|
||||
BonusAnimation.androidSpaceship({
|
||||
Key? key,
|
||||
VoidCallback? onCompleted,
|
||||
}) : this._(
|
||||
Assets.images.bonusAnimation.androidSpaceship.keyName,
|
||||
onCompleted: onCompleted,
|
||||
key: key,
|
||||
);
|
||||
|
||||
/// [Widget] that displays the google word animation.
|
||||
BonusAnimation.googleWord({
|
||||
Key? key,
|
||||
VoidCallback? onCompleted,
|
||||
}) : this._(
|
||||
Assets.images.bonusAnimation.googleWord.keyName,
|
||||
onCompleted: onCompleted,
|
||||
key: key,
|
||||
);
|
||||
|
||||
final String _imagePath;
|
||||
|
||||
final VoidCallback? _onCompleted;
|
||||
|
||||
/// Returns a list of assets to be loaded for animations.
|
||||
static List<Future> loadAssets() {
|
||||
Flame.images.prefix = '';
|
||||
return [
|
||||
Flame.images.load(Assets.images.bonusAnimation.dashNest.keyName),
|
||||
Flame.images.load(Assets.images.bonusAnimation.sparkyTurboCharge.keyName),
|
||||
Flame.images.load(Assets.images.bonusAnimation.dinoChomp.keyName),
|
||||
Flame.images.load(Assets.images.bonusAnimation.androidSpaceship.keyName),
|
||||
Flame.images.load(Assets.images.bonusAnimation.googleWord.keyName),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
State<BonusAnimation> createState() => _BonusAnimationState();
|
||||
}
|
||||
|
||||
class _BonusAnimationState extends State<BonusAnimation>
|
||||
with TickerProviderStateMixin {
|
||||
late SpriteAnimationController controller;
|
||||
late SpriteAnimation animation;
|
||||
bool shouldRunBuildCallback = true;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// When the animation is overwritten by another animation, we need to stop
|
||||
// the callback in the build method as it will break the new animation.
|
||||
// Otherwise we need to set up a new callback when a new animation starts to
|
||||
// show the score view at the end of the animation.
|
||||
@override
|
||||
void didUpdateWidget(BonusAnimation oldWidget) {
|
||||
shouldRunBuildCallback = oldWidget._imagePath == widget._imagePath;
|
||||
|
||||
Future<void>.delayed(
|
||||
Duration(seconds: animation.totalDuration().ceil()),
|
||||
() {
|
||||
widget._onCompleted?.call();
|
||||
},
|
||||
);
|
||||
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final spriteSheet = SpriteSheet.fromColumnsAndRows(
|
||||
image: Flame.images.fromCache(widget._imagePath),
|
||||
columns: 8,
|
||||
rows: 9,
|
||||
);
|
||||
animation = spriteSheet.createAnimation(
|
||||
row: 0,
|
||||
stepTime: 1 / 24,
|
||||
to: spriteSheet.rows * spriteSheet.columns,
|
||||
loop: false,
|
||||
);
|
||||
|
||||
Future<void>.delayed(
|
||||
Duration(seconds: animation.totalDuration().ceil()),
|
||||
() {
|
||||
if (shouldRunBuildCallback) {
|
||||
widget._onCompleted?.call();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
controller = SpriteAnimationController(
|
||||
animation: animation,
|
||||
vsync: this,
|
||||
)..forward();
|
||||
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
child: SpriteAnimationWidget(
|
||||
controller: controller,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,46 +1,122 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball/gen/gen.dart';
|
||||
import 'package:pinball/theme/app_colors.dart';
|
||||
|
||||
/// {@template game_hud}
|
||||
/// Overlay of a [PinballGame] that displays the current [GameState.score] and
|
||||
/// [GameState.balls].
|
||||
/// Overlay on the [PinballGame].
|
||||
///
|
||||
/// Displays the current [GameState.score], [GameState.balls] and animates when
|
||||
/// the player gets a [GameBonus].
|
||||
/// {@endtemplate}
|
||||
class GameHud extends StatelessWidget {
|
||||
class GameHud extends StatefulWidget {
|
||||
/// {@macro game_hud}
|
||||
const GameHud({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<GameHud> createState() => _GameHudState();
|
||||
}
|
||||
|
||||
class _GameHudState extends State<GameHud> {
|
||||
bool showAnimation = false;
|
||||
|
||||
/// 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 state = context.watch<GameBloc>().state;
|
||||
|
||||
return Container(
|
||||
color: Colors.redAccent,
|
||||
width: 200,
|
||||
height: 100,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'${state.score}',
|
||||
style: Theme.of(context).textTheme.headline3,
|
||||
),
|
||||
Wrap(
|
||||
direction: Axis.vertical,
|
||||
children: [
|
||||
for (var i = 0; i < state.balls; i++)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 6, right: 6),
|
||||
child: CircleAvatar(
|
||||
radius: 8,
|
||||
backgroundColor: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
final isGameOver = context.select((GameBloc bloc) => bloc.state.isGameOver);
|
||||
|
||||
return _ScoreViewDecoration(
|
||||
child: SizedBox(
|
||||
height: _width / _ratio,
|
||||
width: _width,
|
||||
child: BlocListener<GameBloc, GameState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.bonusHistory.length != current.bonusHistory.length,
|
||||
listener: (_, __) => setState(() => showAnimation = true),
|
||||
child: AnimatedSwitcher(
|
||||
duration: kThemeAnimationDuration,
|
||||
child: showAnimation && !isGameOver
|
||||
? _AnimationView(
|
||||
onComplete: () {
|
||||
if (mounted) {
|
||||
setState(() => showAnimation = false);
|
||||
}
|
||||
},
|
||||
)
|
||||
: const ScoreView(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ScoreViewDecoration extends StatelessWidget {
|
||||
const _ScoreViewDecoration({
|
||||
Key? key,
|
||||
required this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const radius = BorderRadius.all(Radius.circular(12));
|
||||
const boardWidth = 5.0;
|
||||
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: radius,
|
||||
border: Border.all(
|
||||
color: AppColors.white,
|
||||
width: boardWidth,
|
||||
),
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.cover,
|
||||
image: AssetImage(
|
||||
Assets.images.score.miniScoreBackground.path,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(boardWidth - 1),
|
||||
child: ClipRRect(
|
||||
borderRadius: radius,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AnimationView extends StatelessWidget {
|
||||
const _AnimationView({
|
||||
Key? key,
|
||||
required this.onComplete,
|
||||
}) : super(key: key);
|
||||
|
||||
final VoidCallback onComplete;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final lastBonus = context.select(
|
||||
(GameBloc bloc) => bloc.state.bonusHistory.last,
|
||||
);
|
||||
switch (lastBonus) {
|
||||
case GameBonus.dashNest:
|
||||
return BonusAnimation.dashNest(onCompleted: onComplete);
|
||||
case GameBonus.sparkyTurboCharge:
|
||||
return BonusAnimation.sparkyTurboCharge(onCompleted: onComplete);
|
||||
case GameBonus.dinoChomp:
|
||||
return BonusAnimation.dinoChomp(onCompleted: onComplete);
|
||||
case GameBonus.googleWord:
|
||||
return BonusAnimation.googleWord(onCompleted: onComplete);
|
||||
case GameBonus.androidSpaceship:
|
||||
return BonusAnimation.androidSpaceship(onCompleted: onComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball/l10n/l10n.dart';
|
||||
import 'package:pinball/theme/theme.dart';
|
||||
|
||||
/// {@template round_count_display}
|
||||
/// Colored square indicating if a round is available.
|
||||
/// {@endtemplate}
|
||||
class RoundCountDisplay extends StatelessWidget {
|
||||
/// {@macro round_count_display}
|
||||
const RoundCountDisplay({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
// TODO(arturplaczek): refactor when GameState handle balls and rounds and
|
||||
// select state.rounds property instead of state.ball
|
||||
final balls = context.select((GameBloc bloc) => bloc.state.balls);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
l10n.rounds,
|
||||
style: AppTextStyle.subtitle1.copyWith(
|
||||
color: AppColors.orange,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Row(
|
||||
children: [
|
||||
RoundIndicator(isActive: balls >= 1),
|
||||
RoundIndicator(isActive: balls >= 2),
|
||||
RoundIndicator(isActive: balls >= 3),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template round_indicator}
|
||||
/// [Widget] that displays the round indicator.
|
||||
/// {@endtemplate}
|
||||
@visibleForTesting
|
||||
class RoundIndicator extends StatelessWidget {
|
||||
/// {@macro round_indicator}
|
||||
const RoundIndicator({
|
||||
Key? key,
|
||||
required this.isActive,
|
||||
}) : super(key: key);
|
||||
|
||||
/// A value that describes whether the indicator is active.
|
||||
final bool isActive;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = isActive ? AppColors.orange : AppColors.orange.withAlpha(128);
|
||||
const size = 8.0;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Container(
|
||||
color: color,
|
||||
height: size,
|
||||
width: size,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball/l10n/l10n.dart';
|
||||
import 'package:pinball/theme/theme.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template score_view}
|
||||
/// [Widget] that displays the score.
|
||||
/// {@endtemplate}
|
||||
class ScoreView extends StatelessWidget {
|
||||
/// {@macro score_view}
|
||||
const ScoreView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isGameOver = context.select((GameBloc bloc) => bloc.state.isGameOver);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
child: AnimatedSwitcher(
|
||||
duration: kThemeAnimationDuration,
|
||||
child: isGameOver ? const _GameOver() : const _ScoreDisplay(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GameOver extends StatelessWidget {
|
||||
const _GameOver({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
||||
return Text(
|
||||
l10n.gameOver,
|
||||
style: AppTextStyle.headline1.copyWith(
|
||||
color: AppColors.white,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ScoreDisplay extends StatelessWidget {
|
||||
const _ScoreDisplay({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Text(
|
||||
l10n.score.toLowerCase(),
|
||||
style: AppTextStyle.subtitle1.copyWith(
|
||||
color: AppColors.orange,
|
||||
),
|
||||
),
|
||||
const _ScoreText(),
|
||||
const RoundCountDisplay(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ScoreText extends StatelessWidget {
|
||||
const _ScoreText({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final score = context.select((GameBloc bloc) => bloc.state.score);
|
||||
|
||||
return Text(
|
||||
score.formatScore(),
|
||||
style: AppTextStyle.headline1.copyWith(
|
||||
color: AppColors.white,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,2 +1,5 @@
|
||||
export 'bonus_animation.dart';
|
||||
export 'game_hud.dart';
|
||||
export 'play_button_overlay.dart';
|
||||
export 'round_count_display.dart';
|
||||
export 'score_view.dart';
|
||||
|
@ -0,0 +1 @@
|
||||
export 'assets.gen.dart';
|
@ -1 +0,0 @@
|
||||
export 'view/landing_page.dart';
|
@ -0,0 +1,15 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// TODO(allisonryan0002): Document this section when the API is stable.
|
||||
|
||||
part of 'character_theme_cubit.dart';
|
||||
|
||||
class CharacterThemeState extends Equatable {
|
||||
const CharacterThemeState(this.characterTheme);
|
||||
|
||||
const CharacterThemeState.initial() : characterTheme = const DashTheme();
|
||||
|
||||
final CharacterTheme characterTheme;
|
||||
|
||||
@override
|
||||
List<Object> get props => [characterTheme];
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export 'cubit/character_theme_cubit.dart';
|
||||
export 'view/view.dart';
|
@ -0,0 +1,58 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
|
||||
part 'start_game_event.dart';
|
||||
part 'start_game_state.dart';
|
||||
|
||||
/// {@template start_game_bloc}
|
||||
/// Bloc that manages the app flow before the game starts.
|
||||
/// {@endtemplate}
|
||||
class StartGameBloc extends Bloc<StartGameEvent, StartGameState> {
|
||||
/// {@macro start_game_bloc}
|
||||
StartGameBloc({
|
||||
required PinballGame game,
|
||||
}) : _game = game,
|
||||
super(const StartGameState.initial()) {
|
||||
on<PlayTapped>(_onPlayTapped);
|
||||
on<CharacterSelected>(_onCharacterSelected);
|
||||
on<HowToPlayFinished>(_onHowToPlayFinished);
|
||||
}
|
||||
|
||||
final PinballGame _game;
|
||||
|
||||
void _onPlayTapped(
|
||||
PlayTapped event,
|
||||
Emitter<StartGameState> emit,
|
||||
) {
|
||||
_game.gameFlowController.start();
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: StartGameStatus.selectCharacter,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onCharacterSelected(
|
||||
CharacterSelected event,
|
||||
Emitter<StartGameState> emit,
|
||||
) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: StartGameStatus.howToPlay,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onHowToPlayFinished(
|
||||
HowToPlayFinished event,
|
||||
Emitter<StartGameState> emit,
|
||||
) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: StartGameStatus.play,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
part of 'start_game_bloc.dart';
|
||||
|
||||
/// {@template start_game_event}
|
||||
/// Event added during the start game flow.
|
||||
/// {@endtemplate}
|
||||
abstract class StartGameEvent extends Equatable {
|
||||
/// {@macro start_game_event}
|
||||
const StartGameEvent();
|
||||
}
|
||||
|
||||
/// {@template play_tapped}
|
||||
/// Play tapped event.
|
||||
/// {@endtemplate}
|
||||
class PlayTapped extends StartGameEvent {
|
||||
/// {@macro play_tapped}
|
||||
const PlayTapped();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
/// {@template character_selected}
|
||||
/// Character selected event.
|
||||
/// {@endtemplate}
|
||||
class CharacterSelected extends StartGameEvent {
|
||||
/// {@macro character_selected}
|
||||
const CharacterSelected();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
/// {@template how_to_play_finished}
|
||||
/// How to play finished event.
|
||||
/// {@endtemplate}
|
||||
class HowToPlayFinished extends StartGameEvent {
|
||||
/// {@macro how_to_play_finished}
|
||||
const HowToPlayFinished();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
part of 'start_game_bloc.dart';
|
||||
|
||||
/// Defines status of start game flow.
|
||||
enum StartGameStatus {
|
||||
/// Initial status.
|
||||
initial,
|
||||
|
||||
/// Selection characters status.
|
||||
selectCharacter,
|
||||
|
||||
/// How to play status.
|
||||
howToPlay,
|
||||
|
||||
/// Play status.
|
||||
play,
|
||||
}
|
||||
|
||||
/// {@template start_game_state}
|
||||
/// Represents the state of flow before the game starts.
|
||||
/// {@endtemplate}
|
||||
class StartGameState extends Equatable {
|
||||
/// {@macro start_game_state}
|
||||
const StartGameState({
|
||||
required this.status,
|
||||
});
|
||||
|
||||
/// Initial [StartGameState].
|
||||
const StartGameState.initial() : this(status: StartGameStatus.initial);
|
||||
|
||||
/// Status of [StartGameState].
|
||||
final StartGameStatus status;
|
||||
|
||||
/// Creates a copy of [StartGameState].
|
||||
StartGameState copyWith({
|
||||
StartGameStatus? status,
|
||||
}) {
|
||||
return StartGameState(
|
||||
status: status ?? this.status,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [status];
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export 'bloc/start_game_bloc.dart';
|
||||
export 'widgets/widgets.dart';
|
@ -0,0 +1 @@
|
||||
export 'how_to_play_dialog.dart';
|
@ -0,0 +1,15 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class AppColors {
|
||||
static const Color white = Color(0xFFFFFFFF);
|
||||
|
||||
static const Color darkBlue = Color(0xFF0C32A4);
|
||||
|
||||
static const Color orange = Color(0xFFFFEE02);
|
||||
|
||||
static const Color blue = Color(0xFF4B94F6);
|
||||
|
||||
static const Color transparent = Color(0x00000000);
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:pinball/theme/theme.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
const _fontPackage = 'pinball_components';
|
||||
const _primaryFontFamily = FontFamily.pixeloidSans;
|
||||
|
||||
abstract class AppTextStyle {
|
||||
static const headline1 = TextStyle(
|
||||
fontSize: 28,
|
||||
package: _fontPackage,
|
||||
fontFamily: _primaryFontFamily,
|
||||
);
|
||||
|
||||
static const headline2 = TextStyle(
|
||||
fontSize: 24,
|
||||
package: _fontPackage,
|
||||
fontFamily: _primaryFontFamily,
|
||||
);
|
||||
|
||||
static const headline3 = TextStyle(
|
||||
color: AppColors.white,
|
||||
fontSize: 20,
|
||||
package: _fontPackage,
|
||||
fontFamily: _primaryFontFamily,
|
||||
);
|
||||
|
||||
static const subtitle1 = TextStyle(
|
||||
fontSize: 10,
|
||||
fontFamily: _primaryFontFamily,
|
||||
package: _fontPackage,
|
||||
);
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// TODO(allisonryan0002): Document this section when the API is stable.
|
||||
|
||||
part of 'theme_cubit.dart';
|
||||
|
||||
class ThemeState extends Equatable {
|
||||
const ThemeState(this.theme);
|
||||
|
||||
const ThemeState.initial()
|
||||
: theme = const PinballTheme(characterTheme: DashTheme());
|
||||
|
||||
final PinballTheme theme;
|
||||
|
||||
@override
|
||||
List<Object> get props => [theme];
|
||||
}
|
@ -1,2 +1,2 @@
|
||||
export 'cubit/theme_cubit.dart';
|
||||
export 'view/view.dart';
|
||||
export 'app_colors.dart';
|
||||
export 'app_text_style.dart';
|
||||
|
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 9.8 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 1.9 MiB |
After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 390 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 270 KiB |
After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 825 KiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB |