feat: add slingshots (#148)

* feat: add slingshots

* test: slingshot

* feat: add slingshot to sandbox

* chore: add todo
pull/162/head
Allison Ryan 4 years ago committed by GitHub
parent 655007b2d2
commit 2f40dcc971
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -15,6 +15,10 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.baseboard.right.keyName),
images.load(components.Assets.images.kicker.left.keyName),
images.load(components.Assets.images.kicker.right.keyName),
images.load(components.Assets.images.slingshot.leftUpper.keyName),
images.load(components.Assets.images.slingshot.leftLower.keyName),
images.load(components.Assets.images.slingshot.rightUpper.keyName),
images.load(components.Assets.images.slingshot.rightLower.keyName),
images.load(components.Assets.images.launchRamp.ramp.keyName),
images.load(
components.Assets.images.launchRamp.foregroundRailing.keyName,

@ -46,6 +46,7 @@ class PinballGame extends Forge2DGame
await add(plunger);
unawaited(add(Board()));
unawaited(addFromBlueprint(Slingshots()));
unawaited(addFromBlueprint(DinoWalls()));
unawaited(_addBonusWord());
unawaited(addFromBlueprint(SpaceshipRamp()));

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

@ -29,6 +29,7 @@ class $AssetsImagesGen {
$AssetsImagesKickerGen get kicker => const $AssetsImagesKickerGen();
$AssetsImagesLaunchRampGen get launchRamp =>
const $AssetsImagesLaunchRampGen();
$AssetsImagesSlingshotGen get slingshot => const $AssetsImagesSlingshotGen();
$AssetsImagesSpaceshipGen get spaceship => const $AssetsImagesSpaceshipGen();
$AssetsImagesSparkyBumperGen get sparkyBumper =>
const $AssetsImagesSparkyBumperGen();
@ -127,6 +128,26 @@ class $AssetsImagesLaunchRampGen {
const AssetGenImage('assets/images/launch_ramp/ramp.png');
}
class $AssetsImagesSlingshotGen {
const $AssetsImagesSlingshotGen();
/// File path: assets/images/slingshot/left_lower.png
AssetGenImage get leftLower =>
const AssetGenImage('assets/images/slingshot/left_lower.png');
/// File path: assets/images/slingshot/left_upper.png
AssetGenImage get leftUpper =>
const AssetGenImage('assets/images/slingshot/left_upper.png');
/// File path: assets/images/slingshot/right_lower.png
AssetGenImage get rightLower =>
const AssetGenImage('assets/images/slingshot/right_lower.png');
/// File path: assets/images/slingshot/right_upper.png
AssetGenImage get rightUpper =>
const AssetGenImage('assets/images/slingshot/right_upper.png');
}
class $AssetsImagesSpaceshipGen {
const $AssetsImagesSpaceshipGen();

@ -16,6 +16,7 @@ export 'launch_ramp.dart';
export 'layer.dart';
export 'ramp_opening.dart';
export 'shapes/shapes.dart';
export 'slingshot.dart';
export 'spaceship.dart';
export 'spaceship_rail.dart';
export 'spaceship_ramp.dart';

@ -0,0 +1,138 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template slingshots}
/// A [Blueprint] which creates the left and right pairs of [Slingshot]s.
/// {@endtemplate}
class Slingshots extends Forge2DBlueprint {
@override
void build(_) {
// TODO(allisonryan0002): use radians values instead of converting degrees.
final leftUpperSlingshot = Slingshot(
length: 5.66,
angle: -1.5 * (math.pi / 180),
spritePath: Assets.images.slingshot.leftUpper.keyName,
)..initialPosition = Vector2(-29, 1.5);
final leftLowerSlingshot = Slingshot(
length: 3.54,
angle: -29.1 * (math.pi / 180),
spritePath: Assets.images.slingshot.leftLower.keyName,
)..initialPosition = Vector2(-31, -6.2);
final rightUpperSlingshot = Slingshot(
length: 5.64,
angle: 1 * (math.pi / 180),
spritePath: Assets.images.slingshot.rightUpper.keyName,
)..initialPosition = Vector2(22.3, 1.58);
final rightLowerSlingshot = Slingshot(
length: 3.46,
angle: 26.8 * (math.pi / 180),
spritePath: Assets.images.slingshot.rightLower.keyName,
)..initialPosition = Vector2(24.7, -6.2);
addAll([
leftUpperSlingshot,
leftLowerSlingshot,
rightUpperSlingshot,
rightLowerSlingshot,
]);
}
}
/// {@template slingshot}
/// Elastic bumper that bounces the [Ball] off of its straight sides.
/// {@endtemplate}
class Slingshot extends BodyComponent with InitialPosition {
/// {@macro slingshot}
Slingshot({
required double length,
required double angle,
required String spritePath,
}) : _length = length,
_angle = angle,
_spritePath = spritePath,
super(priority: 1);
final double _length;
final double _angle;
final String _spritePath;
List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[];
const circleRadius = 1.55;
final topCircleShape = CircleShape()..radius = circleRadius;
topCircleShape.position.setValues(0, _length / 2);
final topCircleFixtureDef = FixtureDef(topCircleShape)..friction = 0;
fixturesDef.add(topCircleFixtureDef);
final bottomCircleShape = CircleShape()..radius = circleRadius;
bottomCircleShape.position.setValues(0, -_length / 2);
final bottomCircleFixtureDef = FixtureDef(bottomCircleShape)..friction = 0;
fixturesDef.add(bottomCircleFixtureDef);
final leftEdgeShape = EdgeShape()
..set(
Vector2(circleRadius, _length / 2),
Vector2(circleRadius, -_length / 2),
);
final leftEdgeShapeFixtureDef = FixtureDef(leftEdgeShape)
..friction = 0
..restitution = 5;
fixturesDef.add(leftEdgeShapeFixtureDef);
final rightEdgeShape = EdgeShape()
..set(
Vector2(-circleRadius, _length / 2),
Vector2(-circleRadius, -_length / 2),
);
final rightEdgeShapeFixtureDef = FixtureDef(rightEdgeShape)
..friction = 0
..restitution = 5;
fixturesDef.add(rightEdgeShapeFixtureDef);
return fixturesDef;
}
@override
Body createBody() {
final bodyDef = BodyDef()
..userData = this
..position = initialPosition
..angle = _angle;
final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture);
return body;
}
@override
Future<void> onLoad() async {
await super.onLoad();
await _loadSprite();
renderBody = false;
}
Future<void> _loadSprite() async {
final sprite = await gameRef.loadSprite(_spritePath);
await add(
SpriteComponent(
sprite: sprite,
size: sprite.originalSize / 10,
anchor: Anchor.center,
angle: _angle,
),
);
}
}

