From b256f7b7ac9c59ff109a4ab47d6f036f29f5d1c4 Mon Sep 17 00:00:00 2001 From: alestiago Date: Mon, 7 Mar 2022 12:51:31 +0000 Subject: [PATCH] feat: implemented Flipper --- lib/game/components/flipper.dart | 161 +++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 lib/game/components/flipper.dart diff --git a/lib/game/components/flipper.dart b/lib/game/components/flipper.dart new file mode 100644 index 00000000..96edb5d6 --- /dev/null +++ b/lib/game/components/flipper.dart @@ -0,0 +1,161 @@ +import 'dart:async'; +import 'dart:math' as math; + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball/game/game.dart'; + +/// {@template flipper} +/// A bat, typically found in pairs at the bottom of the board. +/// +/// [Flipper] can be controlled by the player in an arc motion. +/// {@endtemplate flipper} +class Flipper extends BodyComponent { + /// {@macro flipper} + Flipper({ + required Vector2 position, + bool isMirrored = false, + }) : _position = position, + _isMirrored = isMirrored, + _speed = calculateRequiredSpeed() { + // TODO(alestiago): Use sprite instead of color when provided. + paint = Paint() + ..color = const Color(0xFF00FF00) + ..style = PaintingStyle.fill; + } + + static final size = Vector2(12, 2.8); + + /// The total duration of a full flipper's arc motion. + /// + /// A full flipper's arc motion is from the lowest position (resting point) to + /// the highest position. + static const _sweepingAnimationDuration = Duration(milliseconds: 250); + + static double calculateRequiredSpeed() { + // TODO(alestiago): Modify to be mathematically correct. + final sweepingDistance = math.sqrt(2 * math.pow(size.x, 2)); + final seconds = _sweepingAnimationDuration.inMicroseconds / + Duration.microsecondsPerSecond; + return sweepingDistance / seconds; + } + + /// The speed required to move the [Flipper] to its heighst position, while + /// respecting the [_sweepingAnimationDuration]. + /// + /// The speed value is proportional to the time it takes to move the flipper + /// to its highest position. + final double _speed; + + /// The initial position of the [Flipper] body. + final Vector2 _position; + + /// Whether the [Flipper] is mirrored. + /// + /// A mirrored [Flipper] is one positioned at the right of the borad. + final bool _isMirrored; + + /// Applies downard linear velocity to the [Flipper] to move it up. + void moveDown() { + body.linearVelocity = Vector2(0, -_speed); + } + + /// Applies upward linear velocity to the [Flipper] to move it up. + void moveUp() { + body.linearVelocity = Vector2(0, _speed); + } + + // TODO(erickzanardo): Remove this once the issue is solved: + // https://github.com/flame-engine/flame/issues/1417 + final Completer hasMounted = Completer(); + + List _createFixtureDefs() { + final fixtures = []; + + final bigRadius = size.y / 2; + final bigCircleShape = CircleShape() + ..radius = bigRadius + ..position.setValues( + _isMirrored ? size.x - bigRadius : bigRadius, + -bigRadius, + ); + final bigCircleFixtureDef = FixtureDef(bigCircleShape); + fixtures.add(bigCircleFixtureDef); + + final smallRadius = bigRadius / 2; + final smallCircleShape = CircleShape() + ..radius = smallRadius + ..position.setValues( + _isMirrored ? smallRadius : size.x - smallRadius, + -2 * smallRadius, + ); + final smallCircleFixtureDef = FixtureDef(smallCircleShape); + fixtures.add(smallCircleFixtureDef); + + final inclineSpace = (size.y - (2 * smallRadius)) / 2; + final trapeziumVertices = _isMirrored + ? [ + Vector2(smallRadius, -inclineSpace), + Vector2(size.x - bigRadius, 0), + Vector2(size.x - bigRadius, -size.y), + Vector2(smallRadius, -size.y + inclineSpace), + ] + : [ + Vector2(bigCircleShape.radius, 0), + Vector2(size.x - smallCircleShape.radius, -inclineSpace), + Vector2(size.x - smallCircleShape.radius, -size.y + inclineSpace), + Vector2(bigCircleShape.radius, -size.y), + ]; + final trapezium = PolygonShape()..set(trapeziumVertices); + final trapeziumFixtureDef = FixtureDef(trapezium) + ..density = 50.0 + ..friction = .1; + fixtures.add(trapeziumFixtureDef); + + return fixtures; + } + + @override + Body createBody() { + final bodyDef = BodyDef() + ..gravityScale = 0 + ..type = BodyType.dynamic + ..position = _position; + + final body = world.createBody(bodyDef); + _createFixtureDefs().forEach(body.createFixture); + + return body; + } + + @override + void onMount() { + super.onMount(); + hasMounted.complete(); + } +} + +/// {@template flipper_anchor_revolute_joint_def} +/// Hinges one end of [Flipper] to an [Anchor] to achieve an arc motion. +/// {@endtemplate} +class FlipperAnchorRevoluteJointDef extends RevoluteJointDef { + /// {@macro flipper_anchor_revolute_joint_def} + FlipperAnchorRevoluteJointDef({ + required Flipper flipper, + required Anchor anchor, + bool isMirrored = false, + }) { + initialize( + flipper.body, + anchor.body, + anchor.body.position, + ); + enableLimit = true; + final angle = isMirrored ? -_sweepingAngle : _sweepingAngle; + lowerAngle = angle; + upperAngle = angle; + } + + /// The total angle of the arc motion. + static const _sweepingAngle = math.pi / 7; +}