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
| Term | Meaning |
|---|---|
| World units | Physics uses metres, not pixels. Forge2DGame zooms by 10 by default, so 1 unit = 10 pixels. Keep objects between about 0.1 and 10 units. |
| Body | A physical object. dynamic moves and falls, static never moves (ground, walls), kinematic moves only when you tell it (moving platforms). |
| Shape | CircleShape, PolygonShape (boxes: PolygonShape()..setAsBoxXY(w/2, h/2)), EdgeShape (lines). |
| Fixture | Attaches a shape to a body with material values. |
density | Heavier or lighter |
friction | 0 = ice, 1 = rubber |
restitution | Bounciness: 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.