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.

4.6 (5) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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:

  1. OverlayBuilder is a class container for scoping the overlay builder methods.
  2. Declare a static overlay builder method named pregame to instantiate the PreGameOverlay widget.
  3. Declare a PreGameOverlay widget as a stateless widget. The PreGameOverlay widget is a widget that centers a Text widget in the GameWidget 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.

PreGame Overlay

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?