Game development: Create a Breakout clone using Flutter – Part 1

For many years Adobe AIR in conjunction with the Starling framework has been a great way to develop 2D cross platform games for Android and iOs. Flash’s concept of a display list to manage on screen objects and the benefit of utilizing the devices hardware acceleration through Stage3D are fantastic.
Unfortunately Adobe’s plans regarding the future of AIR aren’t too clear. Naturally this scares some developers, making them seek new possibilities. Today there are many options to choose from. One that caught my attention is Google’s Flutter. Although it seems more like an UI framework, you’re also able to make games. Under the hood it utilizes the Skia 2D graphic rendering engine, making it hardware accelerated on mobile platforms too. Sweet!
Flutter applications are written using Google’s own Dart language.

To enhance the functionality of Flutter, you can use packages. The one we’ll be looking into in this tutorial is called flame – a game engine. Well, as the author itself states, it’s minimal and simple so we’ll enhance it in turn.

Prerequisites:

  • A PC running Windows 7 64 bit or above
  • Visual Studio Code
  • Flutter SDK
  • Android emulator or a real device

I assume your development enviroment is set up and running.
Unfortunately there is no way to create an empty Flutter project – it’s always based on a simple demo application showcasing a floating button and a textfield.
To create a new project, select View -> Command Palette… and enter Flutter: New Project in the textbox.

flutterVSCNew

Afterwards it will ask you for a name for the project and where to store the project on your harddrive. Give it the name breakout. Flutter just accepts lower case letters and no spaces.

To make use of the flame package, we need to add a dependency inside a file called pubspec.yaml
Simply select this file in Visual Studio Code’s own Explorer panel:
flutterVSCPubSpec

Inside the file find:

and add flame: ^0.10.2 right below.
CAUTION: It’s crucial that your newly added dependency lines up with flutter: If it’s shifted e.g. one tab to the right, it’s treated as an attribute of the flutter: tag.
In other words, it should look like this now:

If you save the file using ctrl + s, Visual Studio Code will attempt to download the newly added package. If everything went well it will show:

[breakout] flutter packages get
Running “flutter packages get” in breakout… 0,7s
exit code 0

We aren’t done yet! Since our game uses graphical assets, we need to tell Flutter that it should bundle them with the application.
Find the following section:

As the comments say, we could go ahead and list our assets one-by-one now but that’s a bit cumbersome. We’re going to embedd our whole folder by

This will bundle any .jpg, .png or .gif file it can find in the specified folder.
At the moment those folders doesn’t exist yet. So go ahead and inside your projects folder create an assets folder and inside it an images subfolder either using Windows Explorer or directly in Visual Studio Code’s Explorer panel.

flutterVSCNewFolder

Speaking of images, download the following graphic and put it into your freshly created assets/images folder.
backgroundTile

It’s a seamless tile we’ll use for our game’s background we’re about to work on in a bit.

Let’s start some coding! In the Explorer panel select the file main.dart
This file contains the top-level main() function which serves as the entry point for any application. There’s a whole lot of code in there yet. It’s all related to Flutter’s sample application. You know what? We don’t need it. Simply select everything and delete it!
Speaking of deletion, there’s a folder which is part of the demo application that needs some deletion otherwise we’ll get compiler errors later on. Delete the folder called test from your project.
Now put this piece of code into main.dart

I guess you might be wondering what we’re doing here.
To be able to use the graphics stored inside the assets folder in our game, we need to make sure it’s loaded into memory. This is done using Flame.images.loadAll. Since there’s no direct way to list the files bundled, we need to provide an array containing the filenames. The preceeding await keyword makes sure that it doesn’t continue executing the next lines of code before the image(s) have successfully loaded.
To understand the rest of the code I need to go far afield. As you know there is a magnitude of different display resolutions out there. To render our game at the target’s screen size in pixels, we need to know the exact size. Unfortunately things aren’t that easy because there is no direct reliable way to get the exact size! The Flutter developers added the function window.physicalSize which returns a Size object that should contain the stage’s size. As soon as you’ve tried to run an application in release mode, you’ll notice that this size might be 0! That’s because window.physicalSize doesn’t get updated with the correct values fast enough. Even if it would it might not be correct because it also depends on the rotation of the device, being either landscape or portrait and if the navigation & status bar is hidden or not.
To summarize, with Flutter’s built-in methods as well as Flame’s it’s impossible to get the accurate size. So I created a little helper class myself that takes care of it.
var waitForScreenSize = await Dimension(true, orientation).set();

