mirror of
https://github.com/flame-engine/flame.git
synced 2025-10-30 16:36:57 +08:00
docs: MotorJoint doc and example (#2394)
Documentation covering MotorJoint and a usage example.
This commit is contained in:
@ -21,7 +21,7 @@ Currently, Forge2D supports the following joints:
|
||||
- [`DistanceJoint`](#distancejoint)
|
||||
- [`FrictionJoint`](#frictionjoint)
|
||||
- GearJoint
|
||||
- MotorJoint
|
||||
- [`MotorJoint`](#motorjoint)
|
||||
- MouseJoint
|
||||
- PrismaticJoint
|
||||
- PulleyJoint
|
||||
@ -160,3 +160,57 @@ values:
|
||||
|
||||
In other words, the former simulates the friction, when the body is sliding and the latter simulates
|
||||
the friction when the body is spinning.
|
||||
|
||||
|
||||
### `MotorJoint`
|
||||
|
||||
A `MotorJoint` is used to control the relative motion between two bodies. A typical usage is to
|
||||
control the movement of a dynamic body with respect to the fixed point, for example to create
|
||||
animations.
|
||||
|
||||
A `MotorJoint` lets you control the motion of a body by specifying target position and rotation
|
||||
offsets. You can set the maximum motor force and torque that will be applied to reach the target
|
||||
position and rotation. If the body is blocked, it will stop and the contact forces will be
|
||||
proportional the maximum motor force and torque.
|
||||
|
||||
```dart
|
||||
final motorJointDef = MotorJointDef()
|
||||
..initialize(first, second)
|
||||
..maxTorque = 1000
|
||||
..maxForce = 1000
|
||||
..correctionFactor = 0.1;
|
||||
|
||||
world.createJoint(MotorJoint(motorJointDef));
|
||||
```
|
||||
|
||||
A `MotorJointDef` has three optional parameters:
|
||||
|
||||
- `maxForce`: the maximum translational force which will be applied to the joined body to reach the
|
||||
target position.
|
||||
|
||||
- `maxTorque`: the maximum angular force which will be applied to the joined body to reach the
|
||||
target rotation.
|
||||
|
||||
- `correctionFactor`: position correction factor in range [0, 1]. It adjusts the joint's response to
|
||||
deviation from target position. A higher value makes the joint respond faster, while a lower value
|
||||
makes it respond slower. If the value is set too high, the joint may overcompensate and oscillate,
|
||||
becoming unstable. If set too low, it may respond too slowly.
|
||||
|
||||
The linear and angular offsets are the target distance and angle that the bodies should achieve
|
||||
relative to each other's position and rotation. By default, the linear target will be the distance
|
||||
between the two body centers and the angular target will be the relative rotation of the bodies.
|
||||
Use the `setLinearOffset(Vector2)` and `setLinearOffset(double)` methods of the `MotorJoint` to set
|
||||
the desired relative translation and rotate between the bodies.
|
||||
|
||||
For example, this code increments the angular offset of the joint every update cycle, causing the
|
||||
body to rotate.
|
||||
|
||||
```dart
|
||||
@override
|
||||
void update(double dt) {
|
||||
super.update(dt);
|
||||
|
||||
final angularOffset = joint.getAngularOffset() + motorSpeed * dt;
|
||||
joint.setAngularOffset(angularOffset);
|
||||
}
|
||||
```
|
||||
|
||||
@ -8,6 +8,7 @@ import 'package:examples/stories/bridge_libraries/forge2d/flame_forge2d.dart';
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/joints/constant_volume_joint.dart';
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/joints/distance_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/camera_and_viewport/camera_and_viewport.dart';
|
||||
import 'package:examples/stories/collision_detection/collision_detection.dart';
|
||||
import 'package:examples/stories/components/components.dart';
|
||||
@ -37,6 +38,7 @@ void main() {
|
||||
'constant_volume_joint': ConstantVolumeJointExample.new,
|
||||
'distance_joint': DistanceJointExample.new,
|
||||
'friction_joint': FrictionJointExample.new,
|
||||
'motor_joint': MotorJointExample.new,
|
||||
};
|
||||
final game = routes[page]?.call();
|
||||
if (game != null) {
|
||||
|
||||
@ -11,6 +11,7 @@ import 'package:examples/stories/bridge_libraries/forge2d/joint_example.dart';
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/joints/constant_volume_joint.dart';
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/joints/distance_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/mouse_joint_example.dart';
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/raycast_example.dart';
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/revolute_joint_example.dart';
|
||||
@ -117,18 +118,24 @@ void addJointsStories(Dashbook dashbook) {
|
||||
'ConstantVolumeJoint',
|
||||
(DashbookContext ctx) => GameWidget(game: ConstantVolumeJointExample()),
|
||||
codeLink: link('constant_volume_joint.dart'),
|
||||
info: BlobExample.description,
|
||||
info: ConstantVolumeJointExample.description,
|
||||
)
|
||||
.add(
|
||||
'DistanceJoint',
|
||||
(DashbookContext ctx) => GameWidget(game: DistanceJointExample()),
|
||||
codeLink: link('distance_joint.dart'),
|
||||
info: BlobExample.description,
|
||||
info: DistanceJointExample.description,
|
||||
)
|
||||
.add(
|
||||
'FrictionJoint',
|
||||
(DashbookContext ctx) => GameWidget(game: FrictionJointExample()),
|
||||
codeLink: link('friction_joint.dart'),
|
||||
info: BlobExample.description,
|
||||
info: FrictionJointExample.description,
|
||||
)
|
||||
.add(
|
||||
'MotorJoint',
|
||||
(DashbookContext ctx) => GameWidget(game: MotorJointExample()),
|
||||
codeLink: link('motor_joint.dart'),
|
||||
info: MotorJointExample.description,
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,6 +6,11 @@ import 'package:flame/input.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
|
||||
class ConstantVolumeJointExample extends Forge2DGame with TapDetector {
|
||||
static const description = '''
|
||||
This example shows how to use a `ConstantVolumeJoint`. Tap the screen to add
|
||||
a bunch off balls, that maintain a constant volume within them.
|
||||
''';
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
super.onLoad();
|
||||
|
||||
@ -4,6 +4,11 @@ import 'package:flame/input.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
|
||||
class DistanceJointExample extends Forge2DGame with TapDetector {
|
||||
static const description = '''
|
||||
This example shows how to use a `DistanceJoint`. Tap the screen to add a
|
||||
pair of balls joined with a `DistanceJoint`.
|
||||
''';
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
super.onLoad();
|
||||
|
||||
@ -4,6 +4,10 @@ import 'package:flame/input.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
|
||||
class FrictionJointExample extends Forge2DGame with TapDetector {
|
||||
static const description = '''
|
||||
This example shows how to use a `FrictionJoint`. Tap the screen to move the
|
||||
ball around and observe it slows down due to the friction force.
|
||||
''';
|
||||
FrictionJointExample() : super(gravity: Vector2.all(0));
|
||||
|
||||
late Wall border;
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
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 MotorJointExample extends Forge2DGame with TapDetector, HasDraggables {
|
||||
static const description = '''
|
||||
This example shows how to use a `MotorJoint`. The ball spins around the
|
||||
center point. Tap the screen to change the direction.
|
||||
''';
|
||||
|
||||
late Ball ball;
|
||||
late MotorJoint joint;
|
||||
final motorSpeed = 1;
|
||||
|
||||
bool clockWise = true;
|
||||
|
||||
MotorJointExample() : super(gravity: Vector2.zero());
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
super.onLoad();
|
||||
|
||||
final box = Box(size / 2, 2, 1);
|
||||
add(box);
|
||||
|
||||
ball = Ball(Vector2(size.x / 2, size.y / 2 - 5));
|
||||
add(ball);
|
||||
|
||||
await Future.wait([ball.loaded, box.loaded]);
|
||||
|
||||
joint = createJoint(ball.body, box.body);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onTapDown(TapDownInfo details) async {
|
||||
super.onTapDown(details);
|
||||
clockWise = !clockWise;
|
||||
}
|
||||
|
||||
MotorJoint createJoint(Body first, Body second) {
|
||||
final motorJointDef = MotorJointDef()
|
||||
..initialize(first, second)
|
||||
..maxForce = 1000
|
||||
..maxTorque = 1000
|
||||
..correctionFactor = 0.1;
|
||||
|
||||
final joint = MotorJoint(motorJointDef);
|
||||
world.createJoint(joint);
|
||||
return joint;
|
||||
}
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
super.update(dt);
|
||||
|
||||
var deltaOffset = motorSpeed * dt;
|
||||
if (clockWise) {
|
||||
deltaOffset = -deltaOffset;
|
||||
}
|
||||
|
||||
final linearOffsetX = joint.getLinearOffset().x + deltaOffset;
|
||||
final linearOffsetY = joint.getLinearOffset().y + deltaOffset;
|
||||
final linearOffset = Vector2(linearOffsetX, linearOffsetY);
|
||||
final angularOffset = joint.getAngularOffset() + deltaOffset;
|
||||
|
||||
joint.setLinearOffset(linearOffset);
|
||||
joint.setAngularOffset(angularOffset);
|
||||
}
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
super.render(canvas);
|
||||
|
||||
final p1 = worldToScreen(joint.anchorA);
|
||||
final p2 = worldToScreen(joint.anchorB);
|
||||
canvas.drawLine(p1.toOffset(), p2.toOffset(), debugPaint);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
|
||||
class Box extends BodyComponent {
|
||||
final Vector2 _position;
|
||||
final double _width;
|
||||
final double _height;
|
||||
|
||||
Box(this._position, this._width, this._height);
|
||||
|
||||
@override
|
||||
Body createBody() {
|
||||
final shape = PolygonShape()
|
||||
..setAsBox(_width / 2, _height / 2, Vector2.zero(), 0);
|
||||
final fixtureDef = FixtureDef(shape, friction: 0.3);
|
||||
final bodyDef = BodyDef(
|
||||
userData: this, // To be able to determine object in collision
|
||||
position: _position,
|
||||
);
|
||||
|
||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user