mirror of https://github.com/flutter/pinball.git
commit
39e3f5d556
@ -1,8 +1 @@
|
|||||||
// Copyright (c) 2021, Very Good Ventures
|
|
||||||
// https://verygood.ventures
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file or at
|
|
||||||
// https://opensource.org/licenses/MIT.
|
|
||||||
|
|
||||||
export 'view/app.dart';
|
export 'view/app.dart';
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
/// Scales the ball's gravity according to its position on the board.
|
||||||
|
class BallGravitatingBehavior extends Component
|
||||||
|
with ParentIsA<Ball>, HasGameRef<Forge2DGame> {
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
final defaultGravity = gameRef.world.gravity.y;
|
||||||
|
|
||||||
|
final maxXDeviationFromCenter = BoardDimensions.bounds.width / 2;
|
||||||
|
const maxXGravityPercentage =
|
||||||
|
(1 - BoardDimensions.perspectiveShrinkFactor) / 2;
|
||||||
|
final xDeviationFromCenter = parent.body.position.x;
|
||||||
|
|
||||||
|
final positionalXForce = ((xDeviationFromCenter / maxXDeviationFromCenter) *
|
||||||
|
maxXGravityPercentage) *
|
||||||
|
defaultGravity;
|
||||||
|
final positionalYForce = math.sqrt(
|
||||||
|
math.pow(defaultGravity, 2) - math.pow(positionalXForce, 2),
|
||||||
|
);
|
||||||
|
|
||||||
|
final gravityOverride = parent.body.gravityOverride;
|
||||||
|
if (gravityOverride != null) {
|
||||||
|
gravityOverride.setValues(positionalXForce, positionalYForce);
|
||||||
|
} else {
|
||||||
|
parent.body.gravityOverride = Vector2(positionalXForce, positionalYForce);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export 'ball_gravitating_behavior.dart';
|
||||||
|
export 'ball_scaling_behavior.dart';
|
@ -0,0 +1,63 @@
|
|||||||
|
// 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('BallGravitatingBehavior', () {
|
||||||
|
const baseColor = Color(0xFFFFFFFF);
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(
|
||||||
|
BallGravitatingBehavior(),
|
||||||
|
isA<BallGravitatingBehavior>(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('can be loaded', (game) async {
|
||||||
|
final ball = Ball.test(baseColor: baseColor);
|
||||||
|
final behavior = BallGravitatingBehavior();
|
||||||
|
await ball.add(behavior);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
expect(
|
||||||
|
ball.firstChild<BallGravitatingBehavior>(),
|
||||||
|
equals(behavior),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
"overrides the body's horizontal gravity symmetrically",
|
||||||
|
(game) async {
|
||||||
|
final ball1 = Ball.test(baseColor: baseColor)
|
||||||
|
..initialPosition = Vector2(10, 0);
|
||||||
|
await ball1.add(BallGravitatingBehavior());
|
||||||
|
|
||||||
|
final ball2 = Ball.test(baseColor: baseColor)
|
||||||
|
..initialPosition = Vector2(-10, 0);
|
||||||
|
await ball2.add(BallGravitatingBehavior());
|
||||||
|
|
||||||
|
await game.ensureAddAll([ball1, ball2]);
|
||||||
|
game.update(1);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
ball1.body.gravityOverride!.x,
|
||||||
|
equals(-ball2.body.gravityOverride!.x),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
ball1.body.gravityOverride!.y,
|
||||||
|
equals(ball2.body.gravityOverride!.y),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
// 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('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.test(
|
||||||
|
'scales the sprite',
|
||||||
|
(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 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),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:flame_forge2d/world_contact_listener.dart';
|
||||||
|
|
||||||
|
// NOTE(wolfen): This should be removed when https://github.com/flame-engine/flame/pull/1597 is solved.
|
||||||
|
/// {@template pinball_forge2d_game}
|
||||||
|
/// A [Game] that uses the Forge2D physics engine.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class PinballForge2DGame extends FlameGame implements Forge2DGame {
|
||||||
|
/// {@macro pinball_forge2d_game}
|
||||||
|
PinballForge2DGame({
|
||||||
|
required Vector2 gravity,
|
||||||
|
}) : world = World(gravity),
|
||||||
|
super(camera: Camera()) {
|
||||||
|
camera.zoom = Forge2DGame.defaultZoom;
|
||||||
|
world.setContactListener(WorldContactListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
final World world;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
world.stepDt(min(dt, 1 / 60));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Vector2 screenToFlameWorld(Vector2 position) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Vector2 screenToWorld(Vector2 position) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Vector2 worldToScreen(Vector2 position) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:flame/extensions.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final flameTester = FlameTester(
|
||||||
|
() => PinballForge2DGame(gravity: Vector2.zero()),
|
||||||
|
);
|
||||||
|
|
||||||
|
group('PinballForge2DGame', () {
|
||||||
|
test('can instantiate', () {
|
||||||
|
expect(
|
||||||
|
() => PinballForge2DGame(gravity: Vector2.zero()),
|
||||||
|
returnsNormally,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'screenToFlameWorld throws UnimpelementedError',
|
||||||
|
(game) async {
|
||||||
|
expect(
|
||||||
|
() => game.screenToFlameWorld(Vector2.zero()),
|
||||||
|
throwsUnimplementedError,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'screenToWorld throws UnimpelementedError',
|
||||||
|
(game) async {
|
||||||
|
expect(
|
||||||
|
() => game.screenToWorld(Vector2.zero()),
|
||||||
|
throwsUnimplementedError,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'worldToScreen throws UnimpelementedError',
|
||||||
|
(game) async {
|
||||||
|
expect(
|
||||||
|
() => game.worldToScreen(Vector2.zero()),
|
||||||
|
throwsUnimplementedError,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in new issue