mirror of
https://github.com/flame-engine/flame.git
synced 2025-10-31 00:48:47 +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)
|
- [`DistanceJoint`](#distancejoint)
|
||||||
- [`FrictionJoint`](#frictionjoint)
|
- [`FrictionJoint`](#frictionjoint)
|
||||||
- GearJoint
|
- GearJoint
|
||||||
- MotorJoint
|
- [`MotorJoint`](#motorjoint)
|
||||||
- MouseJoint
|
- MouseJoint
|
||||||
- PrismaticJoint
|
- PrismaticJoint
|
||||||
- PulleyJoint
|
- PulleyJoint
|
||||||
@ -160,3 +160,57 @@ values:
|
|||||||
|
|
||||||
In other words, the former simulates the friction, when the body is sliding and the latter simulates
|
In other words, the former simulates the friction, when the body is sliding and the latter simulates
|
||||||
the friction when the body is spinning.
|
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/constant_volume_joint.dart';
|
||||||
import 'package:examples/stories/bridge_libraries/forge2d/joints/distance_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/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/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';
|
||||||
@ -37,6 +38,7 @@ void main() {
|
|||||||
'constant_volume_joint': ConstantVolumeJointExample.new,
|
'constant_volume_joint': ConstantVolumeJointExample.new,
|
||||||
'distance_joint': DistanceJointExample.new,
|
'distance_joint': DistanceJointExample.new,
|
||||||
'friction_joint': FrictionJointExample.new,
|
'friction_joint': FrictionJointExample.new,
|
||||||
|
'motor_joint': MotorJointExample.new,
|
||||||
};
|
};
|
||||||
final game = routes[page]?.call();
|
final game = routes[page]?.call();
|
||||||
if (game != null) {
|
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/constant_volume_joint.dart';
|
||||||
import 'package:examples/stories/bridge_libraries/forge2d/joints/distance_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/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/mouse_joint_example.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';
|
||||||
@ -117,18 +118,24 @@ void addJointsStories(Dashbook dashbook) {
|
|||||||
'ConstantVolumeJoint',
|
'ConstantVolumeJoint',
|
||||||
(DashbookContext ctx) => GameWidget(game: ConstantVolumeJointExample()),
|
(DashbookContext ctx) => GameWidget(game: ConstantVolumeJointExample()),
|
||||||
codeLink: link('constant_volume_joint.dart'),
|
codeLink: link('constant_volume_joint.dart'),
|
||||||
info: BlobExample.description,
|
info: ConstantVolumeJointExample.description,
|
||||||
)
|
)
|
||||||
.add(
|
.add(
|
||||||
'DistanceJoint',
|
'DistanceJoint',
|
||||||
(DashbookContext ctx) => GameWidget(game: DistanceJointExample()),
|
(DashbookContext ctx) => GameWidget(game: DistanceJointExample()),
|
||||||
codeLink: link('distance_joint.dart'),
|
codeLink: link('distance_joint.dart'),
|
||||||
info: BlobExample.description,
|
info: DistanceJointExample.description,
|
||||||
)
|
)
|
||||||
.add(
|
.add(
|
||||||
'FrictionJoint',
|
'FrictionJoint',
|
||||||
(DashbookContext ctx) => GameWidget(game: FrictionJointExample()),
|
(DashbookContext ctx) => GameWidget(game: FrictionJointExample()),
|
||||||
codeLink: link('friction_joint.dart'),
|
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';
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
|
||||||
class ConstantVolumeJointExample extends Forge2DGame with TapDetector {
|
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
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
super.onLoad();
|
super.onLoad();
|
||||||
|
|||||||
@ -4,6 +4,11 @@ import 'package:flame/input.dart';
|
|||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
|
||||||
class DistanceJointExample extends Forge2DGame with TapDetector {
|
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
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
super.onLoad();
|
super.onLoad();
|
||||||
|
|||||||
@ -4,6 +4,10 @@ import 'package:flame/input.dart';
|
|||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
|
||||||
class FrictionJointExample extends Forge2DGame with TapDetector {
|
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));
|
FrictionJointExample() : super(gravity: Vector2.all(0));
|
||||||
|
|
||||||
late Wall border;
|
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