Create a Breakout Game in Flutter With Flame and Forge2D – Part 1

Learn how to create a Flutter version of the classic Breakout game using Flame and Forge2D. By Michael Jordan.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Understanding Forge2D Units and Coordinates

You've created a GameWidget in Flutter, added a Forge2dGameWorld and created two rigid bodies: a dynamic ball and a static arena. In doing so, you used values for the ball's radius and position and the location of the arena's walls. So, what's the context for these units and coordinates?

The Forge2D world is more or less infinite, or at least as limitless as a simulated 2D world can be. This vastness is because you specify most units in Forge2D using the double data type, which can represent a wide range of values. But what do these unit values mean?

Units

Erin Catto, the creator of Box2D — the direct ancestor of Forge2D — wrote Box2D to be tuned for a world of MKS units (meters/kilograms/seconds). This unit tuning is inherent to Forge2D as well. Catto wrote in the Box2D documentation:

"... it is tempting to use pixels as your units. Unfortunately, this will lead to a poor simulation and possibly weird behavior. An object of length 200 pixels would be seen by Box2D as the size of a 45 story building."

A 45-story building? How is that? Well, a length of 200.0 in Forge2D is essentially 200 meters. So, a story on a building is roughly 4.4 meters or 14 feet; 200 divided by 4.4 is 45.4545, thus a 45-story building.

Catto recommends keeping the size of moving objects between 0.1 and 10 meters, roughly from the size of a soup can up to a school bus. He also recommends keeping the world size to less than 2 kilometers.

The remaining units used in Forge2D are angles, measured in radians and not degrees; mass, measured in kilograms and time, measured in seconds.

Coordinate Systems

The Forge2D game world uses a typical, two-dimensional Cartesian coordinate system. You learned that while length units aren't explicitly defined, you must think of them in terms of meters. Lengths, forces, distances and positions are all defined by two-dimensional vectors of meter values. For example, a vector of Vector2(2,3) extends two meters in the x-direction and three meters in the y-direction in Forge2D.

2D Cartesian Coordinate System

Flutter also uses a two-dimensional Cartesian coordinate system, but its units are device-independent pixels with an inverted y-axis.

2D Screen Coordinates

So, what does this mean for your Breakout game? First, you must remember to use device-independent pixels when giving size, position and offset values to Flutter widgets and meters when giving similar values to Forge2D components. Second, when transitioning between the two coordinate spaces, you need to convert between screen coordinates, which are Flutter's device-independent pixels, and world coordinates, which are Forge2D's metric units.

Flame and Forge2D provide tools to help you with these translations. For example, the flame_forge2d package inverts Forge2D world y-axis values to align with screen coordinates. In addition, there are several methods for converting positions between the screen and the Forge2D world.

The Flame Camera

When you peer into the Forge2D world through the GameWidget, you're looking through the lens of a Camera. The Camera translates the Forge2D coordinate system to your screen size. Forge2DGame provides your game with a reasonable default camera. There's no translation, so the camera's position is set to the origin of the Forge2D world. The camera zoom is 10.0. Remember Catto's recommendation not to use pixel units for your Forge2D world? A camera zoom effectively makes 10 device-independent pixels equal to one meter. For example, a GameWidget with a size of 330 by 760 pixels, a typical screen size, means the visible Forge2D world is 33.0 by 76.0 meters.

Breakout Game Metrics

Looking at the settings used in your Breakout game, you have a 2.0-meter ball (1.0 radius) moving in an arena of approximately 33.0 by 76.0 meters on most mobile devices. Or, in English units, a 6.5-foot ball in a 36.0-by-81.1-yard arena. A Chrome browser will have a more variable arena size. That's a big ball in a large arena.

Why does this matter? Speed. The ball in a Breakout game moves fast, traveling the length of the game area in 1 or 2 seconds or less. That means the ball travels at 160 km/hr (100 m/hr) or more. Whew!

Fine-tuning the parameters of bodies in a Forge2D world is a bit of intuition mixed with some experimentation. In the remaining sections, you'll use parameters that produce a playable game, but feel free to experiment and try your values.

Adjusting the Camera, Arena and Ball Body Properties

Applying your newfound knowledge to your Breakout game, you'll first adjust the size of the Forge2D world your Breakout game occupies. Add the following constructor at the top of Forge2dGameWorld in forge2d_game_world.dart:

  Forge2dGameWorld() : super(gravity: Vector2.zero(), zoom: 20);

The default gravitational force is Vector2(0.0, 10.0); this is why the ball falls to the bottom of the screen. Remember, flame_forge2d inverts the y-axis to align with the screen. Breakout doesn't need gravity, so setting gravity to Vector2.zero() turns it off, like floating in space.

Setting the zoom parameter halves the size of the world from its previous setting by equating 20 device-independent screen pixels to one meter. As a result, the screen area of 330 by 760 pixels is now an 18.0-by-40.5-yard arena.

Run your project now, and you'll see the ball has doubled in size and remains stationary in the center of the GameWidget.

Adjusted Gravity and Zoom

That's not very interesting. So next, you'll adjust the properties of the arena and ball. Open arena.dart, and change the FixtureDef to add density, friction and restitution properties to the arena walls.

    for (var index = 0; index < chain.childCount; index++) {
      arenaBody.createFixture(
        FixtureDef(chain.childEdge(index))
          ..density = 2000.0
          ..friction = 0.0
          ..restitution = 0.4,
      );
    }

A density of 2,000 kg/m^2 is a substantial concrete wall. In addition, the surface is frictionless, so there's no loss of moment from the ball contacting the wall. Finally, give the wall some elastic recoil with a restitution value of 40%.

Now, adjust the properties of the ball. Open ball.dart, and change FixtureDef to add restitution and density to the ball:

    final fixtureDef = FixtureDef(shape)
      ..restitution = 1.0
      ..density = 1.0;

Restitution of 100% is a very bouncy ball. A density of 1 kg/m^2 is acceptable for the ball.

To complete your adjustments, open forge2d_game_world.dart. Since the world has no gravity now, you'll need to apply a force to the ball to set it in motion. Make the following changes to your Forge2D game:

class Forge2dGameWorld extends Forge2DGame {
  Forge2dGameWorld() : super(gravity: Vector2.zero(), zoom: 20);

  // 1
  late final Ball _ball;

  @override
  Future<void> onLoad() async {
    await _initializeGame();

    // 2
    _ball.body.applyLinearImpulse(Vector2(-10, -10));
  }

  Future<void> _initializeGame() async {
    final arena = Arena();
    await add(arena);
    // 3
    _ball = Ball(
      
      radius: 0.5,
      position: size / 2,
    );
    await add(_ball);
  }
}

In this code, you:

  1. Create a private variable reference for the ball.
  2. Apply a force to the ball after Forge2D has completed creating the ball and adding it to the world.
  3. Use the class level _ball variable and adjust the size of the ball to have a radius of 0.5.

Build and run your project. The ball now ricochets off the arena walls in the GameWidget area. Congratulations! You've taken another important step toward creating your Breakout game.

Ball Bouncing in Arena

Michael Jordan

Contributors

Michael Jordan

Author

Simone Alessandria

Tech Editor

Adriana Kutenko

Illustrator

Aldo Olivares

Final Pass Editor

Brian Moakley

Team Lead

Over 300 content creators. Join our team.