Create a Breakout Game With Flame and Forge2D – Part 3
Learn how to create a Flutter version of the classic Breakout game using Flame and Forge2D. By Michael Jordan.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Create a Breakout Game With Flame and Forge2D – Part 3
30 mins
- Getting Started
- Adding Game Rules and Behaviors
- Adding Gameplay Logic
- Adding a Forge2D Sensor
- Adding the Win Game State
- Adding Start and Reset Controls
- Flame Overlays
- Adding a Game-Ready Overlay
- Adding Player Tap Input
- Adding a Game-Over Overlay
- Resetting the Game
- Skinning Your Game
- Rendering the Ball
- Rendering the Paddle
- Rendering the Brick Wall
- Where to Go From Here?
This article is Part 3 of a three-part tutorial on creating a Flutter Breakout game with Flame and Forge2D.
The companion articles to this tutorial are:
- Create a Breakout Game With Flame and Forge2D – Part 1
- Create a Breakout Game With Flame and Forge2D – Part 2
In Part 2 of this tutorial, you expanded your knowledge of Forge2D. You learned how to create the brick wall and paddle for your Breakout game. You also learned how to add user input controls and create joints to connect rigid bodies.
Your game is beginning to look like the Breakout game.
In this tutorial, you’ll complete your Breakout game by adding gameplay logic and skinning the game. Also, you’ll learn:
- Add game rules and behaviors.
- Add gameplay logic.
- Create a Forge2D sensor.
- Use the Flame Widgets Overlay API to add Flutter widgets to control the game.
- Add user tap input.
- Use
Canvas
to skin your game by painting the rigidBodyComponent
in the game to give them color.
Canvas
class. If you aren’t familiar with Canvas
, Wilberforce Uwadiegwu’s article Flutter Canvas API: Getting Started is a great introduction.
Getting Started
You can use the project you worked on in Part 2 of this tutorial or the starter project for this tutorial. Download it by clicking the Download Materials button at the top or bottom of the tutorial.
Both of these projects have a Forge2D ball bouncing inside an arena. Also, you have a brick wall, a paddle the player can control and collision detection that removes bricks from the wall. This is the starting point for this tutorial.
Adding Game Rules and Behaviors
Games have rules and must pose a challenge to players. Unfortunately, your game at this point doesn’t have any rules and it isn’t much of a challenge — when the player misses the ball, it bounces off the bottom wall and continues. If the player destroys all the bricks, the ball continues to bounce in an empty arena. You’ll now add gameplay logic to your game.
Adding Gameplay Logic
A Breakout game is over when the player misses the ball with the paddle. Game rules also include that when a player destroys all the bricks, the player wins and the game is over. You’ll now add this gameplay logic to your game.
Open forge2d_game_world.dart and add the following enum
at the top of the file before the Forge2dGameWorld
class definition:
enum GameState {
initializing,
ready,
running,
paused,
won,
lost,
}
These will be the six states for your game. Now, add a gameState
property to Forge2dGameWorld
and set the initial state to initializing
.
GameState gameState = GameState.initializing;
Next, set the game state to ready once the game completes initializing. Add the following state change as the last line in _initializeGame
:
gameState = GameState.ready;
You now have the first two states of your game in place.
Winning and losing are two critical game states. First, you’ll see how to determine when the player loses the game and set the game state to GameState.lost
. Then, you’ll add a check for when all the bricks in the wall are destroyed and set the game state to GameState.won
.
Adding a Forge2D Sensor
You’ll now add a Forge2D sensor for the dead zone to detect when the player has missed the ball. What’s a dead zone? It’s a region at the bottom of the arena. The dead zone will use a Fixture
sensor that detects collisions without generating a response. Restated, this means you can get notified of a collision, but the colliding body will pass through without responding to the collision.
Create a dead_zone.dart file in the components folder and add the following lines of code to the file:
import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import '../forge2d_game_world.dart';
import 'ball.dart';
// 1
class DeadZone extends BodyComponent<Forge2dGameWorld> with ContactCallbacks {
final Size size;
final Vector2 position;
DeadZone({
required this.size,
required this.position,
});
@override
Body createBody() {
final bodyDef = BodyDef()
..type = BodyType.static
..userData = this
..position = position;
final zoneBody = world.createBody(bodyDef);
final shape = PolygonShape()
..setAsBox(
size.width / 2.0,
size.height / 2.0,
Vector2.zero(),
0.0,
);
// 2
zoneBody.createFixture(FixtureDef(shape)..isSensor = true);
return zoneBody;
}
// 3
@override
void beginContact(Object other, Contact contact) {
if (other is Ball) {
gameRef.gameState = GameState.lost;
}
}
}
- The declaration for
DeadZone
body should look familiar to you.DeadZone
needs to react to the ball coming into contact with it, so add theContactCallbacks
mixin. - Setting the
isSensor
flag of theFixtureDef
totrue
makes this body distinctive. Sensor bodies detect collisions but don’t react to them. - If the ball comes into contact with the dead zone, set the
gameState
toGameState.lost
.Forge2dGameWorld
will detect the game state change in the game loopupdate
method.
The game loop needs to check the game state and act appropriately. In this case, when the player loses, the game needs to stop. With the Flame game engine, pausing the engine is the appropriate action.
Open forge2d_game_world.dart and add these imports:
import 'package:flame/extensions.dart';
import 'components/dead_zone.dart';
Then add the DeadZone
body to the _initializeGame
routine between BrickWall
and Paddle
.
final deadZoneSize = Size(size.x, size.y * 0.1);
final deadZonePosition = Vector2(
size.x / 2.0,
size.y - (size.y * 0.1) / 2.0,
);
final deadZone = DeadZone(
size: deadZoneSize,
position: deadZonePosition,
);
await add(deadZone);
You want the dead zone to fill the arena area at the bottom of the screen. First, set the deadZoneSize
to be the same width and 10% of the height of the game area. Next, set the deadZonePosition
, so the DeadZone
center is at the bottom of the game area.
Now with a dead zone in place, you can properly position the paddle. The paddle should move along the top edge of the dead zone. Change paddlePosition
to place the bottom edge of the paddle at the top edge of the dead zone.
final paddlePosition = Vector2(
size.x / 2.0,
size.y - deadZoneSize.height - paddleSize.height / 2.0,
);
Add the following update
routine to forge2d_game_world.dart. The update
routine will listen for changes to the game state.
@override
void update(double dt) {
super.update(dt);
if (gameState == GameState.lost) {
pauseEngine();
}
}
Flame calls your update
routine from the game loop, allowing you to make changes or respond to events such as game state changes. Here, you’re calling pauseEngine
to stop the execution of the game loop.
Build and run the project. Now, you’ll get a white rectangular area at the bottom of the screen, which is the dead zone sensor body. The game stops when the ball comes into contact with the dead zone.
Why is the DeadZone
body white? For that matter, why are all the Forge2D bodies white? Forge2D’s BodyComponent
default behavior is to render body fixture shapes, making them visible. You can turn off this default behavior by setting the renderBody
property of a BodyComponent
to false
.
Open dead_zone.dart and add the following line of code at the top of the DeadZone
class after the constructor.
@override
bool get renderBody => false;
Build and run the project. The dead zone body remains, but Forge2D is not rendering the fixture shapes on the body. In an upcoming section, you’ll learn more about rendering bodies when you “skin” the game.