8 minutes
Dart Box2D Fundamentals: Fixtures
Fixtures
Fixures describe the size, shape and physical properties, like material, of an object. One body can have more than one fixture, its center of mass will be affected by the properties and arrangement of its fixtures. The fixtures are used to decide how bodies will react when they collide with each other.
A fixture can exist out of the following properties:
- Shape, either a Polygon, Circle, Edge or Chain shape.
- Restitution, how bouncy the fixture is.
- Friction, how much friction a fixture will have, aka slipperiness.
- Density, How heavy it is, in relation to the area.
Don’t worry if you are not grasping what each property does, we will go through all of them and do some experiments with them as well. Let’s start first with a simple dynamic body, like we did in the Bodies article. Rewrite the Box2DAndFlame
constructor so that it will look like this:
...
Box2DAndFlame() : _world = Box2D.World.withGravity(Box2D.Vector2(0, 9.81)) {
var bodyDef = Box2D.BodyDef();
bodyDef.type = Box2D.BodyType.DYNAMIC; // The body type.
bodyDef.position = Box2D.Vector2(100, 100); // The position of the body.
bodyDef.angle = 1; // The angle of the body.
var dynamicBody1 =
_world.createBody(bodyDef); // Add the bodyDef to the world.
...
Source: /examples/fixtures/lib/main.dart
Shapes
Each fixture you create requires a shape, the shape is used for collision checks with other fixtures. There are four kinds of shapes, a circle, polygon, edge and a chain shape. We are going to talk about the first three shapes. Let’s start with a circle shape:
...
var circleShape = Box2D.CircleShape();
circleShape.p.setFrom(Box2D.Vector2(
10, 20)); // The position, it is relative to the body position.
circleShape.radius = 20; // Radius of the circle.
...
Source: /examples/fixtures/lib/main.dart
And let’s also create a fixture to use with the shape:
...
var fixtureDef = Box2D.FixtureDef();
fixtureDef.shape = circleShape; // We add the shape to the fixture.
dynamicBody1.createFixtureFromFixtureDef(
fixtureDef); // And then add the fixture to the body.
...
Source: /examples/fixtures/lib/main.dart
When we run the game you should see a circle falling down your screen:
The coordinates of the circle are always relative to the body’s position. We set the position of the body at (100, 100), but we attached a circle that had an offset of (10, 20). So the circle will be at (110, 120).
Next step is to use a polygon shape. With polygons you can set each vertex individually, using those vertexes you can create boxes or lines, or even custom shapes. Let’s try a custom shape:
...
var polygonShape = Box2D.PolygonShape();
polygonShape.set(
vertices, vertices.length); // Set the vertices to the shape.
fixtureDef.shape = polygonShape; // Changing the shape.
bodyDef.position = Box2D.Vector2(200, 100); // Move the body.
bodyDef.angle = 0; // Reseting the angle.
var dynamicBody2 = _world.createBody(bodyDef);
dynamicBody2.createFixtureFromFixtureDef(fixtureDef);
...
Source: /examples/fixtures/lib/main.dart
And if we run the game now we see our custom shape:
When using polygons there are a few things you have to keep in mind. Firstly you can only use 8 vertices per polygon. Technically speaking you could use more, but the Box2D does not allow that because the more vertices you use, the lower your performance will be. If you do want to create complexer polygons you should triangulate them into smaller triangles shapes and append them to your body as fixtures. But that is a series on it’s own that I will try to tackle on a later date.
Another requirements is that your vertices must be specified in a counter-clockwise order, and that the final polygon is a convex one. A convex polygon is one which, if you were to go around its edges you would always turn the same way at each corner. You can still use concave polygon by applying triangulation (because triangles are always convex).
If you want to create a simple rectangular fixture, you can use the setAsBox
method like we did in the previous article:
...
polygonShape.setAsBox(20, 10, Box2D.Vector2(70, 20),
0); // A 40x20 rectangle that is moved a bit to the right.
var dynamicBody3 = _world.createBody(bodyDef);
dynamicBody3.createFixtureFromFixtureDef(fixtureDef);
...
Source: /examples/fixtures/lib/main.dart
This will result in the following:
The first two parameters of the setAsBox
are the half-width and half-height of the box, and it is centered at the location of the body it gets attached to. As you can see we change only the shape, this is because the fixture already has a reference to our polygonShape
and that is why we can reuse it.
Sometimes you want a simple line instead of a solid shape. This can be done using the edge shape. You can set two points which will be the ends of the edge. Let’s make a static body with a line fixture, so that objects can fall onto it:
...
bodyDef.type = Box2D.BodyType.STATIC;
bodyDef.position = Box2D.Vector2(100, 200); // Somewhere down on our screen.
var edgeShape = Box2D.EdgeShape();
edgeShape.set(Box2D.Vector2(-50, 0), Box2D.Vector2(200, 0));
fixtureDef.shape = edgeShape;
var staticBody = _world.createBody(bodyDef);
staticBody.createFixtureFromFixtureDef(fixtureDef);
...
Source: /examples/fixtures/lib/main.dart
And you will see the bodies fall on to our new line:
Density
As you might have wondered already, the bodies don’t fall over when they hit the edge. This is because we are currently not applying a density to our fixtures. Lets try that:
...
var fixtureDef = Box2D.FixtureDef();
fixtureDef.density = 1;
...
Source: /examples/fixtures/lib/main-with-density.dart
If you run the program now, you see them tip over:
In this example only the custom polygon moves, but because we are using the same fixture definition for all of them, this will affect them all. And because of that our bodies weight as much as their visual size suggests. The circle would be the lightest and the rectangle would be the heaviest.
Multiple fixtures
Instead of having multiple bodies we can also use one body with multiple fixtures. To do so is quite easy, where we call createFixtureFromFixtureDef
, instead of using dynamicBody1
, dynamicBody2
and dynamicBody3
we just say dynamicBody1
:
...
dynamicBody1.createFixtureFromFixtureDef(fixtureDef);
...
Source: /examples/fixtures/lib/main-with-multiple-fixtures.dart
As you can see this causes all the fixture locations to be relative to the dynamicBody1
. Fixtures can be placed anywhere on the body, and as you can see even with empty space between them.
Friction
So we now know how to setup a body with multiple fixtures, let’s setup a body with four square fixtures:
...
Box2DAndFlame() : _world = Box2D.World.withGravity(Box2D.Vector2(0, 9.81)) {
// Setup a dynamic body.
var bodyDef = Box2D.BodyDef();
bodyDef.type = Box2D.BodyType.DYNAMIC; // The body type.
bodyDef.position = Box2D.Vector2(100, 100); // The position of the body.
var dynamicBody = _world.createBody(bodyDef);
// Prepare a shape definition.
var polygonShape = Box2D.PolygonShape();
var fixtureDef = Box2D.FixtureDef();
fixtureDef.shape = polygonShape;
fixtureDef.density = 1;
fixtureDef.friction = 0;
// Add four squares around our body center.
for (var i = 0; i < 4; i++) {
var pos = Box2D.Vector2(i * 10.0, i * 10.0);
polygonShape.setAsBox(10, 10, pos, 0);
dynamicBody.createFixtureFromFixtureDef(fixtureDef);
}
// Make a sloped static floor.
bodyDef.type = Box2D.BodyType.STATIC;
bodyDef.position = Box2D.Vector2(100, 200);
var staticBody = _world.createBody(bodyDef);
// Create a slightly sloped edge.
var edgeShape = Box2D.EdgeShape();
edgeShape.set(Box2D.Vector2(-100, -30), Box2D.Vector2(100, 0));
fixtureDef.shape = edgeShape;
staticBody.createFixtureFromFixtureDef(fixtureDef);
...
Source: /examples/fixtures/lib/main-with-friction.dart
As you can see the body slides down the slope, when a fixture contacts the ground it’s friction setting is used to calculate how much it slides.
...
fixtureDef.friction = 0;
...
Source: /examples/fixtures/lib/main-with-friction.dart
We set our friction to 0. A friction can be any value between 0 and 1. Zero being completely frictionless. If two fixtures collide, the resulting friction tends towards the lower of both friction values.
If you would change the friction of the fixture definition in the for-loop than the body could behave differently. I suggest you play around with it yourself, just keep in mind to change it right before the createFixtureFromFixtureDef
.
Restitution
Restitution makes a fixture bouncy or not. And just like with friction, you can give it a value between 0 and 1. Where zero means no bouncing at all and one means it is super bouncy, all the energy will be conserved. If two fixtures collide, the resulting restitution tends towards the higher of both restitution values.
You can play around with restitution values just like we did with the friction, try to set a different value for each fixture:
fixtureDef.restitution = 0;
Fixture attributes at run-time
Sometimes you want to read out the attributes of a fixture at run-time, or update them depending on in-game events. You can change the friction, density and restitution by using the setter methods on a fixture (these methods exists both on the fixture definition as the fixture itself):
fixture.setDensity(...);
fixture.setRestitution(...);
fixture.setFriction(...);
Loop over the fixtures of a body
You can loop over the fixtures of a body using the getFixtureList
methods. It returns the first fixture in a linked list of fixtures, you have to call getNext
on the returned fixture to go to the next one:
...
for (var fixture = body.getFixtureList();
fixture != null;
fixture = fixture.getNext()) {
...
Source: /examples/fixtures/lib/main.dart
If there is only one fixture on the body you can just use the first element:
var fixture = body.getFixtureList();
And finally destruction
You can destroy and remove a fixture from a body by calling the `destroyFixture function:
var fixture = dynamicBody.createFixtureFromFixtureDef(fixtureDef);
...
dynamicBody.destroyFixture(fixtureDef);
You can also listen to fixture destruction by using the DestructionListener class.
Previous: Bodies • Next up: Worlds
See Dart Box2D Fundamentals series for all the articles.
Author: The explanations and code examples are based on https://www.iforce2d.net/b2dtut, I highly suggests to read those articles as well.