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.
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
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 2
25 mins
- Getting Started
- Creating the Brick Wall
- Creating a Brick
- Creating a Custom Flame Component
- Creating the Brick Wall
- Creating the Paddle
- Giving User Control of the Paddle
- Setting Up Draggable Mixin
- Constraining Body Movement with Joints
- Creating a Mouse Joint
- Creating a Prismatic Joint
- Adding Collision Detection
- Where to Go From Here?
Creating the Paddle
The final element of the Breakout game to make is the user-controlled paddle. Like the ball and bricks, the paddle is also a rigid body and your first step is to declare the Paddle
body component.
Create a paddle.dart file in the components folder and add the following lines of code to this file:
import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import '../forge2d_game_world.dart';
class Paddle extends BodyComponent<Forge2dGameWorld> {
final Size size;
final Vector2 position;
Paddle({
required this.size,
required this.position,
});
@override
Body createBody() {
final bodyDef = BodyDef()
..type = BodyType.dynamic
..position = position
..fixedRotation = true
..angularDamping = 1.0
..linearDamping = 10.0;
final paddleBody = world.createBody(bodyDef);
final shape = PolygonShape()
..setAsBox(
size.width / 2.0,
size.height / 2.0,
Vector2(0.0, 0.0),
0.0,
);
paddleBody.createFixture(FixtureDef(shape)
..density = 100.0
..friction = 0.0
..restitution = 1.0);
return paddleBody;
}
}
The Paddle
code should be very familiar at this point. There's nothing new here — it's just another rigid body in your Forge2D world.
Now you can add the paddle to your game.
Open the file forge2d_game_world.dart then add an import for paddle.dart as well as for the size component:
import 'package:flame/extensions.dart';
import 'components/paddle.dart';
Then, create an instance of Paddle
in _initializeGame
just after the BrickWall
:
const paddleSize = Size(4.0, 0.8);
final paddlePosition = Vector2(
size.x / 2.0,
size.y * 0.85,
);
final paddle = Paddle(
size: paddleSize,
position: paddlePosition,
);
await add(paddle);
You've set the paddle to four meters wide by 80 centimeters high, a reasonable size for the game area. The position is relative to the center of the paddle body. This paddlePosition
centers the paddle on the x-axis and down 85% from the top of the game area.
Build and run your project. You now have all the elements for a Breakout game: a ball, a brick wall and a paddle. Woohoo!
Giving User Control of the Paddle
You have your paddle, but your breakout game won’t be much fun until it responds to user input. That’s what you’ll build next.
Flame supports several input forms, including gesture input. The Flame Draggable
mixin is the perfect feature for implementing user control of the paddle.
Setting Up Draggable Mixin
Open forge2d_game_world.dart and add the following import:
import 'package:flame/game.dart';
You’re including the mixin HasDraggables
in your Forge2DGame
to inform the game world that it’ll have draggable components.
Insert this:
class Forge2dGameWorld extends Forge2DGame with HasDraggables {
You’ve just added the HasDraggables
mixin to your Forge2dGameWorld
class.
Open the paddle.dart file and add:
class Paddle extends BodyComponent<Forge2dGameWorld> with Draggable {
You’ve just added the Draggable
mixin to the Paddle
class.
Then include the following imports to get the Draggable
mixin:
import 'package:flame/components.dart';
import 'package:flame/input.dart';
And now override
the mixin routine onDragUpdate
, like so:
@override
bool onDragUpdate(DragUpdateInfo info) {
body.setTransform(info.eventPosition.game, 0.0);
// Don't continue passing the event.
return false;
}
Flame sends your draggable component’s data about the drag event so you can use it to update the paddle’s position. For now, you’re using setTransform
to update the location and rotation of the paddle body.
Build and run!
To drag the paddle, you must be inside the shape area of the paddle.
The paddle recognizes user input but still doesn't behave how you’d expect. In this game format, it should be horizontally constrained within the game area.
in the next section, you’ll use a MouseJoint to constrain the paddle’s movement.
Constraining Body Movement with Joints
Using setTransform
to define the location of a body in the Forge2d world works, but it's not the best method to move the paddle.
Why?
Because using setTransform
is like being beamed from point A to point B. If points A and B are far apart, it looks unnatural—unless you live in the Star Trek universe.
It’s more natural for a body to move through a series of locations, starting a point A and ending at point B. You’ll accomplish this effect with a MouseJoint
.
But a MouseJoint
alone isn't enough to implement the correct Breakout paddle behavior — it must also be constrained to only move side to side.
A PrismaticJoint
restricts the movement of a body along an axis.
You'll use these two joints together on the paddle body to create the desired behavior!
Creating a Mouse Joint
A MouseJoint
is used to make a body track to a world point.
Joints connect bodies. The paddle is one body, but what will be the second body?
The arena body fills the screen area and will make a good anchor body for the MouseJoint
. The arena will be the "ground" for the MouseJoint
joint.
In other words, you'll create a MouseJoint
and have it track to a world point provided by DragUpdateInfo
.
Open paddle.dart and add a new ground
parameter to the Paddle
class:
final Size size;
final BodyComponent ground;
final Vector2 position;
Paddle({
required this.size,
required this.ground,
required this.position,
});
Next, add these variables:
MouseJoint? _mouseJoint;
Vector2 dragStartPosition = Vector2.zero();
Vector2 dragAccumlativePosition = Vector2.zero();
These will hold the mouse joint, the drag start position and the accumulative drag offset.
Now, you're going to change the onDragUpdate
routine and add new routines for handling the start, end and cancel drag events.
// 1
@override
bool onDragStart(DragStartInfo info) {
if (_mouseJoint != null) {
return true;
}
dragStartPosition = info.eventPosition.game;
_setupDragControls();
// Don't continue passing the event.
return false;
}
// 2
@override
bool onDragUpdate(DragUpdateInfo info) {
dragAccumlativePosition += info.delta.game;
if ((dragAccumlativePosition - dragStartPosition).length > 0.1) {
_mouseJoint?.setTarget(dragAccumlativePosition);
dragStartPosition = dragAccumlativePosition;
}
// Don't continue passing the event.
return false;
}
// 3
@override
bool onDragEnd(DragEndInfo info) {
_resetDragControls();
// Don't continue passing the event.
return false;
}
// 4
@override
bool onDragCancel() {
_resetDragControls();
// Don't continue passing the event.
return false;
}
// 5
void _setupDragControls() {
final mouseJointDef = MouseJointDef()
..bodyA = ground.body
..bodyB = body
..frequencyHz = 5.0
..dampingRatio = 0.9
..collideConnected = false
..maxForce = 2000.0 * body.mass;
_mouseJoint = MouseJoint(mouseJointDef);
world.createJoint(_mouseJoint!);
}
// 6
// Clear the drag position accumulator and remove the mouse joint.
void _resetDragControls() {
dragAccumlativePosition = Vector2.zero();
if (_mouseJoint != null) {
world.destroyJoint(_mouseJoint!);
_mouseJoint = null;
}
}
This code looks lengthy, but it's pretty straightforward. Here’s a step-by-step explanation:
-
onDragStart
checks to ensure there isn't already aMouseJoint
in use. If not, it gets the drag start position and sets up the drag controls. Note that a mouse joint is active only during a drag event. -
onDragUpdate
gets the current drag offset position and then checks the accumulative drag position against the paddle’s current position. The paddle position is updated only when the new position is far enough away to justify moving. Note that you removedbody.setTransform
fromonDragUpdate
and replaced it with this new code. -
onDragEnd
resets the drag controls. -
onDragCancel
also resets the drag controls. -
MouseJointDef
identifies the two bodies connected by the joint and their relationship,frequencyHz
is the response speed,dampingRatio
is how quickly the body will stop moving, andcollideConnected
flags whether or not the two bodies can collide with each other. Note that this is similar to creating a body or fixture. - Remove the mouse joint and reset the mouse joint variables.
Open the file forge2d_game_world.dart and update the Paddle
instance, like so:
final paddle = Paddle(
size: paddleSize,
ground: arena,
position: paddlePosition,
);
await add(paddle);
Now your Paddle
includes the new ground
parameter — remember, a joint needs two bodies. The Arena
is now the second body tied to the paddle.
Build and run.
Drag the paddle. You'll notice that the paddle follows the drag input. The behavior is subtle but important. Your finger doesn’t set the paddle's position; your input asks Forge2D to move the paddle to a new location.