Create a Breakout Game With Flame and Forge2D – Part 2

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

This article is part two of a three-part series that walks you through the creation of a Flutter Breakout game with Flame and Forge2D.

The companion articles to this tutorial are:

In part one of this series, you created a Breakout game and learned how to use Forge2D to make a ball and arena, as well as made the ball bounce off the arena’s walls.

Ball Bouncing in Arena

You’re well on the way to building your very own Breakout game.

Breakout Game

By the end of this article, you’ll add a brick wall to smash and a paddle to control the bounce. You’ll also learn how to:

  • Create a custom Flame component.
  • Add user input to control a body in Forge2D.
  • Create a Joint to hold bodies together and restrict their movement.
  • Add rigid body collision detection.

Getting Started

You can start with your project from part one or the starter project that’s available from the Download Materials button at the top or bottom of the tutorial.

Build and run. Your project should have a Forge2D ball bouncing inside an arena. This is the starting point for this part of the tutorial series.

Ball Bouncing in Arena

Creating the Brick Wall

You have a ball, and now you’re going to create a brick wall for it to destroy. There are several steps ahead, the first being to define the brick body.

Creating a Brick

Creating the brick body will be very similar to the other rigid body components you’ve built, and you’ll start by defining a Brick extending from BodyComponent.

Create a brick.dart file in the components folder and add the following lines of code to this file:

import 'package:flutter/material.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import '../forge2d_game_world.dart';

// 1
class Brick extends BodyComponent<Forge2dGameWorld> {
 final Size size;
 final Vector2 position;

 // 2
 Brick({
  required this.size,
  required this.position,
 });

 // 3
 @override
 Body createBody() {
  final bodyDef = BodyDef()
   ..type = BodyType.static
   ..position = position
   ..angularDamping = 1.0
   ..linearDamping = 1.0;

  final brickBody = world.createBody(bodyDef);

  // 4
  final shape = PolygonShape()
   ..setAsBox(
    size.width / 2.0,
    size.height / 2.0,
    Vector2(0.0, 0.0),
    0.0,
   );

  // 5
  brickBody.createFixture(
   FixtureDef(shape)
    ..density = 100.0
    ..friction = 0.0
    ..restitution = 0.1,
  );

  return brickBody;
 }
}

This code may be familiar to you — in case it’s not, see below.

  1. Declare a Brick component.
  2. Define the Brick component, which you’ll use multiple times to create the wall. The size and position constructor parameters set the unique values for individual bricks.
  3. Set up the brick bodies to be static, but static doesn’t mean immobile. Think about a box in your garage — it doesn’t move on its own. But it moves when you push it, kick it, or brush it aside. Your bricks will behave similarly when the ball collides with them. Then you set angularDamping and linearDamping to 100% to prevent any movement. Remember that we represent these values with a floating point number between 0.0 and 1.0
  4. Make the shape of the brick to be a polygon box shape.
  5. Define and create the fixture for the body.

Creating a Custom Flame Component

Now that you have the Brick body component, you can build a wall — one brick at a time. How painful!

In this section, you’ll create a Flame component so you can treat the entire wall as a single component.

Create a file named brick_wall.dart in the components folder then add the following code to it:

import 'package:flutter/material.dart';
import 'package:flame/components.dart';
import '../forge2d_game_world.dart';
import 'brick.dart';

// 1
class BrickWall extends Component with HasGameRef<Forge2dGameWorld> {
 final Vector2 position;
 final Size? size;
 final int rows;
 final int columns;
 final double gap;

 // 2
 BrickWall({
  Vector2? position,
  this.size,
  int? rows,
  int? columns,
  double? gap,
 }) : position = position ?? Vector2.zero(),
    rows = rows ?? 1,
    columns = columns ?? 1,
    gap = gap ?? 0.1;

 // 3
 @override
 Future<void> onLoad() async {
  await _buildWall();
 }

 Future<void> _buildWall() async {
 }
}

The brick wall is a collection of Brick components where each brick is a BodyComponent. With Flame, you need to create a custom component so that you can treat the entire wall as a single component with the following logic:

  1. Declare BrickWall as a subclass of Component with a mix of HasGameRef. The HasGameRef is like the glue that binds the component to your Forge2dGameWorld.
  2. Define a BrickWall constructor to allow for setting the position, overall size, the number of brick rows and columns, and the size of the gap between bricks.
  3. Create a the Flame game loop since the BrickWall is a Flame component. The loop will call onLoad during the load cycle.

Creating the Brick Wall

Now you’re going to actually build a brick wall.

In brick_wall.dart, add the following code to _buildWall:

  // 1
  final wallSize = size ??
    Size(
     gameRef.size.x,
     gameRef.size.y * 0.25,
    );

  // 2
  final brickSize = Size(
   ((wallSize.width - gap * 2.0) - (columns - 1) * gap) / columns,
   (wallSize.height - (rows - 1) * gap) / rows,
  );

  // 3
  var brickPosition = Vector2(
   brickSize.width / 2.0 + gap,
   brickSize.height / 2.0 + position.y,
  );

  // 4
  for (var i = 0; i < rows; i++) {
   for (var j = 0; j < columns; j++) {
    await add(Brick(
     size: brickSize,
     position: brickPosition,
    ));
    brickPosition += Vector2(brickSize.width + gap, 0.0);
   }
   brickPosition += Vector2(
    (brickSize.width / 2.0 + gap) - brickPosition.x,
    brickSize.height + gap,
   );
  }

The construction of the brick wall is pretty straightforward. First, you calculate the brick size and wall position. Then you build the wall one row at a time.

Here's some more detail:

  1. If the caller doesn't specify the size of the brick wall, this sets the area to fill to the full width of the game area and 25% of the height.
  2. Calculate the brick size from the given wall dimensions.
  3. Set the position of the first brick.
  4. Create a wall of bricks by adding each brick to the game world.

You're now ready to add the wall to your game!

Open the file forge2d_game_world.dart, add an import for brick_wall.dart:

import 'components/brick_wall.dart';

Create an instance of BrickWall in _initializeGame just after the Arena:

  final brickWallPosition = Vector2(0.0, size.y * 0.075);

  final brickWall = BrickWall(
   position: brickWallPosition,
   rows: 8,
   columns: 6,
  );
  await add(brickWall);

BrickWall uses the position parameter to locate the first brick in the wall.

Then, BrickWall builds the wall row by row from top to bottom, and Vector2(0.0, size.y * 0.075) places the wall against the left edge while leaving 7.5% of the game area above.

Build and run your project. You'll now see a brick wall at the top of the game arena. Another major Breakout game component is now in place.

Ball and Brick Wall