From 567b0cf293353748c7c4282318bc8b980970db83 Mon Sep 17 00:00:00 2001 From: Viktor Lidholt Date: Wed, 28 Oct 2015 16:51:00 -0700 Subject: [PATCH] Adds API documentation to sprite physics and renames a few private variables for clarity. --- packages/flutter_sprites/lib/src/node.dart | 7 + .../flutter_sprites/lib/src/physics_body.dart | 143 ++++++++++++++--- .../lib/src/physics_debug.dart | 10 +- .../lib/src/physics_group.dart | 7 + .../lib/src/physics_joint.dart | 146 ++++++++++++++++-- .../lib/src/physics_shape.dart | 35 +++++ .../lib/src/physics_world.dart | 61 ++++++++ 7 files changed, 373 insertions(+), 36 deletions(-) diff --git a/packages/flutter_sprites/lib/src/node.dart b/packages/flutter_sprites/lib/src/node.dart index b49707fed8d..ecce1d20aaf 100644 --- a/packages/flutter_sprites/lib/src/node.dart +++ b/packages/flutter_sprites/lib/src/node.dart @@ -786,6 +786,13 @@ class Node { PhysicsBody _physicsBody; + /// The physics body associated with this node. If a physics body is assigned, + /// and the node is a child of a [PhysicsWorld] or a [PhysicsGroup] the + /// node's position and rotation will be controlled by the body. + /// + /// myNode.physicsBody = new PhysicsBody( + /// new PhysicsShapeCircle(Point.zero, 20.0) + /// ); PhysicsBody get physicsBody => _physicsBody; set physicsBody(PhysicsBody physicsBody) { diff --git a/packages/flutter_sprites/lib/src/physics_body.dart b/packages/flutter_sprites/lib/src/physics_body.dart index 7d91dda1eaa..9afed576e36 100644 --- a/packages/flutter_sprites/lib/src/physics_body.dart +++ b/packages/flutter_sprites/lib/src/physics_body.dart @@ -5,6 +5,18 @@ enum PhysicsBodyType { dynamic } +/// A physics body can be assigned to any node to make it simulated by physics. +/// The body has a shape, and physical properties such as density, friction, +/// and velocity. +/// +/// Bodies can be either dynamic or static. Dynamic bodies will move and rotate +/// the nodes that are associated with it. Static bodies can be moved by moving +/// or animating the node associated with them. +/// +/// For a body to be simulated it needs to be associated with a [Node], through +/// the node's physicsBody property. The node also need to be a child of either +/// a [PhysicsWorld] or a [PhysicsGroup] (which in turn is a child of a +/// [PhysicsWorld] or a [Physics Group]). class PhysicsBody { PhysicsBody(this.shape, { this.tag: null, @@ -52,14 +64,31 @@ class PhysicsBody { double _scale; + /// An object associated with this body, normally used for detecting + /// collisions. + /// + /// myBody.tag = "SpaceShip"; Object tag; + /// The shape of this physics body. The shape cannot be modified once the + /// body is created. If the shape is required to change, create a new body. + /// + /// myShape = myBody.shape; final PhysicsShape shape; + /// The type of the body. This is either [PhysicsBodyType.dynamic] or + /// [PhysicsBodyType.static]. Dynamic bodies are simulated by the physics, + /// static objects affect the physics but are not moved by the physics. + /// + /// myBody.type = PhysicsBodyType.static; PhysicsBodyType type; double _density; + /// The density of the body, default value is 1.0. The density has no specific + /// unit, instead densities are relative to each other. + /// + /// myBody.density = 0.5; double get density => _density; set density(double density) { @@ -74,6 +103,10 @@ class PhysicsBody { double _friction; + /// The fricion of the body, the default is 0.0 and the value should be in + /// the range of 0.0 to 1.0. + /// + /// myBody.friction = 0.4; double get friction => _friction; set friction(double friction) { @@ -90,6 +123,10 @@ class PhysicsBody { double get restitution => _restitution; + /// The restitution of the body, the default is 0.0 and the value should be in + /// the range of 0.0 to 1.0. + /// + /// myBody.restitution = 0.5; set restitution(double restitution) { _restitution = restitution; @@ -102,6 +139,11 @@ class PhysicsBody { bool _isSensor; + /// True if the body is a sensor. Sensors doesn't collide with other bodies, + /// but will return collision callbacks. Use a sensor body to detect if two + /// bodies are overlapping. + /// + /// myBody.isSensor = true; bool get isSensor => _isSensor; set isSensor(bool isSensor) { @@ -116,12 +158,15 @@ class PhysicsBody { Offset _linearVelocity; + /// The current linear velocity of the body in points / second. + /// + /// myBody.velocity = Offset.zero; Offset get linearVelocity { if (_body == null) return _linearVelocity; else { - double dx = _body.linearVelocity.x * _physicsNode.b2WorldToNodeConversionFactor; - double dy = _body.linearVelocity.y * _physicsNode.b2WorldToNodeConversionFactor; + double dx = _body.linearVelocity.x * _physicsWorld.b2WorldToNodeConversionFactor; + double dy = _body.linearVelocity.y * _physicsWorld.b2WorldToNodeConversionFactor; return new Offset(dx, dy); } } @@ -131,8 +176,8 @@ class PhysicsBody { if (_body != null) { Vector2 vec = new Vector2( - linearVelocity.dx / _physicsNode.b2WorldToNodeConversionFactor, - linearVelocity.dy / _physicsNode.b2WorldToNodeConversionFactor + linearVelocity.dx / _physicsWorld.b2WorldToNodeConversionFactor, + linearVelocity.dy / _physicsWorld.b2WorldToNodeConversionFactor ); _body.linearVelocity = vec; } @@ -140,6 +185,9 @@ class PhysicsBody { double _angularVelocity; + /// The angular velocity of the body in degrees / second. + /// + /// myBody.angularVelocity = 0.0; double get angularVelocity { if (_body == null) return _angularVelocity; @@ -156,10 +204,17 @@ class PhysicsBody { } // TODO: Should this be editable in box2d.Body ? + + /// Linear dampening, in the 0.0 to 1.0 range, default is 0.0. + /// + /// double dampening = myBody.linearDampening; final double linearDampening; double _angularDampening; + /// Angular dampening, in the 0.0 to 1.0 range, default is 0.0. + /// + /// myBody.angularDampening = 0.1; double get angularDampening => _angularDampening; set angularDampening(double angularDampening) { @@ -171,6 +226,9 @@ class PhysicsBody { bool _allowSleep; + /// Allows the body to sleep if it hasn't moved. + /// + /// myBody.allowSleep = false; bool get allowSleep => _allowSleep; set allowSleep(bool allowSleep) { @@ -182,6 +240,9 @@ class PhysicsBody { bool _awake; + /// True if the body is currently awake. + /// + /// bool isAwake = myBody.awake; bool get awake { if (_body != null) return _body.isAwake(); @@ -198,6 +259,9 @@ class PhysicsBody { bool _fixedRotation; + /// If true, the body cannot be rotated by the physics simulation. + /// + /// myBody.fixedRotation = true; bool get fixedRotation => _fixedRotation; set fixedRotation(bool fixedRotation) { @@ -211,6 +275,11 @@ class PhysicsBody { bool get bullet => _bullet; + /// If true, the body cannot pass through other objects when moved at high + /// speed. Bullet bodies are slower to simulate, so only use this option + /// if neccessary. + /// + /// myBody.bullet = true; set bullet(bool bullet) { _bullet = bullet; @@ -221,6 +290,10 @@ class PhysicsBody { bool _active; + /// An active body is used in the physics simulation. Set this to false if + /// you want to temporarily exclude a body from the simulation. + /// + /// myBody.active = false; bool get active { if (_body != null) return _body.isActive(); @@ -239,6 +312,11 @@ class PhysicsBody { Object _collisionCategory = null; + /// The collision category assigned to this body. The default value is + /// "Default". The body will only collide with bodies that have the either + /// the [collisionMask] set to null or has the category in the mask. + /// + /// myBody.collisionCategory = "Air"; Object get collisionCategory { return _collisionCategory; } @@ -250,6 +328,10 @@ class PhysicsBody { List _collisionMask = null; + /// A list of collision categories that this object will collide with. If set + /// to null (the default value) the body will collide with all other bodies. + /// + /// myBody.collisionMask = ["Air", "Ground"]; List get collisionMask => _collisionMask; set collisionMask(List collisionMask) { @@ -258,10 +340,10 @@ class PhysicsBody { } box2d.Filter get _b2Filter { - print("_physicsNode: $_physicsNode groups: ${_physicsNode._collisionGroups}"); + print("_physicsNode: $_physicsWorld groups: ${_physicsWorld._collisionGroups}"); box2d.Filter f = new box2d.Filter(); - f.categoryBits = _physicsNode._collisionGroups.getBitmaskForKeys([_collisionCategory]); - f.maskBits = _physicsNode._collisionGroups.getBitmaskForKeys(_collisionMask); + f.categoryBits = _physicsWorld._collisionGroups.getBitmaskForKeys([_collisionCategory]); + f.maskBits = _physicsWorld._collisionGroups.getBitmaskForKeys(_collisionMask); print("Filter: $f category: ${f.categoryBits} mask: ${f.maskBits}"); @@ -277,7 +359,7 @@ class PhysicsBody { } } - PhysicsWorld _physicsNode; + PhysicsWorld _physicsWorld; Node _node; box2d.Body _body; @@ -286,62 +368,79 @@ class PhysicsBody { bool _attached = false; + /// Applies a force to the body at the [worldPoint] position in world + /// cordinates. + /// + /// myBody.applyForce(new Offset(0.0, 100.0), myNode.position); void applyForce(Offset force, Point worldPoint) { assert(_body != null); Vector2 b2Force = new Vector2( - force.dx / _physicsNode.b2WorldToNodeConversionFactor, - force.dy / _physicsNode.b2WorldToNodeConversionFactor); + force.dx / _physicsWorld.b2WorldToNodeConversionFactor, + force.dy / _physicsWorld.b2WorldToNodeConversionFactor); Vector2 b2Point = new Vector2( - worldPoint.x / _physicsNode.b2WorldToNodeConversionFactor, - worldPoint.y / _physicsNode.b2WorldToNodeConversionFactor + worldPoint.x / _physicsWorld.b2WorldToNodeConversionFactor, + worldPoint.y / _physicsWorld.b2WorldToNodeConversionFactor ); _body.applyForce(b2Force, b2Point); } + /// Applice a force to the body at the its center of gravity. + /// + /// myBody.applyForce(new Offset(0.0, 100.0)); void applyForceToCenter(Offset force) { assert(_body != null); Vector2 b2Force = new Vector2( - force.dx / _physicsNode.b2WorldToNodeConversionFactor, - force.dy / _physicsNode.b2WorldToNodeConversionFactor); + force.dx / _physicsWorld.b2WorldToNodeConversionFactor, + force.dy / _physicsWorld.b2WorldToNodeConversionFactor); _body.applyForceToCenter(b2Force); } + /// Applies a torque to the body. + /// + /// myBody.applyTorque(10.0); void applyTorque(double torque) { assert(_body != null); - _body.applyTorque(torque / _physicsNode.b2WorldToNodeConversionFactor); + _body.applyTorque(torque / _physicsWorld.b2WorldToNodeConversionFactor); } + /// Applies a linear impulse to the body at the [worldPoint] position in world + /// cordinates. + /// + /// myBody.applyLinearImpulse(new Offset(0.0, 100.0), myNode.position); void applyLinearImpulse(Offset impulse, Point worldPoint, [bool wake = true]) { assert(_body != null); Vector2 b2Impulse = new Vector2( - impulse.dx / _physicsNode.b2WorldToNodeConversionFactor, - impulse.dy / _physicsNode.b2WorldToNodeConversionFactor); + impulse.dx / _physicsWorld.b2WorldToNodeConversionFactor, + impulse.dy / _physicsWorld.b2WorldToNodeConversionFactor); Vector2 b2Point = new Vector2( - worldPoint.x / _physicsNode.b2WorldToNodeConversionFactor, - worldPoint.y / _physicsNode.b2WorldToNodeConversionFactor + worldPoint.x / _physicsWorld.b2WorldToNodeConversionFactor, + worldPoint.y / _physicsWorld.b2WorldToNodeConversionFactor ); _body.applyLinearImpulse(b2Impulse, b2Point, wake); } + /// Applies an angular impulse to the body. + /// + /// myBody.applyAngularImpulse(20.0); void applyAngularImpulse(double impulse) { assert(_body != null); - _body.applyAngularImpulse(impulse / _physicsNode.b2WorldToNodeConversionFactor); + _body.applyAngularImpulse(impulse / _physicsWorld.b2WorldToNodeConversionFactor); } void _attach(PhysicsWorld physicsNode, Node node) { assert(_attached == false); - _physicsNode = physicsNode; + _physicsWorld = physicsNode; // Account for physics groups Point positionWorld = node._positionToPhysics(node.position, node.parent); @@ -419,7 +518,7 @@ class PhysicsBody { void _detach() { if (_attached) { - _physicsNode._bodiesScheduledForDestruction.add(_body); + _physicsWorld._bodiesScheduledForDestruction.add(_body); _attached = false; } } diff --git a/packages/flutter_sprites/lib/src/physics_debug.dart b/packages/flutter_sprites/lib/src/physics_debug.dart index acf77294faf..cb766b8c1aa 100644 --- a/packages/flutter_sprites/lib/src/physics_debug.dart +++ b/packages/flutter_sprites/lib/src/physics_debug.dart @@ -3,7 +3,7 @@ part of flutter_sprites; class _PhysicsDebugDraw extends box2d.DebugDraw { _PhysicsDebugDraw( box2d.ViewportTransform transform, - this.physicsNode + this.physicsWorld ) : super(transform) { appendFlags( box2d.DebugDraw.JOINT_BIT | @@ -12,7 +12,7 @@ class _PhysicsDebugDraw extends box2d.DebugDraw { ); } - PhysicsWorld physicsNode; + PhysicsWorld physicsWorld; PaintingCanvas canvas; @@ -93,12 +93,12 @@ class _PhysicsDebugDraw extends box2d.DebugDraw { Point _toPoint(Vector2 vec) { return new Point( - vec.x * physicsNode.b2WorldToNodeConversionFactor, - vec.y * physicsNode.b2WorldToNodeConversionFactor + vec.x * physicsWorld.b2WorldToNodeConversionFactor, + vec.y * physicsWorld.b2WorldToNodeConversionFactor ); } double _scale(double value) { - return value * physicsNode.b2WorldToNodeConversionFactor; + return value * physicsWorld.b2WorldToNodeConversionFactor; } } diff --git a/packages/flutter_sprites/lib/src/physics_group.dart b/packages/flutter_sprites/lib/src/physics_group.dart index b858a8eda03..a78b0d5e223 100644 --- a/packages/flutter_sprites/lib/src/physics_group.dart +++ b/packages/flutter_sprites/lib/src/physics_group.dart @@ -1,5 +1,12 @@ part of flutter_sprites; +/// A [Node] that acts as a middle layer between a [PhysicsWorld] and a node +/// with an assigned [PhysicsBody]. The group's transformations are limited to +/// [position], [rotation], and uniform [scale]. +/// +/// PhysicsGroup group = new PhysicsGroup(); +/// myWorld.addChild(group); +/// group.addChild(myNode); class PhysicsGroup extends Node { set scaleX(double scaleX) { diff --git a/packages/flutter_sprites/lib/src/physics_joint.dart b/packages/flutter_sprites/lib/src/physics_joint.dart index d5241f0e6ba..d5a0eefd9e4 100644 --- a/packages/flutter_sprites/lib/src/physics_joint.dart +++ b/packages/flutter_sprites/lib/src/physics_joint.dart @@ -2,6 +2,9 @@ part of flutter_sprites; typedef void PhysicsJointBreakCallback(PhysicsJoint joint); +/// A joint connects two physics bodies and restricts their movements. Some +/// types of joints also support motors that adds forces to the connected +/// bodies. abstract class PhysicsJoint { PhysicsJoint(this._bodyA, this._bodyB, this.breakingForce, this.breakCallback) { bodyA._joints.add(this); @@ -10,12 +13,20 @@ abstract class PhysicsJoint { PhysicsBody _bodyA; + /// The first body connected to the joint. + /// + /// PhysicsBody body = myJoint.bodyA; PhysicsBody get bodyA => _bodyA; PhysicsBody _bodyB; + /// The second body connected to the joint. + /// + /// PhysicsBody body = myJoint.bodyB; PhysicsBody get bodyB => _bodyB; + /// The maximum force the joint can handle before it breaks. If set to null, + /// the joint will never break. final double breakingForce; final PhysicsJointBreakCallback breakCallback; @@ -23,33 +34,35 @@ abstract class PhysicsJoint { bool _active = true; box2d.Joint _joint; - PhysicsWorld _physicsNode; + PhysicsWorld _physicsWorld; void _completeCreation() { if (bodyA._attached && bodyB._attached) { - _attach(bodyA._physicsNode); + _attach(bodyA._physicsWorld); } } void _attach(PhysicsWorld physicsNode) { if (_joint == null) { - _physicsNode = physicsNode; + _physicsWorld = physicsNode; _joint = _createB2Joint(physicsNode); - _physicsNode._joints.add(this); + _physicsWorld._joints.add(this); } } void _detach() { if (_joint != null && _active) { - _physicsNode.b2World.destroyJoint(_joint); + _physicsWorld.b2World.destroyJoint(_joint); _joint = null; - _physicsNode._joints.remove(this); + _physicsWorld._joints.remove(this); } _active = false; } box2d.Joint _createB2Joint(PhysicsWorld physicsNode); + /// If the joint is no longer needed, call the the [destroy] method to detach + /// if from its connected bodies. void destroy() { _detach(); } @@ -73,6 +86,26 @@ abstract class PhysicsJoint { } } +/// The revolute joint can be thought of as a hinge, a pin, or an axle. +/// An anchor point is defined in global space. +/// +/// Revolute joints can be given limits so that the bodies can rotate only to a +/// certain point using [lowerAngle], [upperAngle], and [enableLimit]. +/// They can also be given a motor using [enableMotore] together with +/// [motorSpeed] and [maxMotorTorque] so that the bodies will try +/// to rotate at a given speed, with a given torque. +/// +/// Common uses for revolute joints include: +/// - wheels or rollers +/// - chains or swingbridges (using multiple revolute joints) +/// - rag-doll joints +/// - rotating doors, catapults, levers +/// +/// new PhysicsJointRevolute( +/// nodeA.physicsBody, +/// nodeB.physicsBody, +/// nodeB.position +/// ); class PhysicsJointRevolute extends PhysicsJoint { PhysicsJointRevolute( PhysicsBody bodyA, @@ -94,12 +127,24 @@ class PhysicsJointRevolute extends PhysicsJoint { } final Point _worldAnchor; + + /// The lower angle of the limits of this joint, only used if [enableLimit] + /// is set to true. final double lowerAngle; + + /// The upper angle of the limits of this joint, only used if [enableLimit] + /// is set to true. final double upperAngle; + + /// If set to true, the rotation will be limited to a value between + /// [lowerAngle] and [upperAngle]. final bool enableLimit; bool _enableMotor; + /// By setting enableMotor to true, the joint will automatically rotate, e.g. + /// this can be used for creating an engine for a wheel. For this to be + /// useful you also need to set [motorSpeed] and [maxMotorTorque]. bool get enableMotor => _enableMotor; set enableMotor(bool enableMotor) { @@ -112,6 +157,8 @@ class PhysicsJointRevolute extends PhysicsJoint { double _motorSpeed; + /// Sets the motor speed of this joint, will only work if [enableMotor] is + /// set to true and [maxMotorTorque] is set to a non zero value. double get motorSpeed => _motorSpeed; set motorSpeed(double motorSpeed) { @@ -126,6 +173,8 @@ class PhysicsJointRevolute extends PhysicsJoint { double get maxMotorTorque => _maxMotorTorque; + /// Sets the motor torque of this joint, will only work if [enableMotor] is + /// set to true and [motorSpeed] is set to a non zero value. set maxMotorTorque(double maxMotorTorque) { _maxMotorTorque = maxMotorTorque; if (_joint != null) { @@ -156,6 +205,25 @@ class PhysicsJointRevolute extends PhysicsJoint { } } +/// The prismatic joint is probably more commonly known as a slider joint. +/// The two joined bodies have their rotation held fixed relative to each +/// other, and they can only move along a specified axis. +/// +/// Prismatic joints can be given limits so that the bodies can only move +/// along the axis within a specific range. They can also be given a motor so +/// that the bodies will try to move at a given speed, with a given force. +/// +/// Common uses for prismatic joints include: +/// - elevators +/// - moving platforms +/// - sliding doors +/// - pistons +/// +/// new PhysicsJointPrismatic( +/// nodeA.physicsBody, +/// nodeB.physicsBody, +/// new Offset(0.0, 1.0) +/// ); class PhysicsJointPrismatic extends PhysicsJoint { PhysicsJointPrismatic( PhysicsBody bodyA, @@ -174,10 +242,14 @@ class PhysicsJointPrismatic extends PhysicsJoint { _completeCreation(); } + /// Axis that the movement is restricted to (in global space at the time of + /// creation) final Offset axis; bool _enableMotor; + /// For the motor to be effective you also need to set [motorSpeed] and + /// [maxMotorForce]. bool get enableMotor => _enableMotor; set enableMotor(bool enableMotor) { @@ -190,25 +262,29 @@ class PhysicsJointPrismatic extends PhysicsJoint { double _motorSpeed; + /// Sets the motor speed of this joint, will only work if [enableMotor] is + /// set to true and [maxMotorForce] is set to a non zero value. double get motorSpeed => _motorSpeed; set motorSpeed(double motorSpeed) { _motorSpeed = motorSpeed; if (_joint != null) { box2d.PrismaticJoint prismaticJoint = _joint; - prismaticJoint.setMotorSpeed(motorSpeed / _physicsNode.b2WorldToNodeConversionFactor); + prismaticJoint.setMotorSpeed(motorSpeed / _physicsWorld.b2WorldToNodeConversionFactor); } } double _maxMotorForce; + /// Sets the motor force of this joint, will only work if [enableMotor] is + /// set to true and [motorSpeed] is set to a non zero value. double get maxMotorForce => _maxMotorForce; set maxMotorForce(double maxMotorForce) { _maxMotorForce = maxMotorForce; if (_joint != null) { box2d.PrismaticJoint prismaticJoint = _joint; - prismaticJoint.setMaxMotorForce(maxMotorForce / _physicsNode.b2WorldToNodeConversionFactor); + prismaticJoint.setMaxMotorForce(maxMotorForce / _physicsWorld.b2WorldToNodeConversionFactor); } } @@ -223,6 +299,9 @@ class PhysicsJointPrismatic extends PhysicsJoint { } } +/// The weld joint attempts to constrain all relative motion between two bodies. +/// +/// new PhysicsJointWeld(bodyA.physicsJoint, bodyB.physicsJoint) class PhysicsJointWeld extends PhysicsJoint { PhysicsJointWeld( PhysicsBody bodyA, @@ -252,6 +331,22 @@ class PhysicsJointWeld extends PhysicsJoint { } } +/// A pulley is used to create an idealized pulley. The pulley connects two +/// bodies to ground and to each other. As one body goes up, the other goes +/// down. +/// +/// The total length of the pulley rope is conserved according to the initial +/// configuration. +/// +/// new PhysicsJointPulley( +/// nodeA.physicsBody, +/// nodeB.physicsBody, +/// new Point(0.0, 100.0), +/// new Point(100.0, 100.0), +/// nodeA.position, +/// nodeB.position, +/// 1.0 +/// ); class PhysicsJointPulley extends PhysicsJoint { PhysicsJointPulley( PhysicsBody bodyA, @@ -289,18 +384,27 @@ class PhysicsJointPulley extends PhysicsJoint { } } +/// The gear joint can only connect revolute and/or prismatic joints. +/// +/// Like the pulley ratio, you can specify a gear ratio. However, in this case +/// the gear ratio can be negative. Also keep in mind that when one joint is a +/// revolute joint (angular) and the other joint is prismatic (translation), +/// and then the gear ratio will have units of length or one over length. +/// +/// new PhysicsJointGear(nodeA.physicsBody, nodeB.physicsBody); class PhysicsJointGear extends PhysicsJoint { PhysicsJointGear( PhysicsBody bodyA, PhysicsBody bodyB, { double breakingForce, PhysicsJointBreakCallback breakCallback, - this.ratio: 0.0 + this.ratio: 1.0 } ) : super(bodyA, bodyB, breakingForce, breakCallback) { _completeCreation(); } + /// The ratio of the rotation for bodyA relative bodyB. final double ratio; box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { @@ -313,6 +417,8 @@ class PhysicsJointGear extends PhysicsJoint { } } +/// Keeps a fixed distance between two bodies, [anchorA] and [anchorB] are +/// defined in world coordinates. class PhysicsJointDistance extends PhysicsJoint { PhysicsJointDistance( PhysicsBody bodyA, @@ -329,10 +435,21 @@ class PhysicsJointDistance extends PhysicsJoint { _completeCreation(); } + /// The anchor of bodyA in world coordinates at the time of creation. final Point anchorA; + + /// The anchor of bodyB in world coordinates at the time of creation. final Point anchorB; + + /// The desired distance between the joints, if not passed in at creation + /// it will be set automatically to the distance between the anchors at the + /// time of creation. final double length; + + /// Dampening factor. final double dampening; + + /// Dampening frequency. final double frequency; box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { @@ -352,6 +469,8 @@ class PhysicsJointDistance extends PhysicsJoint { } } +/// The wheel joint restricts a point on bodyB to a line on bodyA. The wheel +/// joint also optionally provides a suspension spring. class PhysicsJointWheel extends PhysicsJoint { PhysicsJointWheel( PhysicsBody bodyA, @@ -367,9 +486,16 @@ class PhysicsJointWheel extends PhysicsJoint { _completeCreation(); } + /// The rotational point in global space at the time of creation. final Point anchor; + + /// The axis which to restrict the movement to. final Offset axis; + + /// Dampening factor. final double dampening; + + /// Dampening frequency. final double frequency; box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { @@ -387,6 +513,8 @@ class PhysicsJointWheel extends PhysicsJoint { } } +/// The friction joint is used for top-down friction. The joint provides 2D +/// translational friction and angular friction. class PhysicsJointFriction extends PhysicsJoint { PhysicsJointFriction( PhysicsBody bodyA, diff --git a/packages/flutter_sprites/lib/src/physics_shape.dart b/packages/flutter_sprites/lib/src/physics_shape.dart index 374bb95bb68..8f7eef99808 100644 --- a/packages/flutter_sprites/lib/src/physics_shape.dart +++ b/packages/flutter_sprites/lib/src/physics_shape.dart @@ -1,5 +1,6 @@ part of flutter_sprites; +/// Defines the shape of a [PhysicsBody]. abstract class PhysicsShape { box2d.Shape _b2Shape; @@ -20,6 +21,9 @@ abstract class PhysicsShape { } } +/// Defines a circle shape with a given center [point] and [radius]. +/// +/// var shape = PhysicsShapeCircle(Point.origin, 20.0); class PhysicsShapeCircle extends PhysicsShape { PhysicsShapeCircle(this.point, this.radius); @@ -35,6 +39,14 @@ class PhysicsShapeCircle extends PhysicsShape { } } +/// Defines a polygon shape from a list of [points]; +/// +/// var points = [ +/// new Point(-10.0, 0.0), +/// new Point(0.0, 10.0), +/// new Point(10.0, 0.0) +/// ]; +/// var shape = new PhysicsShapePolygon(points); class PhysicsShapePolygon extends PhysicsShape { PhysicsShapePolygon(this.points); @@ -56,6 +68,9 @@ class PhysicsShapePolygon extends PhysicsShape { } } +/// Defines a box shape from a [width] and [height]. +/// +/// var shape = new PhysicsShapeBox(50.0, 100.0); class PhysicsShapeBox extends PhysicsShape { PhysicsShapeBox( this.width, @@ -84,6 +99,15 @@ class PhysicsShapeBox extends PhysicsShape { } } +/// Defines a chain shape from a set of [points]. This can be used to create +/// a continuous chain of edges or, if [loop] is set to true, concave polygons. +/// +/// var points = [ +/// new Point(-10.0, 0.0), +/// new Point(0.0, 10.0), +/// new Point(10.0, 0.0) +/// ]; +/// var shape = new PhysicsShapeChain(points); class PhysicsShapeChain extends PhysicsShape { PhysicsShapeChain(this.points, [this.loop=false]); @@ -109,6 +133,12 @@ class PhysicsShapeChain extends PhysicsShape { } } +/// Defines a single edge line shape from [pointA] to [pointB]. +/// +/// var shape = new PhysicsShapeEdge( +/// new Point(20.0, 20.0), +/// new Point(50.0, 20.0) +/// ); class PhysicsShapeEdge extends PhysicsShape { PhysicsShapeEdge(this.pointA, this.pointB); @@ -131,6 +161,11 @@ class PhysicsShapeEdge extends PhysicsShape { } } +/// A group combines several [shapes] into a single shape. +/// +/// var s0 = new PhysicsShapeCircle(new Point(-10.0, 0.0), 20.0); +/// var s1 = new PhysicsShapeCircle(new Point(10.0, 0.0), 20.0); +/// var shape = new PhysicsShapeGroup([s0, s1]); class PhysicsShapeGroup extends PhysicsShape { PhysicsShapeGroup(this.shapes); diff --git a/packages/flutter_sprites/lib/src/physics_world.dart b/packages/flutter_sprites/lib/src/physics_world.dart index e61c065ce24..5e7ba722dd2 100644 --- a/packages/flutter_sprites/lib/src/physics_world.dart +++ b/packages/flutter_sprites/lib/src/physics_world.dart @@ -9,6 +9,14 @@ enum PhysicsContactType { typedef void PhysicsContactCallback(PhysicsContactType type, PhysicsContact contact); +/// A [Node] that performs a 2D physics simulation on any children with a +/// [PhysicsBody] attached. To simulate grand children, they need to be placed +/// in a [PhysicsGroup]. +/// +/// The PhysicsWorld uses Box2D.dart to perform the actual simulation, but +/// wraps its behavior in a way that is more integrated with the sprite node +/// tree. If needed, you can still access the Box2D world through the [b2World] +/// property. class PhysicsWorld extends Node { PhysicsWorld(Offset gravity) { b2World = new box2d.World.withGravity( @@ -35,6 +43,7 @@ class PhysicsWorld extends Node { b2World.debugDraw = _debugDraw; } + /// The Box2D world used to perform the physics simulations. box2d.World b2World; _ContactHandler _contactHandler; @@ -47,14 +56,19 @@ class PhysicsWorld extends Node { List _bodiesScheduledForUpdate = []; + /// If set to true, a debug image of all physics shapes and joints will + /// be drawn on top of the [SpriteBox]. bool drawDebug = false; Matrix4 _debugDrawTransform ; _PhysicsDebugDraw _debugDraw; + /// The conversion factor that is used to convert points in the physics world + /// node to points in the Box2D physics simulation. double b2WorldToNodeConversionFactor = 10.0; + /// The gravity vector used in the simulation. Offset get gravity { Vector2 g = b2World.getGravity(); return new Offset(g.x, g.y); @@ -66,12 +80,14 @@ class PhysicsWorld extends Node { gravity.dy / b2WorldToNodeConversionFactor)); } + /// If set to true, objects can fall asleep if the haven't moved in a while. bool get allowSleep => b2World.isAllowSleep(); set allowSleep(bool allowSleep) { b2World.setAllowSleep(allowSleep); } + /// True if sub stepping should be used in the simulation. bool get subStepping => b2World.isSubStepping(); set subStepping(bool subStepping) { @@ -227,6 +243,26 @@ class PhysicsWorld extends Node { } } + /// Adds a contact callback, the callback will be invoked when bodies collide + /// in the world. + /// + /// To match specific sets bodies, use the [tagA] and [tagB] + /// which will be matched to the tag property that is set on the + /// [PhysicsBody]. If [tagA] or [tagB] is set to null, it will match any + /// body. + /// + /// By default, callbacks are made at four different times during a + /// collision; preSolve, postSolve, begin, and end. If you are only interested + /// in one of these events you can pass in a [type]. + /// + /// myWorld.addContactCallback( + /// (PhysicsContactType type, PhysicsContact contact) { + /// print("Collision between ship and asteroid"); + /// }, + /// "Ship", + /// "Asteroid", + /// PhysicsContactType.begin + /// ); void addContactCallback(PhysicsContactCallback callback, Object tagA, Object tagB, [PhysicsContactType type]) { _contactHandler.addContactCallback(callback, tagA, tagB, type); } @@ -238,12 +274,21 @@ class PhysicsWorld extends Node { super.paint(canvas); } + /// Draws the debug data of the physics world, normally this method isn't + /// invoked directly. Instead, set the [drawDebug] property to true. void paintDebug(PaintingCanvas canvas) { _debugDraw.canvas = canvas; b2World.drawDebugData(); } } +/// Contains information about a physics collision and is normally passed back +/// in callbacks from the [PhysicsWorld]. +/// +/// void myCallback(PhysicsContactType type, PhysicsContact contact) { +/// if (contact.isTouching) +/// print("Bodies are touching"); +/// } class PhysicsContact { PhysicsContact( this.nodeA, @@ -256,13 +301,29 @@ class PhysicsContact { this.touchingNormal ); + /// The first node as matched in the rules set when adding the callback. final Node nodeA; + + /// The second node as matched in the rules set when adding the callback. final Node nodeB; + + /// The first shape as matched in the rules set when adding the callback. final PhysicsShape shapeA; + + /// The second shape as matched in the rules set when adding the callback. final PhysicsShape shapeB; + + /// True if the two nodes are touching. final isTouching; + + /// To ignore the collision to take place, you can set isEnabled to false + /// during the preSolve phase. bool isEnabled; + + /// List of points that are touching, in world coordinates. final List touchingPoints; + + /// The normal from [shapeA] to [shapeB] at the touchingPoint. final Offset touchingNormal; }