Basics of Game Development using Flame

Happy Makadiya
Simform Engineering
9 min readAug 29, 2022

Flutter is maturing in numerous aspects, from cross-platform application development to game development. Yes, You catch it correctly. You can use the Flutter Framework to develop everything from arcade games to casual games and many more.

Assuming you have basic knowledge of Dart and Flutter, we’ve covered the basic required concepts to develop games in this blog.

So, let’s dive in.

There is one amazing software available that provides complete control over the creation of an elegant game.

Flame is a 2D game engine made on top of Flutter that offers a wide variety of elements to build up an appropriate toolset for your game.

It provides effective game loop implementation with necessary functionalities like animations, sprite sheets, collision detection, and various components like CameraComponent, PositionComponent, TiledComponent, and many more. Also, Flame facilitates input functionalities like Tap, Drag, Gesture, and Keyboard.

Add Flame dependencies to the pubspec.yml file, and do not forget to do flutter pub get.

dependencies:  
flutter:
sdk: flutter
flame: ^1.3.0

Start by adding the 2 lines given below into the main function. Next, we need to ensure Flutter is ready by calling WidgetsFlutterBinding.ensureInitialized(); and set the Game mode to FullScreen or based on your requirement.

void main() {
WidgetsFlutterBinding.ensureInitialized();
Flame.device.fullScreen();
runApp(const GameApp());
}

Components

Components are the basic building blocks for a FlameGame. Any Entity or object within the game can be represented as a Component. It is quite similar to widgets in Flutter.

Component Tree: Components must be added to the Component tree to become fully functional. In the Component Tree, every component has a single parent and can own any number of children.

In any framework, Everything from start to end and even updating state is managed by its lifecycle. In Flame also, every component has its own lifecycle to follow.

Component LifeCycle: During its lifetime, each component goes through several stages that you can optionally implement.

The Usage of all these methods is based on their lifecycle behavior and user requirements.

  1. onGameResize(): Once in the beginning, when the component is added to the tree, and then invoked every time the screen is resized.
  2. onLoad(): Used once during the component’s lifetime and to run asynchronous initialization code for components; you can think of it as an “asynchronous constructor.”
  3. onMount(): When the component is done loading, and its parent is mounted correctly. If a component is removed from the tree and later added to another component, onMountwill be called again. For every onMountcall, there will be a corresponding call to onRemove. This method will run only if the parent is already mounted; else will wait in a queue.
  4. update(): On Every Game tick. i.e., when you have to change the position of any component every second.
  5. render(): After all the components are done updating. i.e., if you want to redraw/edit the canvas component after every update.

Now let’s deep dive into the most crucial part of Flame, where every heart of logic is being written.

Game Loop:

Most games are built using either of these methods:

  1. render(): Takes canvas for drawing the current state of the game.
  2. update(): Receive the delta time in seconds since the last update and allow it to move to the next state.

Flame Game:

The FlameGame class implements a Component-based Game. The FlameGame is the root of the component tree. It can be extended to add your game logic, or you can keep that logic into child components.

Components are added to the tree using add(), addAll() or addToParent() methods. And can be removed using remove(), removeAll() or removeFromParent() methods.

class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
//Load Components.
}
@override
Color backgroundColor() => const Color(0x00000000);
}

Pause/Resuming game:

A FlameGame can be paused and resumed in two ways:

  • With the use of the pauseEngine and resumeEngine methods.
  • By changing the paused attribute.

When pausing a FlameGame, the GameLoop is effectively paused, so that no updates or new renders happen until it is resumed.

Game Widget:

It is in charge of attaching a Game instance into the Flutter tree.

void main() {
final game = MyGame();
runApp(GameWidget(game: game));
}

Now let’s explore some of the essential components already provided by Flame.

PositionComponent:

It represents a positioned object on the screen and has a position, size, scale, angle, and anchor, transforming how the component is rendered. Children of the PositionComponent will be transformed in relation to the parent, which means that the position, angle, and scale will be relative to the parent’s state.

Position: Represent the position of the component’s anchor in relation to its parent components in the form of Vector2.

Size: The logical size of the component. It is used primarily for tap and collision detection. Thus, the size should equal the approximate bounding rectangle of the rendered picture. If you don’t specify the size of a PositionComponent, it will be equal to zero, and the component will not be able to respond to taps.

Scale: The scale factor of the component. Scale can be distinct along the X and Y dimensions. A scale that’s less than 1 makes the component smaller and greater than 1 bigger. The scale can also be negative, which results in a mirror reflection along the corresponding axis.

Anchor: This point is considered the logical “center” of the component or the point where Flame grabs the component. All transformations occur around this point.

Angle: Rotation angle (in radians) of the component. The component will be rotated around its anchor point in the clockwise direction if the angle is positive or counterclockwise if the angle is negative.

See the below example for more detail.

Create a positional component by extending PositionalComponent. You can load other components as children into the load method.

