Flutter Tutorial

Flutter Intermediate Lesson 115 of 135 3 min read

Physics in Flutter Games with Forge2D: Gravity, Bodies and Bouncing

Add real physics to a Flame game with flame_forge2d: Forge2DGame, bodies, shapes, fixtures, bounciness and tap to drop balls. Runs in your browser.

On this page

Writing gravity and bounces by hand (as in the game loop) is fine for simple games. For stacking boxes, rolling balls, ropes and Angry Birds-style games, use a physics engine. Forge2D is a Dart port of Box2D, the engine behind many famous 2D games, and flame_forge2d connects it to Flame.

Install

flutter pub add flame_forge2d

Tap to drop balls

import 'dart:math';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';

void main() => runApp(GameWidget(game: PhysicsGame()));

class PhysicsGame extends Forge2DGame with TapCallbacks {
  PhysicsGame() : super(gravity: Vector2(0, 15)); // world units per second²

  final random = Random();

  @override
  Future<void> onLoad() async {
    await super.onLoad();
    final view = camera.visibleWorldRect; // the area of the world on screen
    world.add(Ground(view));
    for (var i = 0; i < 5; i++) {
      world.add(Ball(Vector2(-10 + i * 5.0, -10), random));
    }
  }

  @override
  void onTapDown(TapDownEvent event) {
    world.add(Ball(screenToWorld(event.canvasPosition), random));
  }
}

class Ball extends BodyComponent {
  Ball(this.start, Random random)
      : radius = 1 + random.nextDouble() * 1.5,
        super(paint: Paint()..color = Colors.primaries[random.nextInt(Colors.primaries.length)]);

  final Vector2 start;
  final double radius;

  @override
  Body createBody() {
    final shape = CircleShape()..radius = radius;
    final fixture = FixtureDef(shape, density: 1, friction: 0.4, restitution: 0.6);
    final def = BodyDef(position: start, type: BodyType.dynamic);
    return world.createBody(def)..createFixture(fixture);
  }
}

class Ground extends BodyComponent {
  Ground(this.view);
  final Rect view;

  @override
  Body createBody() {
    final body = world.createBody(BodyDef(type: BodyType.static));
    final bl = Vector2(view.left, view.bottom - 1);
    final br = Vector2(view.right, view.bottom - 1);
    final tl = Vector2(view.left, view.top - 50);
    final tr = Vector2(view.right, view.top - 50);
    // Floor and two walls
    body.createFixture(FixtureDef(EdgeShape()..set(bl, br), friction: 0.6));
    body.createFixture(FixtureDef(EdgeShape()..set(bl, tl)));
    body.createFixture(FixtureDef(EdgeShape()..set(br, tr)));
    return body;
  }
}

Tap anywhere to drop a new ball. They fall, bounce, roll and stack, with no movement code from you.

The key ideas

TermMeaning
World unitsPhysics uses metres, not pixels. Forge2DGame zooms by 10 by default, so 1 unit = 10 pixels. Keep objects between about 0.1 and 10 units.
BodyA physical object. dynamic moves and falls, static never moves (ground, walls), kinematic moves only when you tell it (moving platforms).
ShapeCircleShape, PolygonShape (boxes: PolygonShape()..setAsBoxXY(w/2, h/2)), EdgeShape (lines).
FixtureAttaches a shape to a body with material values.
densityHeavier or lighter
friction0 = ice, 1 = rubber
restitutionBounciness: 0 = no bounce, 1 = bounces forever

Making things move

Do not set position of a physics body every frame; push it instead:

body.applyLinearImpulse(Vector2(0, -20));  // a jump
body.applyForce(Vector2(5, 0));            // a steady push
body.linearVelocity = Vector2(10, 0);      // set speed directly

Collisions in Forge2D

Add ContactCallbacks to a BodyComponent and set userData: this in its BodyDef:

class Ball extends BodyComponent with ContactCallbacks {
  @override
  Body createBody() {
    final def = BodyDef(position: Vector2.zero(), type: BodyType.dynamic, userData: this);
    return world.createBody(def)..createFixture(FixtureDef(CircleShape()..radius = 1));
  }

  @override
  void beginContact(Object other, Contact contact) {
    if (other is Ground) {
      // play a sound, lose a life...
    }
  }
}

Try it yourself

Add a Box body using PolygonShape()..setAsBoxXY(1.5, 1.5) and drop boxes with a long press, so balls and boxes pile up together.

Practise in the playground Updated