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 2 of 4 of this article. Click here to view the first page.

Creating the Ball

FlameGame, and by extension, Forge2DGame, is a component-based game framework. It manages a tree of components, similar to how Flutter manages a tree of widgets. The game loop repeatedly calls the update and render methods of the components you add to your game, allowing you to interact with components and add game logic.

To create a ball, you need to describe the physical properties of the ball as a rigid body for Forge2D and wrap it in a component for Flame to manage. You’ll provide this description by declaring a Ball class that extends from a BodyComponent.

Defining a Ball’s Physical Properties

Bodies are the fundamental objects in the physics scene. They hold a rigid body’s physical properties. There are three types of bodies in Forge2D: static, dynamic and kinematic:

  • Static bodies don’t move. The bricks in the brick wall will be static bodies.
  • Dynamic bodies react to forces. Forge2D updates dynamic bodies while obeying Newton’s laws of motion. The ball and paddle are dynamic bodies.
  • Kinematic bodies are a hybrid between static and dynamic bodies. A Ferris wheel is an example of a kinematic body. The Ferris wheel position remains fixed, but the motion of the Ferris wheel rotating around its center is dynamic. The Breakout game doesn’t use kinematic bodies.

Fixtures are the shape of a body. Forge2D uses fixtures to determine collisions between bodies. Bodies can have zero or more fixtures. A body with no fixtures is relatively meaningless, as fixtures give the body a physical presence in the Forge2D world. Fixtures have a shape and density, thus providing mass to the body. For example, the ball in your game will have a single circular shape fixture. So why would a body have multiple fixtures? Consider a fan with four blades. A fan body would have four fixtures, a polygon shape for each fan blade positioned at 90-degree intervals around the body’s center.

Create a new folder named components in the lib folder. You’ll keep your game components files in this folder. Then, create a ball.dart file in this folder, and add the following lines of code to this file:

import 'package:flame_forge2d/flame_forge2d.dart';

import '../forge2d_game_world.dart';

// 1
class Ball extends BodyComponent<Forge2dGameWorld> {
  // 2
  final Vector2 position;
  final double radius;

  Ball({required this.position, required this.radius});

  // 3
  @override
  Body createBody() {
    // 4
    final bodyDef = BodyDef()
      ..type = BodyType.dynamic
      ..position = position;

    // 5
    final ball = world.createBody(bodyDef);

    // 6
    final shape = CircleShape()..radius = radius;

    // 7
    final fixtureDef = FixtureDef(shape);

    // 8
    ball.createFixture(fixtureDef);
    return ball;
  }
}

Going through this step-by-step:

  1. You begin by declaring your Ball to be a BodyComponent, a rigid body in Forge2D and a component for Flame. Then, specify Forge2dGameWorld as the BodyComponent game world type. This association gives your ball class access to the public properties of your game world.
  2. When you create your ball, you specify its initial position and size.
  3. You tell Forge2D how to create your body in createBody. Forge2D calls createBody when you add a body to the game world.
  4. Define the base properties of the ball’s body. The ball moves freely around the world, so its type is dynamic. The position will be passed into the constructor when adding the ball to the world; this allows you to set the beginning position of the ball.
  5. Use the body definition to create a rigid body in your game world. world is an inherited property from BodyComponent to your Forge2dGameWorld instance.
  6. If the Body is the soul of the rigid body, Fixtures are its skin and bones. To define a fixture, you begin by defining a shape. In this case, your ball will have a circle shape in this 2D world.
  7. Using the shape, you create a fixture definition.
  8. Use the ball’s createFixture method to create and add the fixture to the ball’s body.

Next, create the ball and add it to your Forge2D world by opening the file forge2d_game_world.dart and creating a private method named _initializeGame. Now, call the routine from onLoad like so:

import 'package:flame_forge2d/flame_forge2d.dart';

import 'components/ball.dart';

class Forge2dGameWorld extends Forge2DGame {
  @override
  Future<void> onLoad() async {
    await _initializeGame();
  }

  Future<void> _initializeGame() async {
    final ball = Ball(
      radius: 1.0,
      position: size / 2,
    );
    await add(ball);
  }
}

