Flutter — Game Development using Flame — Part One

Jayaramanan Kumar
5 min readDec 22, 2022

--

This blog is about developing a 2D game using flutter and FLAME. The Flame is a game engine that has been around for quite some time and the blog is a bit late catchup.

Game concept:

Let’s keep the game theme quite simple. The player is a hunter with a bow and arrow who run across the woods hunting down various type of ghost.

A brief about Flame:

Flame is a game engine that provides a framework for developing games using flutter. Just like the flutter, the Flame provides us with several components and more importantly the game loop implementation to help us create games. Instead of overwhelming by explaining more concepts about Flame, let’s see them one by one as we go into development.

Project setup:

Go to the pubspec.yaml file and add the flame dependencies as below. Note that the recent version of flame required flutter 3.3.0 and dart 1.7.5 as the minimum requirement.

dependencies:
flutter:
sdk: flutter
flame: ^1.5.0

Let’s clear the template MyApp and MyHomePage widgets. Then create an instance of the FlameGame Component and GameWidget and pass it to the run app.

import 'package:flame/game.dart';

void main() {
WidgetsFlutterBinding.ensureInitialized();
var game = FlameGame();
runApp(GameWidget(game: game));
}

What’s this FlameGame class and GameWidget? So FlameGame can be viewed as the core component that is going to host all our game components and it can be added as a widget to our app using the GameWidget.

Now when running the app in an emulator, it just shows an empty black blank screen. This is because there is no component like players or any game objects. Let’s add the first game object — the game player “Hunter”.

SpriteAnimationComponent

Flame provides a few special components like SpriteComponent and SpriteAnimationComponent where we can render images. SpriteComponent is usually used to render a single static sprite image. whereas the SpriteAnimatedComponent will display an animated image by rendering a sequence of images from a list of individual image files or a single sprite sheet.

Many free characters sprite are available on itch.com and made use of a few for this example.

Flame look for a specific folder structure to load the images and audio files. So let’s create a folder named asset in the project root directory. Under the assets create the images folder and add the sprite sheet image files.

And finally, declare the assets folders under asset in the pubspec.yaml as well.

assets:
- assets/images/
- assets/audio/

Now let’s create the SpriteAnimatedComponent as below and add that to the game using the add method.

 
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final sprites = [1, 2].map((i) => Sprite.load('hunter/idle_$i.png'));
final animation = SpriteAnimation.spriteList(
await Future.wait(sprites),
stepTime: 0.05,
);
final player = SpriteAnimationComponent(
animation: animation,
size: Vector2.all(150.0),
);
var game = FlameGame();
game.add(player);
runApp(GameWidget(game: game));
}

The SpriteAnimationComponent constructor takes two parameters:

Animation — SpriteAnimation object that holds information about the sprite image which needs to be loaded

Size -Vector2 object representing the size of the component that needs to render

The game is rendered in full screen and landscape mode by adding the below lines

Flame.device.setLandscape();
Flame.device.fullScreen();

If run the app, it is seen that the character is displayed at the top left corner of the screen by default.

Parallax Background

The next step is to add some background to the game. Rather than adding a static image, the background needs to be responsive to the player’s action. For example, the background has to go behind or forward as the player runs. Parallax is a good option to achieve that. This component takes a list of images with transparent backgrounds and makes each image move cyclically at a differential speed.

Before adding parallax, let’s refactor the code a bit so that the main function is not clustered. Let’s create a class named GhostHuntGame, extend the FlameGame class and move all related logic into it. And similarly, the Hunter class is created which extends SpriteAnimationComponent and holds all logic related to the hunter. Finally, GhosthuntGame class, Hunter class and the main function will look like below.

class GhostHuntGame extends FlameGame {
late final DeathGhost deathGhost;
late final GhostHuntParallax ghostHuntParallax;

@override
Future<void>? onLoad() async {
ghostHuntParallax = await gameParallexBackground();

deathGhost = DeathGhost();
deathGhost.scale = Vector2.all(1.5);
deathGhost.flipHorizontallyAroundCenter();
add(ghostHuntParallax);
add(deathGhost);
}
}

gameParallexBackground() async {
final layers = [
ParallaxLayer.load(
ParallaxImageData('background/background_layer_1.png'),
velocityMultiplier: Vector2(1.4, 1.0),
fill: LayerFill.height,
),
ParallaxLayer.load(
ParallaxImageData('background/background_layer_2.png'),
velocityMultiplier: Vector2(2.8, 2.0),
fill: LayerFill.height,
),
ParallaxLayer.load(
ParallaxImageData('background/background_layer_3.png'),
velocityMultiplier: Vector2(4.2, 3.0),
fill: LayerFill.height,
),
ParallaxLayer.load(
ParallaxImageData('background/ground.png'),
velocityMultiplier: Vector2(4.2, 3.0),
fill: LayerFill.none,
)
];
final parallax = Parallax(
await Future.wait(layers),
baseVelocity: Vector2.zero(),
);

return ParallaxComponent<GhostHuntGame>(parallax: parallax);
}
class Hunter extends SpriteAnimationComponent {
Hunter() : super(size: Vector2.all(100.0));
@override
Future<void>? onLoad() async {
final sprites = [1, 2].map((i) => Sprite.load('hunter/idle_$i.png'));
animation = SpriteAnimation.spriteList(
await Future.wait(sprites),
stepTime: 0.05,
);
}
}

Now on executing the `flutter run` command, the parallax background along with the player character is displayed.

One thing to note here is the character is added at last after the parallax as it has to be displayed on top of the background, not vice versa.

In the next post, let’s see how we can control the characters, position them and all other actions.

The complete source code for this project can be found in the below link.

--

--