@ -39,6 +39,7 @@ flutter:
- assets/images/spaceship/ramp/
- assets/images/chrome_dino/
- assets/images/kicker/
- assets/images/slingshot/
- assets/images/sparky_bumper/a/
- assets/images/sparky_bumper/b/
- assets/images/sparky_bumper/c/

@ -21,6 +21,7 @@ void main() {
addChromeDinoStories(dashbook);
addDashNestBumperStories(dashbook);
addKickerStories(dashbook);
addSlingshotStories(dashbook);
addSparkyBumperStories(dashbook);
runApp(dashbook);
}

@ -0,0 +1,66 @@
import 'dart:math' as math;
import 'package:flame/extensions.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class SlingshotGame extends BasicBallGame {
SlingshotGame({
required this.trace,
}) : super(color: const Color(0xFFFF0000));
static const info = '''
Shows how Slingshots are rendered.
- Activate the "trace" parameter to overlay the body.
- Tap anywhere on the screen to spawn a ball into the game.
''';
final bool trace;
@override
Future<void> onLoad() async {
await super.onLoad();
final center = screenToWorld(camera.viewport.canvasSize! / 2);
final leftUpperSlingshot = Slingshot(
length: 5.66,
angle: -1.5 * (math.pi / 180),
spritePath: Assets.images.slingshot.leftUpper.keyName,
)..initialPosition = center + Vector2(-29, 1.5);
final leftLowerSlingshot = Slingshot(
length: 3.54,
angle: -29.1 * (math.pi / 180),
spritePath: Assets.images.slingshot.leftLower.keyName,
)..initialPosition = center + Vector2(-31, -6.2);
final rightUpperSlingshot = Slingshot(
length: 5.64,
angle: 1 * (math.pi / 180),
spritePath: Assets.images.slingshot.rightUpper.keyName,
)..initialPosition = center + Vector2(22.3, 1.58);
final rightLowerSlingshot = Slingshot(
length: 3.46,
angle: 26.8 * (math.pi / 180),
spritePath: Assets.images.slingshot.rightLower.keyName,
)..initialPosition = center + Vector2(24.7, -6.2);
await addAll([
leftUpperSlingshot,
leftLowerSlingshot,
rightUpperSlingshot,
rightLowerSlingshot,
]);
if (trace) {
leftUpperSlingshot.trace();
leftLowerSlingshot.trace();
rightUpperSlingshot.trace();
rightLowerSlingshot.trace();
}
}
}

