mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-02 11:43:19 +08:00
docs: PulleyJoint documentation and example (#2425)
PulleyJoint documentation and example
This commit is contained in:
@ -24,7 +24,7 @@ Currently, Forge2D supports the following joints:
|
|||||||
- [`MotorJoint`](#motorjoint)
|
- [`MotorJoint`](#motorjoint)
|
||||||
- [`MouseJoint`](#mousejoint)
|
- [`MouseJoint`](#mousejoint)
|
||||||
- PrismaticJoint
|
- PrismaticJoint
|
||||||
- PulleyJoint
|
- [`PulleyJoint`](#pulleyjoint)
|
||||||
- RevoluteJoint
|
- RevoluteJoint
|
||||||
- RopeJoint
|
- RopeJoint
|
||||||
- WeldJoint
|
- WeldJoint
|
||||||
@ -272,3 +272,69 @@ final mouseJointDef = MouseJointDef()
|
|||||||
|
|
||||||
- `target`: The initial world target point. This is assumed to coincide with the body anchor
|
- `target`: The initial world target point. This is assumed to coincide with the body anchor
|
||||||
initially.
|
initially.
|
||||||
|
|
||||||
|
|
||||||
|
### `PulleyJoint`
|
||||||
|
|
||||||
|
A `PulleyJoint` is used to create an idealized pulley. The pulley connects two bodies to the 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:
|
||||||
|
|
||||||
|
```text
|
||||||
|
length1 + length2 == constant
|
||||||
|
```
|
||||||
|
|
||||||
|
You can supply a ratio that simulates a block and tackle. This causes one side of the pulley to
|
||||||
|
extend faster than the other. At the same time the constraint force is smaller on one side than the
|
||||||
|
other. You can use this to create a mechanical leverage.
|
||||||
|
|
||||||
|
```text
|
||||||
|
length1 + ratio * length2 == constant
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, if the ratio is 2, then `length1` will vary at twice the rate of `length2`. Also the
|
||||||
|
force in the rope attached to the first body will have half the constraint force as the rope
|
||||||
|
attached to the second body.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final pulleyJointDef = PulleyJointDef()
|
||||||
|
..initialize(
|
||||||
|
firstBody,
|
||||||
|
secondBody,
|
||||||
|
firstPulley.worldCenter,
|
||||||
|
secondPulley.worldCenter,
|
||||||
|
firstBody.worldCenter,
|
||||||
|
secondBody.worldCenter,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
world.createJoint(PulleyJoint(pulleyJointDef));
|
||||||
|
```
|
||||||
|
|
||||||
|
```{flutter-app}
|
||||||
|
:sources: ../../examples
|
||||||
|
:page: pulley_joint
|
||||||
|
:subfolder: stories/bridge_libraries/forge2d/joints
|
||||||
|
:show: code popup
|
||||||
|
```
|
||||||
|
|
||||||
|
The `initialize` method of `PulleyJointDef` requires two ground anchors, two dynamic bodies and
|
||||||
|
their anchor points, and a pulley ratio.
|
||||||
|
|
||||||
|
- `b1`, `b2`: Two dynamic bodies connected with the joint
|
||||||
|
- `ga1`, `ga2`: Two ground anchors
|
||||||
|
- `anchor1`, `anchor2`: Anchors on the dynamic bodies the joint will be attached to
|
||||||
|
- `r`: Pulley ratio to simulate a block and tackle
|
||||||
|
|
||||||
|
`PulleyJoint` also provides the current lengths:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
joint.getCurrentLengthA()
|
||||||
|
joint.getCurrentLengthB()
|
||||||
|
```
|
||||||
|
|
||||||
|
```{warning}
|
||||||
|
`PulleyJoint` can get a bit troublesome by itself. They often work better when
|
||||||
|
combined with prismatic joints. You should also cover the the anchor points
|
||||||
|
with static shapes to prevent one side from going to zero length.
|
||||||
|
```
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import 'package:examples/stories/bridge_libraries/forge2d/joints/distance_joint.
|
|||||||
import 'package:examples/stories/bridge_libraries/forge2d/joints/friction_joint.dart';
|
import 'package:examples/stories/bridge_libraries/forge2d/joints/friction_joint.dart';
|
||||||
import 'package:examples/stories/bridge_libraries/forge2d/joints/motor_joint.dart';
|
import 'package:examples/stories/bridge_libraries/forge2d/joints/motor_joint.dart';
|
||||||
import 'package:examples/stories/bridge_libraries/forge2d/joints/mouse_joint.dart';
|
import 'package:examples/stories/bridge_libraries/forge2d/joints/mouse_joint.dart';
|
||||||
|
import 'package:examples/stories/bridge_libraries/forge2d/joints/pulley_joint.dart';
|
||||||
import 'package:examples/stories/camera_and_viewport/camera_and_viewport.dart';
|
import 'package:examples/stories/camera_and_viewport/camera_and_viewport.dart';
|
||||||
import 'package:examples/stories/collision_detection/collision_detection.dart';
|
import 'package:examples/stories/collision_detection/collision_detection.dart';
|
||||||
import 'package:examples/stories/components/components.dart';
|
import 'package:examples/stories/components/components.dart';
|
||||||
@ -39,6 +40,7 @@ void main() {
|
|||||||
'friction_joint': FrictionJointExample.new,
|
'friction_joint': FrictionJointExample.new,
|
||||||
'motor_joint': MotorJointExample.new,
|
'motor_joint': MotorJointExample.new,
|
||||||
'mouse_joint': MouseJointExample.new,
|
'mouse_joint': MouseJointExample.new,
|
||||||
|
'pulley_joint': PulleyJointExample.new,
|
||||||
};
|
};
|
||||||
final game = routes[page]?.call();
|
final game = routes[page]?.call();
|
||||||
if (game != null) {
|
if (game != null) {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import 'package:examples/stories/bridge_libraries/forge2d/joints/distance_joint.
|
|||||||
import 'package:examples/stories/bridge_libraries/forge2d/joints/friction_joint.dart';
|
import 'package:examples/stories/bridge_libraries/forge2d/joints/friction_joint.dart';
|
||||||
import 'package:examples/stories/bridge_libraries/forge2d/joints/motor_joint.dart';
|
import 'package:examples/stories/bridge_libraries/forge2d/joints/motor_joint.dart';
|
||||||
import 'package:examples/stories/bridge_libraries/forge2d/joints/mouse_joint.dart';
|
import 'package:examples/stories/bridge_libraries/forge2d/joints/mouse_joint.dart';
|
||||||
|
import 'package:examples/stories/bridge_libraries/forge2d/joints/pulley_joint.dart';
|
||||||
import 'package:examples/stories/bridge_libraries/forge2d/raycast_example.dart';
|
import 'package:examples/stories/bridge_libraries/forge2d/raycast_example.dart';
|
||||||
import 'package:examples/stories/bridge_libraries/forge2d/revolute_joint_example.dart';
|
import 'package:examples/stories/bridge_libraries/forge2d/revolute_joint_example.dart';
|
||||||
import 'package:examples/stories/bridge_libraries/forge2d/sprite_body_example.dart';
|
import 'package:examples/stories/bridge_libraries/forge2d/sprite_body_example.dart';
|
||||||
@ -137,5 +138,11 @@ void addJointsStories(Dashbook dashbook) {
|
|||||||
(DashbookContext ctx) => GameWidget(game: MouseJointExample()),
|
(DashbookContext ctx) => GameWidget(game: MouseJointExample()),
|
||||||
codeLink: link('mouse_joint.dart'),
|
codeLink: link('mouse_joint.dart'),
|
||||||
info: MouseJointExample.description,
|
info: MouseJointExample.description,
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
'PulleyJoint',
|
||||||
|
(DashbookContext ctx) => GameWidget(game: PulleyJointExample()),
|
||||||
|
codeLink: link('pulley_joint.dart'),
|
||||||
|
info: PulleyJointExample.description,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,12 @@ class MotorJointExample extends Forge2DGame with TapDetector, HasDraggables {
|
|||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
super.onLoad();
|
super.onLoad();
|
||||||
|
|
||||||
final box = Box(size / 2, 2, 1);
|
final box = Box(
|
||||||
|
startPosition: size / 2,
|
||||||
|
width: 2,
|
||||||
|
height: 1,
|
||||||
|
bodyType: BodyType.static,
|
||||||
|
);
|
||||||
add(box);
|
add(box);
|
||||||
|
|
||||||
ball = Ball(Vector2(size.x / 2, size.y / 2 - 5));
|
ball = Ball(Vector2(size.x / 2, size.y / 2 - 5));
|
||||||
|
|||||||
@ -0,0 +1,79 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:examples/stories/bridge_libraries/forge2d/utils/balls.dart';
|
||||||
|
import 'package:examples/stories/bridge_libraries/forge2d/utils/boxes.dart';
|
||||||
|
import 'package:flame/events.dart';
|
||||||
|
import 'package:flame/input.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
|
||||||
|
class PulleyJointExample extends Forge2DGame with TapDetector, HasDraggables {
|
||||||
|
static const description = '''
|
||||||
|
This example shows how to use a `PulleyJoint`. Drag one of the boxes and see
|
||||||
|
how the other one gets moved by the pulley
|
||||||
|
''';
|
||||||
|
|
||||||
|
late final Ball firstPulley;
|
||||||
|
late final Ball secondPulley;
|
||||||
|
late final PulleyJoint joint;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
super.onLoad();
|
||||||
|
|
||||||
|
firstPulley = Ball(Vector2(size.x * 0.33, 10), bodyType: BodyType.static);
|
||||||
|
secondPulley = Ball(Vector2(size.x * 0.66, 10), bodyType: BodyType.static);
|
||||||
|
|
||||||
|
final firstBox = DraggableBox(
|
||||||
|
startPosition: Vector2(size.x * 0.33, size.y / 2),
|
||||||
|
width: 5,
|
||||||
|
height: 10,
|
||||||
|
);
|
||||||
|
final secondBox = DraggableBox(
|
||||||
|
startPosition: Vector2(size.x * 0.66, size.y / 2),
|
||||||
|
width: 7,
|
||||||
|
height: 10,
|
||||||
|
);
|
||||||
|
addAll([firstBox, secondBox, firstPulley, secondPulley]);
|
||||||
|
|
||||||
|
await Future.wait([
|
||||||
|
firstBox.loaded,
|
||||||
|
secondBox.loaded,
|
||||||
|
firstPulley.loaded,
|
||||||
|
secondPulley.loaded
|
||||||
|
]);
|
||||||
|
|
||||||
|
createJoint(firstBox, secondBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
void createJoint(Box first, Box second) {
|
||||||
|
final pulleyJointDef = PulleyJointDef()
|
||||||
|
..initialize(
|
||||||
|
first.body,
|
||||||
|
second.body,
|
||||||
|
firstPulley.center,
|
||||||
|
secondPulley.center,
|
||||||
|
first.body.worldPoint(Vector2(0, -first.height / 2)),
|
||||||
|
second.body.worldPoint(Vector2(0, -second.height / 2)),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
joint = PulleyJoint(pulleyJointDef);
|
||||||
|
world.createJoint(joint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
super.render(canvas);
|
||||||
|
|
||||||
|
final firstBodyAnchor = worldToScreen(joint.anchorA).toOffset();
|
||||||
|
final firstPulleyAnchor =
|
||||||
|
worldToScreen(joint.getGroundAnchorA()).toOffset();
|
||||||
|
canvas.drawLine(firstBodyAnchor, firstPulleyAnchor, debugPaint);
|
||||||
|
|
||||||
|
final secondBodyAnchor = worldToScreen(joint.anchorB).toOffset();
|
||||||
|
final secondPulleyAnchor =
|
||||||
|
worldToScreen(joint.getGroundAnchorB()).toOffset();
|
||||||
|
canvas.drawLine(secondBodyAnchor, secondPulleyAnchor, debugPaint);
|
||||||
|
|
||||||
|
canvas.drawLine(firstPulleyAnchor, secondPulleyAnchor, debugPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,13 +7,14 @@ class Ball extends BodyComponent with ContactCallbacks {
|
|||||||
late Paint originalPaint;
|
late Paint originalPaint;
|
||||||
bool giveNudge = false;
|
bool giveNudge = false;
|
||||||
final double radius;
|
final double radius;
|
||||||
|
final BodyType bodyType;
|
||||||
final Vector2 _position;
|
final Vector2 _position;
|
||||||
double _timeSinceNudge = 0.0;
|
double _timeSinceNudge = 0.0;
|
||||||
static const double _minNudgeRest = 2.0;
|
static const double _minNudgeRest = 2.0;
|
||||||
|
|
||||||
final Paint _blue = BasicPalette.blue.paint();
|
final Paint _blue = BasicPalette.blue.paint();
|
||||||
|
|
||||||
Ball(this._position, {this.radius = 2}) {
|
Ball(this._position, {this.radius = 2, this.bodyType = BodyType.dynamic}) {
|
||||||
originalPaint = randomPaint();
|
originalPaint = randomPaint();
|
||||||
paint = originalPaint;
|
paint = originalPaint;
|
||||||
}
|
}
|
||||||
@ -36,7 +37,7 @@ class Ball extends BodyComponent with ContactCallbacks {
|
|||||||
userData: this,
|
userData: this,
|
||||||
angularDamping: 0.8,
|
angularDamping: 0.8,
|
||||||
position: _position,
|
position: _position,
|
||||||
type: BodyType.dynamic,
|
type: bodyType,
|
||||||
);
|
);
|
||||||
|
|
||||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||||
|
|||||||
@ -1,22 +1,72 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/events.dart';
|
||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
|
||||||
class Box extends BodyComponent {
|
class Box extends BodyComponent {
|
||||||
final Vector2 _position;
|
final Vector2 startPosition;
|
||||||
final double _width;
|
final double width;
|
||||||
final double _height;
|
final double height;
|
||||||
|
final BodyType bodyType;
|
||||||
|
|
||||||
Box(this._position, this._width, this._height);
|
Box({
|
||||||
|
required this.startPosition,
|
||||||
|
required this.width,
|
||||||
|
required this.height,
|
||||||
|
this.bodyType = BodyType.dynamic,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Body createBody() {
|
Body createBody() {
|
||||||
final shape = PolygonShape()
|
final shape = PolygonShape()
|
||||||
..setAsBox(_width / 2, _height / 2, Vector2.zero(), 0);
|
..setAsBox(width / 2, height / 2, Vector2.zero(), 0);
|
||||||
final fixtureDef = FixtureDef(shape, friction: 0.3);
|
final fixtureDef = FixtureDef(shape, friction: 0.3, density: 10);
|
||||||
final bodyDef = BodyDef(
|
final bodyDef = BodyDef(
|
||||||
userData: this, // To be able to determine object in collision
|
userData: this, // To be able to determine object in collision
|
||||||
position: _position,
|
position: startPosition,
|
||||||
|
type: bodyType,
|
||||||
);
|
);
|
||||||
|
|
||||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DraggableBox extends Box with Draggable {
|
||||||
|
MouseJoint? mouseJoint;
|
||||||
|
late final groundBody = world.createBody(BodyDef());
|
||||||
|
|
||||||
|
DraggableBox({
|
||||||
|
required super.startPosition,
|
||||||
|
required super.width,
|
||||||
|
required super.height,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool onDragUpdate(DragUpdateInfo info) {
|
||||||
|
final mouseJointDef = MouseJointDef()
|
||||||
|
..maxForce = 3000 * body.mass * 10
|
||||||
|
..dampingRatio = 0
|
||||||
|
..frequencyHz = 20
|
||||||
|
..target.setFrom(info.eventPosition.game)
|
||||||
|
..collideConnected = false
|
||||||
|
..bodyA = groundBody
|
||||||
|
..bodyB = body;
|
||||||
|
|
||||||
|
if (mouseJoint == null) {
|
||||||
|
mouseJoint = MouseJoint(mouseJointDef);
|
||||||
|
world.createJoint(mouseJoint!);
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseJoint?.setTarget(info.eventPosition.game);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool onDragEnd(DragEndInfo info) {
|
||||||
|
if (mouseJoint == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
world.destroyJoint(mouseJoint!);
|
||||||
|
mouseJoint = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user