The first parameter indicates if the game should run in fullscreen mode while the second locks the device to a specific orientation – portrait in this case. As before, the await keyword prevents the next lines from executing before the helper class signaled that the screen dimensions are available. To benefit from this helper class, you need to download the following file and put it into the lib folder of your project: helpers.dart

If you have an Android device connected to your computer or the Android emulator is running it’s now time to hit F5 to test the project. If everything went well you’ll see the following in Visual Studio Code’s debug console:
flutterVSCOutput

A look at your device/emulator will just give you a white screen – nothing too fancy. We’ll change that in a bit. 😉
Right-click the lib folder in the Explorer panel and select New File. Name it breakout.dart

flutterVSCNewFile

Paste this code:

This will serve as a template for our actual game! But wait! The constructor is empty! Why this? This has to do with Flutter’s asynchronous nature. For our game we need to do preprocess the graphical assets to match the device’s screen size. This involves waiting for some operations in a ‘synchronous fashion’ using the await keyword. The problem is, in Flutter the constructor can’t be marked as async – which is required. To overcome this issue we move all the initializing and preprocessing in a public async function called init() which returns a Future<void> once ready.
If you take a close look at this function you might be a little surprised. There’s more code to handle the device’s display resolution! Didn’t we do this in the main() function yet? Yes we did! Unfortunately sometimes the device gets confused by the screen orientation and returns the reversed numbers for the width and the height. This code takes care of this.

The next important thing is the variable scale. As I mentioned before there are countless different screen resolutions out there. For simplicity we design our game at a fixed resolution of 720 x 1280 and scale everything on screen appropriately depending on the physical resolution.
Take a look at this to get a better understanding:

flutterResizeDot

If we design our game for a 720×1280 resolution we know that the red dot should be 20×20 pixels for example. On our target device we have a resolution of 960×1600 pixels. This means the scale factor is 1.33333 thus we need to resize our red dot by the factor of 1.33333 resulting in roughly 27×27 pixels.
That’s it for the basics. Let’s finally add our tiled background!
Simply insert the following lines right after: scale = physicalWidth / layoutWidth;


This puts a new instance of Flame’s Sprite class holding our background image into the textures array. At the moment it doesn’t make sense to store a single Sprite in an array but we’ll add more later on.


This is a reference to the actual image inside memory. We need it to get the actual size of the image.

This calculation gives us the final scale for a single tile on the target device.

Now that we know how big a single tile should be, we need to take this tile and floodfill the whole screen with it! Again this turns out to be not that easy.
To do this, we need to create a temporary canvas. As the name implies this is some sort of artboard which you can draw onto using code. The contents of this canvas is then recorded using Flutter’s PictureRecorder class and finally converted to a standard Image object we can then use to draw the floodfilled background onto the real canvas of our game using the drawImage() function.

The final step of drawing the background to our canvas is done using the following render function. Add it below the init() function.

Your curious what happens if you hit F5 again? Do it – you’ll be surprised!
Ain’t a damn thing changed – it’s still the white background.
The reason is simple: we didn’t create an instance of our breakout game yet and furthermore we didn’t add it to Flutter’s widget list.
There’s remedy of course. Head back to the main.dart file using the Explorer panel and add the following lines to the end of the main() function:

If you hit F5 this time you should be pleased by something like this:
flutterFilledBackgroundEmulator

You might be asking yourself now why on earth we needed that much lines of code just to display a background image. This could have been much easier by just composing this image directly inside e.g. Photoshop and use this instead of the background tile.
Well our background is resolution independent – it will automatically adjust to any screen size and look the same. If the aspect ratio is different from 16:9 there’ll be no distortion. Great!

That’s it for the first part! We’ll continue with this game in the next part!

Leave a Reply

Your email address will not be published. Required fields are marked *