@ -0,0 +1,17 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/slingshot/slingshot_game.dart';
void addSlingshotStories(Dashbook dashbook) {
dashbook.storiesOf('Slingshots').add(
'Basic',
(context) => GameWidget(
game: SlingshotGame(
trace: context.boolProperty('Trace', true),
),
),
codeLink: buildSourceLink('slingshot_game/basic.dart'),
info: SlingshotGame.info,
);
}

@ -5,5 +5,6 @@ export 'dash_nest_bumper/stories.dart';
export 'effects/stories.dart';
export 'flipper/stories.dart';
export 'layer/stories.dart';
export 'slingshot/stories.dart';
export 'spaceship/stories.dart';
export 'sparky_bumper/stories.dart';

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

@ -0,0 +1,97 @@
// 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:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
group('Slingshot', () {
final flameTester = FlameTester(TestGame.new);
const length = 2.0;
const angle = 0.0;
final spritePath = Assets.images.slingshot.leftUpper.keyName;
flameTester.testGameWidget(
'renders correctly',
setUp: (game, tester) async {
await game.addFromBlueprint(Slingshots());
await game.ready();
game.camera.followVector2(Vector2.zero());
},
// TODO(allisonryan0002): enable test when workflows are fixed.
// verify: (game, tester) async {
// await expectLater(
// find.byGame<Forge2DGame>(),
// matchesGoldenFile('golden/slingshots.png'),
// );
// },
);
flameTester.test(
'loads correctly',
(game) async {
final slingshot = Slingshot(
length: length,
angle: angle,
spritePath: spritePath,
);
await game.ensureAdd(slingshot);
expect(game.contains(slingshot), isTrue);
},
);
flameTester.test(
'body is static',
(game) async {
final slingshot = Slingshot(
length: length,
angle: angle,
spritePath: spritePath,
);
await game.ensureAdd(slingshot);
expect(slingshot.body.bodyType, equals(BodyType.static));
},
);
flameTester.test(
'has restitution',
(game) async {
final slingshot = Slingshot(
length: length,
angle: angle,
spritePath: spritePath,
);
await game.ensureAdd(slingshot);
final totalRestitution = slingshot.body.fixtures.fold<double>(
0,
(total, fixture) => total + fixture.restitution,
);
expect(totalRestitution, greaterThan(0));
},
);
flameTester.test(
'has no friction',
(game) async {
final slingshot = Slingshot(
length: length,
angle: angle,
spritePath: spritePath,
);
await game.ensureAdd(slingshot);
final totalFriction = slingshot.body.fixtures.fold<double>(
0,
(total, fixture) => total + fixture.friction,
);
expect(totalFriction, equals(0));
},
);
});
}
Loading…
Cancel
Save