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?
Adding the Win Game State
Your game knows when a player loses, but not when they win. So you’re now going to add the remaining game states to your game. Begin by adding the win state. Players win when they destroy all the bricks.
Open brick_wall.dart and add the following code to update
just after the for
loop that removed destroyed bricks from the wall:
if (children.isEmpty) {
gameRef.gameState = GameState.won;
}
Now, open forge2d_game_world.dart and change the if
statement condition in the update function to check gameState
for either GameState.lost
or GameState.won
.
if (gameState == GameState.lost || gameState == GameState.won) {
pauseEngine();
}
Your game will now recognize when the player wins or loses, and the gameplay stops.
Adding Start and Reset Controls
Your game begins to play when you run the app, regardless of whether the player is ready. When the game ends with a loss or a win, there’s no way to replay the game without restarting the app. This behavior isn’t user-friendly. You’ll now add controls for the player to start and replay the game.
You’ll use overlays to present standard Flutter widgets to the user.
Flame Overlays
The Flame Widgets Overlay API provides a convenient method for layering Flutter widgets on top of your game widget. In your Breakout game, the Widgets Overlay API is perfect for communicating to the player when the game is ready to begin and getting input from the player about replaying the game.
You define an Overlay in an overlay builder map provided to the GameWidget
. The map
declares a String
and an OverlayWidgetBuilder
builder method for each overlay. Flame calls the overlay builder method and adds the overlay when you add the overlay to the active overlays list.
You’ll start by adding a simple overlay informing the player the game is ready to begin.
Adding a Game-Ready Overlay
Create an overlay_builder.dart file in the ui folder and add the following lines of code to the file:
import 'package:flutter/material.dart';
import '../forge2d_game_world.dart';
// 1
class OverlayBuilder {
OverlayBuilder._();
// 2
static Widget preGame(BuildContext context, Forge2dGameWorld game) {
return const PreGameOverlay();
}
}
// 3
class PreGameOverlay extends StatelessWidget {
const PreGameOverlay({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text(
'Tap Paddle to Begin',
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
);
}
}
Let’s examine this code:
-
OverlayBuilder
is a class container for scoping the overlay builder methods. - Declare a static overlay builder method named
pregame
to instantiate thePreGameOverlay
widget. - Declare a
PreGameOverlay
widget as a stateless widget. ThePreGameOverlay
widget is a widget that centers aText
widget in theGameWidget
container with text instructing the player to tap the paddle to begin the game.
Open main_game_page.dart and include the following import to get the OverlayBuilder.preGame
builder method:
import 'overlay_builder.dart';
And provide GameWidget
with an overlay builder map:
child: GameWidget(
game: forge2dGameWorld,
overlayBuilderMap: const {
'PreGame': OverlayBuilder.preGame,
},
),
You’ve created the overlay and notified Flame how to build the overlay. Now you can use the overlay in your game. You need to present the pregame overlay when the game state is GameState.ready
.
Open forge2d_game_world.dart and add the following line of code at the end of _initializeGame
after setting gameState
to GameState.ready
:
gameState = GameState.ready;
overlays.add('PreGame');
Adding Player Tap Input
Currently, a force is applied to the ball after the game is initialized and the Breakout game begins. Unfortunately, this isn’t player-friendly. The simplest way to let the player control the game start is to wait until they tap the game widget.
Open forge2d_game_world.dart and remove the call to _ball.body.applyLinearImpulse
from onLoad
. The onLoad
method will now only call _initializeGame
.
@override
Future<void> onLoad() async {
await _initializeGame();
}
Now, include the following import and add the HasTappables
mixin to your Forge2dGameWorld
:
import 'package:flame/input.dart';
class Forge2dGameWorld extends Forge2DGame with HasDraggables, HasTappables {
Next, add a new onTapDown
method to Forge2dGameWorld
.
@override
void onTapDown(int pointerId, TapDownInfo info) {
if (gameState == GameState.ready) {
overlays.remove('PreGame');
_ball.body.applyLinearImpulse(Vector2(-10.0, -10.0));
gameState = GameState.running;
}
super.onTapDown(pointerId, info);
}
When a player taps the screen, onTapDown
gets called. If the game is in the ready and waiting state, remove the pregame overlay and apply the linear impulse force that begins the ball’s movement. Finally, don’t forget to change the game state to GameState.running
.
Before trying your new pregame overlay, move the ball’s starting position. Otherwise, the overlay text will be on top of the ball. Inside the _initialize
method, change the starting position of the ball to this:
final ballPosition = Vector2(size.x / 2.0, size.y / 2.0 + 10.0);
_ball = Ball(
radius: 0.5,
position: ballPosition,
);
await add(_ball);
Build and run the project. Your Breakout game is waiting for you to tap the screen to begin.
Very cool! But you still need a way to reset the game and play again.
Adding a Game-Over Overlay
The game-over overlay will be like the game-ready overlay you created. Yet, while the overlay will be similar, you must modify your game to reset the game components to their initial pregame states.
Begin by opening forge2d_game_world.dart and add the following resetGame
method.
Future<void> resetGame() async {}
resetGame
is a placeholder method that you’ll come back to shortly.
Now, open overlay_builder.dart and create a new postGame
overlay builder method in OverlayBuilder
.
static Widget postGame(BuildContext context, Forge2dGameWorld game) {
assert(game.gameState == GameState.lost || game.gameState == GameState.won);
final message = game.gameState == GameState.won ? 'Winner!' : 'Game Over';
return PostGameOverlay(message: message, game: game);
}
The postGame
overlay will congratulate the player on a win or let them know the game is over on a loss.
Now, declare a PostGameOverlay
stateless widget to display the appropriate postgame message to the player and give them a replay button to reset the game. Add the PostGameOverlay
class at the bottom of overlay_builder.dart.
class PostGameOverlay extends StatelessWidget {
final String message;
final Forge2dGameWorld game;
const PostGameOverlay({
super.key,
required this.message,
required this.game,
});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
message,
style: const TextStyle(
color: Colors.white,
fontSize: 24,
),
),
const SizedBox(height: 24),
_resetButton(context, game),
],
),
);
}
Widget _resetButton(BuildContext context, Forge2dGameWorld game) {
return OutlinedButton.icon(
style: OutlinedButton.styleFrom(
side: const BorderSide(
color: Colors.blue,
),
),
onPressed: () => game.resetGame(),
icon: const Icon(Icons.restart_alt_outlined),
label: const Text('Replay'),
);
}
}
The PostGameOverlay
widget should feel familiar. The postgame overlay is defined using Flutter widgets, a Text
widget to display a message and a button to reset the game.
Notice the onPressed
callback method in the reset button. The overlay builder method API provides a reference to the game loop. Your overlay can use this reference to send a message to the game loop to reset the game. Pretty cool, huh?