@ -1,11 +1,33 @@
|
|||||||
{
|
{
|
||||||
|
"firestore": {
|
||||||
|
"rules": "firestore.rules"
|
||||||
|
},
|
||||||
"hosting": {
|
"hosting": {
|
||||||
"public": "build/web",
|
"public": "build/web",
|
||||||
"site": "ashehwkdkdjruejdnensjsjdne",
|
"site": "ashehwkdkdjruejdnensjsjdne",
|
||||||
"ignore": [
|
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
|
||||||
"firebase.json",
|
"headers": [
|
||||||
"**/.*",
|
{
|
||||||
"**/node_modules/**"
|
"source": "**/*.@(jpg|jpeg|gif|png)",
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "Cache-Control",
|
||||||
|
"value": "max-age=3600"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "**",
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "Cache-Control",
|
||||||
|
"value": "no-cache, no-store, must-revalidate"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"rules": "storage.rules"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
rules_version = '2';
|
||||||
|
service cloud.firestore {
|
||||||
|
match /databases/{database}/documents {
|
||||||
|
match /leaderboard/{userId} {
|
||||||
|
|
||||||
|
function prohibited(initials) {
|
||||||
|
let prohibitedInitials = get(/databases/$(database)/documents/prohibitedInitials/list).data.prohibitedInitials;
|
||||||
|
return initials in prohibitedInitials;
|
||||||
|
}
|
||||||
|
|
||||||
|
function inCharLimit(initials) {
|
||||||
|
return initials.size() < 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAuthedUser(auth) {
|
||||||
|
return request.auth.uid != null && auth.token.firebase.sign_in_provider == "anonymous"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leaderboard can be read if it doesn't contain any prohibited initials
|
||||||
|
allow read: if !prohibited(resource.data.playerInitials);
|
||||||
|
|
||||||
|
// A leaderboard entry can be created if the user is authenticated,
|
||||||
|
// it's 3 characters long, and not a prohibited combination.
|
||||||
|
allow create: if isAuthedUser(request.auth) &&
|
||||||
|
inCharLimit(request.resource.data.playerInitials) &&
|
||||||
|
!prohibited(request.resource.data.playerInitials);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1,3 @@
|
|||||||
export 'android_spaceship_bonus_behavior.dart';
|
export 'android_spaceship_bonus_behavior.dart';
|
||||||
|
export 'ramp_bonus_behavior.dart';
|
||||||
|
export 'ramp_shot_behavior.dart';
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball/game/behaviors/behaviors.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
/// {@template ramp_bonus_behavior}
|
||||||
|
/// Increases the score when a [Ball] is shot 10 times into the [SpaceshipRamp].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class RampBonusBehavior extends Component
|
||||||
|
with ParentIsA<SpaceshipRamp>, HasGameRef<PinballGame> {
|
||||||
|
/// {@macro ramp_bonus_behavior}
|
||||||
|
RampBonusBehavior({
|
||||||
|
required Points points,
|
||||||
|
}) : _points = points,
|
||||||
|
super();
|
||||||
|
|
||||||
|
/// Creates a [RampBonusBehavior].
|
||||||
|
///
|
||||||
|
/// This can be used for testing [RampBonusBehavior] in isolation.
|
||||||
|
@visibleForTesting
|
||||||
|
RampBonusBehavior.test({
|
||||||
|
required Points points,
|
||||||
|
required this.subscription,
|
||||||
|
}) : _points = points,
|
||||||
|
super();
|
||||||
|
|
||||||
|
final Points _points;
|
||||||
|
|
||||||
|
/// Subscription to [SpaceshipRampState] at [SpaceshipRamp].
|
||||||
|
@visibleForTesting
|
||||||
|
StreamSubscription? subscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onMount() {
|
||||||
|
super.onMount();
|
||||||
|
|
||||||
|
subscription = subscription ??
|
||||||
|
parent.bloc.stream.listen((state) {
|
||||||
|
final achievedOneMillionPoints = state.hits % 10 == 0;
|
||||||
|
|
||||||
|
if (achievedOneMillionPoints) {
|
||||||
|
parent.add(
|
||||||
|
ScoringBehavior(
|
||||||
|
points: _points,
|
||||||
|
position: Vector2(0, -60),
|
||||||
|
duration: 2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onRemove() {
|
||||||
|
subscription?.cancel();
|
||||||
|
super.onRemove();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:pinball/game/behaviors/behaviors.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
/// {@template ramp_shot_behavior}
|
||||||
|
/// Increases the score when a [Ball] is shot into the [SpaceshipRamp].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class RampShotBehavior extends Component
|
||||||
|
with ParentIsA<SpaceshipRamp>, HasGameRef<PinballGame> {
|
||||||
|
/// {@macro ramp_shot_behavior}
|
||||||
|
RampShotBehavior({
|
||||||
|
required Points points,
|
||||||
|
}) : _points = points,
|
||||||
|
super();
|
||||||
|
|
||||||
|
/// Creates a [RampShotBehavior].
|
||||||
|
///
|
||||||
|
/// This can be used for testing [RampShotBehavior] in isolation.
|
||||||
|
@visibleForTesting
|
||||||
|
RampShotBehavior.test({
|
||||||
|
required Points points,
|
||||||
|
required this.subscription,
|
||||||
|
}) : _points = points,
|
||||||
|
super();
|
||||||
|
|
||||||
|
final Points _points;
|
||||||
|
|
||||||
|
/// Subscription to [SpaceshipRampState] at [SpaceshipRamp].
|
||||||
|
@visibleForTesting
|
||||||
|
StreamSubscription? subscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onMount() {
|
||||||
|
super.onMount();
|
||||||
|
|
||||||
|
subscription = subscription ??
|
||||||
|
parent.bloc.stream.listen((state) {
|
||||||
|
final achievedOneMillionPoints = state.hits % 10 == 0;
|
||||||
|
|
||||||
|
if (!achievedOneMillionPoints) {
|
||||||
|
gameRef.read<GameBloc>().add(const MultiplierIncreased());
|
||||||
|
|
||||||
|
parent.add(
|
||||||
|
ScoringBehavior(
|
||||||
|
points: _points,
|
||||||
|
position: Vector2(0, -45),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onRemove() {
|
||||||
|
subscription?.cancel();
|
||||||
|
super.onRemove();
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
@ -1,90 +0,0 @@
|
|||||||
// ignore_for_file: avoid_renaming_method_parameters
|
|
||||||
|
|
||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
|
||||||
|
|
||||||
/// {@template layer_entrance_orientation}
|
|
||||||
/// Determines if a layer entrance is oriented [up] or [down] on the board.
|
|
||||||
/// {@endtemplate}
|
|
||||||
enum LayerEntranceOrientation {
|
|
||||||
/// Facing up on the Board.
|
|
||||||
up,
|
|
||||||
|
|
||||||
/// Facing down on the Board.
|
|
||||||
down,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// {@template layer_sensor}
|
|
||||||
/// [BodyComponent] located at the entrance and exit of a [Layer].
|
|
||||||
///
|
|
||||||
/// By default the base [layer] is set to [Layer.board] and the
|
|
||||||
/// [_outsideZIndex] is set to [ZIndexes.ballOnBoard].
|
|
||||||
/// {@endtemplate}
|
|
||||||
abstract class LayerSensor extends BodyComponent
|
|
||||||
with InitialPosition, Layered, ContactCallbacks {
|
|
||||||
/// {@macro layer_sensor}
|
|
||||||
LayerSensor({
|
|
||||||
required Layer insideLayer,
|
|
||||||
Layer? outsideLayer,
|
|
||||||
required int insideZIndex,
|
|
||||||
int? outsideZIndex,
|
|
||||||
required this.orientation,
|
|
||||||
}) : _insideLayer = insideLayer,
|
|
||||||
_outsideLayer = outsideLayer ?? Layer.board,
|
|
||||||
_insideZIndex = insideZIndex,
|
|
||||||
_outsideZIndex = outsideZIndex ?? ZIndexes.ballOnBoard,
|
|
||||||
super(renderBody: false) {
|
|
||||||
layer = Layer.opening;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Layer _insideLayer;
|
|
||||||
final Layer _outsideLayer;
|
|
||||||
final int _insideZIndex;
|
|
||||||
final int _outsideZIndex;
|
|
||||||
|
|
||||||
/// The [Shape] of the [LayerSensor].
|
|
||||||
Shape get shape;
|
|
||||||
|
|
||||||
/// {@macro layer_entrance_orientation}
|
|
||||||
// TODO(ruimiguel): Try to remove the need of [LayerEntranceOrientation] for
|
|
||||||
// collision calculations.
|
|
||||||
final LayerEntranceOrientation orientation;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Body createBody() {
|
|
||||||
final fixtureDef = FixtureDef(
|
|
||||||
shape,
|
|
||||||
isSensor: true,
|
|
||||||
);
|
|
||||||
final bodyDef = BodyDef(
|
|
||||||
position: initialPosition,
|
|
||||||
userData: this,
|
|
||||||
);
|
|
||||||
|
|
||||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void beginContact(Object other, Contact contact) {
|
|
||||||
super.beginContact(other, contact);
|
|
||||||
if (other is! Ball) return;
|
|
||||||
|
|
||||||
if (other.layer != _insideLayer) {
|
|
||||||
final isBallEnteringOpening =
|
|
||||||
(orientation == LayerEntranceOrientation.down &&
|
|
||||||
other.body.linearVelocity.y < 0) ||
|
|
||||||
(orientation == LayerEntranceOrientation.up &&
|
|
||||||
other.body.linearVelocity.y > 0);
|
|
||||||
|
|
||||||
if (isBallEnteringOpening) {
|
|
||||||
other
|
|
||||||
..layer = _insideLayer
|
|
||||||
..zIndex = _insideZIndex;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
other
|
|
||||||
..layer = _outsideLayer
|
|
||||||
..zIndex = _outsideZIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,2 @@
|
|||||||
|
export 'behaviors.dart';
|
||||||
|
export 'layer_filtering_behavior.dart';
|
@ -0,0 +1,31 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
class LayerFilteringBehavior extends ContactBehavior<LayerSensor> {
|
||||||
|
@override
|
||||||
|
void beginContact(Object other, Contact contact) {
|
||||||
|
super.beginContact(other, contact);
|
||||||
|
if (other is! Ball) return;
|
||||||
|
|
||||||
|
if (other.layer != parent.insideLayer) {
|
||||||
|
final isBallEnteringOpening =
|
||||||
|
(parent.orientation == LayerEntranceOrientation.down &&
|
||||||
|
other.body.linearVelocity.y < 0) ||
|
||||||
|
(parent.orientation == LayerEntranceOrientation.up &&
|
||||||
|
other.body.linearVelocity.y > 0);
|
||||||
|
|
||||||
|
if (isBallEnteringOpening) {
|
||||||
|
other
|
||||||
|
..layer = parent.insideLayer
|
||||||
|
..zIndex = parent.insideZIndex;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
other
|
||||||
|
..layer = parent.outsideLayer
|
||||||
|
..zIndex = parent.outsideZIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
// ignore_for_file: avoid_renaming_method_parameters, public_member_api_docs
|
||||||
|
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_components/src/components/layer_sensor/behaviors/layer_filtering_behavior.dart';
|
||||||
|
|
||||||
|
/// {@template layer_entrance_orientation}
|
||||||
|
/// Determines if a layer entrance is oriented [up] or [down] on the board.
|
||||||
|
/// {@endtemplate}
|
||||||
|
enum LayerEntranceOrientation {
|
||||||
|
/// Facing up on the Board.
|
||||||
|
up,
|
||||||
|
|
||||||
|
/// Facing down on the Board.
|
||||||
|
down,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@template layer_sensor}
|
||||||
|
/// [BodyComponent] located at the entrance and exit of a [Layer].
|
||||||
|
///
|
||||||
|
/// By default the base [layer] is set to [Layer.board] and the
|
||||||
|
/// [outsideZIndex] is set to [ZIndexes.ballOnBoard].
|
||||||
|
/// {@endtemplate}
|
||||||
|
abstract class LayerSensor extends BodyComponent with InitialPosition, Layered {
|
||||||
|
/// {@macro layer_sensor}
|
||||||
|
LayerSensor({
|
||||||
|
required this.insideLayer,
|
||||||
|
Layer? outsideLayer,
|
||||||
|
required this.insideZIndex,
|
||||||
|
int? outsideZIndex,
|
||||||
|
required this.orientation,
|
||||||
|
}) : outsideLayer = outsideLayer ?? Layer.board,
|
||||||
|
outsideZIndex = outsideZIndex ?? ZIndexes.ballOnBoard,
|
||||||
|
super(
|
||||||
|
renderBody: false,
|
||||||
|
children: [LayerFilteringBehavior()],
|
||||||
|
) {
|
||||||
|
layer = Layer.opening;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Layer insideLayer;
|
||||||
|
|
||||||
|
final Layer outsideLayer;
|
||||||
|
|
||||||
|
final int insideZIndex;
|
||||||
|
|
||||||
|
final int outsideZIndex;
|
||||||
|
|
||||||
|
/// The [Shape] of the [LayerSensor].
|
||||||
|
Shape get shape;
|
||||||
|
|
||||||
|
/// {@macro layer_entrance_orientation}
|
||||||
|
// TODO(ruimiguel): Try to remove the need of [LayerEntranceOrientation] for
|
||||||
|
// collision calculations.
|
||||||
|
final LayerEntranceOrientation orientation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final fixtureDef = FixtureDef(
|
||||||
|
shape,
|
||||||
|
isSensor: true,
|
||||||
|
);
|
||||||
|
final bodyDef = BodyDef(position: initialPosition);
|
||||||
|
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export 'skill_shot_ball_contact_behavior.dart';
|
||||||
|
export 'skill_shot_blinking_behavior.dart';
|
@ -0,0 +1,16 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
class SkillShotBallContactBehavior extends ContactBehavior<SkillShot> {
|
||||||
|
@override
|
||||||
|
void beginContact(Object other, Contact contact) {
|
||||||
|
super.beginContact(other, contact);
|
||||||
|
if (other is! Ball) return;
|
||||||
|
parent.bloc.onBallContacted();
|
||||||
|
parent.firstChild<SpriteAnimationComponent>()?.playing = true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
/// {@template skill_shot_blinking_behavior}
|
||||||
|
/// Makes a [SkillShot] blink between [SkillShotSpriteState.lit] and
|
||||||
|
/// [SkillShotSpriteState.dimmed] for a set amount of blinks.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class SkillShotBlinkingBehavior extends TimerComponent
|
||||||
|
with ParentIsA<SkillShot> {
|
||||||
|
/// {@macro skill_shot_blinking_behavior}
|
||||||
|
SkillShotBlinkingBehavior() : super(period: 0.15);
|
||||||
|
|
||||||
|
final _maxBlinks = 4;
|
||||||
|
int _blinks = 0;
|
||||||
|
|
||||||
|
void _onNewState(SkillShotState state) {
|
||||||
|
if (state.isBlinking) {
|
||||||
|
timer
|
||||||
|
..reset()
|
||||||
|
..start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
timer.stop();
|
||||||
|
parent.bloc.stream.listen(_onNewState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTick() {
|
||||||
|
super.onTick();
|
||||||
|
if (_blinks != _maxBlinks * 2) {
|
||||||
|
parent.bloc.switched();
|
||||||
|
_blinks++;
|
||||||
|
} else {
|
||||||
|
_blinks = 0;
|
||||||
|
timer.stop();
|
||||||
|
parent.bloc.onBlinkingFinished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
part 'skill_shot_state.dart';
|
||||||
|
|
||||||
|
class SkillShotCubit extends Cubit<SkillShotState> {
|
||||||
|
SkillShotCubit() : super(const SkillShotState.initial());
|
||||||
|
|
||||||
|
void onBallContacted() {
|
||||||
|
emit(
|
||||||
|
const SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.lit,
|
||||||
|
isBlinking: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void switched() {
|
||||||
|
switch (state.spriteState) {
|
||||||
|
case SkillShotSpriteState.lit:
|
||||||
|
emit(state.copyWith(spriteState: SkillShotSpriteState.dimmed));
|
||||||
|
break;
|
||||||
|
case SkillShotSpriteState.dimmed:
|
||||||
|
emit(state.copyWith(spriteState: SkillShotSpriteState.lit));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onBlinkingFinished() {
|
||||||
|
emit(
|
||||||
|
const SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.dimmed,
|
||||||
|
isBlinking: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
part of 'skill_shot_cubit.dart';
|
||||||
|
|
||||||
|
enum SkillShotSpriteState {
|
||||||
|
lit,
|
||||||
|
dimmed,
|
||||||
|
}
|
||||||
|
|
||||||
|
class SkillShotState extends Equatable {
|
||||||
|
const SkillShotState({
|
||||||
|
required this.spriteState,
|
||||||
|
required this.isBlinking,
|
||||||
|
});
|
||||||
|
|
||||||
|
const SkillShotState.initial()
|
||||||
|
: this(
|
||||||
|
spriteState: SkillShotSpriteState.dimmed,
|
||||||
|
isBlinking: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final SkillShotSpriteState spriteState;
|
||||||
|
|
||||||
|
final bool isBlinking;
|
||||||
|
|
||||||
|
SkillShotState copyWith({
|
||||||
|
SkillShotSpriteState? spriteState,
|
||||||
|
bool? isBlinking,
|
||||||
|
}) =>
|
||||||
|
SkillShotState(
|
||||||
|
spriteState: spriteState ?? this.spriteState,
|
||||||
|
isBlinking: isBlinking ?? this.isBlinking,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [spriteState, isBlinking];
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_components/src/components/skill_shot/behaviors/behaviors.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
export 'cubit/skill_shot_cubit.dart';
|
||||||
|
|
||||||
|
/// {@template skill_shot}
|
||||||
|
/// Rollover awarding extra points.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class SkillShot extends BodyComponent with ZIndex {
|
||||||
|
/// {@macro skill_shot}
|
||||||
|
SkillShot({Iterable<Component>? children})
|
||||||
|
: this._(
|
||||||
|
children: children,
|
||||||
|
bloc: SkillShotCubit(),
|
||||||
|
);
|
||||||
|
|
||||||
|
SkillShot._({
|
||||||
|
Iterable<Component>? children,
|
||||||
|
required this.bloc,
|
||||||
|
}) : super(
|
||||||
|
renderBody: false,
|
||||||
|
children: [
|
||||||
|
SkillShotBallContactBehavior(),
|
||||||
|
SkillShotBlinkingBehavior(),
|
||||||
|
_RolloverDecalSpriteComponent(),
|
||||||
|
PinSpriteAnimationComponent(),
|
||||||
|
_TextDecalSpriteGroupComponent(state: bloc.state.spriteState),
|
||||||
|
...?children,
|
||||||
|
],
|
||||||
|
) {
|
||||||
|
zIndex = ZIndexes.decal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [SkillShot] without any children.
|
||||||
|
///
|
||||||
|
/// This can be used for testing [SkillShot]'s behaviors in isolation.
|
||||||
|
// TODO(alestiago): Refactor injecting bloc once the following is merged:
|
||||||
|
// https://github.com/flame-engine/flame/pull/1538
|
||||||
|
@visibleForTesting
|
||||||
|
SkillShot.test({
|
||||||
|
required this.bloc,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO(alestiago): Consider refactoring once the following is merged:
|
||||||
|
// https://github.com/flame-engine/flame/pull/1538
|
||||||
|
// ignore: public_member_api_docs
|
||||||
|
final SkillShotCubit bloc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onRemove() {
|
||||||
|
bloc.close();
|
||||||
|
super.onRemove();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final shape = PolygonShape()
|
||||||
|
..setAsBox(
|
||||||
|
0.1,
|
||||||
|
3.7,
|
||||||
|
Vector2(-31.9, 9.1),
|
||||||
|
0.11,
|
||||||
|
);
|
||||||
|
final fixtureDef = FixtureDef(shape, isSensor: true);
|
||||||
|
return world.createBody(BodyDef())..createFixture(fixtureDef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RolloverDecalSpriteComponent extends SpriteComponent with HasGameRef {
|
||||||
|
_RolloverDecalSpriteComponent()
|
||||||
|
: super(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(-31.9, 9.1),
|
||||||
|
angle: 0.11,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
final sprite = Sprite(
|
||||||
|
gameRef.images.fromCache(
|
||||||
|
Assets.images.skillShot.decal.keyName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.sprite = sprite;
|
||||||
|
size = sprite.originalSize / 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@template pin_sprite_animation_component}
|
||||||
|
/// Animation for pin in [SkillShot] rollover.
|
||||||
|
/// {@endtemplate}
|
||||||
|
@visibleForTesting
|
||||||
|
class PinSpriteAnimationComponent extends SpriteAnimationComponent
|
||||||
|
with HasGameRef {
|
||||||
|
/// {@macro pin_sprite_animation_component}
|
||||||
|
PinSpriteAnimationComponent()
|
||||||
|
: super(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(-31.9, 9.1),
|
||||||
|
angle: 0,
|
||||||
|
playing: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
final spriteSheet = gameRef.images.fromCache(
|
||||||
|
Assets.images.skillShot.pin.keyName,
|
||||||
|
);
|
||||||
|
|
||||||
|
const amountPerRow = 3;
|
||||||
|
const amountPerColumn = 1;
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
)..onComplete = () {
|
||||||
|
animation?.reset();
|
||||||
|
playing = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TextDecalSpriteGroupComponent
|
||||||
|
extends SpriteGroupComponent<SkillShotSpriteState>
|
||||||
|
with HasGameRef, ParentIsA<SkillShot> {
|
||||||
|
_TextDecalSpriteGroupComponent({
|
||||||
|
required SkillShotSpriteState state,
|
||||||
|
}) : super(
|
||||||
|
anchor: Anchor.center,
|
||||||
|
position: Vector2(-35.55, 3.59),
|
||||||
|
current: state,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
parent.bloc.stream.listen((state) => current = state.spriteState);
|
||||||
|
|
||||||
|
final sprites = {
|
||||||
|
SkillShotSpriteState.lit: Sprite(
|
||||||
|
gameRef.images.fromCache(Assets.images.skillShot.lit.keyName),
|
||||||
|
),
|
||||||
|
SkillShotSpriteState.dimmed: Sprite(
|
||||||
|
gameRef.images.fromCache(Assets.images.skillShot.dimmed.keyName),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
this.sprites = sprites;
|
||||||
|
size = sprites[current]!.originalSize / 10;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export 'ramp_ball_ascending_contact_behavior.dart';
|
@ -0,0 +1,24 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
/// {@template ramp_ball_ascending_contact_behavior}
|
||||||
|
/// Detects an ascending [Ball] that enters into the [SpaceshipRamp].
|
||||||
|
///
|
||||||
|
/// The [Ball] can hit with sensor to recognize if a [Ball] goes into or out of
|
||||||
|
/// the [SpaceshipRamp].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class RampBallAscendingContactBehavior
|
||||||
|
extends ContactBehavior<RampScoringSensor> {
|
||||||
|
@override
|
||||||
|
void beginContact(Object other, Contact contact) {
|
||||||
|
super.beginContact(other, contact);
|
||||||
|
if (other is! Ball) return;
|
||||||
|
|
||||||
|
if (other.body.linearVelocity.y < 0) {
|
||||||
|
parent.parent.bloc.onAscendingBallEntered();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
part 'spaceship_ramp_state.dart';
|
||||||
|
|
||||||
|
class SpaceshipRampCubit extends Cubit<SpaceshipRampState> {
|
||||||
|
SpaceshipRampCubit() : super(const SpaceshipRampState.initial());
|
||||||
|
|
||||||
|
void onAscendingBallEntered() {
|
||||||
|
emit(
|
||||||
|
state.copyWith(hits: state.hits + 1),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
part of 'spaceship_ramp_cubit.dart';
|
||||||
|
|
||||||
|
class SpaceshipRampState extends Equatable {
|
||||||
|
const SpaceshipRampState({
|
||||||
|
required this.hits,
|
||||||
|
}) : assert(hits >= 0, "Hits can't be negative");
|
||||||
|
|
||||||
|
const SpaceshipRampState.initial() : this(hits: 0);
|
||||||
|
|
||||||
|
final int hits;
|
||||||
|
|
||||||
|
SpaceshipRampState copyWith({
|
||||||
|
int? hits,
|
||||||
|
}) {
|
||||||
|
return SpaceshipRampState(
|
||||||
|
hits: hits ?? this.hits,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [hits];
|
||||||
|
}
|
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 27 KiB |
@ -0,0 +1,136 @@
|
|||||||
|
// 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_components/src/components/layer_sensor/behaviors/behaviors.dart';
|
||||||
|
|
||||||
|
import '../../../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
class _TestLayerSensor extends LayerSensor {
|
||||||
|
_TestLayerSensor({
|
||||||
|
required LayerEntranceOrientation orientation,
|
||||||
|
required int insideZIndex,
|
||||||
|
required Layer insideLayer,
|
||||||
|
}) : super(
|
||||||
|
insideLayer: insideLayer,
|
||||||
|
insideZIndex: insideZIndex,
|
||||||
|
orientation: orientation,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Shape get shape => PolygonShape()..setAsBoxXY(1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MockBall extends Mock implements Ball {}
|
||||||
|
|
||||||
|
class _MockBody extends Mock implements Body {}
|
||||||
|
|
||||||
|
class _MockContact extends Mock implements Contact {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(TestGame.new);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'LayerSensorBehavior',
|
||||||
|
() {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(
|
||||||
|
LayerFilteringBehavior(),
|
||||||
|
isA<LayerFilteringBehavior>(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'loads',
|
||||||
|
(game) async {
|
||||||
|
final behavior = LayerFilteringBehavior();
|
||||||
|
final parent = _TestLayerSensor(
|
||||||
|
orientation: LayerEntranceOrientation.down,
|
||||||
|
insideZIndex: 1,
|
||||||
|
insideLayer: Layer.spaceshipEntranceRamp,
|
||||||
|
);
|
||||||
|
|
||||||
|
await parent.add(behavior);
|
||||||
|
await game.ensureAdd(parent);
|
||||||
|
|
||||||
|
expect(game.contains(parent), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('beginContact', () {
|
||||||
|
late Ball ball;
|
||||||
|
late Body body;
|
||||||
|
late int insideZIndex;
|
||||||
|
late Layer insideLayer;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
ball = _MockBall();
|
||||||
|
body = _MockBody();
|
||||||
|
insideZIndex = 1;
|
||||||
|
insideLayer = Layer.spaceshipEntranceRamp;
|
||||||
|
|
||||||
|
when(() => ball.body).thenReturn(body);
|
||||||
|
when(() => ball.layer).thenReturn(Layer.board);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'changes ball layer and zIndex '
|
||||||
|
'when a ball enters and exits a downward oriented LayerSensor',
|
||||||
|
(game) async {
|
||||||
|
final parent = _TestLayerSensor(
|
||||||
|
orientation: LayerEntranceOrientation.down,
|
||||||
|
insideZIndex: 1,
|
||||||
|
insideLayer: insideLayer,
|
||||||
|
)..initialPosition = Vector2(0, 10);
|
||||||
|
final behavior = LayerFilteringBehavior();
|
||||||
|
|
||||||
|
await parent.add(behavior);
|
||||||
|
await game.ensureAdd(parent);
|
||||||
|
|
||||||
|
when(() => body.linearVelocity).thenReturn(Vector2(0, -1));
|
||||||
|
|
||||||
|
behavior.beginContact(ball, _MockContact());
|
||||||
|
verify(() => ball.layer = insideLayer).called(1);
|
||||||
|
verify(() => ball.zIndex = insideZIndex).called(1);
|
||||||
|
|
||||||
|
when(() => ball.layer).thenReturn(insideLayer);
|
||||||
|
|
||||||
|
behavior.beginContact(ball, _MockContact());
|
||||||
|
verify(() => ball.layer = Layer.board);
|
||||||
|
verify(() => ball.zIndex = ZIndexes.ballOnBoard).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'changes ball layer and zIndex '
|
||||||
|
'when a ball enters and exits an upward oriented LayerSensor',
|
||||||
|
(game) async {
|
||||||
|
final parent = _TestLayerSensor(
|
||||||
|
orientation: LayerEntranceOrientation.up,
|
||||||
|
insideZIndex: 1,
|
||||||
|
insideLayer: insideLayer,
|
||||||
|
)..initialPosition = Vector2(0, 10);
|
||||||
|
final behavior = LayerFilteringBehavior();
|
||||||
|
|
||||||
|
await parent.add(behavior);
|
||||||
|
await game.ensureAdd(parent);
|
||||||
|
|
||||||
|
when(() => body.linearVelocity).thenReturn(Vector2(0, 1));
|
||||||
|
|
||||||
|
behavior.beginContact(ball, _MockContact());
|
||||||
|
verify(() => ball.layer = insideLayer).called(1);
|
||||||
|
verify(() => ball.zIndex = 1).called(1);
|
||||||
|
|
||||||
|
when(() => ball.layer).thenReturn(insideLayer);
|
||||||
|
|
||||||
|
behavior.beginContact(ball, _MockContact());
|
||||||
|
verify(() => ball.layer = Layer.board);
|
||||||
|
verify(() => ball.zIndex = ZIndexes.ballOnBoard).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
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_components/src/components/skill_shot/behaviors/behaviors.dart';
|
||||||
|
|
||||||
|
import '../../../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
class _MockBall extends Mock implements Ball {}
|
||||||
|
|
||||||
|
class _MockContact extends Mock implements Contact {}
|
||||||
|
|
||||||
|
class _MockSkillShotCubit extends Mock implements SkillShotCubit {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(TestGame.new);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'SkillShotBallContactBehavior',
|
||||||
|
() {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(
|
||||||
|
SkillShotBallContactBehavior(),
|
||||||
|
isA<SkillShotBallContactBehavior>(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'beginContact animates pin and calls onBallContacted '
|
||||||
|
'when contacts with a ball',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
await game.images.load(Assets.images.skillShot.pin.keyName);
|
||||||
|
final behavior = SkillShotBallContactBehavior();
|
||||||
|
final bloc = _MockSkillShotCubit();
|
||||||
|
whenListen(
|
||||||
|
bloc,
|
||||||
|
const Stream<SkillShotState>.empty(),
|
||||||
|
initialState: const SkillShotState.initial(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final skillShot = SkillShot.test(bloc: bloc);
|
||||||
|
await skillShot.addAll([behavior, PinSpriteAnimationComponent()]);
|
||||||
|
await game.ensureAdd(skillShot);
|
||||||
|
|
||||||
|
behavior.beginContact(_MockBall(), _MockContact());
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
skillShot.firstChild<PinSpriteAnimationComponent>()!.playing,
|
||||||
|
isTrue,
|
||||||
|
);
|
||||||
|
verify(skillShot.bloc.onBallContacted).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
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:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_components/src/components/skill_shot/behaviors/behaviors.dart';
|
||||||
|
|
||||||
|
import '../../../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
class _MockSkillShotCubit extends Mock implements SkillShotCubit {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(TestGame.new);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'SkillShotBlinkingBehavior',
|
||||||
|
() {
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'calls switched after 0.15 seconds when isBlinking and lit',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final behavior = SkillShotBlinkingBehavior();
|
||||||
|
final bloc = _MockSkillShotCubit();
|
||||||
|
final streamController = StreamController<SkillShotState>();
|
||||||
|
whenListen(
|
||||||
|
bloc,
|
||||||
|
streamController.stream,
|
||||||
|
initialState: const SkillShotState.initial(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final skillShot = SkillShot.test(bloc: bloc);
|
||||||
|
await skillShot.add(behavior);
|
||||||
|
await game.ensureAdd(skillShot);
|
||||||
|
|
||||||
|
streamController.add(
|
||||||
|
const SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.lit,
|
||||||
|
isBlinking: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
game.update(0.15);
|
||||||
|
|
||||||
|
await streamController.close();
|
||||||
|
verify(bloc.switched).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'calls switched after 0.15 seconds when isBlinking and dimmed',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final behavior = SkillShotBlinkingBehavior();
|
||||||
|
final bloc = _MockSkillShotCubit();
|
||||||
|
final streamController = StreamController<SkillShotState>();
|
||||||
|
whenListen(
|
||||||
|
bloc,
|
||||||
|
streamController.stream,
|
||||||
|
initialState: const SkillShotState.initial(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final skillShot = SkillShot.test(bloc: bloc);
|
||||||
|
await skillShot.add(behavior);
|
||||||
|
await game.ensureAdd(skillShot);
|
||||||
|
|
||||||
|
streamController.add(
|
||||||
|
const SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.dimmed,
|
||||||
|
isBlinking: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
game.update(0.15);
|
||||||
|
|
||||||
|
await streamController.close();
|
||||||
|
verify(bloc.switched).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'calls onBlinkingFinished after all blinks complete',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final behavior = SkillShotBlinkingBehavior();
|
||||||
|
final bloc = _MockSkillShotCubit();
|
||||||
|
final streamController = StreamController<SkillShotState>();
|
||||||
|
whenListen(
|
||||||
|
bloc,
|
||||||
|
streamController.stream,
|
||||||
|
initialState: const SkillShotState.initial(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final skillShot = SkillShot.test(bloc: bloc);
|
||||||
|
await skillShot.add(behavior);
|
||||||
|
await game.ensureAdd(skillShot);
|
||||||
|
|
||||||
|
for (var i = 0; i <= 8; i++) {
|
||||||
|
if (i.isEven) {
|
||||||
|
streamController.add(
|
||||||
|
const SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.lit,
|
||||||
|
isBlinking: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
streamController.add(
|
||||||
|
const SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.dimmed,
|
||||||
|
isBlinking: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await tester.pump();
|
||||||
|
game.update(0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
await streamController.close();
|
||||||
|
verify(bloc.onBlinkingFinished).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
// 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(
|
||||||
|
'SkillShotCubit',
|
||||||
|
() {
|
||||||
|
blocTest<SkillShotCubit, SkillShotState>(
|
||||||
|
'onBallContacted emits lit and true',
|
||||||
|
build: SkillShotCubit.new,
|
||||||
|
act: (bloc) => bloc.onBallContacted(),
|
||||||
|
expect: () => [
|
||||||
|
SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.lit,
|
||||||
|
isBlinking: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<SkillShotCubit, SkillShotState>(
|
||||||
|
'switched emits lit when dimmed',
|
||||||
|
build: SkillShotCubit.new,
|
||||||
|
act: (bloc) => bloc.switched(),
|
||||||
|
expect: () => [
|
||||||
|
isA<SkillShotState>().having(
|
||||||
|
(state) => state.spriteState,
|
||||||
|
'spriteState',
|
||||||
|
SkillShotSpriteState.lit,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<SkillShotCubit, SkillShotState>(
|
||||||
|
'switched emits dimmed when lit',
|
||||||
|
build: SkillShotCubit.new,
|
||||||
|
seed: () => SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.lit,
|
||||||
|
isBlinking: false,
|
||||||
|
),
|
||||||
|
act: (bloc) => bloc.switched(),
|
||||||
|
expect: () => [
|
||||||
|
isA<SkillShotState>().having(
|
||||||
|
(state) => state.spriteState,
|
||||||
|
'spriteState',
|
||||||
|
SkillShotSpriteState.dimmed,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<SkillShotCubit, SkillShotState>(
|
||||||
|
'onBlinkingFinished emits dimmed and false',
|
||||||
|
build: SkillShotCubit.new,
|
||||||
|
act: (bloc) => bloc.onBlinkingFinished(),
|
||||||
|
expect: () => [
|
||||||
|
SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.dimmed,
|
||||||
|
isBlinking: false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('SkillShotState', () {
|
||||||
|
test('supports value equality', () {
|
||||||
|
expect(
|
||||||
|
SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.lit,
|
||||||
|
isBlinking: true,
|
||||||
|
),
|
||||||
|
equals(
|
||||||
|
const SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.lit,
|
||||||
|
isBlinking: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('constructor', () {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(
|
||||||
|
const SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.lit,
|
||||||
|
isBlinking: true,
|
||||||
|
),
|
||||||
|
isNotNull,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('initial is idle with mouth closed', () {
|
||||||
|
const initialState = SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.dimmed,
|
||||||
|
isBlinking: false,
|
||||||
|
);
|
||||||
|
expect(SkillShotState.initial(), equals(initialState));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('copyWith', () {
|
||||||
|
test(
|
||||||
|
'copies correctly '
|
||||||
|
'when no argument specified',
|
||||||
|
() {
|
||||||
|
const chromeDinoState = SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.lit,
|
||||||
|
isBlinking: true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
chromeDinoState.copyWith(),
|
||||||
|
equals(chromeDinoState),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'copies correctly '
|
||||||
|
'when all arguments specified',
|
||||||
|
() {
|
||||||
|
const chromeDinoState = SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.lit,
|
||||||
|
isBlinking: true,
|
||||||
|
);
|
||||||
|
final otherSkillShotState = SkillShotState(
|
||||||
|
spriteState: SkillShotSpriteState.dimmed,
|
||||||
|
isBlinking: false,
|
||||||
|
);
|
||||||
|
expect(chromeDinoState, isNot(equals(otherSkillShotState)));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
chromeDinoState.copyWith(
|
||||||
|
spriteState: SkillShotSpriteState.dimmed,
|
||||||
|
isBlinking: false,
|
||||||
|
),
|
||||||
|
equals(otherSkillShotState),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
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 'package:pinball_components/src/components/skill_shot/behaviors/behaviors.dart';
|
||||||
|
|
||||||
|
import '../../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
class _MockSkillShotCubit extends Mock implements SkillShotCubit {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final assets = [
|
||||||
|
Assets.images.skillShot.decal.keyName,
|
||||||
|
Assets.images.skillShot.pin.keyName,
|
||||||
|
Assets.images.skillShot.lit.keyName,
|
||||||
|
Assets.images.skillShot.dimmed.keyName,
|
||||||
|
];
|
||||||
|
final flameTester = FlameTester(() => TestGame(assets));
|
||||||
|
|
||||||
|
group('SkillShot', () {
|
||||||
|
flameTester.test('loads correctly', (game) async {
|
||||||
|
final skillShot = SkillShot();
|
||||||
|
await game.ensureAdd(skillShot);
|
||||||
|
expect(game.contains(skillShot), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO(alestiago): Consider refactoring once the following is merged:
|
||||||
|
// https://github.com/flame-engine/flame/pull/1538
|
||||||
|
// ignore: public_member_api_docs
|
||||||
|
flameTester.test('closes bloc when removed', (game) async {
|
||||||
|
final bloc = _MockSkillShotCubit();
|
||||||
|
whenListen(
|
||||||
|
bloc,
|
||||||
|
const Stream<SkillShotState>.empty(),
|
||||||
|
initialState: const SkillShotState.initial(),
|
||||||
|
);
|
||||||
|
when(bloc.close).thenAnswer((_) async {});
|
||||||
|
final skillShot = SkillShot.test(bloc: bloc);
|
||||||
|
|
||||||
|
await game.ensureAdd(skillShot);
|
||||||
|
game.remove(skillShot);
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
verify(bloc.close).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('adds', () {
|
||||||
|
flameTester.test('new children', (game) async {
|
||||||
|
final component = Component();
|
||||||
|
final skillShot = SkillShot(
|
||||||
|
children: [component],
|
||||||
|
);
|
||||||
|
await game.ensureAdd(skillShot);
|
||||||
|
expect(skillShot.children, contains(component));
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('a SkillShotBallContactBehavior', (game) async {
|
||||||
|
final skillShot = SkillShot();
|
||||||
|
await game.ensureAdd(skillShot);
|
||||||
|
expect(
|
||||||
|
skillShot.children.whereType<SkillShotBallContactBehavior>().single,
|
||||||
|
isNotNull,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('a SkillShotBlinkingBehavior', (game) async {
|
||||||
|
final skillShot = SkillShot();
|
||||||
|
await game.ensureAdd(skillShot);
|
||||||
|
expect(
|
||||||
|
skillShot.children.whereType<SkillShotBlinkingBehavior>().single,
|
||||||
|
isNotNull,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'pin stops animating after animation completes',
|
||||||
|
(game) async {
|
||||||
|
final skillShot = SkillShot();
|
||||||
|
await game.ensureAdd(skillShot);
|
||||||
|
|
||||||
|
final pinSpriteAnimationComponent =
|
||||||
|
skillShot.firstChild<PinSpriteAnimationComponent>()!;
|
||||||
|
|
||||||
|
pinSpriteAnimationComponent.playing = true;
|
||||||
|
game.update(
|
||||||
|
pinSpriteAnimationComponent.animation!.totalDuration() + 0.1,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(pinSpriteAnimationComponent.playing, isFalse);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
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_components/src/components/spaceship_ramp/behavior/behavior.dart';
|
||||||
|
|
||||||
|
import '../../../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
class _MockSpaceshipRampCubit extends Mock implements SpaceshipRampCubit {}
|
||||||
|
|
||||||
|
class _MockBall extends Mock implements Ball {}
|
||||||
|
|
||||||
|
class _MockBody extends Mock implements Body {}
|
||||||
|
|
||||||
|
class _MockContact extends Mock implements Contact {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final assets = [
|
||||||
|
Assets.images.android.ramp.boardOpening.keyName,
|
||||||
|
Assets.images.android.ramp.railingForeground.keyName,
|
||||||
|
Assets.images.android.ramp.railingBackground.keyName,
|
||||||
|
Assets.images.android.ramp.main.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.inactive.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active1.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active2.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active3.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active4.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active5.keyName,
|
||||||
|
];
|
||||||
|
|
||||||
|
final flameTester = FlameTester(() => TestGame(assets));
|
||||||
|
|
||||||
|
group(
|
||||||
|
'RampBallAscendingContactBehavior',
|
||||||
|
() {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(
|
||||||
|
RampBallAscendingContactBehavior(),
|
||||||
|
isA<RampBallAscendingContactBehavior>(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('beginContact', () {
|
||||||
|
late Ball ball;
|
||||||
|
late Body body;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
ball = _MockBall();
|
||||||
|
body = _MockBody();
|
||||||
|
|
||||||
|
when(() => ball.body).thenReturn(body);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
"calls 'onAscendingBallEntered' when a ball enters into the ramp",
|
||||||
|
(game) async {
|
||||||
|
final behavior = RampBallAscendingContactBehavior();
|
||||||
|
final bloc = _MockSpaceshipRampCubit();
|
||||||
|
whenListen(
|
||||||
|
bloc,
|
||||||
|
const Stream<SpaceshipRampState>.empty(),
|
||||||
|
initialState: const SpaceshipRampState.initial(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final rampSensor = RampScoringSensor.test();
|
||||||
|
final spaceshipRamp = SpaceshipRamp.test(
|
||||||
|
bloc: bloc,
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => body.linearVelocity).thenReturn(Vector2(0, -1));
|
||||||
|
|
||||||
|
await spaceshipRamp.add(rampSensor);
|
||||||
|
await game.ensureAddAll([spaceshipRamp, ball]);
|
||||||
|
await rampSensor.add(behavior);
|
||||||
|
|
||||||
|
behavior.beginContact(ball, _MockContact());
|
||||||
|
|
||||||
|
verify(bloc.onAscendingBallEntered).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
"doesn't call 'onAscendingBallEntered' when a ball goes out the ramp",
|
||||||
|
(game) async {
|
||||||
|
final behavior = RampBallAscendingContactBehavior();
|
||||||
|
final bloc = _MockSpaceshipRampCubit();
|
||||||
|
whenListen(
|
||||||
|
bloc,
|
||||||
|
const Stream<SpaceshipRampState>.empty(),
|
||||||
|
initialState: const SpaceshipRampState.initial(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final rampSensor = RampScoringSensor.test();
|
||||||
|
final spaceshipRamp = SpaceshipRamp.test(
|
||||||
|
bloc: bloc,
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => body.linearVelocity).thenReturn(Vector2(0, 1));
|
||||||
|
|
||||||
|
await spaceshipRamp.add(rampSensor);
|
||||||
|
await game.ensureAddAll([spaceshipRamp, ball]);
|
||||||
|
await rampSensor.add(behavior);
|
||||||
|
|
||||||
|
behavior.beginContact(ball, _MockContact());
|
||||||
|
|
||||||
|
verifyNever(bloc.onAscendingBallEntered);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
// 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('SpaceshipRampCubit', () {
|
||||||
|
group('onAscendingBallEntered', () {
|
||||||
|
blocTest<SpaceshipRampCubit, SpaceshipRampState>(
|
||||||
|
'emits hits incremented and arrow goes to the next value',
|
||||||
|
build: SpaceshipRampCubit.new,
|
||||||
|
act: (bloc) => bloc
|
||||||
|
..onAscendingBallEntered()
|
||||||
|
..onAscendingBallEntered()
|
||||||
|
..onAscendingBallEntered(),
|
||||||
|
expect: () => [
|
||||||
|
SpaceshipRampState(hits: 1),
|
||||||
|
SpaceshipRampState(hits: 2),
|
||||||
|
SpaceshipRampState(hits: 3),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_components/src/components/components.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('SpaceshipRampState', () {
|
||||||
|
test('supports value equality', () {
|
||||||
|
expect(
|
||||||
|
SpaceshipRampState(hits: 0),
|
||||||
|
equals(
|
||||||
|
SpaceshipRampState(hits: 0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('constructor', () {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(
|
||||||
|
SpaceshipRampState(hits: 0),
|
||||||
|
isNotNull,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'throws AssertionError '
|
||||||
|
'when hits is negative',
|
||||||
|
() {
|
||||||
|
expect(
|
||||||
|
() => SpaceshipRampState(hits: -1),
|
||||||
|
throwsAssertionError,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('copyWith', () {
|
||||||
|
test(
|
||||||
|
'throws AssertionError '
|
||||||
|
'when hits is decreased',
|
||||||
|
() {
|
||||||
|
const rampState = SpaceshipRampState(hits: 0);
|
||||||
|
expect(
|
||||||
|
() => rampState.copyWith(hits: rampState.hits - 1),
|
||||||
|
throwsAssertionError,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'copies correctly '
|
||||||
|
'when no argument specified',
|
||||||
|
() {
|
||||||
|
const rampState = SpaceshipRampState(hits: 0);
|
||||||
|
expect(
|
||||||
|
rampState.copyWith(),
|
||||||
|
equals(rampState),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'copies correctly '
|
||||||
|
'when all arguments specified',
|
||||||
|
() {
|
||||||
|
const rampState = SpaceshipRampState(hits: 0);
|
||||||
|
final otherRampState = SpaceshipRampState(hits: rampState.hits + 1);
|
||||||
|
expect(rampState, isNot(equals(otherRampState)));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
rampState.copyWith(hits: rampState.hits + 1),
|
||||||
|
equals(otherRampState),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export 'canvas_component.dart';
|
||||||
|
export 'z_canvas_component.dart';
|
@ -0,0 +1,47 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:pinball_flame/src/canvas/canvas_wrapper.dart';
|
||||||
|
|
||||||
|
/// Called right before [Canvas.drawImageRect] is called.
|
||||||
|
///
|
||||||
|
/// This is useful since [Sprite.render] uses [Canvas.drawImageRect] to draw
|
||||||
|
/// the [Sprite].
|
||||||
|
typedef PaintFunction = void Function(Paint);
|
||||||
|
|
||||||
|
/// {@template canvas_component}
|
||||||
|
/// Allows listening before the rendering of [Sprite]s.
|
||||||
|
///
|
||||||
|
/// The existance of this class is to hack around the fact that Flame doesn't
|
||||||
|
/// provide a global way to modify the default [Paint] before rendering a
|
||||||
|
/// [Sprite].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class CanvasComponent extends Component {
|
||||||
|
/// {@macro canvas_component}
|
||||||
|
CanvasComponent({
|
||||||
|
PaintFunction? onSpritePainted,
|
||||||
|
Iterable<Component>? children,
|
||||||
|
}) : _canvas = _Canvas(onSpritePainted: onSpritePainted),
|
||||||
|
super(children: children);
|
||||||
|
|
||||||
|
final _Canvas _canvas;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void renderTree(Canvas canvas) {
|
||||||
|
_canvas.canvas = canvas;
|
||||||
|
super.renderTree(_canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Canvas extends CanvasWrapper {
|
||||||
|
_Canvas({PaintFunction? onSpritePainted})
|
||||||
|
: _onSpritePainted = onSpritePainted;
|
||||||
|
|
||||||
|
final PaintFunction? _onSpritePainted;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawImageRect(Image image, Rect src, Rect dst, Paint paint) {
|
||||||
|
_onSpritePainted?.call(paint);
|
||||||
|
super.drawImageRect(image, src, dst, paint);
|
||||||
|
}
|
||||||
|
}
|
@ -1,85 +1,11 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flame/components.dart';
|
class CanvasWrapper implements Canvas {
|
||||||
|
|
||||||
/// {@template z_canvas_component}
|
|
||||||
/// Draws [ZIndex] components after the all non-[ZIndex] components have been
|
|
||||||
/// drawn.
|
|
||||||
/// {@endtemplate}
|
|
||||||
class ZCanvasComponent extends Component {
|
|
||||||
/// {@macro z_canvas_component}
|
|
||||||
ZCanvasComponent({
|
|
||||||
Iterable<Component>? children,
|
|
||||||
}) : _zCanvas = ZCanvas(),
|
|
||||||
super(children: children);
|
|
||||||
|
|
||||||
final ZCanvas _zCanvas;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void renderTree(Canvas canvas) {
|
|
||||||
_zCanvas.canvas = canvas;
|
|
||||||
super.renderTree(_zCanvas);
|
|
||||||
_zCanvas.render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply to any [Component] that will be rendered according to a
|
|
||||||
/// [ZIndex.zIndex].
|
|
||||||
///
|
|
||||||
/// [ZIndex] components must be descendants of a [ZCanvasComponent].
|
|
||||||
///
|
|
||||||
/// {@macro z_canvas.render}
|
|
||||||
mixin ZIndex on Component {
|
|
||||||
/// The z-index of this component.
|
|
||||||
///
|
|
||||||
/// The higher the value, the later the component will be drawn. Hence,
|
|
||||||
/// rendering in front of [Component]s with lower [zIndex] values.
|
|
||||||
int zIndex = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void renderTree(
|
|
||||||
Canvas canvas,
|
|
||||||
) {
|
|
||||||
if (canvas is ZCanvas) {
|
|
||||||
canvas.buffer(this);
|
|
||||||
} else {
|
|
||||||
super.renderTree(canvas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The [ZCanvas] allows to postpone the rendering of [ZIndex] components.
|
|
||||||
///
|
|
||||||
/// You should not use this class directly.
|
|
||||||
class ZCanvas implements Canvas {
|
|
||||||
/// The [Canvas] to render to.
|
|
||||||
///
|
|
||||||
/// This is set by [ZCanvasComponent] when rendering.
|
|
||||||
late Canvas canvas;
|
late Canvas canvas;
|
||||||
|
|
||||||
final List<ZIndex> _zBuffer = [];
|
|
||||||
|
|
||||||
/// Postpones the rendering of [ZIndex] component and its children.
|
|
||||||
void buffer(ZIndex component) => _zBuffer.add(component);
|
|
||||||
|
|
||||||
/// Renders all [ZIndex] components and their children.
|
|
||||||
///
|
|
||||||
/// {@template z_canvas.render}
|
|
||||||
/// The rendering order is defined by the parent [ZIndex]. The children of
|
|
||||||
/// the same parent are rendered in the order they were added.
|
|
||||||
///
|
|
||||||
/// If two [Component]s ever overlap each other, and have the same
|
|
||||||
/// [ZIndex.zIndex], there is no guarantee that the first one will be rendered
|
|
||||||
/// before the second one.
|
|
||||||
/// {@endtemplate}
|
|
||||||
void render() => _zBuffer
|
|
||||||
..sort((a, b) => a.zIndex.compareTo(b.zIndex))
|
|
||||||
..whereType<Component>().forEach(_render)
|
|
||||||
..clear();
|
|
||||||
|
|
||||||
void _render(Component component) => component.renderTree(canvas);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void clipPath(Path path, {bool doAntiAlias = true}) =>
|
void clipPath(Path path, {bool doAntiAlias = true}) =>
|
||||||
canvas.clipPath(path, doAntiAlias: doAntiAlias);
|
canvas.clipPath(path, doAntiAlias: doAntiAlias);
|
@ -0,0 +1,77 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:pinball_flame/src/canvas/canvas_wrapper.dart';
|
||||||
|
|
||||||
|
/// {@template z_canvas_component}
|
||||||
|
/// Draws [ZIndex] components after the all non-[ZIndex] components have been
|
||||||
|
/// drawn.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class ZCanvasComponent extends Component {
|
||||||
|
/// {@macro z_canvas_component}
|
||||||
|
ZCanvasComponent({
|
||||||
|
Iterable<Component>? children,
|
||||||
|
}) : _zCanvas = _ZCanvas(),
|
||||||
|
super(children: children);
|
||||||
|
|
||||||
|
final _ZCanvas _zCanvas;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void renderTree(Canvas canvas) {
|
||||||
|
_zCanvas.canvas = canvas;
|
||||||
|
super.renderTree(_zCanvas);
|
||||||
|
_zCanvas.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply to any [Component] that will be rendered according to a
|
||||||
|
/// [ZIndex.zIndex].
|
||||||
|
///
|
||||||
|
/// [ZIndex] components must be descendants of a [ZCanvasComponent].
|
||||||
|
///
|
||||||
|
/// {@macro z_canvas.render}
|
||||||
|
mixin ZIndex on Component {
|
||||||
|
/// The z-index of this component.
|
||||||
|
///
|
||||||
|
/// The higher the value, the later the component will be drawn. Hence,
|
||||||
|
/// rendering in front of [Component]s with lower [zIndex] values.
|
||||||
|
int zIndex = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void renderTree(
|
||||||
|
Canvas canvas,
|
||||||
|
) {
|
||||||
|
if (canvas is _ZCanvas) {
|
||||||
|
canvas.buffer(this);
|
||||||
|
} else {
|
||||||
|
super.renderTree(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [_ZCanvas] allows to postpone the rendering of [ZIndex] components.
|
||||||
|
///
|
||||||
|
/// You should not use this class directly.
|
||||||
|
class _ZCanvas extends CanvasWrapper {
|
||||||
|
final List<ZIndex> _zBuffer = [];
|
||||||
|
|
||||||
|
/// Postpones the rendering of [ZIndex] component and its children.
|
||||||
|
void buffer(ZIndex component) => _zBuffer.add(component);
|
||||||
|
|
||||||
|
/// Renders all [ZIndex] components and their children.
|
||||||
|
///
|
||||||
|
/// {@template z_canvas.render}
|
||||||
|
/// The rendering order is defined by the parent [ZIndex]. The children of
|
||||||
|
/// the same parent are rendered in the order they were added.
|
||||||
|
///
|
||||||
|
/// If two [Component]s ever overlap each other, and have the same
|
||||||
|
/// [ZIndex.zIndex], there is no guarantee that the first one will be rendered
|
||||||
|
/// before the second one.
|
||||||
|
/// {@endtemplate}
|
||||||
|
void render() => _zBuffer
|
||||||
|
..sort((a, b) => a.zIndex.compareTo(b.zIndex))
|
||||||
|
..whereType<Component>().forEach(_render)
|
||||||
|
..clear();
|
||||||
|
|
||||||
|
void _render(Component component) => component.renderTree(canvas);
|
||||||
|
}
|
@ -0,0 +1,144 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_flame/src/canvas/canvas_component.dart';
|
||||||
|
|
||||||
|
class _TestSpriteComponent extends SpriteComponent {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('CanvasComponent', () {
|
||||||
|
final flameTester = FlameTester(FlameGame.new);
|
||||||
|
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(
|
||||||
|
CanvasComponent(),
|
||||||
|
isA<CanvasComponent>(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('loads correctly', (game) async {
|
||||||
|
final component = CanvasComponent();
|
||||||
|
await game.ensureAdd(component);
|
||||||
|
expect(game.contains(component), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'adds children',
|
||||||
|
(game) async {
|
||||||
|
final component = Component();
|
||||||
|
final canvas = CanvasComponent(
|
||||||
|
onSpritePainted: (paint) => paint.filterQuality = FilterQuality.high,
|
||||||
|
children: [component],
|
||||||
|
);
|
||||||
|
|
||||||
|
await game.ensureAdd(canvas);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
canvas.children.contains(component),
|
||||||
|
isTrue,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'calls onSpritePainted when paiting a sprite',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final spriteComponent = _TestSpriteComponent();
|
||||||
|
|
||||||
|
final completer = Completer<Image>();
|
||||||
|
decodeImageFromList(
|
||||||
|
Uint8List.fromList(_image),
|
||||||
|
completer.complete,
|
||||||
|
);
|
||||||
|
spriteComponent.sprite = Sprite(await completer.future);
|
||||||
|
|
||||||
|
var calls = 0;
|
||||||
|
final canvas = CanvasComponent(
|
||||||
|
onSpritePainted: (paint) => calls++,
|
||||||
|
children: [spriteComponent],
|
||||||
|
);
|
||||||
|
|
||||||
|
await game.ensureAdd(canvas);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(calls, equals(1));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const List<int> _image = <int>[
|
||||||
|
0x89,
|
||||||
|
0x50,
|
||||||
|
0x4E,
|
||||||
|
0x47,
|
||||||
|
0x0D,
|
||||||
|
0x0A,
|
||||||
|
0x1A,
|
||||||
|
0x0A,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0D,
|
||||||
|
0x49,
|
||||||
|
0x48,
|
||||||
|
0x44,
|
||||||
|
0x52,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x08,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x1F,
|
||||||
|
0x15,
|
||||||
|
0xC4,
|
||||||
|
0x89,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0A,
|
||||||
|
0x49,
|
||||||
|
0x44,
|
||||||
|
0x41,
|
||||||
|
0x54,
|
||||||
|
0x78,
|
||||||
|
0x9C,
|
||||||
|
0x63,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x0D,
|
||||||
|
0x0A,
|
||||||
|
0x2D,
|
||||||
|
0xB4,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x49,
|
||||||
|
0x45,
|
||||||
|
0x4E,
|
||||||
|
0x44,
|
||||||
|
0xAE,
|
||||||
|
];
|
@ -0,0 +1,353 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart' hide Image;
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball_flame/src/canvas/canvas_wrapper.dart';
|
||||||
|
|
||||||
|
class _MockCanvas extends Mock implements Canvas {}
|
||||||
|
|
||||||
|
class _MockImage extends Mock implements Image {}
|
||||||
|
|
||||||
|
class _MockPicture extends Mock implements Picture {}
|
||||||
|
|
||||||
|
class _MockParagraph extends Mock implements Paragraph {}
|
||||||
|
|
||||||
|
class _MockVertices extends Mock implements Vertices {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('CanvasWrapper', () {
|
||||||
|
group('CanvasWrapper', () {
|
||||||
|
late Canvas canvas;
|
||||||
|
late Path path;
|
||||||
|
late RRect rRect;
|
||||||
|
late Rect rect;
|
||||||
|
late Paint paint;
|
||||||
|
late Image atlas;
|
||||||
|
late BlendMode blendMode;
|
||||||
|
late Color color;
|
||||||
|
late Offset offset;
|
||||||
|
late Float64List float64list;
|
||||||
|
late Float32List float32list;
|
||||||
|
late Int32List int32list;
|
||||||
|
late Picture picture;
|
||||||
|
late Paragraph paragraph;
|
||||||
|
late Vertices vertices;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
canvas = _MockCanvas();
|
||||||
|
path = Path();
|
||||||
|
rRect = RRect.zero;
|
||||||
|
rect = Rect.zero;
|
||||||
|
paint = Paint();
|
||||||
|
atlas = _MockImage();
|
||||||
|
blendMode = BlendMode.clear;
|
||||||
|
color = Colors.black;
|
||||||
|
offset = Offset.zero;
|
||||||
|
float64list = Float64List(1);
|
||||||
|
float32list = Float32List(1);
|
||||||
|
int32list = Int32List(1);
|
||||||
|
picture = _MockPicture();
|
||||||
|
paragraph = _MockParagraph();
|
||||||
|
vertices = _MockVertices();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clipPath calls Canvas's clipPath", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..clipPath(path, doAntiAlias: false);
|
||||||
|
verify(
|
||||||
|
() => canvas.clipPath(path, doAntiAlias: false),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clipRRect calls Canvas's clipRRect", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..clipRRect(rRect, doAntiAlias: false);
|
||||||
|
verify(
|
||||||
|
() => canvas.clipRRect(rRect, doAntiAlias: false),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clipRect calls Canvas's clipRect", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..clipRect(rect, doAntiAlias: false);
|
||||||
|
verify(
|
||||||
|
() => canvas.clipRect(rect, doAntiAlias: false),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawArc calls Canvas's drawArc", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawArc(rect, 0, 1, false, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawArc(rect, 0, 1, false, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawAtlas calls Canvas's drawAtlas", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawAtlas(atlas, [], [], [], blendMode, rect, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawAtlas(atlas, [], [], [], blendMode, rect, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawCircle calls Canvas's drawCircle", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawCircle(offset, 0, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawCircle(offset, 0, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawColor calls Canvas's drawColor", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawColor(color, blendMode);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawColor(color, blendMode),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawDRRect calls Canvas's drawDRRect", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawDRRect(rRect, rRect, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawDRRect(rRect, rRect, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawImage calls Canvas's drawImage", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawImage(atlas, offset, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawImage(atlas, offset, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawImageNine calls Canvas's drawImageNine", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawImageNine(atlas, rect, rect, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawImageNine(atlas, rect, rect, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawImageRect calls Canvas's drawImageRect", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawImageRect(atlas, rect, rect, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawImageRect(atlas, rect, rect, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawLine calls Canvas's drawLine", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawLine(offset, offset, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawLine(offset, offset, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawOval calls Canvas's drawOval", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawOval(rect, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawOval(rect, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawPaint calls Canvas's drawPaint", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawPaint(paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawPaint(paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawParagraph calls Canvas's drawParagraph", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawParagraph(paragraph, offset);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawParagraph(paragraph, offset),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawPath calls Canvas's drawPath", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawPath(path, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawPath(path, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawPicture calls Canvas's drawPicture", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawPicture(picture);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawPicture(picture),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawPoints calls Canvas's drawPoints", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawPoints(PointMode.points, [offset], paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawPoints(PointMode.points, [offset], paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawRRect calls Canvas's drawRRect", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawRRect(rRect, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawRRect(rRect, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawRawAtlas calls Canvas's drawRawAtlas", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawRawAtlas(
|
||||||
|
atlas,
|
||||||
|
float32list,
|
||||||
|
float32list,
|
||||||
|
int32list,
|
||||||
|
BlendMode.clear,
|
||||||
|
rect,
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawRawAtlas(
|
||||||
|
atlas,
|
||||||
|
float32list,
|
||||||
|
float32list,
|
||||||
|
int32list,
|
||||||
|
BlendMode.clear,
|
||||||
|
rect,
|
||||||
|
paint,
|
||||||
|
),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawRawPoints calls Canvas's drawRawPoints", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawRawPoints(PointMode.points, float32list, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawRawPoints(PointMode.points, float32list, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawRect calls Canvas's drawRect", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawRect(rect, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawRect(rect, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawShadow calls Canvas's drawShadow", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawShadow(path, color, 0, false);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawShadow(path, color, 0, false),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawVertices calls Canvas's drawVertices", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..drawVertices(vertices, blendMode, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawVertices(vertices, blendMode, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getSaveCount calls Canvas's getSaveCount", () {
|
||||||
|
final canvasWrapper = CanvasWrapper()..canvas = canvas;
|
||||||
|
when(() => canvas.getSaveCount()).thenReturn(1);
|
||||||
|
canvasWrapper.getSaveCount();
|
||||||
|
verify(() => canvas.getSaveCount()).called(1);
|
||||||
|
expect(canvasWrapper.getSaveCount(), 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restore calls Canvas's restore", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..restore();
|
||||||
|
verify(() => canvas.restore()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rotate calls Canvas's rotate", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..rotate(0);
|
||||||
|
verify(() => canvas.rotate(0)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("save calls Canvas's save", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..save();
|
||||||
|
verify(() => canvas.save()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("saveLayer calls Canvas's saveLayer", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..saveLayer(rect, paint);
|
||||||
|
verify(() => canvas.saveLayer(rect, paint)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("scale calls Canvas's scale", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..scale(0, 0);
|
||||||
|
verify(() => canvas.scale(0, 0)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("skew calls Canvas's skew", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..skew(0, 0);
|
||||||
|
verify(() => canvas.skew(0, 0)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("transform calls Canvas's transform", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..transform(float64list);
|
||||||
|
verify(() => canvas.transform(float64list)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("translate calls Canvas's translate", () {
|
||||||
|
CanvasWrapper()
|
||||||
|
..canvas = canvas
|
||||||
|
..translate(0, 0);
|
||||||
|
verify(() => canvas.translate(0, 0)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter/material.dart' hide Image;
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
class _TestCircleComponent extends CircleComponent with ZIndex {
|
||||||
|
_TestCircleComponent(Color color)
|
||||||
|
: super(
|
||||||
|
paint: Paint()..color = color,
|
||||||
|
radius: 10,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('ZCanvasComponent', () {
|
||||||
|
final flameTester = FlameTester(FlameGame.new);
|
||||||
|
const goldensFilePath = '../goldens/rendering/';
|
||||||
|
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(
|
||||||
|
ZCanvasComponent(),
|
||||||
|
isA<ZCanvasComponent>(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('loads correctly', (game) async {
|
||||||
|
final component = ZCanvasComponent();
|
||||||
|
await game.ensureAdd(component);
|
||||||
|
expect(game.contains(component), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'red circle renders behind blue circle',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final canvas = ZCanvasComponent(
|
||||||
|
children: [
|
||||||
|
_TestCircleComponent(Colors.blue)..zIndex = 1,
|
||||||
|
_TestCircleComponent(Colors.red)..zIndex = 0,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
await game.ensureAdd(canvas);
|
||||||
|
|
||||||
|
game.camera.followVector2(Vector2.zero());
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
await expectLater(
|
||||||
|
find.byGame<FlameGame>(),
|
||||||
|
matchesGoldenFile('${goldensFilePath}red_blue.png'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'blue circle renders behind red circle',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final canvas = ZCanvasComponent(
|
||||||
|
children: [
|
||||||
|
_TestCircleComponent(Colors.blue)..zIndex = 0,
|
||||||
|
_TestCircleComponent(Colors.red)..zIndex = 1
|
||||||
|
],
|
||||||
|
);
|
||||||
|
await game.ensureAdd(canvas);
|
||||||
|
|
||||||
|
game.camera.followVector2(Vector2.zero());
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
await expectLater(
|
||||||
|
find.byGame<FlameGame>(),
|
||||||
|
matchesGoldenFile('${goldensFilePath}blue_red.png'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 22 KiB |
@ -1,385 +0,0 @@
|
|||||||
// ignore_for_file: cascade_invocations
|
|
||||||
|
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flame/components.dart';
|
|
||||||
import 'package:flame/game.dart';
|
|
||||||
import 'package:flame_test/flame_test.dart';
|
|
||||||
import 'package:flutter/material.dart' hide Image;
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:mocktail/mocktail.dart';
|
|
||||||
import 'package:pinball_flame/pinball_flame.dart';
|
|
||||||
|
|
||||||
class _TestCircleComponent extends CircleComponent with ZIndex {
|
|
||||||
_TestCircleComponent(Color color)
|
|
||||||
: super(
|
|
||||||
paint: Paint()..color = color,
|
|
||||||
radius: 10,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MockCanvas extends Mock implements Canvas {}
|
|
||||||
|
|
||||||
class _MockImage extends Mock implements Image {}
|
|
||||||
|
|
||||||
class _MockPicture extends Mock implements Picture {}
|
|
||||||
|
|
||||||
class _MockParagraph extends Mock implements Paragraph {}
|
|
||||||
|
|
||||||
class _MockVertices extends Mock implements Vertices {}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
final flameTester = FlameTester(FlameGame.new);
|
|
||||||
const goldenPrefix = 'golden/rendering/';
|
|
||||||
|
|
||||||
group('ZCanvasComponent', () {
|
|
||||||
flameTester.test('loads correctly', (game) async {
|
|
||||||
final component = ZCanvasComponent();
|
|
||||||
await game.ensureAdd(component);
|
|
||||||
expect(game.contains(component), isTrue);
|
|
||||||
});
|
|
||||||
|
|
||||||
flameTester.testGameWidget(
|
|
||||||
'red circle renders behind blue circle',
|
|
||||||
setUp: (game, tester) async {
|
|
||||||
final canvas = ZCanvasComponent(
|
|
||||||
children: [
|
|
||||||
_TestCircleComponent(Colors.blue)..zIndex = 1,
|
|
||||||
_TestCircleComponent(Colors.red)..zIndex = 0,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
await game.ensureAdd(canvas);
|
|
||||||
|
|
||||||
game.camera.followVector2(Vector2.zero());
|
|
||||||
},
|
|
||||||
verify: (game, tester) async {
|
|
||||||
await expectLater(
|
|
||||||
find.byGame<FlameGame>(),
|
|
||||||
matchesGoldenFile('${goldenPrefix}red_blue.png'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
flameTester.testGameWidget(
|
|
||||||
'blue circle renders behind red circle',
|
|
||||||
setUp: (game, tester) async {
|
|
||||||
final canvas = ZCanvasComponent(
|
|
||||||
children: [
|
|
||||||
_TestCircleComponent(Colors.blue)..zIndex = 0,
|
|
||||||
_TestCircleComponent(Colors.red)..zIndex = 1
|
|
||||||
],
|
|
||||||
);
|
|
||||||
await game.ensureAdd(canvas);
|
|
||||||
|
|
||||||
game.camera.followVector2(Vector2.zero());
|
|
||||||
},
|
|
||||||
verify: (game, tester) async {
|
|
||||||
await expectLater(
|
|
||||||
find.byGame<FlameGame>(),
|
|
||||||
matchesGoldenFile('${goldenPrefix}blue_red.png'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
group('ZCanvas', () {
|
|
||||||
late Canvas canvas;
|
|
||||||
late Path path;
|
|
||||||
late RRect rRect;
|
|
||||||
late Rect rect;
|
|
||||||
late Paint paint;
|
|
||||||
late Image atlas;
|
|
||||||
late BlendMode blendMode;
|
|
||||||
late Color color;
|
|
||||||
late Offset offset;
|
|
||||||
late Float64List float64list;
|
|
||||||
late Float32List float32list;
|
|
||||||
late Int32List int32list;
|
|
||||||
late Picture picture;
|
|
||||||
late Paragraph paragraph;
|
|
||||||
late Vertices vertices;
|
|
||||||
|
|
||||||
setUp(() {
|
|
||||||
canvas = _MockCanvas();
|
|
||||||
path = Path();
|
|
||||||
rRect = RRect.zero;
|
|
||||||
rect = Rect.zero;
|
|
||||||
paint = Paint();
|
|
||||||
atlas = _MockImage();
|
|
||||||
blendMode = BlendMode.clear;
|
|
||||||
color = Colors.black;
|
|
||||||
offset = Offset.zero;
|
|
||||||
float64list = Float64List(1);
|
|
||||||
float32list = Float32List(1);
|
|
||||||
int32list = Int32List(1);
|
|
||||||
picture = _MockPicture();
|
|
||||||
paragraph = _MockParagraph();
|
|
||||||
vertices = _MockVertices();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("clipPath calls Canvas's clipPath", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.clipPath(path, doAntiAlias: false);
|
|
||||||
verify(
|
|
||||||
() => canvas.clipPath(path, doAntiAlias: false),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("clipRRect calls Canvas's clipRRect", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.clipRRect(rRect, doAntiAlias: false);
|
|
||||||
verify(
|
|
||||||
() => canvas.clipRRect(rRect, doAntiAlias: false),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("clipRect calls Canvas's clipRect", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.clipRect(rect, doAntiAlias: false);
|
|
||||||
verify(
|
|
||||||
() => canvas.clipRect(rect, doAntiAlias: false),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawArc calls Canvas's drawArc", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawArc(rect, 0, 1, false, paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawArc(rect, 0, 1, false, paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawAtlas calls Canvas's drawAtlas", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawAtlas(atlas, [], [], [], blendMode, rect, paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawAtlas(atlas, [], [], [], blendMode, rect, paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawCircle calls Canvas's drawCircle", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawCircle(offset, 0, paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawCircle(offset, 0, paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawColor calls Canvas's drawColor", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawColor(color, blendMode);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawColor(color, blendMode),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawDRRect calls Canvas's drawDRRect", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawDRRect(rRect, rRect, paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawDRRect(rRect, rRect, paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawImage calls Canvas's drawImage", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawImage(atlas, offset, paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawImage(atlas, offset, paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawImageNine calls Canvas's drawImageNine", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawImageNine(atlas, rect, rect, paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawImageNine(atlas, rect, rect, paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawImageRect calls Canvas's drawImageRect", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawImageRect(atlas, rect, rect, paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawImageRect(atlas, rect, rect, paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawLine calls Canvas's drawLine", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawLine(offset, offset, paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawLine(offset, offset, paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawOval calls Canvas's drawOval", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawOval(rect, paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawOval(rect, paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawPaint calls Canvas's drawPaint", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawPaint(paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawPaint(paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawParagraph calls Canvas's drawParagraph", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawParagraph(paragraph, offset);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawParagraph(paragraph, offset),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawPath calls Canvas's drawPath", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawPath(path, paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawPath(path, paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawPicture calls Canvas's drawPicture", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawPicture(picture);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawPicture(picture),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawPoints calls Canvas's drawPoints", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawPoints(PointMode.points, [offset], paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawPoints(PointMode.points, [offset], paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawRRect calls Canvas's drawRRect", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawRRect(rRect, paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawRRect(rRect, paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawRawAtlas calls Canvas's drawRawAtlas", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawRawAtlas(
|
|
||||||
atlas,
|
|
||||||
float32list,
|
|
||||||
float32list,
|
|
||||||
int32list,
|
|
||||||
BlendMode.clear,
|
|
||||||
rect,
|
|
||||||
paint,
|
|
||||||
);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawRawAtlas(
|
|
||||||
atlas,
|
|
||||||
float32list,
|
|
||||||
float32list,
|
|
||||||
int32list,
|
|
||||||
BlendMode.clear,
|
|
||||||
rect,
|
|
||||||
paint,
|
|
||||||
),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawRawPoints calls Canvas's drawRawPoints", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawRawPoints(PointMode.points, float32list, paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawRawPoints(PointMode.points, float32list, paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawRect calls Canvas's drawRect", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawRect(rect, paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawRect(rect, paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawShadow calls Canvas's drawShadow", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawShadow(path, color, 0, false);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawShadow(path, color, 0, false),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("drawVertices calls Canvas's drawVertices", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.drawVertices(vertices, blendMode, paint);
|
|
||||||
verify(
|
|
||||||
() => canvas.drawVertices(vertices, blendMode, paint),
|
|
||||||
).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("getSaveCount calls Canvas's getSaveCount", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
when(() => canvas.getSaveCount()).thenReturn(1);
|
|
||||||
zcanvas.getSaveCount();
|
|
||||||
verify(() => canvas.getSaveCount()).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore calls Canvas's restore", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.restore();
|
|
||||||
verify(() => canvas.restore()).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("rotate calls Canvas's rotate", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.rotate(0);
|
|
||||||
verify(() => canvas.rotate(0)).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("save calls Canvas's save", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.save();
|
|
||||||
verify(() => canvas.save()).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("saveLayer calls Canvas's saveLayer", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.saveLayer(rect, paint);
|
|
||||||
verify(() => canvas.saveLayer(rect, paint)).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("scale calls Canvas's scale", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.scale(0, 0);
|
|
||||||
verify(() => canvas.scale(0, 0)).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("skew calls Canvas's skew", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.skew(0, 0);
|
|
||||||
verify(() => canvas.skew(0, 0)).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("transform calls Canvas's transform", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.transform(float64list);
|
|
||||||
verify(() => canvas.transform(float64list)).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("translate calls Canvas's translate", () {
|
|
||||||
final zcanvas = ZCanvas()..canvas = canvas;
|
|
||||||
zcanvas.translate(0, 0);
|
|
||||||
verify(() => canvas.translate(0, 0)).called(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 6.3 KiB |
@ -0,0 +1,152 @@
|
|||||||
|
// 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:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball/game/behaviors/behaviors.dart';
|
||||||
|
import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
import '../../../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
class _MockGameBloc extends Mock implements GameBloc {}
|
||||||
|
|
||||||
|
class _MockSpaceshipRampCubit extends Mock implements SpaceshipRampCubit {}
|
||||||
|
|
||||||
|
class _MockStreamSubscription extends Mock
|
||||||
|
implements StreamSubscription<SpaceshipRampState> {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final assets = [
|
||||||
|
Assets.images.android.ramp.boardOpening.keyName,
|
||||||
|
Assets.images.android.ramp.railingForeground.keyName,
|
||||||
|
Assets.images.android.ramp.railingBackground.keyName,
|
||||||
|
Assets.images.android.ramp.main.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.inactive.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active1.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active2.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active3.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active4.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active5.keyName,
|
||||||
|
Assets.images.android.rail.main.keyName,
|
||||||
|
Assets.images.android.rail.exit.keyName,
|
||||||
|
Assets.images.score.oneMillion.keyName,
|
||||||
|
];
|
||||||
|
|
||||||
|
group('RampBonusBehavior', () {
|
||||||
|
const shotPoints = Points.oneMillion;
|
||||||
|
|
||||||
|
late GameBloc gameBloc;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
gameBloc = _MockGameBloc();
|
||||||
|
whenListen(
|
||||||
|
gameBloc,
|
||||||
|
const Stream<GameState>.empty(),
|
||||||
|
initialState: const GameState.initial(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
|
||||||
|
gameBuilder: EmptyPinballTestGame.new,
|
||||||
|
blocBuilder: () => gameBloc,
|
||||||
|
assets: assets,
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
'when hits are multiples of 10 times adds a ScoringBehavior',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final bloc = _MockSpaceshipRampCubit();
|
||||||
|
final streamController = StreamController<SpaceshipRampState>();
|
||||||
|
whenListen(
|
||||||
|
bloc,
|
||||||
|
streamController.stream,
|
||||||
|
initialState: SpaceshipRampState(hits: 9),
|
||||||
|
);
|
||||||
|
final behavior = RampBonusBehavior(
|
||||||
|
points: shotPoints,
|
||||||
|
);
|
||||||
|
final parent = SpaceshipRamp.test(
|
||||||
|
bloc: bloc,
|
||||||
|
);
|
||||||
|
|
||||||
|
await game.ensureAdd(ZCanvasComponent(children: [parent]));
|
||||||
|
await parent.ensureAdd(behavior);
|
||||||
|
|
||||||
|
streamController.add(SpaceshipRampState(hits: 10));
|
||||||
|
|
||||||
|
final scores = game.descendants().whereType<ScoringBehavior>();
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
expect(scores.length, 1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
"when hits are not multiple of 10 times doesn't add any ScoringBehavior",
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final bloc = _MockSpaceshipRampCubit();
|
||||||
|
final streamController = StreamController<SpaceshipRampState>();
|
||||||
|
whenListen(
|
||||||
|
bloc,
|
||||||
|
streamController.stream,
|
||||||
|
initialState: SpaceshipRampState.initial(),
|
||||||
|
);
|
||||||
|
final behavior = RampBonusBehavior(
|
||||||
|
points: shotPoints,
|
||||||
|
);
|
||||||
|
final parent = SpaceshipRamp.test(
|
||||||
|
bloc: bloc,
|
||||||
|
);
|
||||||
|
|
||||||
|
await game.ensureAdd(ZCanvasComponent(children: [parent]));
|
||||||
|
await parent.ensureAdd(behavior);
|
||||||
|
|
||||||
|
streamController.add(SpaceshipRampState(hits: 1));
|
||||||
|
|
||||||
|
final scores = game.descendants().whereType<ScoringBehavior>();
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
expect(scores.length, 0);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
'closes subscription when removed',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final bloc = _MockSpaceshipRampCubit();
|
||||||
|
whenListen(
|
||||||
|
bloc,
|
||||||
|
const Stream<SpaceshipRampState>.empty(),
|
||||||
|
initialState: SpaceshipRampState.initial(),
|
||||||
|
);
|
||||||
|
when(bloc.close).thenAnswer((_) async {});
|
||||||
|
|
||||||
|
final subscription = _MockStreamSubscription();
|
||||||
|
when(subscription.cancel).thenAnswer((_) async {});
|
||||||
|
|
||||||
|
final behavior = RampBonusBehavior.test(
|
||||||
|
points: shotPoints,
|
||||||
|
subscription: subscription,
|
||||||
|
);
|
||||||
|
final parent = SpaceshipRamp.test(
|
||||||
|
bloc: bloc,
|
||||||
|
);
|
||||||
|
|
||||||
|
await game.ensureAdd(ZCanvasComponent(children: [parent]));
|
||||||
|
await parent.ensureAdd(behavior);
|
||||||
|
|
||||||
|
parent.remove(behavior);
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
verify(subscription.cancel).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,156 @@
|
|||||||
|
// 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:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball/game/behaviors/behaviors.dart';
|
||||||
|
import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
import '../../../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
class _MockGameBloc extends Mock implements GameBloc {}
|
||||||
|
|
||||||
|
class _MockSpaceshipRampCubit extends Mock implements SpaceshipRampCubit {}
|
||||||
|
|
||||||
|
class _MockStreamSubscription extends Mock
|
||||||
|
implements StreamSubscription<SpaceshipRampState> {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final assets = [
|
||||||
|
Assets.images.android.ramp.boardOpening.keyName,
|
||||||
|
Assets.images.android.ramp.railingForeground.keyName,
|
||||||
|
Assets.images.android.ramp.railingBackground.keyName,
|
||||||
|
Assets.images.android.ramp.main.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.inactive.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active1.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active2.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active3.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active4.keyName,
|
||||||
|
Assets.images.android.ramp.arrow.active5.keyName,
|
||||||
|
Assets.images.android.rail.main.keyName,
|
||||||
|
Assets.images.android.rail.exit.keyName,
|
||||||
|
Assets.images.score.fiveThousand.keyName,
|
||||||
|
];
|
||||||
|
|
||||||
|
group('RampShotBehavior', () {
|
||||||
|
const shotPoints = Points.fiveThousand;
|
||||||
|
|
||||||
|
late GameBloc gameBloc;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
gameBloc = _MockGameBloc();
|
||||||
|
whenListen(
|
||||||
|
gameBloc,
|
||||||
|
const Stream<GameState>.empty(),
|
||||||
|
initialState: const GameState.initial(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
|
||||||
|
gameBuilder: EmptyPinballTestGame.new,
|
||||||
|
blocBuilder: () => gameBloc,
|
||||||
|
assets: assets,
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
'when hits are not multiple of 10 times '
|
||||||
|
'increases multiplier and adds a ScoringBehavior',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final bloc = _MockSpaceshipRampCubit();
|
||||||
|
final streamController = StreamController<SpaceshipRampState>();
|
||||||
|
whenListen(
|
||||||
|
bloc,
|
||||||
|
streamController.stream,
|
||||||
|
initialState: SpaceshipRampState.initial(),
|
||||||
|
);
|
||||||
|
final behavior = RampShotBehavior(
|
||||||
|
points: shotPoints,
|
||||||
|
);
|
||||||
|
final parent = SpaceshipRamp.test(
|
||||||
|
bloc: bloc,
|
||||||
|
);
|
||||||
|
|
||||||
|
await game.ensureAdd(ZCanvasComponent(children: [parent]));
|
||||||
|
await parent.ensureAdd(behavior);
|
||||||
|
|
||||||
|
streamController.add(SpaceshipRampState(hits: 1));
|
||||||
|
|
||||||
|
final scores = game.descendants().whereType<ScoringBehavior>();
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
verify(() => gameBloc.add(MultiplierIncreased())).called(1);
|
||||||
|
expect(scores.length, 1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
'when hits multiple of 10 times '
|
||||||
|
"doesn't increase multiplier, neither ScoringBehavior",
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final bloc = _MockSpaceshipRampCubit();
|
||||||
|
final streamController = StreamController<SpaceshipRampState>();
|
||||||
|
whenListen(
|
||||||
|
bloc,
|
||||||
|
streamController.stream,
|
||||||
|
initialState: SpaceshipRampState(hits: 9),
|
||||||
|
);
|
||||||
|
final behavior = RampShotBehavior(
|
||||||
|
points: shotPoints,
|
||||||
|
);
|
||||||
|
final parent = SpaceshipRamp.test(
|
||||||
|
bloc: bloc,
|
||||||
|
);
|
||||||
|
|
||||||
|
await game.ensureAdd(ZCanvasComponent(children: [parent]));
|
||||||
|
await parent.ensureAdd(behavior);
|
||||||
|
|
||||||
|
streamController.add(SpaceshipRampState(hits: 10));
|
||||||
|
|
||||||
|
final scores = game.children.whereType<ScoringBehavior>();
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
verifyNever(() => gameBloc.add(MultiplierIncreased()));
|
||||||
|
expect(scores.length, 0);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
'closes subscription when removed',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final bloc = _MockSpaceshipRampCubit();
|
||||||
|
whenListen(
|
||||||
|
bloc,
|
||||||
|
const Stream<SpaceshipRampState>.empty(),
|
||||||
|
initialState: SpaceshipRampState.initial(),
|
||||||
|
);
|
||||||
|
when(bloc.close).thenAnswer((_) async {});
|
||||||
|
|
||||||
|
final subscription = _MockStreamSubscription();
|
||||||
|
when(subscription.cancel).thenAnswer((_) async {});
|
||||||
|
|
||||||
|
final behavior = RampShotBehavior.test(
|
||||||
|
points: shotPoints,
|
||||||
|
subscription: subscription,
|
||||||
|
);
|
||||||
|
final parent = SpaceshipRamp.test(
|
||||||
|
bloc: bloc,
|
||||||
|
);
|
||||||
|
|
||||||
|
await game.ensureAdd(ZCanvasComponent(children: [parent]));
|
||||||
|
await parent.ensureAdd(behavior);
|
||||||
|
|
||||||
|
parent.remove(behavior);
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
verify(subscription.cancel).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|