@ -0,0 +1 @@
|
||||
export 'multipliers_behavior.dart';
|
@ -0,0 +1,25 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_bloc/flame_bloc.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// Toggle each [Multiplier] when GameState.multiplier changes.
|
||||
class MultipliersBehavior extends Component
|
||||
with
|
||||
HasGameRef<PinballGame>,
|
||||
ParentIsA<Multipliers>,
|
||||
BlocComponent<GameBloc, GameState> {
|
||||
@override
|
||||
bool listenWhen(GameState? previousState, GameState newState) {
|
||||
return previousState?.multiplier != newState.multiplier;
|
||||
}
|
||||
|
||||
@override
|
||||
void onNewState(GameState state) {
|
||||
final multipliers = parent.children.whereType<Multiplier>();
|
||||
for (final multiplier in multipliers) {
|
||||
multiplier.bloc.next(state.multiplier);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball/game/components/multipliers/behaviors/behaviors.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template multipliers}
|
||||
/// A group for the multipliers on the board.
|
||||
/// {@endtemplate}
|
||||
class Multipliers extends Component {
|
||||
/// {@macro multipliers}
|
||||
Multipliers()
|
||||
: super(
|
||||
children: [
|
||||
Multiplier.x2(
|
||||
position: Vector2(-19.5, -2),
|
||||
angle: -15 * math.pi / 180,
|
||||
),
|
||||
Multiplier.x3(
|
||||
position: Vector2(13, -9.4),
|
||||
angle: 15 * math.pi / 180,
|
||||
),
|
||||
Multiplier.x4(
|
||||
position: Vector2(0, -21.2),
|
||||
angle: 0,
|
||||
),
|
||||
Multiplier.x5(
|
||||
position: Vector2(-8.5, -28),
|
||||
angle: -3 * math.pi / 180,
|
||||
),
|
||||
Multiplier.x6(
|
||||
position: Vector2(10, -30.7),
|
||||
angle: 8 * math.pi / 180,
|
||||
),
|
||||
MultipliersBehavior(),
|
||||
],
|
||||
);
|
||||
|
||||
/// Creates [Multipliers] without any children.
|
||||
///
|
||||
/// This can be used for testing [Multipliers]'s behaviors in isolation.
|
||||
@visibleForTesting
|
||||
Multipliers.test();
|
||||
}
|
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 |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 616 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 735 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 231 KiB |
Before Width: | Height: | Size: 38 KiB |
@ -0,0 +1,209 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:pinball_components/gen/assets.gen.dart';
|
||||
import 'package:pinball_components/pinball_components.dart' hide Assets;
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
class AndroidSpaceship extends Blueprint {
|
||||
AndroidSpaceship({required Vector2 position})
|
||||
: super(
|
||||
components: [
|
||||
_SpaceshipSaucer()..initialPosition = position,
|
||||
_SpaceshipSaucerSpriteAnimationComponent()..position = position,
|
||||
_LightBeamSpriteComponent()..position = position + Vector2(2.5, 5),
|
||||
_AndroidHead()..initialPosition = position + Vector2(0.5, 0.25),
|
||||
_SpaceshipHole(
|
||||
outsideLayer: Layer.spaceshipExitRail,
|
||||
outsidePriority: RenderPriority.ballOnSpaceshipRail,
|
||||
)..initialPosition = position - Vector2(5.3, -5.4),
|
||||
_SpaceshipHole(
|
||||
outsideLayer: Layer.board,
|
||||
outsidePriority: RenderPriority.ballOnBoard,
|
||||
)..initialPosition = position - Vector2(-7.5, -1.1),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
class _SpaceshipSaucer extends BodyComponent with InitialPosition, Layered {
|
||||
_SpaceshipSaucer() : super(renderBody: false) {
|
||||
layer = Layer.spaceship;
|
||||
}
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = _SpaceshipSaucerShape();
|
||||
final bodyDef = BodyDef(
|
||||
position: initialPosition,
|
||||
userData: this,
|
||||
angle: -1.7,
|
||||
);
|
||||
|
||||
return world.createBody(bodyDef)..createFixtureFromShape(shape);
|
||||
}
|
||||
}
|
||||
|
||||
class _SpaceshipSaucerShape extends ChainShape {
|
||||
_SpaceshipSaucerShape() {
|
||||
const minorRadius = 9.75;
|
||||
const majorRadius = 11.9;
|
||||
|
||||
createChain(
|
||||
[
|
||||
for (var angle = 0.2618; angle <= 6.0214; angle += math.pi / 180)
|
||||
Vector2(
|
||||
minorRadius * math.cos(angle),
|
||||
majorRadius * math.sin(angle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SpaceshipSaucerSpriteAnimationComponent extends SpriteAnimationComponent
|
||||
with HasGameRef {
|
||||
_SpaceshipSaucerSpriteAnimationComponent()
|
||||
: super(
|
||||
anchor: Anchor.center,
|
||||
priority: RenderPriority.spaceshipSaucer,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
final spriteSheet = gameRef.images.fromCache(
|
||||
Assets.images.android.spaceship.saucer.keyName,
|
||||
);
|
||||
|
||||
const amountPerRow = 5;
|
||||
const amountPerColumn = 3;
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(allisonryan0002): add pulsing behavior.
|
||||
class _LightBeamSpriteComponent extends SpriteComponent with HasGameRef {
|
||||
_LightBeamSpriteComponent()
|
||||
: super(
|
||||
anchor: Anchor.center,
|
||||
priority: RenderPriority.spaceshipLightBeam,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
final sprite = Sprite(
|
||||
gameRef.images.fromCache(
|
||||
Assets.images.android.spaceship.lightBeam.keyName,
|
||||
),
|
||||
);
|
||||
this.sprite = sprite;
|
||||
size = sprite.originalSize / 10;
|
||||
}
|
||||
}
|
||||
|
||||
class _AndroidHead extends BodyComponent with InitialPosition, Layered {
|
||||
_AndroidHead()
|
||||
: super(
|
||||
priority: RenderPriority.androidHead,
|
||||
children: [_AndroidHeadSpriteAnimationComponent()],
|
||||
renderBody: false,
|
||||
) {
|
||||
layer = Layer.spaceship;
|
||||
}
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = EllipseShape(
|
||||
center: Vector2.zero(),
|
||||
majorRadius: 3.1,
|
||||
minorRadius: 2,
|
||||
)..rotate(1.4);
|
||||
// TODO(allisonryan0002): use bumping behavior.
|
||||
final fixtureDef = FixtureDef(
|
||||
shape,
|
||||
restitution: 0.1,
|
||||
);
|
||||
final bodyDef = BodyDef(position: initialPosition);
|
||||
|
||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||
}
|
||||
}
|
||||
|
||||
class _AndroidHeadSpriteAnimationComponent extends SpriteAnimationComponent
|
||||
with HasGameRef {
|
||||
_AndroidHeadSpriteAnimationComponent()
|
||||
: super(
|
||||
anchor: Anchor.center,
|
||||
position: Vector2(-0.24, -2.6),
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
final spriteSheet = gameRef.images.fromCache(
|
||||
Assets.images.android.spaceship.animatronic.keyName,
|
||||
);
|
||||
|
||||
const amountPerRow = 18;
|
||||
const amountPerColumn = 4;
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SpaceshipHole extends LayerSensor {
|
||||
_SpaceshipHole({required Layer outsideLayer, required int outsidePriority})
|
||||
: super(
|
||||
insideLayer: Layer.spaceship,
|
||||
outsideLayer: outsideLayer,
|
||||
orientation: LayerEntranceOrientation.down,
|
||||
insidePriority: RenderPriority.ballOnSpaceship,
|
||||
outsidePriority: outsidePriority,
|
||||
) {
|
||||
layer = Layer.spaceship;
|
||||
}
|
||||
|
||||
@override
|
||||
Shape get shape {
|
||||
return ArcShape(
|
||||
center: Vector2(0, -3.2),
|
||||
arcRadius: 5,
|
||||
angle: 1,
|
||||
rotation: -2,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
part 'multiplier_state.dart';
|
||||
|
||||
class MultiplierCubit extends Cubit<MultiplierState> {
|
||||
MultiplierCubit(MultiplierValue multiplierValue)
|
||||
: super(MultiplierState.initial(multiplierValue));
|
||||
|
||||
/// Event added when the game's current multiplier changes.
|
||||
void next(int multiplier) {
|
||||
if (state.value.equals(multiplier)) {
|
||||
if (state.spriteState == MultiplierSpriteState.dimmed) {
|
||||
emit(state.copyWith(spriteState: MultiplierSpriteState.lit));
|
||||
}
|
||||
} else {
|
||||
if (state.spriteState == MultiplierSpriteState.lit) {
|
||||
emit(state.copyWith(spriteState: MultiplierSpriteState.dimmed));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
part of 'multiplier_cubit.dart';
|
||||
|
||||
enum MultiplierSpriteState {
|
||||
lit,
|
||||
dimmed,
|
||||
}
|
||||
|
||||
class MultiplierState extends Equatable {
|
||||
const MultiplierState({
|
||||
required this.value,
|
||||
required this.spriteState,
|
||||
});
|
||||
|
||||
const MultiplierState.initial(MultiplierValue multiplierValue)
|
||||
: this(
|
||||
value: multiplierValue,
|
||||
spriteState: MultiplierSpriteState.dimmed,
|
||||
);
|
||||
|
||||
/// Current value for the [Multiplier]
|
||||
final MultiplierValue value;
|
||||
|
||||
/// The [MultiplierSpriteGroupComponent] current sprite state
|
||||
final MultiplierSpriteState spriteState;
|
||||
|
||||
MultiplierState copyWith({
|
||||
MultiplierSpriteState? spriteState,
|
||||
}) {
|
||||
return MultiplierState(
|
||||
value: value,
|
||||
spriteState: spriteState ?? this.spriteState,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [value, spriteState];
|
||||
}
|
||||
|
||||
extension MultiplierValueX on MultiplierValue {
|
||||
bool equals(int value) {
|
||||
switch (this) {
|
||||
case MultiplierValue.x2:
|
||||
return value == 2;
|
||||
case MultiplierValue.x3:
|
||||
return value == 3;
|
||||
case MultiplierValue.x4:
|
||||
return value == 4;
|
||||
case MultiplierValue.x5:
|
||||
return value == 5;
|
||||
case MultiplierValue.x6:
|
||||
return value == 6;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball_components/gen/assets.gen.dart';
|
||||
import 'package:pinball_components/src/components/multiplier/cubit/multiplier_cubit.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
export 'cubit/multiplier_cubit.dart';
|
||||
|
||||
/// {@template multiplier}
|
||||
/// Backlit multiplier decal displayed on the board.
|
||||
/// {@endtemplate}
|
||||
class Multiplier extends Component {
|
||||
/// {@macro multiplier}
|
||||
Multiplier._({
|
||||
required MultiplierValue value,
|
||||
required Vector2 position,
|
||||
required double angle,
|
||||
required this.bloc,
|
||||
}) : _value = value,
|
||||
_position = position,
|
||||
_angle = angle,
|
||||
super();
|
||||
|
||||
/// {@macro multiplier}
|
||||
Multiplier.x2({
|
||||
required Vector2 position,
|
||||
required double angle,
|
||||
}) : this._(
|
||||
value: MultiplierValue.x2,
|
||||
position: position,
|
||||
angle: angle,
|
||||
bloc: MultiplierCubit(MultiplierValue.x2),
|
||||
);
|
||||
|
||||
/// {@macro multiplier}
|
||||
Multiplier.x3({
|
||||
required Vector2 position,
|
||||
required double angle,
|
||||
}) : this._(
|
||||
value: MultiplierValue.x3,
|
||||
position: position,
|
||||
angle: angle,
|
||||
bloc: MultiplierCubit(MultiplierValue.x3),
|
||||
);
|
||||
|
||||
/// {@macro multiplier}
|
||||
Multiplier.x4({
|
||||
required Vector2 position,
|
||||
required double angle,
|
||||
}) : this._(
|
||||
value: MultiplierValue.x4,
|
||||
position: position,
|
||||
angle: angle,
|
||||
bloc: MultiplierCubit(MultiplierValue.x4),
|
||||
);
|
||||
|
||||
/// {@macro multiplier}
|
||||
Multiplier.x5({
|
||||
required Vector2 position,
|
||||
required double angle,
|
||||
}) : this._(
|
||||
value: MultiplierValue.x5,
|
||||
position: position,
|
||||
angle: angle,
|
||||
bloc: MultiplierCubit(MultiplierValue.x5),
|
||||
);
|
||||
|
||||
/// {@macro multiplier}
|
||||
Multiplier.x6({
|
||||
required Vector2 position,
|
||||
required double angle,
|
||||
}) : this._(
|
||||
value: MultiplierValue.x6,
|
||||
position: position,
|
||||
angle: angle,
|
||||
bloc: MultiplierCubit(MultiplierValue.x6),
|
||||
);
|
||||
|
||||
/// Creates a [Multiplier] without any children.
|
||||
///
|
||||
/// This can be used for testing [Multiplier]'s behaviors in isolation.
|
||||
// TODO(alestiago): Refactor injecting bloc once the following is merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
@visibleForTesting
|
||||
Multiplier.test({
|
||||
required MultiplierValue value,
|
||||
required this.bloc,
|
||||
}) : _value = value,
|
||||
_position = Vector2.zero(),
|
||||
_angle = 0;
|
||||
|
||||
// TODO(ruimiguel): Consider refactoring once the following is merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
final MultiplierCubit bloc;
|
||||
|
||||
final MultiplierValue _value;
|
||||
final Vector2 _position;
|
||||
final double _angle;
|
||||
late final MultiplierSpriteGroupComponent _sprite;
|
||||
|
||||
@override
|
||||
void onRemove() {
|
||||
bloc.close();
|
||||
super.onRemove();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
_sprite = MultiplierSpriteGroupComponent(
|
||||
position: _position,
|
||||
litAssetPath: _value.litAssetPath,
|
||||
dimmedAssetPath: _value.dimmedAssetPath,
|
||||
angle: _angle,
|
||||
current: bloc.state,
|
||||
);
|
||||
await add(_sprite);
|
||||
}
|
||||
}
|
||||
|
||||
/// Available multiplier values.
|
||||
enum MultiplierValue {
|
||||
x2,
|
||||
x3,
|
||||
x4,
|
||||
x5,
|
||||
x6,
|
||||
}
|
||||
|
||||
extension on MultiplierValue {
|
||||
String get litAssetPath {
|
||||
switch (this) {
|
||||
case MultiplierValue.x2:
|
||||
return Assets.images.multiplier.x2.lit.keyName;
|
||||
case MultiplierValue.x3:
|
||||
return Assets.images.multiplier.x3.lit.keyName;
|
||||
case MultiplierValue.x4:
|
||||
return Assets.images.multiplier.x4.lit.keyName;
|
||||
case MultiplierValue.x5:
|
||||
return Assets.images.multiplier.x5.lit.keyName;
|
||||
case MultiplierValue.x6:
|
||||
return Assets.images.multiplier.x6.lit.keyName;
|
||||
}
|
||||
}
|
||||
|
||||
String get dimmedAssetPath {
|
||||
switch (this) {
|
||||
case MultiplierValue.x2:
|
||||
return Assets.images.multiplier.x2.dimmed.keyName;
|
||||
case MultiplierValue.x3:
|
||||
return Assets.images.multiplier.x3.dimmed.keyName;
|
||||
case MultiplierValue.x4:
|
||||
return Assets.images.multiplier.x4.dimmed.keyName;
|
||||
case MultiplierValue.x5:
|
||||
return Assets.images.multiplier.x5.dimmed.keyName;
|
||||
case MultiplierValue.x6:
|
||||
return Assets.images.multiplier.x6.dimmed.keyName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template multiplier_sprite_group_component}
|
||||
/// A [SpriteGroupComponent] for a [Multiplier] with lit and dimmed states.
|
||||
/// {@endtemplate}
|
||||
@visibleForTesting
|
||||
class MultiplierSpriteGroupComponent
|
||||
extends SpriteGroupComponent<MultiplierSpriteState>
|
||||
with HasGameRef, ParentIsA<Multiplier> {
|
||||
/// {@macro multiplier_sprite_group_component}
|
||||
MultiplierSpriteGroupComponent({
|
||||
required Vector2 position,
|
||||
required String litAssetPath,
|
||||
required String dimmedAssetPath,
|
||||
required double angle,
|
||||
required MultiplierState current,
|
||||
}) : _litAssetPath = litAssetPath,
|
||||
_dimmedAssetPath = dimmedAssetPath,
|
||||
super(
|
||||
anchor: Anchor.center,
|
||||
position: position,
|
||||
angle: angle,
|
||||
current: current.spriteState,
|
||||
);
|
||||
|
||||
final String _litAssetPath;
|
||||
final String _dimmedAssetPath;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
parent.bloc.stream.listen((state) => current = state.spriteState);
|
||||
|
||||
final sprites = {
|
||||
MultiplierSpriteState.lit:
|
||||
Sprite(gameRef.images.fromCache(_litAssetPath)),
|
||||
MultiplierSpriteState.dimmed:
|
||||
Sprite(gameRef.images.fromCache(_dimmedAssetPath)),
|
||||
};
|
||||
this.sprites = sprites;
|
||||
size = sprites[current]!.originalSize / 10;
|
||||
}
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:pinball_components/gen/assets.gen.dart';
|
||||
import 'package:pinball_components/pinball_components.dart' hide Assets;
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// {@template spaceship}
|
||||
/// A [Blueprint] which creates the spaceship feature.
|
||||
/// {@endtemplate}
|
||||
class Spaceship extends Blueprint {
|
||||
/// {@macro spaceship}
|
||||
Spaceship({required Vector2 position})
|
||||
: super(
|
||||
components: [
|
||||
SpaceshipSaucer()..initialPosition = position,
|
||||
_SpaceshipEntrance()..initialPosition = position,
|
||||
AndroidHead()..initialPosition = position,
|
||||
_SpaceshipHole(
|
||||
outsideLayer: Layer.spaceshipExitRail,
|
||||
outsidePriority: RenderPriority.ballOnSpaceshipRail,
|
||||
)..initialPosition = position - Vector2(5.2, -4.8),
|
||||
_SpaceshipHole(
|
||||
outsideLayer: Layer.board,
|
||||
outsidePriority: RenderPriority.ballOnBoard,
|
||||
)..initialPosition = position - Vector2(-7.2, -0.8),
|
||||
SpaceshipWall()..initialPosition = position,
|
||||
],
|
||||
);
|
||||
|
||||
/// Total size of the spaceship.
|
||||
static final size = Vector2(25, 19);
|
||||
}
|
||||
|
||||
/// {@template spaceship_saucer}
|
||||
/// A [BodyComponent] for the base, or the saucer of the spaceship
|
||||
/// {@endtemplate}
|
||||
class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered {
|
||||
/// {@macro spaceship_saucer}
|
||||
SpaceshipSaucer()
|
||||
: super(
|
||||
priority: RenderPriority.spaceshipSaucer,
|
||||
renderBody: false,
|
||||
children: [
|
||||
_SpaceshipSaucerSpriteComponent(),
|
||||
],
|
||||
) {
|
||||
layer = Layer.spaceship;
|
||||
}
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = CircleShape()..radius = 3;
|
||||
final fixtureDef = FixtureDef(
|
||||
shape,
|
||||
isSensor: true,
|
||||
);
|
||||
final bodyDef = BodyDef(
|
||||
position: initialPosition,
|
||||
userData: this,
|
||||
);
|
||||
|
||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||
}
|
||||
}
|
||||
|
||||
class _SpaceshipSaucerSpriteComponent extends SpriteComponent with HasGameRef {
|
||||
_SpaceshipSaucerSpriteComponent()
|
||||
: super(
|
||||
anchor: Anchor.center,
|
||||
// TODO(alestiago): Refactor to use sprite orignial size instead.
|
||||
size: Spaceship.size,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
// TODO(alestiago): Use cached sprite.
|
||||
sprite = await gameRef.loadSprite(
|
||||
Assets.images.spaceship.saucer.keyName,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template spaceship_bridge}
|
||||
/// A [BodyComponent] that provides both the collision and the rotation
|
||||
/// animation for the bridge.
|
||||
/// {@endtemplate}
|
||||
class AndroidHead extends BodyComponent with InitialPosition, Layered {
|
||||
/// {@macro spaceship_bridge}
|
||||
AndroidHead()
|
||||
: super(
|
||||
priority: RenderPriority.androidHead,
|
||||
children: [_AndroidHeadSpriteAnimation()],
|
||||
renderBody: false,
|
||||
) {
|
||||
layer = Layer.spaceship;
|
||||
}
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final circleShape = CircleShape()..radius = 2;
|
||||
|
||||
final bodyDef = BodyDef(
|
||||
position: initialPosition,
|
||||
userData: this,
|
||||
);
|
||||
|
||||
return world.createBody(bodyDef)
|
||||
..createFixture(
|
||||
FixtureDef(circleShape)..restitution = 0.4,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AndroidHeadSpriteAnimation extends SpriteAnimationComponent
|
||||
with HasGameRef {
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
final image = await gameRef.images.load(
|
||||
Assets.images.spaceship.bridge.keyName,
|
||||
);
|
||||
size = Vector2(8.2, 10);
|
||||
position = Vector2(0, -2);
|
||||
anchor = Anchor.center;
|
||||
|
||||
final data = SpriteAnimationData.sequenced(
|
||||
amount: 72,
|
||||
amountPerRow: 24,
|
||||
stepTime: 0.05,
|
||||
textureSize: size * 10,
|
||||
);
|
||||
animation = SpriteAnimation.fromFrameData(image, data);
|
||||
}
|
||||
}
|
||||
|
||||
class _SpaceshipEntrance extends LayerSensor {
|
||||
_SpaceshipEntrance()
|
||||
: super(
|
||||
insideLayer: Layer.spaceship,
|
||||
orientation: LayerEntranceOrientation.up,
|
||||
insidePriority: RenderPriority.ballOnSpaceship,
|
||||
) {
|
||||
layer = Layer.spaceship;
|
||||
}
|
||||
|
||||
@override
|
||||
Shape get shape {
|
||||
final radius = Spaceship.size.y / 2;
|
||||
return PolygonShape()
|
||||
..setAsEdge(
|
||||
Vector2(
|
||||
radius * cos(20 * pi / 180),
|
||||
radius * sin(20 * pi / 180),
|
||||
)..rotate(90 * pi / 180),
|
||||
Vector2(
|
||||
radius * cos(340 * pi / 180),
|
||||
radius * sin(340 * pi / 180),
|
||||
)..rotate(90 * pi / 180),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SpaceshipHole extends LayerSensor {
|
||||
_SpaceshipHole({required Layer outsideLayer, required int outsidePriority})
|
||||
: super(
|
||||
insideLayer: Layer.spaceship,
|
||||
outsideLayer: outsideLayer,
|
||||
orientation: LayerEntranceOrientation.down,
|
||||
insidePriority: RenderPriority.ballOnSpaceship,
|
||||
outsidePriority: outsidePriority,
|
||||
) {
|
||||
layer = Layer.spaceship;
|
||||
}
|
||||
|
||||
@override
|
||||
Shape get shape {
|
||||
return ArcShape(
|
||||
center: Vector2(0, -3.2),
|
||||
arcRadius: 5,
|
||||
angle: 1,
|
||||
rotation: -2,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template spaceship_wall_shape}
|
||||
/// The [ChainShape] that defines the shape of the [SpaceshipWall].
|
||||
/// {@endtemplate}
|
||||
class _SpaceshipWallShape extends ChainShape {
|
||||
/// {@macro spaceship_wall_shape}
|
||||
_SpaceshipWallShape() {
|
||||
final minorRadius = (Spaceship.size.y - 2) / 2;
|
||||
final majorRadius = (Spaceship.size.x - 2) / 2;
|
||||
|
||||
createChain(
|
||||
[
|
||||
// TODO(alestiago): Try converting this logic to radian.
|
||||
for (var angle = 20; angle <= 340; angle++)
|
||||
Vector2(
|
||||
minorRadius * cos(angle * pi / 180),
|
||||
majorRadius * sin(angle * pi / 180),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template spaceship_wall}
|
||||
/// A [BodyComponent] that provides the collision for the wall
|
||||
/// surrounding the spaceship.
|
||||
///
|
||||
/// It has a small opening to allow the [Ball] to get inside the spaceship
|
||||
/// saucer.
|
||||
///
|
||||
/// It also contains the [SpriteComponent] for the lower wall
|
||||
/// {@endtemplate}
|
||||
class SpaceshipWall extends BodyComponent with InitialPosition, Layered {
|
||||
/// {@macro spaceship_wall}
|
||||
SpaceshipWall()
|
||||
: super(
|
||||
priority: RenderPriority.spaceshipSaucerWall,
|
||||
renderBody: false,
|
||||
) {
|
||||
layer = Layer.spaceship;
|
||||
}
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = _SpaceshipWallShape();
|
||||
final fixtureDef = FixtureDef(shape);
|
||||
|
||||
final bodyDef = BodyDef(
|
||||
position: initialPosition,
|
||||
userData: this,
|
||||
angle: -1.7,
|
||||
);
|
||||
|
||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
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 AndroidBumperCowGame extends BallGame {
|
||||
AndroidBumperCowGame()
|
||||
: super(
|
||||
imagesFileNames: [
|
||||
Assets.images.android.bumper.cow.lit.keyName,
|
||||
Assets.images.android.bumper.cow.dimmed.keyName,
|
||||
],
|
||||
);
|
||||
|
||||
static const description = '''
|
||||
Shows how a AndroidBumper.cow is rendered.
|
||||
|
||||
- Activate the "trace" parameter to overlay the body.
|
||||
''';
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
camera.followVector2(Vector2.zero());
|
||||
await add(
|
||||
AndroidBumper.cow()..priority = 1,
|
||||
);
|
||||
|
||||
await traceAllBodies();
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flame/input.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
import 'package:sandbox/stories/ball/basic_ball_game.dart';
|
||||
|
||||
class AndroidSpaceshipGame extends BallGame {
|
||||
AndroidSpaceshipGame()
|
||||
: super(
|
||||
ballPriority: RenderPriority.ballOnSpaceship,
|
||||
ballLayer: Layer.spaceship,
|
||||
imagesFileNames: [
|
||||
Assets.images.android.spaceship.saucer.keyName,
|
||||
Assets.images.android.spaceship.animatronic.keyName,
|
||||
Assets.images.android.spaceship.lightBeam.keyName,
|
||||
],
|
||||
);
|
||||
|
||||
static const description = '''
|
||||
Shows how the AndroidSpaceship is rendered.
|
||||
|
||||
- Activate the "trace" parameter to overlay the body.
|
||||
- Tap anywhere on the screen to spawn a Ball into the game.
|
||||
''';
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
camera.followVector2(Vector2.zero());
|
||||
await addFromBlueprint(
|
||||
AndroidSpaceship(position: Vector2.zero()),
|
||||
);
|
||||
|
||||
await traceAllBodies();
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flame/input.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
import 'package:sandbox/common/common.dart';
|
||||
|
||||
class SpaceshipGame extends AssetsGame with TapDetector {
|
||||
static const description = '''
|
||||
Shows how a Spaceship works.
|
||||
|
||||
- Tap anywhere on the screen to spawn a Ball into the game.
|
||||
''';
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
camera.followVector2(Vector2.zero());
|
||||
await addFromBlueprint(
|
||||
Spaceship(position: Vector2.zero()),
|
||||
);
|
||||
await ready();
|
||||
}
|
||||
|
||||
@override
|
||||
void onTapUp(TapUpInfo info) {
|
||||
add(
|
||||
Ball(baseColor: Colors.blue)
|
||||
..initialPosition = info.eventPosition.game
|
||||
..layer = Layer.spaceshipEntranceRamp,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
import 'dart:math' as math;
|
||||
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/stories/ball/basic_ball_game.dart';
|
||||
|
||||
class MultipliersGame extends BallGame with KeyboardEvents {
|
||||
MultipliersGame()
|
||||
: super(
|
||||
imagesFileNames: [
|
||||
Assets.images.multiplier.x2.lit.keyName,
|
||||
Assets.images.multiplier.x2.dimmed.keyName,
|
||||
Assets.images.multiplier.x3.lit.keyName,
|
||||
Assets.images.multiplier.x3.dimmed.keyName,
|
||||
Assets.images.multiplier.x4.lit.keyName,
|
||||
Assets.images.multiplier.x4.dimmed.keyName,
|
||||
Assets.images.multiplier.x5.lit.keyName,
|
||||
Assets.images.multiplier.x5.dimmed.keyName,
|
||||
Assets.images.multiplier.x6.lit.keyName,
|
||||
Assets.images.multiplier.x6.dimmed.keyName,
|
||||
],
|
||||
);
|
||||
|
||||
static const description = '''
|
||||
Shows how the Multipliers are rendered.
|
||||
|
||||
- Tap anywhere on the screen to spawn a ball into the game.
|
||||
- Press digits 2 to 6 for toggle state multipliers 2 to 6.
|
||||
''';
|
||||
|
||||
final List<Multiplier> multipliers = [
|
||||
Multiplier.x2(
|
||||
position: Vector2(-20, 0),
|
||||
angle: -15 * math.pi / 180,
|
||||
),
|
||||
Multiplier.x3(
|
||||
position: Vector2(20, -5),
|
||||
angle: 15 * math.pi / 180,
|
||||
),
|
||||
Multiplier.x4(
|
||||
position: Vector2(0, -15),
|
||||
angle: 0,
|
||||
),
|
||||
Multiplier.x5(
|
||||
position: Vector2(-10, -25),
|
||||
angle: -3 * math.pi / 180,
|
||||
),
|
||||
Multiplier.x6(
|
||||
position: Vector2(10, -35),
|
||||
angle: 8 * math.pi / 180,
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
camera.followVector2(Vector2.zero());
|
||||
|
||||
await addAll(multipliers);
|
||||
await traceAllBodies();
|
||||
}
|
||||
|
||||
@override
|
||||
KeyEventResult onKeyEvent(
|
||||
RawKeyEvent event,
|
||||
Set<LogicalKeyboardKey> keysPressed,
|
||||
) {
|
||||
if (event is RawKeyDownEvent) {
|
||||
var currentMultiplier = 1;
|
||||
|
||||
if (event.logicalKey == LogicalKeyboardKey.digit2) {
|
||||
currentMultiplier = 2;
|
||||
}
|
||||
if (event.logicalKey == LogicalKeyboardKey.digit3) {
|
||||
currentMultiplier = 3;
|
||||
}
|
||||
if (event.logicalKey == LogicalKeyboardKey.digit4) {
|
||||
currentMultiplier = 4;
|
||||
}
|
||||
if (event.logicalKey == LogicalKeyboardKey.digit5) {
|
||||
currentMultiplier = 5;
|
||||
}
|
||||
if (event.logicalKey == LogicalKeyboardKey.digit6) {
|
||||
currentMultiplier = 6;
|
||||
}
|
||||
|
||||
for (final multiplier in multipliers) {
|
||||
multiplier.bloc.next(currentMultiplier);
|
||||
}
|
||||
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import 'package:dashbook/dashbook.dart';
|
||||
import 'package:sandbox/common/common.dart';
|
||||
import 'package:sandbox/stories/multipliers/multipliers_game.dart';
|
||||
|
||||
void addMultipliersStories(Dashbook dashbook) {
|
||||
dashbook.storiesOf('Multipliers').addGame(
|
||||
title: 'Multipliers',
|
||||
description: MultipliersGame.description,
|
||||
gameBuilder: (_) => MultipliersGame(),
|
||||
);
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
// 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 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
import '../../helpers/helpers.dart';
|
||||
|
||||
void main() {
|
||||
group('AndroidSpaceship', () {
|
||||
group('Spaceship', () {
|
||||
final assets = [
|
||||
Assets.images.android.spaceship.saucer.keyName,
|
||||
Assets.images.android.spaceship.animatronic.keyName,
|
||||
Assets.images.android.spaceship.lightBeam.keyName,
|
||||
];
|
||||
final flameTester = FlameTester(() => TestGame(assets));
|
||||
|
||||
flameTester.test('loads correctly', (game) async {
|
||||
await game.addFromBlueprint(AndroidSpaceship(position: Vector2.zero()));
|
||||
await game.ready();
|
||||
});
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'renders correctly',
|
||||
setUp: (game, tester) async {
|
||||
await game.images.loadAll(assets);
|
||||
await game
|
||||
.addFromBlueprint(AndroidSpaceship(position: Vector2.zero()));
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
await game.ready();
|
||||
await tester.pump();
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
final animationDuration = game
|
||||
.descendants()
|
||||
.whereType<SpriteAnimationComponent>()
|
||||
.last
|
||||
.animation!
|
||||
.totalDuration();
|
||||
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('golden/android_spaceship/start.png'),
|
||||
);
|
||||
|
||||
game.update(animationDuration * 0.5);
|
||||
await tester.pump();
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('golden/android_spaceship/middle.png'),
|
||||
);
|
||||
|
||||
game.update(animationDuration * 0.5);
|
||||
await tester.pump();
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('golden/android_spaceship/end.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
After Width: | Height: | Size: 121 KiB |
After Width: | Height: | Size: 121 KiB |
After Width: | Height: | Size: 121 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 78 KiB |
@ -0,0 +1,118 @@
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
void main() {
|
||||
group(
|
||||
'MultiplierCubit',
|
||||
() {
|
||||
blocTest<MultiplierCubit, MultiplierState>(
|
||||
"emits [lit] when 'next' on x2 dimmed with x2 multiplier value",
|
||||
build: () => MultiplierCubit(MultiplierValue.x2),
|
||||
act: (bloc) => bloc.next(2),
|
||||
expect: () => [
|
||||
isA<MultiplierState>()
|
||||
..having(
|
||||
(state) => state.spriteState,
|
||||
'spriteState',
|
||||
MultiplierSpriteState.lit,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<MultiplierCubit, MultiplierState>(
|
||||
"emits [lit] when 'next' on x3 dimmed with x3 multiplier value",
|
||||
build: () => MultiplierCubit(MultiplierValue.x3),
|
||||
act: (bloc) => bloc.next(3),
|
||||
expect: () => [
|
||||
isA<MultiplierState>()
|
||||
..having(
|
||||
(state) => state.spriteState,
|
||||
'spriteState',
|
||||
MultiplierSpriteState.lit,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<MultiplierCubit, MultiplierState>(
|
||||
"emits [lit] when 'next' on x4 dimmed with x4 multiplier value",
|
||||
build: () => MultiplierCubit(MultiplierValue.x4),
|
||||
act: (bloc) => bloc.next(4),
|
||||
expect: () => [
|
||||
isA<MultiplierState>()
|
||||
..having(
|
||||
(state) => state.spriteState,
|
||||
'spriteState',
|
||||
MultiplierSpriteState.lit,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<MultiplierCubit, MultiplierState>(
|
||||
"emits [lit] when 'next' on x5 dimmed with x5 multiplier value",
|
||||
build: () => MultiplierCubit(MultiplierValue.x5),
|
||||
act: (bloc) => bloc.next(5),
|
||||
expect: () => [
|
||||
isA<MultiplierState>()
|
||||
..having(
|
||||
(state) => state.spriteState,
|
||||
'spriteState',
|
||||
MultiplierSpriteState.lit,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<MultiplierCubit, MultiplierState>(
|
||||
"emits [lit] when 'next' on x6 dimmed with x6 multiplier value",
|
||||
build: () => MultiplierCubit(MultiplierValue.x6),
|
||||
act: (bloc) => bloc.next(6),
|
||||
expect: () => [
|
||||
isA<MultiplierState>()
|
||||
..having(
|
||||
(state) => state.spriteState,
|
||||
'spriteState',
|
||||
MultiplierSpriteState.lit,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<MultiplierCubit, MultiplierState>(
|
||||
"emits [dimmed] when 'next' on lit with different multiplier value",
|
||||
build: () => MultiplierCubit(MultiplierValue.x2),
|
||||
seed: () => MultiplierState(
|
||||
value: MultiplierValue.x2,
|
||||
spriteState: MultiplierSpriteState.lit,
|
||||
),
|
||||
act: (bloc) => bloc.next(3),
|
||||
expect: () => [
|
||||
isA<MultiplierState>()
|
||||
..having(
|
||||
(state) => state.spriteState,
|
||||
'spriteState',
|
||||
MultiplierSpriteState.dimmed,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<MultiplierCubit, MultiplierState>(
|
||||
"emits nothing when 'next' on lit with same multiplier value",
|
||||
build: () => MultiplierCubit(MultiplierValue.x2),
|
||||
seed: () => MultiplierState(
|
||||
value: MultiplierValue.x2,
|
||||
spriteState: MultiplierSpriteState.lit,
|
||||
),
|
||||
act: (bloc) => bloc.next(2),
|
||||
expect: () => <MultiplierState>[],
|
||||
);
|
||||
|
||||
blocTest<MultiplierCubit, MultiplierState>(
|
||||
"emits nothing when 'next' on dimmed with different multiplier value",
|
||||
build: () => MultiplierCubit(MultiplierValue.x2),
|
||||
act: (bloc) => bloc.next(3),
|
||||
expect: () => <MultiplierState>[],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pinball_components/src/pinball_components.dart';
|
||||
|
||||
void main() {
|
||||
group('MultiplierState', () {
|
||||
test('supports value equality', () {
|
||||
expect(
|
||||
MultiplierState(
|
||||
value: MultiplierValue.x2,
|
||||
spriteState: MultiplierSpriteState.lit,
|
||||
),
|
||||
equals(
|
||||
MultiplierState(
|
||||
value: MultiplierValue.x2,
|
||||
spriteState: MultiplierSpriteState.lit,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
group('constructor', () {
|
||||
test('can be instantiated', () {
|
||||
expect(
|
||||
MultiplierState(
|
||||
value: MultiplierValue.x2,
|
||||
spriteState: MultiplierSpriteState.lit,
|
||||
),
|
||||
isNotNull,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('copyWith', () {
|
||||
test(
|
||||
'copies correctly '
|
||||
'when no argument specified',
|
||||
() {
|
||||
const multiplierState = MultiplierState(
|
||||
value: MultiplierValue.x2,
|
||||
spriteState: MultiplierSpriteState.lit,
|
||||
);
|
||||
expect(
|
||||
multiplierState.copyWith(),
|
||||
equals(multiplierState),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'copies correctly '
|
||||
'when all arguments specified',
|
||||
() {
|
||||
const multiplierState = MultiplierState(
|
||||
value: MultiplierValue.x2,
|
||||
spriteState: MultiplierSpriteState.lit,
|
||||
);
|
||||
final otherMultiplierState = MultiplierState(
|
||||
value: MultiplierValue.x2,
|
||||
spriteState: MultiplierSpriteState.dimmed,
|
||||
);
|
||||
expect(multiplierState, isNot(equals(otherMultiplierState)));
|
||||
|
||||
expect(
|
||||
multiplierState.copyWith(
|
||||
spriteState: MultiplierSpriteState.dimmed,
|
||||
),
|
||||
equals(otherMultiplierState),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,517 @@
|
||||
// ignore_for_file: cascade_invocations, prefer_const_constructors
|
||||
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
import '../../../helpers/helpers.dart';
|
||||
|
||||
void main() {
|
||||
final bloc = MockMultiplierCubit();
|
||||
|
||||
group('Multiplier', () {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
final assets = [
|
||||
Assets.images.multiplier.x2.lit.keyName,
|
||||
Assets.images.multiplier.x2.dimmed.keyName,
|
||||
Assets.images.multiplier.x3.lit.keyName,
|
||||
Assets.images.multiplier.x3.dimmed.keyName,
|
||||
Assets.images.multiplier.x4.lit.keyName,
|
||||
Assets.images.multiplier.x4.dimmed.keyName,
|
||||
Assets.images.multiplier.x5.lit.keyName,
|
||||
Assets.images.multiplier.x5.dimmed.keyName,
|
||||
Assets.images.multiplier.x6.lit.keyName,
|
||||
Assets.images.multiplier.x6.dimmed.keyName,
|
||||
];
|
||||
final flameTester = FlameTester(() => TestGame(assets));
|
||||
|
||||
flameTester.test('"x2" loads correctly', (game) async {
|
||||
final multiplier = Multiplier.x2(
|
||||
position: Vector2.zero(),
|
||||
angle: 0,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
expect(game.contains(multiplier), isTrue);
|
||||
});
|
||||
|
||||
flameTester.test('"x3" loads correctly', (game) async {
|
||||
final multiplier = Multiplier.x3(
|
||||
position: Vector2.zero(),
|
||||
angle: 0,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
expect(game.contains(multiplier), isTrue);
|
||||
});
|
||||
|
||||
flameTester.test('"x4" loads correctly', (game) async {
|
||||
final multiplier = Multiplier.x4(
|
||||
position: Vector2.zero(),
|
||||
angle: 0,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
expect(game.contains(multiplier), isTrue);
|
||||
});
|
||||
|
||||
flameTester.test('"x5" loads correctly', (game) async {
|
||||
final multiplier = Multiplier.x5(
|
||||
position: Vector2.zero(),
|
||||
angle: 0,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
expect(game.contains(multiplier), isTrue);
|
||||
});
|
||||
|
||||
flameTester.test('"x6" loads correctly', (game) async {
|
||||
final multiplier = Multiplier.x6(
|
||||
position: Vector2.zero(),
|
||||
angle: 0,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
expect(game.contains(multiplier), isTrue);
|
||||
});
|
||||
|
||||
group('renders correctly', () {
|
||||
group('x2', () {
|
||||
const multiplierValue = MultiplierValue.x2;
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'lit when bloc state is lit',
|
||||
setUp: (game, tester) async {
|
||||
await game.images.loadAll(assets);
|
||||
|
||||
whenListen(
|
||||
bloc,
|
||||
const Stream<MultiplierState>.empty(),
|
||||
initialState: MultiplierState(
|
||||
value: multiplierValue,
|
||||
spriteState: MultiplierSpriteState.lit,
|
||||
),
|
||||
);
|
||||
|
||||
final multiplier = Multiplier.test(
|
||||
value: multiplierValue,
|
||||
bloc: bloc,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
await tester.pump();
|
||||
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
expect(
|
||||
game
|
||||
.descendants()
|
||||
.whereType<MultiplierSpriteGroupComponent>()
|
||||
.first
|
||||
.current,
|
||||
MultiplierSpriteState.lit,
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('../golden/multipliers/x2-lit.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'dimmed when bloc state is dimmed',
|
||||
setUp: (game, tester) async {
|
||||
await game.images.loadAll(assets);
|
||||
|
||||
whenListen(
|
||||
bloc,
|
||||
const Stream<MultiplierState>.empty(),
|
||||
initialState: MultiplierState(
|
||||
value: multiplierValue,
|
||||
spriteState: MultiplierSpriteState.dimmed,
|
||||
),
|
||||
);
|
||||
|
||||
final multiplier = Multiplier.test(
|
||||
value: multiplierValue,
|
||||
bloc: bloc,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
await tester.pump();
|
||||
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
expect(
|
||||
game
|
||||
.descendants()
|
||||
.whereType<MultiplierSpriteGroupComponent>()
|
||||
.first
|
||||
.current,
|
||||
MultiplierSpriteState.dimmed,
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('../golden/multipliers/x2-dimmed.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('x3', () {
|
||||
const multiplierValue = MultiplierValue.x3;
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'lit when bloc state is lit',
|
||||
setUp: (game, tester) async {
|
||||
await game.images.loadAll(assets);
|
||||
|
||||
whenListen(
|
||||
bloc,
|
||||
const Stream<MultiplierState>.empty(),
|
||||
initialState: MultiplierState(
|
||||
value: multiplierValue,
|
||||
spriteState: MultiplierSpriteState.lit,
|
||||
),
|
||||
);
|
||||
|
||||
final multiplier = Multiplier.test(
|
||||
value: multiplierValue,
|
||||
bloc: bloc,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
await tester.pump();
|
||||
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
expect(
|
||||
game
|
||||
.descendants()
|
||||
.whereType<MultiplierSpriteGroupComponent>()
|
||||
.first
|
||||
.current,
|
||||
MultiplierSpriteState.lit,
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('../golden/multipliers/x3-lit.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'dimmed when bloc state is dimmed',
|
||||
setUp: (game, tester) async {
|
||||
await game.images.loadAll(assets);
|
||||
|
||||
whenListen(
|
||||
bloc,
|
||||
const Stream<MultiplierState>.empty(),
|
||||
initialState: MultiplierState(
|
||||
value: multiplierValue,
|
||||
spriteState: MultiplierSpriteState.dimmed,
|
||||
),
|
||||
);
|
||||
|
||||
final multiplier = Multiplier.test(
|
||||
value: multiplierValue,
|
||||
bloc: bloc,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
await tester.pump();
|
||||
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
expect(
|
||||
game
|
||||
.descendants()
|
||||
.whereType<MultiplierSpriteGroupComponent>()
|
||||
.first
|
||||
.current,
|
||||
MultiplierSpriteState.dimmed,
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('../golden/multipliers/x3-dimmed.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('x4', () {
|
||||
const multiplierValue = MultiplierValue.x4;
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'lit when bloc state is lit',
|
||||
setUp: (game, tester) async {
|
||||
await game.images.loadAll(assets);
|
||||
|
||||
whenListen(
|
||||
bloc,
|
||||
const Stream<MultiplierState>.empty(),
|
||||
initialState: MultiplierState(
|
||||
value: multiplierValue,
|
||||
spriteState: MultiplierSpriteState.lit,
|
||||
),
|
||||
);
|
||||
|
||||
final multiplier = Multiplier.test(
|
||||
value: multiplierValue,
|
||||
bloc: bloc,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
await tester.pump();
|
||||
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
expect(
|
||||
game
|
||||
.descendants()
|
||||
.whereType<MultiplierSpriteGroupComponent>()
|
||||
.first
|
||||
.current,
|
||||
MultiplierSpriteState.lit,
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('../golden/multipliers/x4-lit.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'dimmed when bloc state is dimmed',
|
||||
setUp: (game, tester) async {
|
||||
await game.images.loadAll(assets);
|
||||
|
||||
whenListen(
|
||||
bloc,
|
||||
const Stream<MultiplierState>.empty(),
|
||||
initialState: MultiplierState(
|
||||
value: multiplierValue,
|
||||
spriteState: MultiplierSpriteState.dimmed,
|
||||
),
|
||||
);
|
||||
|
||||
final multiplier = Multiplier.test(
|
||||
value: multiplierValue,
|
||||
bloc: bloc,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
await tester.pump();
|
||||
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
expect(
|
||||
game
|
||||
.descendants()
|
||||
.whereType<MultiplierSpriteGroupComponent>()
|
||||
.first
|
||||
.current,
|
||||
MultiplierSpriteState.dimmed,
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('../golden/multipliers/x4-dimmed.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('x5', () {
|
||||
const multiplierValue = MultiplierValue.x5;
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'lit when bloc state is lit',
|
||||
setUp: (game, tester) async {
|
||||
await game.images.loadAll(assets);
|
||||
|
||||
whenListen(
|
||||
bloc,
|
||||
const Stream<MultiplierState>.empty(),
|
||||
initialState: MultiplierState(
|
||||
value: multiplierValue,
|
||||
spriteState: MultiplierSpriteState.lit,
|
||||
),
|
||||
);
|
||||
|
||||
final multiplier = Multiplier.test(
|
||||
value: multiplierValue,
|
||||
bloc: bloc,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
await tester.pump();
|
||||
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
expect(
|
||||
game
|
||||
.descendants()
|
||||
.whereType<MultiplierSpriteGroupComponent>()
|
||||
.first
|
||||
.current,
|
||||
MultiplierSpriteState.lit,
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('../golden/multipliers/x5-lit.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'dimmed when bloc state is dimmed',
|
||||
setUp: (game, tester) async {
|
||||
await game.images.loadAll(assets);
|
||||
|
||||
whenListen(
|
||||
bloc,
|
||||
const Stream<MultiplierState>.empty(),
|
||||
initialState: MultiplierState(
|
||||
value: multiplierValue,
|
||||
spriteState: MultiplierSpriteState.dimmed,
|
||||
),
|
||||
);
|
||||
|
||||
final multiplier = Multiplier.test(
|
||||
value: multiplierValue,
|
||||
bloc: bloc,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
await tester.pump();
|
||||
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
expect(
|
||||
game
|
||||
.descendants()
|
||||
.whereType<MultiplierSpriteGroupComponent>()
|
||||
.first
|
||||
.current,
|
||||
MultiplierSpriteState.dimmed,
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('../golden/multipliers/x5-dimmed.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('x6', () {
|
||||
const multiplierValue = MultiplierValue.x6;
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'lit when bloc state is lit',
|
||||
setUp: (game, tester) async {
|
||||
await game.images.loadAll(assets);
|
||||
|
||||
whenListen(
|
||||
bloc,
|
||||
const Stream<MultiplierState>.empty(),
|
||||
initialState: MultiplierState(
|
||||
value: multiplierValue,
|
||||
spriteState: MultiplierSpriteState.lit,
|
||||
),
|
||||
);
|
||||
|
||||
final multiplier = Multiplier.test(
|
||||
value: multiplierValue,
|
||||
bloc: bloc,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
await tester.pump();
|
||||
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
expect(
|
||||
game
|
||||
.descendants()
|
||||
.whereType<MultiplierSpriteGroupComponent>()
|
||||
.first
|
||||
.current,
|
||||
MultiplierSpriteState.lit,
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('../golden/multipliers/x6-lit.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'dimmed when bloc state is dimmed',
|
||||
setUp: (game, tester) async {
|
||||
await game.images.loadAll(assets);
|
||||
|
||||
whenListen(
|
||||
bloc,
|
||||
const Stream<MultiplierState>.empty(),
|
||||
initialState: MultiplierState(
|
||||
value: multiplierValue,
|
||||
spriteState: MultiplierSpriteState.dimmed,
|
||||
),
|
||||
);
|
||||
|
||||
final multiplier = Multiplier.test(
|
||||
value: multiplierValue,
|
||||
bloc: bloc,
|
||||
);
|
||||
await game.ensureAdd(multiplier);
|
||||
await tester.pump();
|
||||
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
expect(
|
||||
game
|
||||
.descendants()
|
||||
.whereType<MultiplierSpriteGroupComponent>()
|
||||
.first
|
||||
.current,
|
||||
MultiplierSpriteState.dimmed,
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('../golden/multipliers/x6-dimmed.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
flameTester.test('closes bloc when removed', (game) async {
|
||||
whenListen(
|
||||
bloc,
|
||||
const Stream<MultiplierState>.empty(),
|
||||
initialState: MultiplierState(
|
||||
value: MultiplierValue.x2,
|
||||
spriteState: MultiplierSpriteState.dimmed,
|
||||
),
|
||||
);
|
||||
when(bloc.close).thenAnswer((_) async {});
|
||||
final multiplier = Multiplier.test(value: MultiplierValue.x2, bloc: bloc);
|
||||
|
||||
await game.ensureAdd(multiplier);
|
||||
game.remove(multiplier);
|
||||
await game.ready();
|
||||
|
||||
verify(bloc.close).called(1);
|
||||
});
|
||||
});
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
import '../../helpers/helpers.dart';
|
||||
|
||||
void main() {
|
||||
group('Spaceship', () {
|
||||
late Filter filterData;
|
||||
late Fixture fixture;
|
||||
late Body body;
|
||||
late Ball ball;
|
||||
late Forge2DGame game;
|
||||
|
||||
setUp(() {
|
||||
filterData = MockFilter();
|
||||
|
||||
fixture = MockFixture();
|
||||
when(() => fixture.filterData).thenReturn(filterData);
|
||||
|
||||
body = MockBody();
|
||||
when(() => body.fixtures).thenReturn([fixture]);
|
||||
|
||||
game = MockGame();
|
||||
|
||||
ball = MockBall();
|
||||
when(() => ball.gameRef).thenReturn(game);
|
||||
when(() => ball.body).thenReturn(body);
|
||||
});
|
||||
|
||||
group('Spaceship', () {
|
||||
final tester = FlameTester(TestGame.new);
|
||||
|
||||
tester.testGameWidget(
|
||||
'renders correctly',
|
||||
setUp: (game, tester) async {
|
||||
final position = Vector2(30, -30);
|
||||
await game.addFromBlueprint(Spaceship(position: position));
|
||||
game.camera.followVector2(position);
|
||||
await game.ready();
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('golden/spaceship.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
// ignore_for_file: cascade_invocations, prefer_const_constructors
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockingjay/mockingjay.dart';
|
||||
import 'package:pinball/game/components/multipliers/behaviors/behaviors.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
import '../../../../helpers/helpers.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
final assets = [
|
||||
Assets.images.multiplier.x2.lit.keyName,
|
||||
Assets.images.multiplier.x2.dimmed.keyName,
|
||||
Assets.images.multiplier.x3.lit.keyName,
|
||||
Assets.images.multiplier.x3.dimmed.keyName,
|
||||
Assets.images.multiplier.x4.lit.keyName,
|
||||
Assets.images.multiplier.x4.dimmed.keyName,
|
||||
Assets.images.multiplier.x5.lit.keyName,
|
||||
Assets.images.multiplier.x5.dimmed.keyName,
|
||||
Assets.images.multiplier.x6.lit.keyName,
|
||||
Assets.images.multiplier.x6.dimmed.keyName,
|
||||
];
|
||||
|
||||
group('MultipliersBehavior', () {
|
||||
late GameBloc gameBloc;
|
||||
|
||||
setUp(() {
|
||||
registerFallbackValue(MockComponent());
|
||||
gameBloc = MockGameBloc();
|
||||
whenListen(
|
||||
gameBloc,
|
||||
const Stream<GameState>.empty(),
|
||||
initialState: const GameState.initial(),
|
||||
);
|
||||
});
|
||||
|
||||
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
|
||||
gameBuilder: EmptyPinballTestGame.new,
|
||||
blocBuilder: () => gameBloc,
|
||||
assets: assets,
|
||||
);
|
||||
|
||||
group('listenWhen', () {
|
||||
test('is true when the multiplier has changed', () {
|
||||
final state = GameState(
|
||||
score: 10,
|
||||
multiplier: 2,
|
||||
rounds: 0,
|
||||
bonusHistory: const [],
|
||||
);
|
||||
|
||||
final previous = GameState.initial();
|
||||
expect(
|
||||
MultipliersBehavior().listenWhen(previous, state),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('is false when the multiplier state is the same', () {
|
||||
final state = GameState(
|
||||
score: 10,
|
||||
multiplier: 1,
|
||||
rounds: 0,
|
||||
bonusHistory: const [],
|
||||
);
|
||||
|
||||
final previous = GameState.initial();
|
||||
expect(
|
||||
MultipliersBehavior().listenWhen(previous, state),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('onNewState', () {
|
||||
flameBlocTester.testGameWidget(
|
||||
"calls 'next' once per each multiplier when GameBloc emit state",
|
||||
setUp: (game, tester) async {
|
||||
final behavior = MultipliersBehavior();
|
||||
final parent = Multipliers.test();
|
||||
final multiplierX2Cubit = MockMultiplierCubit();
|
||||
final multiplierX3Cubit = MockMultiplierCubit();
|
||||
final multipliers = [
|
||||
Multiplier.test(
|
||||
value: MultiplierValue.x2,
|
||||
bloc: multiplierX2Cubit,
|
||||
),
|
||||
Multiplier.test(
|
||||
value: MultiplierValue.x3,
|
||||
bloc: multiplierX3Cubit,
|
||||
),
|
||||
];
|
||||
|
||||
whenListen(
|
||||
multiplierX2Cubit,
|
||||
const Stream<MultiplierState>.empty(),
|
||||
initialState: MultiplierState.initial(MultiplierValue.x2),
|
||||
);
|
||||
when(() => multiplierX2Cubit.next(any())).thenAnswer((_) async {});
|
||||
|
||||
whenListen(
|
||||
multiplierX3Cubit,
|
||||
const Stream<MultiplierState>.empty(),
|
||||
initialState: MultiplierState.initial(MultiplierValue.x2),
|
||||
);
|
||||
when(() => multiplierX3Cubit.next(any())).thenAnswer((_) async {});
|
||||
|
||||
await parent.addAll(multipliers);
|
||||
await game.ensureAdd(parent);
|
||||
await parent.ensureAdd(behavior);
|
||||
|
||||
await tester.pump();
|
||||
|
||||
behavior.onNewState(
|
||||
GameState.initial().copyWith(multiplier: 2),
|
||||
);
|
||||
|
||||
for (final multiplier in multipliers) {
|
||||
verify(
|
||||
() => multiplier.bloc.next(any()),
|
||||
).called(1);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:flame_test/flame_test.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 assets = [
|
||||
Assets.images.multiplier.x2.lit.keyName,
|
||||
Assets.images.multiplier.x2.dimmed.keyName,
|
||||
Assets.images.multiplier.x3.lit.keyName,
|
||||
Assets.images.multiplier.x3.dimmed.keyName,
|
||||
Assets.images.multiplier.x4.lit.keyName,
|
||||
Assets.images.multiplier.x4.dimmed.keyName,
|
||||
Assets.images.multiplier.x5.lit.keyName,
|
||||
Assets.images.multiplier.x5.dimmed.keyName,
|
||||
Assets.images.multiplier.x6.lit.keyName,
|
||||
Assets.images.multiplier.x6.dimmed.keyName,
|
||||
];
|
||||
|
||||
late GameBloc gameBloc;
|
||||
|
||||
setUp(() {
|
||||
gameBloc = GameBloc();
|
||||
});
|
||||
|
||||
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
|
||||
gameBuilder: EmptyPinballTestGame.new,
|
||||
blocBuilder: () => gameBloc,
|
||||
assets: assets,
|
||||
);
|
||||
|
||||
group('Multipliers', () {
|
||||
flameBlocTester.testGameWidget(
|
||||
'loads correctly',
|
||||
setUp: (game, tester) async {
|
||||
final multipliersGroup = Multipliers();
|
||||
await game.ensureAdd(multipliersGroup);
|
||||
|
||||
expect(game.contains(multipliersGroup), isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
group('loads', () {
|
||||
flameBlocTester.testGameWidget(
|
||||
'five Multiplier',
|
||||
setUp: (game, tester) async {
|
||||
final multipliersGroup = Multipliers();
|
||||
await game.ensureAdd(multipliersGroup);
|
||||
|
||||
expect(
|
||||
multipliersGroup.descendants().whereType<Multiplier>().length,
|
||||
equals(5),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|