refactor: moved scaling logic to `BallScalingBehavior` (#313)

pull/316/head
Alejandro Santiago 3 years ago committed by GitHub
parent 6e4651f89e
commit c67db0e55c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,11 +1,11 @@
import 'dart:async';
import 'dart:math' as math;
import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/widgets.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/ball/behaviors/ball_scaling_behavior.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template ball}
@ -20,6 +20,7 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
renderBody: false,
children: [
_BallSpriteComponent()..tint(baseColor.withOpacity(0.5)),
BallScalingBehavior(),
],
) {
// TODO(ruimiguel): while developing Ball can be launched by clicking mouse,
@ -30,6 +31,15 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
layer = Layer.board;
}
/// Creates a [Ball] without any behaviors.
///
/// This can be used for testing [Ball]'s behaviors in isolation.
@visibleForTesting
Ball.test({required this.baseColor})
: super(
children: [_BallSpriteComponent()],
);
/// The size of the [Ball].
static final Vector2 size = Vector2.all(4.13);
@ -81,26 +91,9 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
void update(double dt) {
super.update(dt);
_rescaleSize();
_setPositionalGravity();
}
void _rescaleSize() {
final boardHeight = BoardDimensions.bounds.height;
const maxShrinkValue = BoardDimensions.perspectiveShrinkFactor;
final standardizedYPosition = body.position.y + (boardHeight / 2);
final scaleFactor = maxShrinkValue +
((standardizedYPosition / boardHeight) * (1 - maxShrinkValue));
body.fixtures.first.shape.radius = (size.x / 2) * scaleFactor;
// TODO(alestiago): Revisit and see if there's a better way to do this.
final spriteComponent = firstChild<_BallSpriteComponent>();
spriteComponent?.scale = Vector2.all(scaleFactor);
}
void _setPositionalGravity() {
final defaultGravity = gameRef.world.gravity.y;
final maxXDeviationFromCenter = BoardDimensions.bounds.width / 2;

@ -0,0 +1,24 @@
import 'package:flame/components.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// Scales the ball's body and sprite according to its position on the board.
class BallScalingBehavior extends Component with ParentIsA<Ball> {
@override
void update(double dt) {
super.update(dt);
final boardHeight = BoardDimensions.bounds.height;
const maxShrinkValue = BoardDimensions.perspectiveShrinkFactor;
final standardizedYPosition = parent.body.position.y + (boardHeight / 2);
final scaleFactor = maxShrinkValue +
((standardizedYPosition / boardHeight) * (1 - maxShrinkValue));
parent.body.fixtures.first.shape.radius = (Ball.size.x / 2) * scaleFactor;
parent.firstChild<SpriteComponent>()!.scale.setValues(
scaleFactor,
scaleFactor,
);
}
}

@ -2,7 +2,7 @@ export 'android_animatronic.dart';
export 'android_bumper/android_bumper.dart';
export 'android_spaceship/android_spaceship.dart';
export 'backboard/backboard.dart';
export 'ball.dart';
export 'ball/ball.dart';
export 'baseboard.dart';
export 'board_background_sprite_component.dart';
export 'board_dimensions.dart';

@ -6,18 +6,29 @@ import 'package:flame_test/flame_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/ball/behaviors/behaviors.dart';
import '../../helpers/helpers.dart';
import '../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
group('Ball', () {
const baseColor = Color(0xFFFFFFFF);
test(
'can be instantiated',
() {
expect(Ball(baseColor: baseColor), isA<Ball>());
expect(Ball.test(baseColor: baseColor), isA<Ball>());
},
);
flameTester.test(
'loads correctly',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ready();
await game.ensureAdd(ball);
@ -25,11 +36,20 @@ void main() {
},
);
flameTester.test('add a BallScalingBehavior', (game) async {
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
expect(
ball.descendants().whereType<BallScalingBehavior>().length,
equals(1),
);
});
group('body', () {
flameTester.test(
'is dynamic',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
expect(ball.body.bodyType, equals(BodyType.dynamic));
@ -38,7 +58,7 @@ void main() {
group('can be moved', () {
flameTester.test('by its weight', (game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
game.update(1);
@ -46,7 +66,7 @@ void main() {
});
flameTester.test('by applying velocity', (game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
ball.body.gravityScale = Vector2.zero();
@ -61,7 +81,7 @@ void main() {
flameTester.test(
'exists',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
expect(ball.body.fixtures[0], isA<Fixture>());
@ -71,7 +91,7 @@ void main() {
flameTester.test(
'is dense',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
final fixture = ball.body.fixtures[0];
@ -82,7 +102,7 @@ void main() {
flameTester.test(
'shape is circular',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
final fixture = ball.body.fixtures[0];
@ -94,7 +114,7 @@ void main() {
flameTester.test(
'has Layer.all as default filter maskBits',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ready();
await game.ensureAdd(ball);
await game.ready();
@ -108,7 +128,7 @@ void main() {
group('stop', () {
group("can't be moved", () {
flameTester.test('by its weight', (game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
ball.stop();
@ -116,19 +136,6 @@ void main() {
expect(ball.body.position, equals(ball.initialPosition));
});
});
// TODO(allisonryan0002): delete or retest this if/when solution is added
// to prevent forces on a ball while stopped.
// flameTester.test('by applying velocity', (game) async {
// final ball = Ball(baseColor: Colors.blue);
// await game.ensureAdd(ball);
// ball.stop();
// ball.body.linearVelocity.setValues(10, 10);
// game.update(1);
// expect(ball.body.position, equals(ball.initialPosition));
// });
});
group('resume', () {
@ -136,7 +143,7 @@ void main() {
flameTester.test(
'by its weight when previously stopped',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
ball.stop();
ball.resume();
@ -149,7 +156,7 @@ void main() {
flameTester.test(
'by applying velocity when previously stopped',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
ball.stop();
ball.resume();
@ -165,7 +172,7 @@ void main() {
group('boost', () {
flameTester.test('applies an impulse to the ball', (game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
expect(ball.body.linearVelocity, equals(Vector2.zero()));
@ -176,7 +183,7 @@ void main() {
});
flameTester.test('adds TurboChargeSpriteAnimation', (game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
await ball.boost(Vector2.all(10));
@ -190,7 +197,7 @@ void main() {
flameTester.test('removes TurboChargeSpriteAnimation after it finishes',
(game) async {
final ball = Ball(baseColor: Colors.blue);
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
await ball.boost(Vector2.all(10));

@ -0,0 +1,99 @@
// ignore_for_file: cascade_invocations
import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/ball/behaviors/behaviors.dart';
import '../../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final asset = Assets.images.ball.ball.keyName;
final flameTester = FlameTester(() => TestGame([asset]));
group('BallScalingBehavior', () {
const baseColor = Color(0xFFFFFFFF);
test('can be instantiated', () {
expect(
BallScalingBehavior(),
isA<BallScalingBehavior>(),
);
});
flameTester.test('can be loaded', (game) async {
final ball = Ball.test(baseColor: baseColor);
final behavior = BallScalingBehavior();
await ball.add(behavior);
await game.ensureAdd(ball);
expect(
ball.firstChild<BallScalingBehavior>(),
equals(behavior),
);
});
flameTester.test('can be loaded', (game) async {
final ball = Ball.test(baseColor: baseColor);
final behavior = BallScalingBehavior();
await ball.add(behavior);
await game.ensureAdd(ball);
expect(
ball.firstChild<BallScalingBehavior>(),
equals(behavior),
);
});
flameTester.test('scales the shape radius', (game) async {
final ball1 = Ball.test(baseColor: baseColor)
..initialPosition = Vector2(0, 10);
await ball1.add(BallScalingBehavior());
final ball2 = Ball.test(baseColor: baseColor)
..initialPosition = Vector2(0, -10);
await ball2.add(BallScalingBehavior());
await game.ensureAddAll([ball1, ball2]);
game.update(1);
final shape1 = ball1.body.fixtures.first.shape;
final shape2 = ball2.body.fixtures.first.shape;
expect(
shape1.radius,
greaterThan(shape2.radius),
);
});
flameTester.testGameWidget(
'scales the sprite',
setUp: (game, tester) async {
final ball1 = Ball.test(baseColor: baseColor)
..initialPosition = Vector2(0, 10);
await ball1.add(BallScalingBehavior());
final ball2 = Ball.test(baseColor: baseColor)
..initialPosition = Vector2(0, -10);
await ball2.add(BallScalingBehavior());
await game.ensureAddAll([ball1, ball2]);
game.update(1);
await tester.pump();
await game.ready();
final sprite1 = ball1.firstChild<SpriteComponent>()!;
final sprite2 = ball2.firstChild<SpriteComponent>()!;
expect(
sprite1.scale.x,
greaterThan(sprite2.scale.x),
);
expect(
sprite1.scale.y,
greaterThan(sprite2.scale.y),
);
},
);
});
}
Loading…
Cancel
Save