Before Width: | Height: | Size: 306 KiB After Width: | Height: | Size: 306 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 222 KiB |
After Width: | Height: | Size: 11 KiB |
@ -1,60 +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/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template alien_zone}
|
||||
/// Area positioned below [Spaceship] where the [Ball]
|
||||
/// can bounce off [AlienBumper]s.
|
||||
///
|
||||
/// When a [Ball] hits an [AlienBumper], the bumper animates.
|
||||
/// {@endtemplate}
|
||||
class AlienZone extends Component with HasGameRef<PinballGame> {
|
||||
/// {@macro alien_zone}
|
||||
AlienZone();
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
gameRef.addContactCallback(AlienBumperBallContactCallback());
|
||||
|
||||
final lowerBumper = _AlienBumper.a()
|
||||
..initialPosition = Vector2(-32.52, -9.1);
|
||||
final upperBumper = _AlienBumper.b()
|
||||
..initialPosition = Vector2(-22.89, -17.35);
|
||||
|
||||
await addAll([
|
||||
lowerBumper,
|
||||
upperBumper,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(alestiago): Revisit ScorePoints logic once the FlameForge2D
|
||||
// ContactCallback process is enhanced.
|
||||
class _AlienBumper extends AlienBumper with ScorePoints {
|
||||
_AlienBumper.a() : super.a();
|
||||
|
||||
_AlienBumper.b() : super.b();
|
||||
|
||||
@override
|
||||
int get points => 20;
|
||||
}
|
||||
|
||||
/// Listens when a [Ball] bounces against an [AlienBumper].
|
||||
@visibleForTesting
|
||||
class AlienBumperBallContactCallback
|
||||
extends ContactCallback<AlienBumper, Ball> {
|
||||
@override
|
||||
void begin(
|
||||
AlienBumper alienBumper,
|
||||
Ball _,
|
||||
Contact __,
|
||||
) {
|
||||
alienBumper.animate();
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// 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 android_acres}
|
||||
/// Area positioned on the left side of the board containing the [Spaceship],
|
||||
/// [SpaceshipRamp], [SpaceshipRail], and [AndroidBumper]s.
|
||||
/// {@endtemplate}
|
||||
class AndroidAcres extends Blueprint {
|
||||
/// {@macro android_acres}
|
||||
AndroidAcres()
|
||||
: super(
|
||||
components: [
|
||||
AndroidBumper.a(
|
||||
children: [
|
||||
ScoringBehavior(points: 20),
|
||||
],
|
||||
)..initialPosition = Vector2(-32.52, -9.1),
|
||||
AndroidBumper.b(
|
||||
children: [
|
||||
ScoringBehavior(points: 20),
|
||||
],
|
||||
)..initialPosition = Vector2(-22.89, -17.35),
|
||||
],
|
||||
blueprints: [
|
||||
SpaceshipRamp(),
|
||||
Spaceship(position: Vector2(-26.5, -28.5)),
|
||||
SpaceshipRail(),
|
||||
],
|
||||
);
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
export 'alien_zone.dart';
|
||||
export 'android_acres.dart';
|
||||
export 'board.dart';
|
||||
export 'camera_controller.dart';
|
||||
export 'controlled_ball.dart';
|
||||
export 'controlled_flipper.dart';
|
||||
export 'controlled_plunger.dart';
|
||||
export 'flutter_forest.dart';
|
||||
export 'flutter_forest/flutter_forest.dart';
|
||||
export 'game_flow_controller.dart';
|
||||
export 'google_word.dart';
|
||||
export 'google_word/google_word.dart';
|
||||
export 'launcher.dart';
|
||||
export 'score_points.dart';
|
||||
export 'scoring_behavior.dart';
|
||||
export 'sparky_fire_zone.dart';
|
||||
export 'wall.dart';
|
||||
|
@ -1,102 +0,0 @@
|
||||
// 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 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 [DashNestBumper.main] releases a new [Ball].
|
||||
/// {@endtemplate}
|
||||
class FlutterForest extends Component
|
||||
with Controls<_FlutterForestController>, HasGameRef<PinballGame> {
|
||||
/// {@macro flutter_forest}
|
||||
FlutterForest() {
|
||||
controller = _FlutterForestController(this);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
gameRef.addContactCallback(_DashNestBumperBallContactCallback());
|
||||
|
||||
final signpost = Signpost()..initialPosition = Vector2(8.35, -58.3);
|
||||
|
||||
final bigNest = _DashNestBumper.main()
|
||||
..initialPosition = Vector2(18.55, -59.35);
|
||||
final smallLeftNest = _DashNestBumper.a()
|
||||
..initialPosition = Vector2(8.95, -51.95);
|
||||
final smallRightNest = _DashNestBumper.b()
|
||||
..initialPosition = Vector2(23.3, -46.75);
|
||||
final dashAnimatronic = DashAnimatronic()..position = Vector2(20, -66);
|
||||
|
||||
await addAll([
|
||||
signpost,
|
||||
smallLeftNest,
|
||||
smallRightNest,
|
||||
bigNest,
|
||||
dashAnimatronic,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class _FlutterForestController extends ComponentController<FlutterForest>
|
||||
with HasGameRef<PinballGame> {
|
||||
_FlutterForestController(FlutterForest flutterForest) : super(flutterForest);
|
||||
|
||||
final _activatedBumpers = <DashNestBumper>{};
|
||||
|
||||
void activateBumper(DashNestBumper dashNestBumper) {
|
||||
if (!_activatedBumpers.add(dashNestBumper)) return;
|
||||
|
||||
dashNestBumper.activate();
|
||||
|
||||
final activatedBonus = _activatedBumpers.length == 3;
|
||||
if (activatedBonus) {
|
||||
_addBonusBall();
|
||||
|
||||
gameRef.read<GameBloc>().add(const BonusActivated(GameBonus.dashNest));
|
||||
_activatedBumpers
|
||||
..forEach((bumper) => bumper.deactivate())
|
||||
..clear();
|
||||
|
||||
component.firstChild<DashAnimatronic>()?.playing = true;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addBonusBall() async {
|
||||
await gameRef.add(
|
||||
ControlledBall.bonus(theme: gameRef.theme)
|
||||
..initialPosition = Vector2(17.2, -52.7),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(alestiago): Revisit ScorePoints logic once the FlameForge2D
|
||||
// ContactCallback process is enhanced.
|
||||
class _DashNestBumper extends DashNestBumper with ScorePoints {
|
||||
_DashNestBumper.main() : super.main();
|
||||
|
||||
_DashNestBumper.a() : super.a();
|
||||
|
||||
_DashNestBumper.b() : super.b();
|
||||
|
||||
@override
|
||||
int get points => 20;
|
||||
}
|
||||
|
||||
class _DashNestBumperBallContactCallback
|
||||
extends ContactCallback<DashNestBumper, Ball> {
|
||||
@override
|
||||
void begin(DashNestBumper dashNestBumper, _, __) {
|
||||
final parent = dashNestBumper.parent;
|
||||
if (parent is FlutterForest) {
|
||||
parent.controller.activateBumper(dashNestBumper);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
// ignore_for_file: avoid_renaming_method_parameters
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
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 google_word}
|
||||
/// Loads all [GoogleLetter]s to compose a [GoogleWord].
|
||||
/// {@endtemplate}
|
||||
class GoogleWord extends Component
|
||||
with HasGameRef<PinballGame>, Controls<_GoogleWordController> {
|
||||
/// {@macro google_word}
|
||||
GoogleWord({
|
||||
required Vector2 position,
|
||||
}) : _position = position {
|
||||
controller = _GoogleWordController(this);
|
||||
}
|
||||
|
||||
final Vector2 _position;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
gameRef.addContactCallback(_GoogleLetterBallContactCallback());
|
||||
|
||||
final offsets = [
|
||||
Vector2(-12.92, 1.82),
|
||||
Vector2(-8.33, -0.65),
|
||||
Vector2(-2.88, -1.75),
|
||||
Vector2(2.88, -1.75),
|
||||
Vector2(8.33, -0.65),
|
||||
Vector2(12.92, 1.82),
|
||||
];
|
||||
|
||||
final letters = <GoogleLetter>[];
|
||||
for (var index = 0; index < offsets.length; index++) {
|
||||
letters.add(
|
||||
GoogleLetter(index)..initialPosition = _position + offsets[index],
|
||||
);
|
||||
}
|
||||
|
||||
await addAll(letters);
|
||||
}
|
||||
}
|
||||
|
||||
class _GoogleWordController extends ComponentController<GoogleWord>
|
||||
with HasGameRef<PinballGame> {
|
||||
_GoogleWordController(GoogleWord googleWord) : super(googleWord);
|
||||
|
||||
final _activatedLetters = <GoogleLetter>{};
|
||||
|
||||
void activate(GoogleLetter googleLetter) {
|
||||
if (!_activatedLetters.add(googleLetter)) return;
|
||||
|
||||
googleLetter.activate();
|
||||
|
||||
final activatedBonus = _activatedLetters.length == 6;
|
||||
if (activatedBonus) {
|
||||
gameRef.audio.googleBonus();
|
||||
gameRef.read<GameBloc>().add(const BonusActivated(GameBonus.googleWord));
|
||||
component.children.whereType<GoogleLetter>().forEach(
|
||||
(letter) => letter.deactivate(),
|
||||
);
|
||||
_activatedLetters.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Activates a [GoogleLetter] when it contacts with a [Ball].
|
||||
class _GoogleLetterBallContactCallback
|
||||
extends ContactCallback<GoogleLetter, Ball> {
|
||||
@override
|
||||
void begin(GoogleLetter googleLetter, _, __) {
|
||||
final parent = googleLetter.parent;
|
||||
if (parent is GoogleWord) {
|
||||
parent.controller.activate(googleLetter);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -1,47 +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 ball,
|
||||
ScorePoints scorePoints,
|
||||
Contact _,
|
||||
) {
|
||||
_gameRef.read<GameBloc>().add(Scored(points: scorePoints.points));
|
||||
_gameRef.audio.score();
|
||||
|
||||
_gameRef.add(
|
||||
ScoreText(
|
||||
text: scorePoints.points.toString(),
|
||||
position: ball.body.position,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,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.rounds] 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,
|
||||
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(),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,68 @@
|
||||
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;
|
||||
final rounds = context.select((GameBloc bloc) => bloc.state.rounds);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
l10n.rounds,
|
||||
style: AppTextStyle.subtitle1.copyWith(
|
||||
color: AppColors.orange,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Row(
|
||||
children: [
|
||||
RoundIndicator(isActive: rounds >= 1),
|
||||
RoundIndicator(isActive: rounds >= 2),
|
||||
RoundIndicator(isActive: rounds >= 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,3 +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,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';
|
@ -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];
|
||||
}
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 825 KiB After Width: | Height: | Size: 886 KiB |
After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 61 KiB |
@ -1,2 +1,3 @@
|
||||
export 'assets.gen.dart';
|
||||
export 'fonts.gen.dart';
|
||||
export 'pinball_fonts.dart';
|
||||
|
@ -1,121 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template alien_bumper}
|
||||
/// Bumper for area under the [Spaceship].
|
||||
/// {@endtemplate}
|
||||
class AlienBumper extends BodyComponent with InitialPosition {
|
||||
/// {@macro alien_bumper}
|
||||
AlienBumper._({
|
||||
required double majorRadius,
|
||||
required double minorRadius,
|
||||
required String onAssetPath,
|
||||
required String offAssetPath,
|
||||
}) : _majorRadius = majorRadius,
|
||||
_minorRadius = minorRadius,
|
||||
super(
|
||||
priority: RenderPriority.alienBumper,
|
||||
children: [
|
||||
_AlienBumperSpriteGroupComponent(
|
||||
onAssetPath: onAssetPath,
|
||||
offAssetPath: offAssetPath,
|
||||
),
|
||||
],
|
||||
) {
|
||||
renderBody = false;
|
||||
}
|
||||
|
||||
/// {@macro alien_bumper}
|
||||
AlienBumper.a()
|
||||
: this._(
|
||||
majorRadius: 3.52,
|
||||
minorRadius: 2.97,
|
||||
onAssetPath: Assets.images.alienBumper.a.active.keyName,
|
||||
offAssetPath: Assets.images.alienBumper.a.inactive.keyName,
|
||||
);
|
||||
|
||||
/// {@macro alien_bumper}
|
||||
AlienBumper.b()
|
||||
: this._(
|
||||
majorRadius: 3.19,
|
||||
minorRadius: 2.79,
|
||||
onAssetPath: Assets.images.alienBumper.b.active.keyName,
|
||||
offAssetPath: Assets.images.alienBumper.b.inactive.keyName,
|
||||
);
|
||||
|
||||
final double _majorRadius;
|
||||
final double _minorRadius;
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = EllipseShape(
|
||||
center: Vector2.zero(),
|
||||
majorRadius: _majorRadius,
|
||||
minorRadius: _minorRadius,
|
||||
)..rotate(1.29);
|
||||
final fixtureDef = FixtureDef(
|
||||
shape,
|
||||
restitution: 4,
|
||||
);
|
||||
final bodyDef = BodyDef(
|
||||
position: initialPosition,
|
||||
userData: this,
|
||||
);
|
||||
|
||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||
}
|
||||
|
||||
/// Animates the [AlienBumper].
|
||||
Future<void> animate() async {
|
||||
final spriteGroupComponent = firstChild<_AlienBumperSpriteGroupComponent>()
|
||||
?..current = AlienBumperSpriteState.inactive;
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
spriteGroupComponent?.current = AlienBumperSpriteState.active;
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates the [AlienBumper]'s current sprite state.
|
||||
@visibleForTesting
|
||||
enum AlienBumperSpriteState {
|
||||
/// A lit up bumper.
|
||||
active,
|
||||
|
||||
/// A dimmed bumper.
|
||||
inactive,
|
||||
}
|
||||
|
||||
class _AlienBumperSpriteGroupComponent
|
||||
extends SpriteGroupComponent<AlienBumperSpriteState> with HasGameRef {
|
||||
_AlienBumperSpriteGroupComponent({
|
||||
required String onAssetPath,
|
||||
required String offAssetPath,
|
||||
}) : _onAssetPath = onAssetPath,
|
||||
_offAssetPath = offAssetPath,
|
||||
super(
|
||||
anchor: Anchor.center,
|
||||
position: Vector2(0, -0.1),
|
||||
);
|
||||
|
||||
final String _onAssetPath;
|
||||
final String _offAssetPath;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
final sprites = {
|
||||
AlienBumperSpriteState.active:
|
||||
Sprite(gameRef.images.fromCache(_onAssetPath)),
|
||||
AlienBumperSpriteState.inactive:
|
||||
Sprite(gameRef.images.fromCache(_offAssetPath)),
|
||||
};
|
||||
this.sprites = sprites;
|
||||
|
||||
current = AlienBumperSpriteState.active;
|
||||
size = sprites[current]!.originalSize / 10;
|
||||
}
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_components/src/components/android_bumper/behaviors/behaviors.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
export 'cubit/android_bumper_cubit.dart';
|
||||
|
||||
/// {@template android_bumper}
|
||||
/// Bumper for area under the [Spaceship].
|
||||
/// {@endtemplate}
|
||||
class AndroidBumper extends BodyComponent with InitialPosition {
|
||||
/// {@macro android_bumper}
|
||||
AndroidBumper._({
|
||||
required double majorRadius,
|
||||
required double minorRadius,
|
||||
required String litAssetPath,
|
||||
required String dimmedAssetPath,
|
||||
Iterable<Component>? children,
|
||||
required this.bloc,
|
||||
}) : _majorRadius = majorRadius,
|
||||
_minorRadius = minorRadius,
|
||||
super(
|
||||
priority: RenderPriority.androidBumper,
|
||||
renderBody: false,
|
||||
children: [
|
||||
AndroidBumperBallContactBehavior(),
|
||||
AndroidBumperBlinkingBehavior(),
|
||||
_AndroidBumperSpriteGroupComponent(
|
||||
dimmedAssetPath: dimmedAssetPath,
|
||||
litAssetPath: litAssetPath,
|
||||
state: bloc.state,
|
||||
),
|
||||
...?children,
|
||||
],
|
||||
);
|
||||
|
||||
/// {@macro android_bumper}
|
||||
AndroidBumper.a({
|
||||
Iterable<Component>? children,
|
||||
}) : this._(
|
||||
majorRadius: 3.52,
|
||||
minorRadius: 2.97,
|
||||
litAssetPath: Assets.images.androidBumper.a.lit.keyName,
|
||||
dimmedAssetPath: Assets.images.androidBumper.a.dimmed.keyName,
|
||||
bloc: AndroidBumperCubit(),
|
||||
children: children,
|
||||
);
|
||||
|
||||
/// {@macro android_bumper}
|
||||
AndroidBumper.b({
|
||||
Iterable<Component>? children,
|
||||
}) : this._(
|
||||
majorRadius: 3.19,
|
||||
minorRadius: 2.79,
|
||||
litAssetPath: Assets.images.androidBumper.b.lit.keyName,
|
||||
dimmedAssetPath: Assets.images.androidBumper.b.dimmed.keyName,
|
||||
bloc: AndroidBumperCubit(),
|
||||
children: children,
|
||||
);
|
||||
|
||||
/// Creates an [AndroidBumper] without any children.
|
||||
///
|
||||
/// This can be used for testing [AndroidBumper]'s behaviors in isolation.
|
||||
// TODO(alestiago): Refactor injecting bloc once the following is merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
@visibleForTesting
|
||||
AndroidBumper.test({
|
||||
required this.bloc,
|
||||
}) : _majorRadius = 3.52,
|
||||
_minorRadius = 2.97;
|
||||
|
||||
final double _majorRadius;
|
||||
|
||||
final double _minorRadius;
|
||||
|
||||
// TODO(alestiago): Consider refactoring once the following is merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
// ignore: public_member_api_docs
|
||||
final AndroidBumperCubit bloc;
|
||||
|
||||
@override
|
||||
void onRemove() {
|
||||
bloc.close();
|
||||
super.onRemove();
|
||||
}
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = EllipseShape(
|
||||
center: Vector2.zero(),
|
||||
majorRadius: _majorRadius,
|
||||
minorRadius: _minorRadius,
|
||||
)..rotate(1.29);
|
||||
final fixtureDef = FixtureDef(
|
||||
shape,
|
||||
restitution: 4,
|
||||
);
|
||||
final bodyDef = BodyDef(
|
||||
position: initialPosition,
|
||||
);
|
||||
|
||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||
}
|
||||
}
|
||||
|
||||
class _AndroidBumperSpriteGroupComponent
|
||||
extends SpriteGroupComponent<AndroidBumperState>
|
||||
with HasGameRef, ParentIsA<AndroidBumper> {
|
||||
_AndroidBumperSpriteGroupComponent({
|
||||
required String litAssetPath,
|
||||
required String dimmedAssetPath,
|
||||
required AndroidBumperState state,
|
||||
}) : _litAssetPath = litAssetPath,
|
||||
_dimmedAssetPath = dimmedAssetPath,
|
||||
super(
|
||||
anchor: Anchor.center,
|
||||
position: Vector2(0, -0.1),
|
||||
current: state,
|
||||
);
|
||||
|
||||
final String _litAssetPath;
|
||||
final String _dimmedAssetPath;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
parent.bloc.stream.listen((state) => current = state);
|
||||
|
||||
final sprites = {
|
||||
AndroidBumperState.lit: Sprite(
|
||||
gameRef.images.fromCache(_litAssetPath),
|
||||
),
|
||||
AndroidBumperState.dimmed:
|
||||
Sprite(gameRef.images.fromCache(_dimmedAssetPath)),
|
||||
};
|
||||
this.sprites = sprites;
|
||||
size = sprites[current]!.originalSize / 10;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
class AndroidBumperBallContactBehavior extends ContactBehavior<AndroidBumper> {
|
||||
@override
|
||||
void beginContact(Object other, Contact contact) {
|
||||
super.beginContact(other, contact);
|
||||
if (other is! Ball) return;
|
||||
parent.bloc.onBallContacted();
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// {@template android_bumper_blinking_behavior}
|
||||
/// Makes an [AndroidBumper] blink back to [AndroidBumperState.lit] when
|
||||
/// [AndroidBumperState.dimmed].
|
||||
/// {@endtemplate}
|
||||
class AndroidBumperBlinkingBehavior extends TimerComponent
|
||||
with ParentIsA<AndroidBumper> {
|
||||
/// {@macro android_bumper_blinking_behavior}
|
||||
AndroidBumperBlinkingBehavior() : super(period: 0.05);
|
||||
|
||||
void _onNewState(AndroidBumperState state) {
|
||||
switch (state) {
|
||||
case AndroidBumperState.lit:
|
||||
break;
|
||||
case AndroidBumperState.dimmed:
|
||||
timer
|
||||
..reset()
|
||||
..start();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
timer.stop();
|
||||
parent.bloc.stream.listen(_onNewState);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTick() {
|
||||
super.onTick();
|
||||
timer.stop();
|
||||
parent.bloc.onBlinked();
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export 'android_bumper_ball_contact_behavior.dart';
|
||||
export 'android_bumper_blinking_behavior.dart';
|
@ -0,0 +1,17 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
|
||||
part 'android_bumper_state.dart';
|
||||
|
||||
class AndroidBumperCubit extends Cubit<AndroidBumperState> {
|
||||
AndroidBumperCubit() : super(AndroidBumperState.dimmed);
|
||||
|
||||
void onBallContacted() {
|
||||
emit(AndroidBumperState.dimmed);
|
||||
}
|
||||
|
||||
void onBlinked() {
|
||||
emit(AndroidBumperState.lit);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
part of 'android_bumper_cubit.dart';
|
||||
|
||||
enum AndroidBumperState {
|
||||
lit,
|
||||
dimmed,
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// {@template bumping_behavior}
|
||||
/// Makes any [BodyComponent] that contacts with [parent] bounce off.
|
||||
/// {@endtemplate}
|
||||
class BumpingBehavior extends ContactBehavior {
|
||||
/// {@macro bumping_behavior}
|
||||
BumpingBehavior({required double strength}) : _strength = strength;
|
||||
|
||||
/// Determines how strong the bump is.
|
||||
final double _strength;
|
||||
|
||||
@override
|
||||
void postSolve(Object other, Contact contact, ContactImpulse impulse) {
|
||||
super.postSolve(other, contact, impulse);
|
||||
if (other is! BodyComponent) return;
|
||||
|
||||
other.body.applyLinearImpulse(
|
||||
contact.manifold.localPoint
|
||||
..normalize()
|
||||
..multiply(Vector2.all(other.body.mass * _strength)),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export 'dash_nest_bumper_contact_behavior.dart';
|
@ -0,0 +1,15 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
class DashNestBumperBallContactBehavior
|
||||
extends ContactBehavior<DashNestBumper> {
|
||||
@override
|
||||
void beginContact(Object other, Contact contact) {
|
||||
super.beginContact(other, contact);
|
||||
if (other is! Ball) return;
|
||||
parent.bloc.onBallContacted();
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
|
||||
part 'dash_nest_bumper_state.dart';
|
||||
|
||||
class DashNestBumperCubit extends Cubit<DashNestBumperState> {
|
||||
DashNestBumperCubit() : super(DashNestBumperState.inactive);
|
||||
|
||||
/// Event added when the bumper contacts with a ball.
|
||||
void onBallContacted() {
|
||||
emit(DashNestBumperState.active);
|
||||
}
|
||||
|
||||
/// Event added when the bumper should return to its initial configuration.
|
||||
void onReset() {
|
||||
emit(DashNestBumperState.inactive);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
part of 'dash_nest_bumper_cubit.dart';
|
||||
|
||||
/// Indicates the [DashNestBumperCubit]'s current state.
|
||||
enum DashNestBumperState {
|
||||
/// A lit up bumper.
|
||||
active,
|
||||
|
||||
/// A dimmed bumper.
|
||||
inactive,
|
||||
}
|
@ -0,0 +1 @@
|
||||
export 'google_letter_ball_contact_behavior.dart';
|
@ -0,0 +1,14 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
class GoogleLetterBallContactBehavior extends ContactBehavior<GoogleLetter> {
|
||||
@override
|
||||
void beginContact(Object other, Contact contact) {
|
||||
super.beginContact(other, contact);
|
||||
if (other is! Ball) return;
|
||||
parent.bloc.onBallContacted();
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
|
||||
part 'google_letter_state.dart';
|
||||
|
||||
class GoogleLetterCubit extends Cubit<GoogleLetterState> {
|
||||
GoogleLetterCubit() : super(GoogleLetterState.inactive);
|
||||
|
||||
void onBallContacted() {
|
||||
emit(GoogleLetterState.active);
|
||||
}
|
||||
|
||||
void onReset() {
|
||||
emit(GoogleLetterState.inactive);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
part of 'google_letter_cubit.dart';
|
||||
|
||||
/// Indicates the [GoogleLetterCubit]'s current state.
|
||||
enum GoogleLetterState {
|
||||
/// A lit up letter.
|
||||
active,
|
||||
|
||||
/// A dimmed letter.
|
||||
inactive,
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export 'sparky_bumper_ball_contact_behavior.dart';
|
||||
export 'sparky_bumper_blinking_behavior.dart';
|
@ -0,0 +1,14 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
class SparkyBumperBallContactBehavior extends ContactBehavior<SparkyBumper> {
|
||||
@override
|
||||
void beginContact(Object other, Contact contact) {
|
||||
super.beginContact(other, contact);
|
||||
if (other is! Ball) return;
|
||||
parent.bloc.onBallContacted();
|
||||
}
|
||||
}
|