Setting up the project

Firstly you should install Flutter if you have not yet done so. Checkout Flutter Get started for more information.

After you have installed Flutter we can create a new project like so:

flutter create ./box2d_and_flame

Add the following to your dependency list in pubspec.yaml:

...

  flame: ^0.24.0
  box2d_flame: ^0.4.6

...

Source: /examples/setup/pubspec.yaml

And run flutter pub get after saving the file.

After retrieving the dependencies clear the content in your main.dart and lets write our own.

First let us define a class called Box2DAndFlame. This will be the base of our game, it will extends the Flame Game class, providing us with an update and render method:

import 'package:box2d_flame/box2d.dart' as Box2D;
import 'package:flame/game.dart';
import 'package:flutter/material.dart';

class Box2DAndFlame extends Game {
  Box2D.World _world;

  Box2DAndFlame() : _world = Box2D.World.withGravity(Box2D.Vector2(0, 9.81));

  @override
  void render(Canvas canvas) {
    _world.forEachBody((body) {
      for (var fixture = body.getFixtureList();
          fixture != null;
          fixture = fixture.getNext()) {
        final color = body.getType() == Box2D.BodyType.STATIC
            ? Colors.red
            : body.getType() == Box2D.BodyType.DYNAMIC
                ? Colors.blue
                : Colors.green;

        final Box2D.Shape shape = fixture.getShape();
        if (shape is Box2D.EdgeShape) {
          canvas.save();
          canvas.translate(body.position.x, body.position.y);
          canvas.drawLine(
            Offset(shape.vertex1.x, shape.vertex1.y),
            Offset(shape.vertex2.x, shape.vertex2.y),
            Paint()..color = color,
          );
          canvas.restore();
        } else if (shape is Box2D.CircleShape) {
          canvas.save();
          canvas.translate(body.position.x, body.position.y);
          canvas.rotate(body.getAngle());
          canvas.drawCircle(
            Offset(shape.p.x, shape.p.y),
            shape.radius,
            Paint()
              ..color = color
              ..style = PaintingStyle.stroke
              ..strokeWidth = 0.5,
          );
          canvas.drawCircle(
            Offset(shape.p.x, shape.p.y),
            shape.radius,
            Paint()..color = color.withAlpha(50),
          );

          canvas.drawLine(
            Offset(shape.p.x, shape.p.y),
            Offset(shape.p.x + shape.radius, shape.p.y),
            Paint()
              ..color = color
              ..style = PaintingStyle.stroke
              ..strokeWidth = 0.5,
          );
          canvas.restore();
        } else if (shape is Box2D.PolygonShape) {
          final List<Box2D.Vector2> vertices =
              Box2D.Vec2Array().get(shape.count);

          for (int i = 0; i < shape.count; ++i) {
            body.getWorldPointToOut(shape.vertices[i],
                vertices[i]); // Copy world point to our List.
          }

          final List<Offset> points = [];
          for (int i = 0; i < shape.count; i++) {
            points.add(Offset(
                vertices[i].x, vertices[i].y)); // Convert Vertice to Offset.
          }

          final path = Path()
            ..addPolygon(
                points, true); // Create a path based on the points and draw it.

          canvas.drawPath(
            path,
            Paint()
              ..color = color
              ..style = PaintingStyle.stroke
              ..strokeWidth = 0.5,
          );
          canvas.drawPath(path, Paint()..color = color.withAlpha(50));
        }
      }
    });
  }

  @override
  void update(double delta) {
    var velocityIterations = 8; // How strongly to correct velocity.
    var positionIterations = 3; // How strongly to correct position.
    _world.stepDt(delta, velocityIterations, positionIterations);
  }
}


...

Source: /examples/setup/lib/main.dart

Note: Flame provides a Box2DGame class as well, in most situations you would use that for ease-of-use. But I decided to go with the Game class so you get a better hands-on experience with Box2D.

As you can see we also have a property called _world. The world is the main object in Box2D, it handles both the creation and deletion of physics objects.

The render method goes through each object in the world and simply render its shape to our screen. Don’t worry if you are not fully understanding what is happening there, in a later article we will go more in-depth with the rendering, for now we are just going to focus on the physics part.

We also call the stepDt in the update method, this ensures our world goes to the next step in the physics simulation. The velocity iterations and position iterations affect the way objects react when they collide. When two objects collide they get stuck(overlapped) and some calculation is needed get them not to overlap, how to move or rotate them. The higher the values the better your simulation, but at the cost of performance.

In a later article we will also go more in-depth into the World object and his capabilities, for now just accept that it is there.

Now we only need to add a main method and we can run the application:

...

  runApp(Box2DAndFlame().widget);
}

Source: /examples/setup/lib/main.dart

You can run the application using flutter run.


Previous: IntroductionNext up: Bodies


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.