@ -0,0 +1,95 @@
|
|||||||
|
// 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 alien_zone}
|
||||||
|
/// Area positioned below [Spaceship] where the [Ball]
|
||||||
|
/// can bounce off [AlienBumper]s.
|
||||||
|
///
|
||||||
|
/// When a [Ball] hits [AlienBumper]s, they toggle between activated and
|
||||||
|
/// deactivated states.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class AlienZone extends Component with HasGameRef<PinballGame> {
|
||||||
|
/// {@macro alien_zone}
|
||||||
|
AlienZone();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
gameRef.addContactCallback(_ControlledAlienBumperBallContactCallback());
|
||||||
|
|
||||||
|
final lowerBumper = ControlledAlienBumper.a()
|
||||||
|
..initialPosition = Vector2(-32.52, 9.34);
|
||||||
|
final upperBumper = ControlledAlienBumper.b()
|
||||||
|
..initialPosition = Vector2(-22.89, 17.43);
|
||||||
|
|
||||||
|
await addAll([
|
||||||
|
lowerBumper,
|
||||||
|
upperBumper,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@template controlled_alien_bumper}
|
||||||
|
/// [AlienBumper] with [_AlienBumperController] attached.
|
||||||
|
/// {@endtemplate}
|
||||||
|
@visibleForTesting
|
||||||
|
class ControlledAlienBumper extends AlienBumper
|
||||||
|
with Controls<_AlienBumperController>, ScorePoints {
|
||||||
|
/// {@macro controlled_alien_bumper}
|
||||||
|
ControlledAlienBumper.a() : super.a() {
|
||||||
|
controller = _AlienBumperController(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@macro controlled_alien_bumper}
|
||||||
|
ControlledAlienBumper.b() : super.b() {
|
||||||
|
controller = _AlienBumperController(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// TODO(ruimiguel): change points when get final points map.
|
||||||
|
int get points => 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@template alien_bumper_controller}
|
||||||
|
/// Controls a [AlienBumper].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class _AlienBumperController extends ComponentController<AlienBumper>
|
||||||
|
with HasGameRef<PinballGame> {
|
||||||
|
/// {@macro alien_bumper_controller}
|
||||||
|
_AlienBumperController(AlienBumper alienBumper) : super(alienBumper);
|
||||||
|
|
||||||
|
/// Flag for activated state of the [AlienBumper].
|
||||||
|
///
|
||||||
|
/// Used to toggle [AlienBumper]s' state between activated and deactivated.
|
||||||
|
bool isActivated = false;
|
||||||
|
|
||||||
|
/// Registers when a [AlienBumper] is hit by a [Ball].
|
||||||
|
void hit() {
|
||||||
|
if (isActivated) {
|
||||||
|
component.deactivate();
|
||||||
|
} else {
|
||||||
|
component.activate();
|
||||||
|
}
|
||||||
|
isActivated = !isActivated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Listens when a [Ball] bounces bounces against a [AlienBumper].
|
||||||
|
class _ControlledAlienBumperBallContactCallback
|
||||||
|
extends ContactCallback<Controls<_AlienBumperController>, Ball> {
|
||||||
|
@override
|
||||||
|
void begin(
|
||||||
|
Controls<_AlienBumperController> controlledAlienBumper,
|
||||||
|
Ball _,
|
||||||
|
Contact __,
|
||||||
|
) {
|
||||||
|
controlledAlienBumper.controller.hit();
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,14 @@
|
|||||||
|
export 'alien_zone.dart';
|
||||||
export 'board.dart';
|
export 'board.dart';
|
||||||
export 'bonus_word.dart';
|
export 'bonus_word.dart';
|
||||||
export 'camera_controller.dart';
|
export 'camera_controller.dart';
|
||||||
export 'controlled_ball.dart';
|
export 'controlled_ball.dart';
|
||||||
export 'controlled_flipper.dart';
|
export 'controlled_flipper.dart';
|
||||||
|
export 'controlled_plunger.dart';
|
||||||
export 'controlled_sparky_computer.dart';
|
export 'controlled_sparky_computer.dart';
|
||||||
export 'flutter_forest.dart';
|
export 'flutter_forest.dart';
|
||||||
export 'game_flow_controller.dart';
|
export 'game_flow_controller.dart';
|
||||||
export 'plunger.dart';
|
export 'score_effect_controller.dart';
|
||||||
export 'score_points.dart';
|
export 'score_points.dart';
|
||||||
export 'sparky_fire_zone.dart';
|
export 'sparky_fire_zone.dart';
|
||||||
export 'wall.dart';
|
export 'wall.dart';
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:pinball/flame/flame.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.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 {
|
||||||
|
/// {@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 (!_keys.contains(event.logicalKey)) return true;
|
||||||
|
|
||||||
|
if (event is RawKeyDownEvent) {
|
||||||
|
component.pull();
|
||||||
|
} else if (event is RawKeyUpEvent) {
|
||||||
|
component.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 886 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 371 B |
After Width: | Height: | Size: 371 B |
After Width: | Height: | Size: 371 B |
After Width: | Height: | Size: 371 B |
After Width: | Height: | Size: 313 B |
After Width: | Height: | Size: 343 B |
After Width: | Height: | Size: 11 KiB |
@ -0,0 +1,16 @@
|
|||||||
|
/// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
/// *****************************************************
|
||||||
|
/// FlutterGen
|
||||||
|
/// *****************************************************
|
||||||
|
|
||||||
|
// ignore_for_file: directives_ordering,unnecessary_import
|
||||||
|
|
||||||
|
class FontFamily {
|
||||||
|
FontFamily._();
|
||||||
|
|
||||||
|
/// Font family: PixeloidMono
|
||||||
|
static const String pixeloidMono = 'PixeloidMono';
|
||||||
|
|
||||||
|
/// Font family: PixeloidSans
|
||||||
|
static const String pixeloidSans = 'PixeloidSans';
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import 'package:pinball_components/gen/fonts.gen.dart';
|
||||||
|
|
||||||
|
String _prefixFont(String font) {
|
||||||
|
return 'packages/pinball_components/$font';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Class with the fonts available on the pinball game
|
||||||
|
class PinballFonts {
|
||||||
|
PinballFonts._();
|
||||||
|
|
||||||
|
/// Mono variation of the Pixeloid font
|
||||||
|
static final String pixeloidMono = _prefixFont(FontFamily.pixeloidMono);
|
||||||
|
|
||||||
|
/// Sans variation of the Pixeloid font
|
||||||
|
static final String pixeloidSans = _prefixFont(FontFamily.pixeloidMono);
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
library pinball_components;
|
library pinball_components;
|
||||||
|
|
||||||
export 'gen/assets.gen.dart';
|
export 'gen/assets.gen.dart';
|
||||||
|
export 'gen/pinball_fonts.dart';
|
||||||
export 'src/pinball_components.dart';
|
export 'src/pinball_components.dart';
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
/// {@template alien_bumper}
|
||||||
|
/// Bumper for Alien area.
|
||||||
|
/// {@endtemplate}
|
||||||
|
// TODO(ruimiguel): refactor later to unify with DashBumpers.
|
||||||
|
class AlienBumper extends BodyComponent with InitialPosition {
|
||||||
|
/// {@macro alien_bumper}
|
||||||
|
AlienBumper._({
|
||||||
|
required double majorRadius,
|
||||||
|
required double minorRadius,
|
||||||
|
required String activeAssetPath,
|
||||||
|
required String inactiveAssetPath,
|
||||||
|
required SpriteComponent spriteComponent,
|
||||||
|
}) : _majorRadius = majorRadius,
|
||||||
|
_minorRadius = minorRadius,
|
||||||
|
_activeAssetPath = activeAssetPath,
|
||||||
|
_inactiveAssetPath = inactiveAssetPath,
|
||||||
|
_spriteComponent = spriteComponent;
|
||||||
|
|
||||||
|
/// {@macro alien_bumper}
|
||||||
|
AlienBumper.a()
|
||||||
|
: this._(
|
||||||
|
majorRadius: 3.52,
|
||||||
|
minorRadius: 2.97,
|
||||||
|
activeAssetPath: Assets.images.alienBumper.a.active.keyName,
|
||||||
|
inactiveAssetPath: Assets.images.alienBumper.a.inactive.keyName,
|
||||||
|
spriteComponent: SpriteComponent(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(0, -0.1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// {@macro alien_bumper}
|
||||||
|
AlienBumper.b()
|
||||||
|
: this._(
|
||||||
|
majorRadius: 3.19,
|
||||||
|
minorRadius: 2.79,
|
||||||
|
activeAssetPath: Assets.images.alienBumper.b.active.keyName,
|
||||||
|
inactiveAssetPath: Assets.images.alienBumper.b.inactive.keyName,
|
||||||
|
spriteComponent: SpriteComponent(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(0, -0.1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final double _majorRadius;
|
||||||
|
final double _minorRadius;
|
||||||
|
final String _activeAssetPath;
|
||||||
|
late final Sprite _activeSprite;
|
||||||
|
final String _inactiveAssetPath;
|
||||||
|
late final Sprite _inactiveSprite;
|
||||||
|
final SpriteComponent _spriteComponent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
renderBody = false;
|
||||||
|
|
||||||
|
await _loadSprites();
|
||||||
|
|
||||||
|
deactivate();
|
||||||
|
await add(_spriteComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final shape = EllipseShape(
|
||||||
|
center: Vector2.zero(),
|
||||||
|
majorRadius: _majorRadius,
|
||||||
|
minorRadius: _minorRadius,
|
||||||
|
)..rotate(15.9 * math.pi / 180);
|
||||||
|
final fixtureDef = FixtureDef(shape)
|
||||||
|
..friction = 0
|
||||||
|
..restitution = 4;
|
||||||
|
|
||||||
|
final bodyDef = BodyDef()
|
||||||
|
..position = initialPosition
|
||||||
|
..userData = this;
|
||||||
|
|
||||||
|
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadSprites() async {
|
||||||
|
// TODO(alestiago): I think ideally we would like to do:
|
||||||
|
// Sprite(path).load so we don't require to store the activeAssetPath and
|
||||||
|
// the inactive assetPath.
|
||||||
|
_inactiveSprite = await gameRef.loadSprite(_inactiveAssetPath);
|
||||||
|
_activeSprite = await gameRef.loadSprite(_activeAssetPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Activates the [AlienBumper].
|
||||||
|
void activate() {
|
||||||
|
_spriteComponent
|
||||||
|
..sprite = _activeSprite
|
||||||
|
..size = _activeSprite.originalSize / 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deactivates the [AlienBumper].
|
||||||
|
void deactivate() {
|
||||||
|
_spriteComponent
|
||||||
|
..sprite = _inactiveSprite
|
||||||
|
..size = _inactiveSprite.originalSize / 10;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
/// {@template dash_animatronic}
|
||||||
|
/// Animated Dash that sits on top of the [BigDashNestBumper].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class DashAnimatronic extends SpriteAnimationComponent with HasGameRef {
|
||||||
|
/// {@macro dash_animatronic}
|
||||||
|
DashAnimatronic()
|
||||||
|
: super(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
playing: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
final spriteSheet = await gameRef.images.load(
|
||||||
|
Assets.images.dash.animatronic.keyName,
|
||||||
|
);
|
||||||
|
|
||||||
|
const amountPerRow = 12;
|
||||||
|
const amountPerColumn = 8;
|
||||||
|
final textureSize = Vector2(
|
||||||
|
spriteSheet.width / amountPerRow,
|
||||||
|
spriteSheet.height / amountPerColumn,
|
||||||
|
);
|
||||||
|
size = textureSize / 10;
|
||||||
|
|
||||||
|
animation = SpriteAnimation.fromFrameData(
|
||||||
|
spriteSheet,
|
||||||
|
SpriteAnimationData.sequenced(
|
||||||
|
amount: amountPerRow * amountPerColumn,
|
||||||
|
amountPerRow: amountPerRow,
|
||||||
|
stepTime: 1 / 24,
|
||||||
|
textureSize: textureSize,
|
||||||
|
loop: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
if (animation != null) {
|
||||||
|
if (animation!.isLastFrame) {
|
||||||
|
animation!.reset();
|
||||||
|
playing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/effects.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
/// {@template google_letter}
|
||||||
|
/// Circular sensor that represents a letter in "GOOGLE" for a given index.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class GoogleLetter extends BodyComponent with InitialPosition {
|
||||||
|
/// {@macro google_letter}
|
||||||
|
GoogleLetter(int index)
|
||||||
|
: _sprite = _GoogleLetterSprite(
|
||||||
|
_GoogleLetterSprite.spritePaths[index],
|
||||||
|
);
|
||||||
|
|
||||||
|
final _GoogleLetterSprite _sprite;
|
||||||
|
|
||||||
|
/// Activates this [GoogleLetter].
|
||||||
|
// TODO(alestiago): Improve doc comment once activate and deactivate
|
||||||
|
// are implemented with the actual assets.
|
||||||
|
Future<void> activate() => _sprite.activate();
|
||||||
|
|
||||||
|
/// Deactivates this [GoogleLetter].
|
||||||
|
Future<void> deactivate() => _sprite.deactivate();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
await add(_sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final shape = CircleShape()..radius = 1.85;
|
||||||
|
final fixtureDef = FixtureDef(shape)..isSensor = true;
|
||||||
|
|
||||||
|
final bodyDef = BodyDef()
|
||||||
|
..position = initialPosition
|
||||||
|
..userData = this
|
||||||
|
..type = BodyType.static;
|
||||||
|
|
||||||
|
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GoogleLetterSprite extends SpriteComponent with HasGameRef {
|
||||||
|
_GoogleLetterSprite(String path) : _path = path;
|
||||||
|
|
||||||
|
static final spritePaths = [
|
||||||
|
Assets.images.googleWord.letter1.keyName,
|
||||||
|
Assets.images.googleWord.letter2.keyName,
|
||||||
|
Assets.images.googleWord.letter3.keyName,
|
||||||
|
Assets.images.googleWord.letter4.keyName,
|
||||||
|
Assets.images.googleWord.letter5.keyName,
|
||||||
|
Assets.images.googleWord.letter6.keyName,
|
||||||
|
];
|
||||||
|
|
||||||
|
final String _path;
|
||||||
|
|
||||||
|
// TODO(alestiago): Correctly implement activate and deactivate once the
|
||||||
|
// assets are provided.
|
||||||
|
Future<void> activate() async {
|
||||||
|
await add(
|
||||||
|
_GoogleLetterColorEffect(color: Colors.green),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deactivate() async {
|
||||||
|
await add(
|
||||||
|
_GoogleLetterColorEffect(color: Colors.red),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
final sprite = await gameRef.loadSprite(_path);
|
||||||
|
this.sprite = sprite;
|
||||||
|
// TODO(alestiago): Size correctly once the assets are provided.
|
||||||
|
size = sprite.originalSize / 5;
|
||||||
|
anchor = Anchor.center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GoogleLetterColorEffect extends ColorEffect {
|
||||||
|
_GoogleLetterColorEffect({
|
||||||
|
required Color color,
|
||||||
|
}) : super(
|
||||||
|
color,
|
||||||
|
const Offset(0, 1),
|
||||||
|
EffectController(duration: 0.25),
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/effects.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
/// {@template score_text}
|
||||||
|
/// A [TextComponent] that spawns at a given [position] with a moving animation.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class ScoreText extends TextComponent {
|
||||||
|
/// {@macro score_text}
|
||||||
|
ScoreText({
|
||||||
|
required String text,
|
||||||
|
required Vector2 position,
|
||||||
|
this.color = Colors.black,
|
||||||
|
}) : super(
|
||||||
|
text: text,
|
||||||
|
position: position,
|
||||||
|
anchor: Anchor.center,
|
||||||
|
priority: 100,
|
||||||
|
);
|
||||||
|
|
||||||
|
late final Effect _effect;
|
||||||
|
|
||||||
|
/// The [text]'s [Color].
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
textRenderer = TextPaint(
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: PinballFonts.pixeloidMono,
|
||||||
|
color: color,
|
||||||
|
fontSize: 4,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await add(
|
||||||
|
_effect = MoveEffect.by(
|
||||||
|
Vector2(0, -5),
|
||||||
|
EffectController(duration: 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
|
||||||
|
if (_effect.controller.completed) {
|
||||||
|
removeFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flame/extensions.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:sandbox/stories/ball/basic_ball_game.dart';
|
||||||
|
|
||||||
|
class AlienBumperAGame extends BasicBallGame {
|
||||||
|
AlienBumperAGame() : super(color: const Color(0xFF0000FF));
|
||||||
|
|
||||||
|
static const info = '''
|
||||||
|
Shows how a AlienBumperA is rendered.
|
||||||
|
|
||||||
|
- Activate the "trace" parameter to overlay the body.
|
||||||
|
''';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
final center = screenToWorld(camera.viewport.canvasSize! / 2);
|
||||||
|
final alienBumperA = AlienBumper.a()
|
||||||
|
..initialPosition = Vector2(center.x - 20, center.y - 20)
|
||||||
|
..priority = 1;
|
||||||
|
await add(alienBumperA);
|
||||||
|
|
||||||
|
await traceAllBodies();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flame/extensions.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:sandbox/stories/ball/basic_ball_game.dart';
|
||||||
|
|
||||||
|
class AlienBumperBGame extends BasicBallGame {
|
||||||
|
AlienBumperBGame() : super(color: const Color(0xFF0000FF));
|
||||||
|
|
||||||
|
static const info = '''
|
||||||
|
Shows how a AlienBumperB is rendered.
|
||||||
|
|
||||||
|
- Activate the "trace" parameter to overlay the body.
|
||||||
|
''';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
final center = screenToWorld(camera.viewport.canvasSize! / 2);
|
||||||
|
final alienBumperB = AlienBumper.b()
|
||||||
|
..initialPosition = Vector2(center.x - 10, center.y + 10)
|
||||||
|
..priority = 1;
|
||||||
|
await add(alienBumperB);
|
||||||
|
|
||||||
|
await traceAllBodies();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import 'package:dashbook/dashbook.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
import 'package:sandbox/stories/alien_zone/alien_bumper_a_game.dart';
|
||||||
|
import 'package:sandbox/stories/alien_zone/alien_bumper_b_game.dart';
|
||||||
|
|
||||||
|
void addAlienZoneStories(Dashbook dashbook) {
|
||||||
|
dashbook.storiesOf('Alien Zone')
|
||||||
|
..add(
|
||||||
|
'Alien Bumper A',
|
||||||
|
(context) => GameWidget(
|
||||||
|
game: AlienBumperAGame()..trace = context.boolProperty('Trace', true),
|
||||||
|
),
|
||||||
|
codeLink: buildSourceLink('alien_zone/alien_bumper_a.dart'),
|
||||||
|
info: AlienBumperAGame.info,
|
||||||
|
)
|
||||||
|
..add(
|
||||||
|
'Alien Bumper B',
|
||||||
|
(context) => GameWidget(
|
||||||
|
game: AlienBumperBGame()..trace = context.boolProperty('Trace', true),
|
||||||
|
),
|
||||||
|
codeLink: buildSourceLink('alien_zone/alien_bumper_b.dart'),
|
||||||
|
info: AlienBumperAGame.info,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:sandbox/stories/ball/basic_ball_game.dart';
|
||||||
|
|
||||||
|
class GoogleLetterGame extends BasicBallGame {
|
||||||
|
GoogleLetterGame() : super(color: const Color(0xFF009900));
|
||||||
|
|
||||||
|
static const info = '''
|
||||||
|
Shows how a GoogleLetter is rendered.
|
||||||
|
|
||||||
|
- Tap anywhere on the screen to spawn a ball into the game.
|
||||||
|
''';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
addContactCallback(_BallGoogleLetterContactCallback());
|
||||||
|
|
||||||
|
camera.followVector2(Vector2.zero());
|
||||||
|
await add(GoogleLetter(0));
|
||||||
|
|
||||||
|
await traceAllBodies();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BallGoogleLetterContactCallback
|
||||||
|
extends ContactCallback<Ball, GoogleLetter> {
|
||||||
|
@override
|
||||||
|
void begin(Ball<Forge2DGame> a, GoogleLetter b, Contact contact) {
|
||||||
|
super.begin(a, b, contact);
|
||||||
|
b.activate();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import 'package:dashbook/dashbook.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
import 'package:sandbox/stories/google_word/google_letter_game.dart';
|
||||||
|
|
||||||
|
void addGoogleWordStories(Dashbook dashbook) {
|
||||||
|
dashbook.storiesOf('Google Word').add(
|
||||||
|
'Letter',
|
||||||
|
(context) => GameWidget(
|
||||||
|
game: GoogleLetterGame()..trace = context.boolProperty('Trace', true),
|
||||||
|
),
|
||||||
|
codeLink: buildSourceLink('google_word/letter.dart'),
|
||||||
|
info: GoogleLetterGame.info,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
import 'package:flame/input.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
import 'package:sandbox/stories/ball/basic_ball_game.dart';
|
||||||
|
|
||||||
|
class PlungerGame extends BasicBallGame with KeyboardEvents, Traceable {
|
||||||
|
PlungerGame() : super(color: const Color(0xFFFF0000));
|
||||||
|
|
||||||
|
static const info = '''
|
||||||
|
Shows how Plunger is rendered.
|
||||||
|
|
||||||
|
- Activate the "trace" parameter to overlay the body.
|
||||||
|
- Tap anywhere on the screen to spawn a ball into the game.
|
||||||
|
''';
|
||||||
|
|
||||||
|
static const _downKeys = [
|
||||||
|
LogicalKeyboardKey.arrowDown,
|
||||||
|
LogicalKeyboardKey.space,
|
||||||
|
];
|
||||||
|
|
||||||
|
late Plunger plunger;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
final center = screenToWorld(camera.viewport.canvasSize! / 2);
|
||||||
|
|
||||||
|
plunger = Plunger(compressionDistance: 29)
|
||||||
|
..initialPosition = Vector2(center.x - (Kicker.size.x * 2), center.y);
|
||||||
|
await add(plunger);
|
||||||
|
|
||||||
|
await traceAllBodies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
KeyEventResult onKeyEvent(
|
||||||
|
RawKeyEvent event,
|
||||||
|
Set<LogicalKeyboardKey> keysPressed,
|
||||||
|
) {
|
||||||
|
final movedPlungerDown = _downKeys.contains(event.logicalKey);
|
||||||
|
if (movedPlungerDown) {
|
||||||
|
if (event is RawKeyDownEvent) {
|
||||||
|
plunger.pull();
|
||||||
|
} else if (event is RawKeyUpEvent) {
|
||||||
|
plunger.release();
|
||||||
|
}
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import 'package:dashbook/dashbook.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
import 'package:sandbox/stories/plunger/plunger_game.dart';
|
||||||
|
|
||||||
|
void addPlungerStories(Dashbook dashbook) {
|
||||||
|
dashbook.storiesOf('Plunger').add(
|
||||||
|
'Basic',
|
||||||
|
(context) => GameWidget(
|
||||||
|
game: PlungerGame()..trace = context.boolProperty('Trace', true),
|
||||||
|
),
|
||||||
|
codeLink: buildSourceLink('plunger_game/basic.dart'),
|
||||||
|
info: PlungerGame.info,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flame/input.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
|
||||||
|
class ScoreTextBasicGame extends BasicGame with TapDetector {
|
||||||
|
static const info = '''
|
||||||
|
Simple game to show how score text works,
|
||||||
|
|
||||||
|
- Tap anywhere on the screen to spawn an text on the given location.
|
||||||
|
''';
|
||||||
|
|
||||||
|
final random = Random();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
camera.followVector2(Vector2.zero());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTapUp(TapUpInfo info) {
|
||||||
|
add(
|
||||||
|
ScoreText(
|
||||||
|
text: random.nextInt(100000).toString(),
|
||||||
|
color: Colors.white,
|
||||||
|
position: info.eventPosition.game..multiply(Vector2(1, -1)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import 'package:dashbook/dashbook.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
import 'package:sandbox/stories/score_text/basic.dart';
|
||||||
|
|
||||||
|
void addScoreTextStories(Dashbook dashbook) {
|
||||||
|
dashbook.storiesOf('ScoreText').add(
|
||||||
|
'Basic',
|
||||||
|
(context) => GameWidget(
|
||||||
|
game: ScoreTextBasicGame(),
|
||||||
|
),
|
||||||
|
codeLink: buildSourceLink('score_text/basic.dart'),
|
||||||
|
info: ScoreTextBasicGame.info,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(TestGame.new);
|
||||||
|
|
||||||
|
group('AlienBumper', () {
|
||||||
|
flameTester.test('"a" loads correctly', (game) async {
|
||||||
|
final bumper = AlienBumper.a();
|
||||||
|
await game.ensureAdd(bumper);
|
||||||
|
|
||||||
|
expect(game.contains(bumper), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('"b" loads correctly', (game) async {
|
||||||
|
final bumper = AlienBumper.b();
|
||||||
|
await game.ensureAdd(bumper);
|
||||||
|
expect(game.contains(bumper), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('activate returns normally', (game) async {
|
||||||
|
final bumper = AlienBumper.a();
|
||||||
|
await game.ensureAdd(bumper);
|
||||||
|
|
||||||
|
expect(bumper.activate, returnsNormally);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('deactivate returns normally', (game) async {
|
||||||
|
final bumper = AlienBumper.a();
|
||||||
|
await game.ensureAdd(bumper);
|
||||||
|
|
||||||
|
expect(bumper.deactivate, returnsNormally);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('changes sprite', (game) async {
|
||||||
|
final bumper = AlienBumper.a();
|
||||||
|
await game.ensureAdd(bumper);
|
||||||
|
|
||||||
|
final spriteComponent = bumper.firstChild<SpriteComponent>()!;
|
||||||
|
|
||||||
|
final deactivatedSprite = spriteComponent.sprite;
|
||||||
|
bumper.activate();
|
||||||
|
expect(
|
||||||
|
spriteComponent.sprite,
|
||||||
|
isNot(equals(deactivatedSprite)),
|
||||||
|
);
|
||||||
|
|
||||||
|
final activatedSprite = spriteComponent.sprite;
|
||||||
|
bumper.deactivate();
|
||||||
|
expect(
|
||||||
|
spriteComponent.sprite,
|
||||||
|
isNot(equals(activatedSprite)),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
activatedSprite,
|
||||||
|
isNot(equals(deactivatedSprite)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(TestGame.new);
|
||||||
|
|
||||||
|
group('DashAnimatronic', () {
|
||||||
|
flameTester.test(
|
||||||
|
'loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final dashAnimatronic = DashAnimatronic();
|
||||||
|
await game.ensureAdd(dashAnimatronic);
|
||||||
|
|
||||||
|
expect(game.contains(dashAnimatronic), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'stops animating after animation completes',
|
||||||
|
(game) async {
|
||||||
|
final dashAnimatronic = DashAnimatronic();
|
||||||
|
await game.ensureAdd(dashAnimatronic);
|
||||||
|
|
||||||
|
dashAnimatronic.playing = true;
|
||||||
|
dashAnimatronic.animation?.setToLast();
|
||||||
|
game.update(1);
|
||||||
|
|
||||||
|
expect(dashAnimatronic.playing, isFalse);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:flame/effects.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(TestGame.new);
|
||||||
|
|
||||||
|
group('Google Letter', () {
|
||||||
|
flameTester.test(
|
||||||
|
'0th loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final googleLetter = GoogleLetter(0);
|
||||||
|
await game.ready();
|
||||||
|
await game.ensureAdd(googleLetter);
|
||||||
|
|
||||||
|
expect(game.contains(googleLetter), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'1st loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final googleLetter = GoogleLetter(1);
|
||||||
|
await game.ready();
|
||||||
|
await game.ensureAdd(googleLetter);
|
||||||
|
|
||||||
|
expect(game.contains(googleLetter), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'2nd loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final googleLetter = GoogleLetter(2);
|
||||||
|
await game.ready();
|
||||||
|
await game.ensureAdd(googleLetter);
|
||||||
|
|
||||||
|
expect(game.contains(googleLetter), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'3d loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final googleLetter = GoogleLetter(3);
|
||||||
|
await game.ready();
|
||||||
|
await game.ensureAdd(googleLetter);
|
||||||
|
|
||||||
|
expect(game.contains(googleLetter), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'4th loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final googleLetter = GoogleLetter(4);
|
||||||
|
await game.ready();
|
||||||
|
await game.ensureAdd(googleLetter);
|
||||||
|
|
||||||
|
expect(game.contains(googleLetter), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'5th loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final googleLetter = GoogleLetter(5);
|
||||||
|
await game.ready();
|
||||||
|
await game.ensureAdd(googleLetter);
|
||||||
|
|
||||||
|
expect(game.contains(googleLetter), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test('throws error when index out of range', () {
|
||||||
|
expect(() => GoogleLetter(-1), throwsA(isA<RangeError>()));
|
||||||
|
expect(() => GoogleLetter(6), throwsA(isA<RangeError>()));
|
||||||
|
});
|
||||||
|
|
||||||
|
group('activate', () {
|
||||||
|
flameTester.test('returns normally', (game) async {
|
||||||
|
final googleLetter = GoogleLetter(0);
|
||||||
|
await game.ensureAdd(googleLetter);
|
||||||
|
await expectLater(googleLetter.activate, returnsNormally);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('adds an Effect', (game) async {
|
||||||
|
final googleLetter = GoogleLetter(0);
|
||||||
|
await game.ensureAdd(googleLetter);
|
||||||
|
await googleLetter.activate();
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
googleLetter.descendants().whereType<Effect>().length,
|
||||||
|
equals(1),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('deactivate', () {
|
||||||
|
flameTester.test('returns normally', (game) async {
|
||||||
|
final googleLetter = GoogleLetter(0);
|
||||||
|
await game.ensureAdd(googleLetter);
|
||||||
|
await expectLater(googleLetter.deactivate, returnsNormally);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('adds an Effect', (game) async {
|
||||||
|
final googleLetter = GoogleLetter(0);
|
||||||
|
await game.ensureAdd(googleLetter);
|
||||||
|
await googleLetter.deactivate();
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
googleLetter.descendants().whereType<Effect>().length,
|
||||||
|
equals(1),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/effects.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ScoreText', () {
|
||||||
|
final flameTester = FlameTester(TestGame.new);
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'renders correctly',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
game.camera.followVector2(Vector2.zero());
|
||||||
|
await game.ensureAdd(
|
||||||
|
ScoreText(
|
||||||
|
text: '123',
|
||||||
|
position: Vector2.zero(),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
final texts = game.descendants().whereType<TextComponent>().length;
|
||||||
|
expect(texts, equals(1));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'has a movement effect',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
game.camera.followVector2(Vector2.zero());
|
||||||
|
await game.ensureAdd(
|
||||||
|
ScoreText(
|
||||||
|
text: '123',
|
||||||
|
position: Vector2.zero(),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
game.update(0.5);
|
||||||
|
await tester.pump();
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
final text = game.descendants().whereType<TextComponent>().first;
|
||||||
|
expect(text.firstChild<MoveEffect>(), isNotNull);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'is removed once finished',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
game.camera.followVector2(Vector2.zero());
|
||||||
|
await game.ensureAdd(
|
||||||
|
ScoreText(
|
||||||
|
text: '123',
|
||||||
|
position: Vector2.zero(),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
game.update(1);
|
||||||
|
game.update(0); // Ensure all component removals
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
expect(game.children.length, equals(0));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(EmptyPinballGameTest.new);
|
||||||
|
|
||||||
|
group('AlienZone', () {
|
||||||
|
flameTester.test(
|
||||||
|
'loads correctly',
|
||||||
|
(game) async {
|
||||||
|
await game.ready();
|
||||||
|
final alienZone = AlienZone();
|
||||||
|
await game.ensureAdd(alienZone);
|
||||||
|
|
||||||
|
expect(game.contains(alienZone), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('loads', () {
|
||||||
|
flameTester.test(
|
||||||
|
'two AlienBumper',
|
||||||
|
(game) async {
|
||||||
|
await game.ready();
|
||||||
|
final alienZone = AlienZone();
|
||||||
|
await game.ensureAdd(alienZone);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
alienZone.descendants().whereType<AlienBumper>().length,
|
||||||
|
equals(2),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('bumpers', () {
|
||||||
|
late ControlledAlienBumper controlledAlienBumper;
|
||||||
|
late GameBloc gameBloc;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
gameBloc = MockGameBloc();
|
||||||
|
});
|
||||||
|
|
||||||
|
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
|
||||||
|
gameBuilder: EmptyPinballGameTest.new,
|
||||||
|
blocBuilder: () => gameBloc,
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'activate when deactivated bumper is hit',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
controlledAlienBumper = ControlledAlienBumper.a();
|
||||||
|
await game.ensureAdd(controlledAlienBumper);
|
||||||
|
|
||||||
|
controlledAlienBumper.controller.hit();
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
expect(controlledAlienBumper.controller.isActivated, isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'deactivate when activated bumper is hit',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
controlledAlienBumper = ControlledAlienBumper.a();
|
||||||
|
await game.ensureAdd(controlledAlienBumper);
|
||||||
|
|
||||||
|
controlledAlienBumper.controller.hit();
|
||||||
|
controlledAlienBumper.controller.hit();
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
expect(controlledAlienBumper.controller.isActivated, isFalse);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
'add Scored event',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final ball = Ball(baseColor: const Color(0xFF00FFFF));
|
||||||
|
final alienZone = AlienZone();
|
||||||
|
whenListen(
|
||||||
|
gameBloc,
|
||||||
|
const Stream<GameState>.empty(),
|
||||||
|
initialState: const GameState.initial(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await game.ensureAdd(alienZone);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
game.addContactCallback(BallScorePointsCallback(game));
|
||||||
|
|
||||||
|
final bumpers = alienZone.descendants().whereType<ScorePoints>();
|
||||||
|
|
||||||
|
for (final bumper in bumpers) {
|
||||||
|
beginContact(game, bumper, ball);
|
||||||
|
verify(
|
||||||
|
() => gameBloc.add(
|
||||||
|
Scored(points: bumper.points),
|
||||||
|
),
|
||||||
|
).called(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(EmptyPinballGameTest.new);
|
||||||
|
|
||||||
|
group('PlungerController', () {
|
||||||
|
group('onKeyEvent', () {
|
||||||
|
final downKeys = UnmodifiableListView([
|
||||||
|
LogicalKeyboardKey.arrowDown,
|
||||||
|
LogicalKeyboardKey.space,
|
||||||
|
LogicalKeyboardKey.keyS,
|
||||||
|
]);
|
||||||
|
|
||||||
|
late Plunger plunger;
|
||||||
|
late PlungerController controller;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
plunger = Plunger(compressionDistance: 10);
|
||||||
|
controller = PlungerController(plunger);
|
||||||
|
plunger.add(controller);
|
||||||
|
});
|
||||||
|
|
||||||
|
testRawKeyDownEvents(downKeys, (event) {
|
||||||
|
flameTester.test(
|
||||||
|
'moves down '
|
||||||
|
'when ${event.logicalKey.keyLabel} is pressed',
|
||||||
|
(game) async {
|
||||||
|
await game.ensureAdd(plunger);
|
||||||
|
controller.onKeyEvent(event, {});
|
||||||
|
|
||||||
|
expect(plunger.body.linearVelocity.y, isNegative);
|
||||||
|
expect(plunger.body.linearVelocity.x, isZero);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testRawKeyUpEvents(downKeys, (event) {
|
||||||
|
flameTester.test(
|
||||||
|
'moves up '
|
||||||
|
'when ${event.logicalKey.keyLabel} is released '
|
||||||
|
'and plunger is below its starting position',
|
||||||
|
(game) async {
|
||||||
|
await game.ensureAdd(plunger);
|
||||||
|
plunger.body.setTransform(Vector2(0, -1), 0);
|
||||||
|
controller.onKeyEvent(event, {});
|
||||||
|
|
||||||
|
expect(plunger.body.linearVelocity.y, isPositive);
|
||||||
|
expect(plunger.body.linearVelocity.x, isZero);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testRawKeyUpEvents(downKeys, (event) {
|
||||||
|
flameTester.test(
|
||||||
|
'does not move when ${event.logicalKey.keyLabel} is released '
|
||||||
|
'and plunger is in its starting position',
|
||||||
|
(game) async {
|
||||||
|
await game.ensureAdd(plunger);
|
||||||
|
controller.onKeyEvent(event, {});
|
||||||
|
|
||||||
|
expect(plunger.body.linearVelocity.y, isZero);
|
||||||
|
expect(plunger.body.linearVelocity.x, isZero);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ScoreEffectController', () {
|
||||||
|
late ScoreEffectController controller;
|
||||||
|
late PinballGame game;
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
registerFallbackValue(Component());
|
||||||
|
});
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
game = MockPinballGame();
|
||||||
|
when(() => game.add(any())).thenAnswer((_) async {});
|
||||||
|
|
||||||
|
controller = ScoreEffectController(game);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('listenWhen', () {
|
||||||
|
test('returns true when the user has earned points', () {
|
||||||
|
const previous = GameState.initial();
|
||||||
|
const current = GameState(
|
||||||
|
score: 10,
|
||||||
|
balls: 3,
|
||||||
|
activatedBonusLetters: [],
|
||||||
|
bonusHistory: [],
|
||||||
|
activatedDashNests: {},
|
||||||
|
);
|
||||||
|
expect(controller.listenWhen(previous, current), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'returns true when the user has earned points and there was no '
|
||||||
|
'previous state',
|
||||||
|
() {
|
||||||
|
const current = GameState(
|
||||||
|
score: 10,
|
||||||
|
balls: 3,
|
||||||
|
activatedBonusLetters: [],
|
||||||
|
bonusHistory: [],
|
||||||
|
activatedDashNests: {},
|
||||||
|
);
|
||||||
|
expect(controller.listenWhen(null, current), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'returns false when no points were earned',
|
||||||
|
() {
|
||||||
|
const current = GameState.initial();
|
||||||
|
const previous = GameState.initial();
|
||||||
|
expect(controller.listenWhen(previous, current), isFalse);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('onNewState', () {
|
||||||
|
test(
|
||||||
|
'adds a ScoreText with the correct score for the '
|
||||||
|
'first time',
|
||||||
|
() {
|
||||||
|
const state = GameState(
|
||||||
|
score: 10,
|
||||||
|
balls: 3,
|
||||||
|
activatedBonusLetters: [],
|
||||||
|
bonusHistory: [],
|
||||||
|
activatedDashNests: {},
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.onNewState(state);
|
||||||
|
|
||||||
|
final effect =
|
||||||
|
verify(() => game.add(captureAny())).captured.first as ScoreText;
|
||||||
|
|
||||||
|
expect(effect.text, equals('10'));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test('adds a ScoreTextEffect with the correct score', () {
|
||||||
|
controller.onNewState(
|
||||||
|
const GameState(
|
||||||
|
score: 10,
|
||||||
|
balls: 3,
|
||||||
|
activatedBonusLetters: [],
|
||||||
|
bonusHistory: [],
|
||||||
|
activatedDashNests: {},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.onNewState(
|
||||||
|
const GameState(
|
||||||
|
score: 14,
|
||||||
|
balls: 3,
|
||||||
|
activatedBonusLetters: [],
|
||||||
|
bonusHistory: [],
|
||||||
|
activatedDashNests: {},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final effect =
|
||||||
|
verify(() => game.add(captureAny())).captured.last as ScoreText;
|
||||||
|
|
||||||
|
expect(effect.text, equals('4'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|