class CenterComponent extends PositionComponent {
CenterComponent()
: super(
position: Vector2(100, 100),
size: Vector2(50, 50),
anchor: Anchor.center,
);

@override
Future<void> onLoad() async {
await add(
PositionComponent(position: Vector2(0, -50)),
);
}
}

Now add CenterComponent to the game.

class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
add(CenterComponent());
}
}

SpriteComponent:

Now, what if you want to add some assets, say a player’s face, to your game?

Yes, Flame gives you complete control of the canvas to draw the objects you want, but in some cases, if you want to add your own images, then the SpriteComponent comes into the scene.

To add Sprite to your game, first, you need to follow the specific file structure of Flame to load files. The file structure Flame would expect the files to find would be:

Note: Do not forget to add these sub-folders to pubspec.yaml file.

.
└── assets
├── audio
│ └── backgroundMusic.mp3
└── images
├── enemy.png
└── player.png

Consider the below example for loading the sprite components into the game.

class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
final sprite = await Sprite.load('player.png');
final player = SpriteComponent(
sprite: sprite,
size: Vector2.all(100),
);
add(player);
}
}

CameraComponent

CameraComponent is a component through which a World is observed. A camera is a regular component placed anywhere in the game tree. Most games will have at least one “main” camera for displaying the main game world.

A Camera component consists of 2 parts: Viewport and Viewfinder.

Viewport

Imagine that the world is covered with an infinite sheet of paper, but there is a “window.” That window is the viewport through which the world can be observed.

Viewport contains properties like the window’s size, shape, and position on the screen that describe the “window” itself.

The viewport’s size is equal to or smaller than the size of the game canvas. If it is smaller, then the viewport’s position specifies where exactly it is placed on the canvas.

Following viewports are available:

  1. MaxViewport: Expands to the maximum size allowed by the game. It will be equal to the size of the game canvas.
  2. FixedSizeViewport : Rectangular viewport with fixed dimensions.
  3. FixedAspectRatioViewport: Rectangular viewport, which expands to fit into the game canvas but preserves its aspect ratio.
  4. CircularViewport: A fixed-size viewport in the shape of a circle.

ViewFinder:

Viewfinder controls which part of the game world is currently visible through a viewport. The Viewfinder also controls the zoom level and the rotation angle of the view.

Camera:

The camera translates the game coordinate system from 1:1 to camera zoom level. This is useful when the world is not 1:1 with screen size.

There are 3 significant factors that determine the camera position:

  1. Follow: The camera will follow this component, ensuring its position is fixed on the screen.
  2. Move: Move the camera to a specific world coordinate. This will be set to the top left of the camera.
  3. Shake: Add a shake effect on top of the current coordinate for a brief period.

Collision Detection

Collision Detection is an essential feature in most games and responds accordingly when two components interact with each other. For example, hitting the ball on a surface to prevent moving forward. Also, in the Pac-man game, the player collides with the enemy or wall, picking up the coins.

So to detect collision on any component, there must be a precise bounding box around that component; Technically, it’s called HitBoxes.

The first step to enabling Collision Detection is adding HasCollisionDetection mixin to your game to keep track of the components that can collide.

class MyGame extends FlameGame with HasCollisionDetection {}

Hitbox can be built from three different types of shapes: Polygon, Rectangle, and Circle. Multiple hitboxes can be added to a component. Inbuilt Component is provided by Flame to add a hitbox to components that are PolygonHitbox, RectangleHitBox, and CircleHitbox.

class MyComponent extends PositionComponent {
Future<void> onLoad() async {
add(RectangleHitbox());
}
}

The Flame doesn’t handle what should happen when more than one hitboxes collide, so it is up to the developer to implement the necessary kind of functionality.

To react to a collision, you should add the CollisionCallbacks mixin to your component.

class MyCollidable extends PositionComponent with CollisionCallbacks {
@override
void onCollision(Set<Vector2> points, PositionComponent other) {
if (other is MyComponent) {
//...
}
}

@override
void onCollisionEnd(PositionComponent other) {
if (other is MyComponent) {
//...
}
}
}

Debug Mode:

Flame is providing adebugMode variable. By Default value is false. To enable the debugging feature for a specific component, set its value to true for that component.

Wrap up:

Now we have a basic idea of developing a game that is entirely dependent on components and follows its lifecycle. And Game components render on canvas by their position and size and interact with each other.

Still, Flame is not limited to these topics only. It is limitless. You can explore more about it in by the below reference links.

Docs: https://docs.flame-engine.org/1.3.0/

Examples: https://examples.flame-engine.org/#/

Woohoo! Now You have enough knowledge to start creating your dream game using Flutter.

What’s Next?

In the next article, we will try to create a fantastic game with the help of Flame. So you can get a better understanding of developing real-time games.

Happy Coding :)

--

--

Happy Makadiya
Simform Engineering

Flutter Developer at Simform Solutions. Love to explore new things. Happy Coding ;)