Give the ball a radius of 1.0 and a starting position in the center of the game area. size provides you with the size of the visible game area in the Forge2dGameWorld. A discussion of Forge2D units, coordinates, viewports and camera are coming. So, use these values for now with the understanding that you’ll get an explanation shortly.

Build and run your project, and you’ll see a small white circle representing your ball falling off the bottom of the screen.

Ball body falling to the bottom of the screen

What’s going on? Where did the ball go? The ball is still there in your Forge2D world. It’s just forever falling into the vast, dark emptiness beyond the bottom of the screen, much like Voyager 1 and 2 speeding through space.

A ball falling off the screen isn’t much fun. So next, you’ll learn how to build walls to constrain the ball to the game area.

Creating a Game Arena

A Forge2D game world is more like a vast, empty space than a world. You create the bodies and other components of your world to fill the space.

The user plays Breakout within an enclosed area, like an arena. You’ll now create an arena to constrain the ball to a fixed region of your world. Create an arena.dart file in the components folder, and add the following lines of code to this file:

import 'package:flame_forge2d/flame_forge2d.dart';

import '../forge2d_game_world.dart';

// 1
class Arena extends BodyComponent<Forge2dGameWorld> {
  Vector2? size;

  // 2
  Arena({this.size}) {
    assert(size == null || size!.x >= 1.0 && size!.y >= 1.0);
  }

  late Vector2 arenaSize;

  // 3
  @override
  Future<void> onLoad() {
    arenaSize = size ?? gameRef.size;
    return super.onLoad();
  }

  // 4
  @override
  Body createBody() {
    final bodyDef = BodyDef()
      ..position = Vector2(0, 0)
      ..type = BodyType.static;

    final arenaBody = world.createBody(bodyDef);

    // 5
    final vertices = <Vector2>[
      arenaSize,
      Vector2(0, arenaSize.y),
      Vector2(0, 0),
      Vector2(arenaSize.x, 0),
    ];

    // 6
    final chain = ChainShape()..createLoop(vertices);

    // 7
    for (var index = 0; index < chain.childCount; index++) {
      arenaBody.createFixture(FixtureDef(chain.childEdge(index)));
    }

    return arenaBody;
  }
}

The arena has many of the same elements you learned when creating the ball's body. It can be helpful to go over what's the same and what's new step-by-step:

  1. The arena is another body component in your game world. It acts like a fence enclosing the objects in your game.
  2. The Arena constructor has an optional size parameter for defining the extent of the rectangular arena. Leave this blank; the onLoad method will set the size to fill the available widget space.
  3. onLoad is a BodyComponent state method. onLoad is called before createBody to allow for any initialization you might need to perform. Here, you're getting the size of the visible area in Forge2D world coordinates. You'll learn more about world coordinates in the next section.
  4. You've seen this method before. Here's where you build your arena body. Setting the position to the world origin aligns the arena with the upper left-hand corner of the GameWidget. Since the arena walls won't move, the arena's body type is static.
  5. With the arena body created, you now need to define its fixtures. The arena's fixtures will be the walls that enclose the area. Forge2D has a ChainShape, a free-form sequence of line segments perfect for the arena enclosure. So first, you create a list of the locations of the arena's four corners.
  6. Then, create a ChainShape from the vertex list. The createLoop method automatically closes the loop for you.
  7. Now, create fixtures for each edge of the chain. ChainShape provides an excellent method that returns an EdgeShape for each segment in the chain to use to create the fixtures of the arena. These are the walls of your arena.

Now, you need to instantiate the arena and add it to your Forge2D world. Open the file forge2d_game_world.dart, add an import for arena.dart and create an instance of Arena in _initializeGame above where you instantiate the Ball:

import 'components/arena.dart';
  Future<void> _initializeGame() async {
    final arena = Arena();
    await add(arena);

Build and run your project. The white circle now falls and stops at the bottom edge of the GameWidget area. Congratulations! You've corralled the ball and are well along the way to creating your Breakout game.

Ball Constrained 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.