mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-02 20:13:50 +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)
|
||||
- [`MouseJoint`](#mousejoint)
|
||||
- PrismaticJoint
|
||||
- PulleyJoint
|
||||
- [`PulleyJoint`](#pulleyjoint)
|
||||
- RevoluteJoint
|
||||
- RopeJoint
|
||||
- WeldJoint
|
||||
@ -272,3 +272,69 @@ final mouseJointDef = MouseJointDef()
|
||||
|
||||
- `target`: The initial world target point. This is assumed to coincide with the body anchor
|
||||
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/motor_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/collision_detection/collision_detection.dart';
|
||||
import 'package:examples/stories/components/components.dart';
|
||||
@ -39,6 +40,7 @@ void main() {
|
||||
'friction_joint': FrictionJointExample.new,
|
||||
'motor_joint': MotorJointExample.new,
|
||||
'mouse_joint': MouseJointExample.new,
|
||||
'pulley_joint': PulleyJointExample.new,
|
||||
};
|
||||
final game = routes[page]?.call();
|
||||
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/motor_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/revolute_joint_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()),
|
||||
codeLink: link('mouse_joint.dart'),
|
||||
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 {
|
||||
super.onLoad();
|
||||
|
||||
final box = Box(size / 2, 2, 1);
|
||||
final box = Box(
|
||||
startPosition: size / 2,
|
||||
width: 2,
|
||||
height: 1,
|
||||
bodyType: BodyType.static,
|
||||
);
|
||||
add(box);
|
||||
|
||||
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;
|
||||
bool giveNudge = false;
|
||||
final double radius;
|
||||
final BodyType bodyType;
|
||||
final Vector2 _position;
|
||||
double _timeSinceNudge = 0.0;
|
||||
static const double _minNudgeRest = 2.0;
|
||||
|
||||
final Paint _blue = BasicPalette.blue.paint();
|
||||
|
||||
Ball(this._position, {this.radius = 2}) {
|
||||
Ball(this._position, {this.radius = 2, this.bodyType = BodyType.dynamic}) {
|
||||
originalPaint = randomPaint();
|
||||
paint = originalPaint;
|
||||
}
|
||||
@ -36,7 +37,7 @@ class Ball extends BodyComponent with ContactCallbacks {
|
||||
userData: this,
|
||||
angularDamping: 0.8,
|
||||
position: _position,
|
||||
type: BodyType.dynamic,
|
||||
type: bodyType,
|
||||
);
|
||||
|
||||
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';
|
||||
|
||||
class Box extends BodyComponent {
|
||||
final Vector2 _position;
|
||||
final double _width;
|
||||
final double _height;
|
||||
final Vector2 startPosition;
|
||||
final double width;
|
||||
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
|
||||
Body createBody() {
|
||||
final shape = PolygonShape()
|
||||
..setAsBox(_width / 2, _height / 2, Vector2.zero(), 0);
|
||||
final fixtureDef = FixtureDef(shape, friction: 0.3);
|
||||
..setAsBox(width / 2, height / 2, Vector2.zero(), 0);
|
||||
final fixtureDef = FixtureDef(shape, friction: 0.3, density: 10);
|
||||
final bodyDef = BodyDef(
|
||||
userData: this, // To be able to determine object in collision
|
||||
position: _position,
|
||||
position: startPosition,
|
||||
type: bodyType,
|
||||
);
|
||||
|
||||
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