diff --git a/lib/game/components/pathway.dart b/lib/game/components/pathway.dart index d6582a91..3404f2d2 100644 --- a/lib/game/components/pathway.dart +++ b/lib/game/components/pathway.dart @@ -4,8 +4,9 @@ import 'package:flutter/material.dart'; import 'package:geometry/geometry.dart'; /// {@template pathway} -/// [Pathway] creates lines of various shapes that the ball can collide -/// with and move along. +/// [Pathway] creates lines of various shapes. +/// +/// [BodyComponent]s such as a Ball can collide and move along a [Pathway]. /// {@endtemplate} class Pathway extends BodyComponent { Pathway._({ @@ -20,20 +21,19 @@ class Pathway extends BodyComponent { ..style = PaintingStyle.stroke; } - /// {@macro pathway} - /// [Pathway.straight] creates a straight pathway for the ball. + /// Creates a uniform unidirectional (straight) [Pathway]. /// - /// given a [position] for the body, between a [start] and [end] points. - /// It creates two [ChainShape] separated by a [pathwayWidth]. - /// If [singleWall] is true, just one [ChainShape] is created - /// (like a wall instead of a pathway) - /// The pathway could be rotated by [rotation] in degrees. + /// Does so with two [ChainShape] separated by a [width]. Placed + /// at a [position] between [start] and [end] points. Can + /// be rotated by a given [rotation] in radians. + /// + /// If [singleWall] is true, just one [ChainShape] is created. factory Pathway.straight({ Color? color, required Vector2 position, required Vector2 start, required Vector2 end, - required double pathwayWidth, + required double width, double rotation = 0, bool singleWall = false, }) { @@ -41,14 +41,14 @@ class Pathway extends BodyComponent { final wall1 = [ start.clone(), end.clone(), - ].map((vector) => vector..rotate(radians(rotation))).toList(); + ].map((vector) => vector..rotate(rotation)).toList(); paths.add(wall1); if (!singleWall) { final wall2 = [ - start + Vector2(pathwayWidth, 0), - end + Vector2(pathwayWidth, 0), - ].map((vector) => vector..rotate(radians(rotation))).toList(); + start + Vector2(width, 0), + end + Vector2(width, 0), + ].map((vector) => vector..rotate(rotation)).toList(); paths.add(wall2); } @@ -59,21 +59,24 @@ class Pathway extends BodyComponent { ); } - /// {@macro pathway} - /// [Pathway.arc] creates an arc pathway for the ball. + /// Creates an arc [Pathway]. + /// + /// The [angle], in radians, specifies the size of the arc. For example, 2*pi + /// returns a complete circumference and minor angles a semi circumference. + /// + /// The center of the arc is placed at [position]. + /// + /// Does so with two [ChainShape] separated by a [width]. Which can be + /// rotated by a given [rotation] in radians. + /// + /// The outer radius is specified by [radius], whilst the inner one is + /// equivalent to [radius] - [width]. /// - /// The arc is created given a [position] for the body, a [radius] for the - /// circumference and an [angle] to specify the size of it (360 will return - /// a completed circumference and minor angles a semi circumference ). - /// It creates two [ChainShape] separated by a [pathwayWidth], like a circular - /// crown. The specified [radius] is for the outer arc, the inner one will - /// have a radius of radius-pathwayWidth. /// If [singleWall] is true, just one [ChainShape] is created. - /// The pathway could be rotated by [rotation] in degrees. factory Pathway.arc({ Color? color, required Vector2 position, - required double pathwayWidth, + required double width, required double radius, required double angle, double rotation = 0, @@ -92,7 +95,7 @@ class Pathway extends BodyComponent { if (!singleWall) { final wall2 = calculateArc( center: position, - radius: radius - pathwayWidth, + radius: radius - width, angle: angle, offsetAngle: rotation, ); @@ -106,38 +109,36 @@ class Pathway extends BodyComponent { ); } - /// {@macro pathway} - /// [Pathway.bezierCurve] creates a bezier curve pathway for the ball. + /// Creates a bezier curve [Pathway]. /// - /// The curve is created given a [position] for the body, and - /// with a list of control points specified by [controlPoints]. - /// First and last points set the beginning and end of the curve, all the - /// inner points between them set the bezier curve final shape. - /// It creates two [ChainShape] separated by a [pathwayWidth]. - /// If [singleWall] is true, just one [ChainShape] is created - /// (like a wall instead of a pathway) - /// The pathway could be rotated by [rotation] in degrees. + /// Does so with two [ChainShape] separated by a [width]. Which can be + /// rotated by a given [rotation] in radians. + /// + /// First and last [controlPoints] set the beginning and end of the curve, + /// inner points between them set its final shape. + /// + /// If [singleWall] is true, just one [ChainShape] is created. factory Pathway.bezierCurve({ Color? color, required Vector2 position, required List controlPoints, - required double pathwayWidth, + required double width, double rotation = 0, bool singleWall = false, }) { final paths = >[]; final wall1 = calculateBezierCurve(controlPoints: controlPoints) - .map((vector) => vector..rotate(radians(rotation))) + .map((vector) => vector..rotate(rotation)) .toList(); paths.add(wall1); if (!singleWall) { final wall2 = calculateBezierCurve( controlPoints: controlPoints - .map((vector) => vector + Vector2(pathwayWidth, -pathwayWidth)) + .map((vector) => vector + Vector2(width, -width)) .toList(), - ).map((vector) => vector..rotate(radians(rotation))).toList(); + ).map((vector) => vector..rotate(rotation)).toList(); paths.add(wall2); } @@ -158,13 +159,13 @@ class Pathway extends BodyComponent { ..position = _position; final body = world.createBody(bodyDef); - for (final path in _paths) { final chain = ChainShape() ..createChain( path.map(gameRef.screenToWorld).toList(), ); final fixtureDef = FixtureDef(chain); + body.createFixture(fixtureDef); } diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 7c701c09..6fe6e571 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -1,5 +1,4 @@ import 'dart:async'; - import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/game/game.dart'; diff --git a/packages/geometry/lib/src/geometry.dart b/packages/geometry/lib/src/geometry.dart index c992e5a2..8851ed25 100644 --- a/packages/geometry/lib/src/geometry.dart +++ b/packages/geometry/lib/src/geometry.dart @@ -4,7 +4,7 @@ import 'package:flame/extensions.dart'; /// Calculates all [Vector2]s of a circumference. /// /// Circumference is created from a [center] and a [radius] -/// Also semi circumference could be created, specifying its [angle] in degrees +/// Also semi circumference could be created, specifying its [angle] in radians /// and the offset start angle [offsetAngle] for this semi circumference. /// The higher the [precision], the more [Vector2]s will be calculated, /// achieving a more rounded arc. @@ -17,13 +17,12 @@ List calculateArc({ double offsetAngle = 0, int precision = 100, }) { - final stepAngle = radians(angle / (precision - 1)); - final stepOffset = radians(offsetAngle); + final stepAngle = angle / (precision - 1); final points = []; for (var i = 0; i < precision; i++) { - final xCoord = center.x + radius * math.cos((stepAngle * i) + stepOffset); - final yCoord = center.y - radius * math.sin((stepAngle * i) + stepOffset); + final xCoord = center.x + radius * math.cos((stepAngle * i) + offsetAngle); + final yCoord = center.y - radius * math.sin((stepAngle * i) + offsetAngle); final point = Vector2(xCoord, yCoord); points.add(point); @@ -35,11 +34,14 @@ List calculateArc({ /// Calculates all [Vector2]s of a bezier curve. /// /// A bezier curve of [controlPoints] that say how to create this curve. +/// /// First and last points specify the beginning and the end respectively /// of the curve. The inner points specify the shape of the curve and /// its turning points. -/// The [step] must be in range 0<=step<=1 and indicates the precision to -/// calculate the curve. +/// +/// The [step] must be between zero and one (inclusive), indicating the +/// precision to calculate the curve. +/// /// For more information read: https://en.wikipedia.org/wiki/B%C3%A9zier_curve List calculateBezierCurve({ required List controlPoints, @@ -77,7 +79,7 @@ List calculateBezierCurve({ return points; } -/// Method to calculate the binomial coefficient of 'n' and 'k' +/// Calculates the binomial coefficient of 'n' and 'k'. /// For more information read: https://en.wikipedia.org/wiki/Binomial_coefficient num binomial(num n, num k) { assert(0 <= k && k <= n, 'k ($k) and n ($n) must be in range 0 <= k <= n'); @@ -89,7 +91,7 @@ num binomial(num n, num k) { } } -/// Method to calculate the factorial of some number 'n' +/// Calculate the factorial of 'n'. /// For more information read: https://en.wikipedia.org/wiki/Factorial num factorial(num n) { assert(n >= 0, 'Factorial is not defined for negative number n ($n)'); diff --git a/test/game/components/pathway_test.dart b/test/game/components/pathway_test.dart index e1c10455..7f8357d8 100644 --- a/test/game/components/pathway_test.dart +++ b/test/game/components/pathway_test.dart @@ -1,4 +1,5 @@ // ignore_for_file: cascade_invocations, prefer_const_constructors +import 'dart:math' as math; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter/material.dart'; @@ -10,20 +11,21 @@ void main() { final flameTester = FlameTester(PinballGame.new); group('Pathway', () { - const pathwayWidth = 50.0; + const width = 50.0; group('straight', () { group('color', () { flameTester.test( - 'has transparent color by default if not specified', + 'has transparent color by default when no color is specified', (game) async { final pathway = Pathway.straight( position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); expect(pathway.paint, isNotNull); expect( @@ -32,8 +34,9 @@ void main() { ); }, ); + flameTester.test( - 'has a color if set', + 'has a color when is specified', (game) async { const defaultColor = Colors.blue; @@ -42,9 +45,10 @@ void main() { position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); expect(pathway.paint, isNotNull); expect(pathway.paint.color.value, equals(defaultColor.value)); @@ -59,9 +63,10 @@ void main() { position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); }, ); @@ -75,11 +80,11 @@ void main() { position: position, start: Vector2(10, 10), end: Vector2(20, 20), - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); - game.contains(pathway); + game.contains(pathway); expect(pathway.body.position, position); }, ); @@ -91,7 +96,7 @@ void main() { position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); @@ -102,13 +107,13 @@ void main() { group('fixtures', () { flameTester.test( - 'exists only one ChainShape if just one wall', + 'has only one ChainShape when singleWall is true', (game) async { final pathway = Pathway.straight( position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathwayWidth: pathwayWidth, + width: width, singleWall: true, ); await game.ensureAdd(pathway); @@ -121,13 +126,13 @@ void main() { ); flameTester.test( - 'exists two ChainShape if there is by default two walls', + 'has two ChainShape when singleWall is false (default)', (game) async { final pathway = Pathway.straight( position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); @@ -147,11 +152,12 @@ void main() { (game) async { final pathway = Pathway.arc( position: Vector2.zero(), - pathwayWidth: pathwayWidth, - radius: 100, - angle: 90, + width: width, + radius: math.pi / 2, + angle: math.pi / 2, ); await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); }, ); @@ -163,13 +169,13 @@ void main() { final position = Vector2.all(10); final pathway = Pathway.arc( position: position, - pathwayWidth: pathwayWidth, - radius: 100, - angle: 90, + width: width, + radius: math.pi / 2, + angle: math.pi / 2, ); await game.ensureAdd(pathway); - game.contains(pathway); + game.contains(pathway); expect(pathway.body.position, position); }, ); @@ -179,9 +185,9 @@ void main() { (game) async { final pathway = Pathway.arc( position: Vector2.zero(), - pathwayWidth: pathwayWidth, - radius: 100, - angle: 90, + width: width, + radius: math.pi / 2, + angle: math.pi / 2, ); await game.ensureAdd(pathway); @@ -205,9 +211,10 @@ void main() { final pathway = Pathway.bezierCurve( position: Vector2.zero(), controlPoints: controlPoints, - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); }, ); @@ -220,11 +227,11 @@ void main() { final pathway = Pathway.bezierCurve( position: position, controlPoints: controlPoints, - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); - game.contains(pathway); + game.contains(pathway); expect(pathway.body.position, position); }, ); @@ -235,7 +242,7 @@ void main() { final pathway = Pathway.bezierCurve( position: Vector2.zero(), controlPoints: controlPoints, - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway);