Building Games in Flutter with Flame: Getting Started

Learn how to build a beautiful game in Flutter with Flame. In this tutorial, you’ll build a virtual world with a movable and animated character. By Vincenzo Guzzi.

4.7 (3) · 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.

Executing on Player Movement

To start acting on the information passed through to the player component, head back to player.dart and add these two functions:

@override
 void update(double delta) {
   super.update(delta);
   movePlayer(delta);
 }
 
 void movePlayer(double delta) {
   // TODO
 }

update is a function unique to Flame components. It will be called each time a frame must be rendered, and Flame will ensure all your game components update at the same time. The delta represents how much time has passed since the last update cycle and can be used to move the player predictably.

Replace // TODO in the movePlayer function with logic to read the direction. You haven’t written the move methods yet. You’ll take care of that soon enough. For now, you’ll have to endure some compile errors:

switch (direction) {
  case Direction.up:
    moveUp(delta);
    break;
  case Direction.down:
    moveDown(delta);
    break;
  case Direction.left:
    moveLeft(delta);
    break;
  case Direction.right:
    moveRight(delta);
    break;
  case Direction.none:
    break;
}

movePlayer will now delegate out to other more specific methods to move the player. Next, add the logic for moving the player in each direction.

Start by adding a speed variable to the top of your Player class:

final double _playerSpeed = 300.0;

Now, add a moveDown function to the bottom of your Player class:

void moveDown(double delta) {
   position.add(Vector2(0, delta * _playerSpeed));
}

Here, you update the Player position value — represented as an X and a Y inside Vector2 — by your player speed multiplied by the delta.

You can picture your game view drawn on a 2-D plane like so:

2500x2500 grid

If the game view is 2500×2500 pixels in diameter, your player starts in the middle at the coordinates of x:1250, y:1250. Calling moveDown adds about 300 pixels to the player’s Y position each second the user holds the joypad in the down direction, causing the sprite to move down the game viewport.

You must add a similar calculation for the other three missing methods: moveUp, moveLeft and moveRight.

Now for the other move methods:

void moveUp(double delta) {
  position.add(Vector2(0, delta * -_playerSpeed));
}
 
void moveLeft(double delta) {
  position.add(Vector2(delta * -_playerSpeed, 0));
}
 
void moveRight(double delta) {
  position.add(Vector2(delta * _playerSpeed, 0));
}

Run your application once more, and your little dude will move around the screen in all directions based on your joypad input.

RayWorld flame player movement with no animation gif

Animating Your Player

Your player is moving around the screen like a boss – but it looks a bit off because the player is always facing in the same direction! You’ll fix that next using sprite sheets.

What Is a Sprite Sheet?

A sprite sheet is a collection of sprites in a single image. Game developers have used them for a long time to save memory and ensure quick loading times. It’s much quicker to load one image instead of multiple images. Game engines like Flame can then load the sprite sheet and render only a section of the image.

You can also use sprite sheets for animations by lining sprites up next to each other in animation frames so they can easily be iterated over in the game loop.

This is the sprite sheet you’ll use for your playable character in RayWorld:

RayWorld flame player sprite sheet

Each row is a different animation set and simulates moving left, right, up and down.

Adding Sprite Sheet Animations to Your Player

In player.dart, change your Player class extension from SpriteComponent to SpriteAnimationComponent as follows:

class Player extends SpriteAnimationComponent with HasGameRef {

With this new type of component, you’ll be able to set an active animation, which will run on your player Sprite.

Import the package sprite.dart. You’ll need this for setting up a SpriteSheet:

import 'package:flame/sprite.dart';

Add these six new variables to your Player class:

final double _animationSpeed = 0.15;
late final SpriteAnimation _runDownAnimation;
late final SpriteAnimation _runLeftAnimation;
late final SpriteAnimation _runUpAnimation;
late final SpriteAnimation _runRightAnimation;
late final SpriteAnimation _standingAnimation;

Replace the onLoad method with new logic to load your animations. We’ll define the _loadAnimations future in just a moment:

@override
 Future<void> onLoad() async {
   await _loadAnimations().then((_) => {animation = _standingAnimation});
}

_loadAnimations will be an async call. This method waits for the animations to load and then sets the sprite’s first active animation to _standingAnimation.

Create the _loadAnimations method and instantiate your player SpriteSheet:

Future<void> _loadAnimations() async {
   final spriteSheet = SpriteSheet(
     image: await gameRef.images.load('player_spritesheet.png'),
     srcSize: Vector2(29.0, 32.0),
   );
 
   // TODO down animation
 
   // TODO left animation
 
   // TODO up animation
 
   // TODO right animation
 
   // TODO standing animation
 }

This code loads a sprite sheet image from your Flutter assets folder that you saw previously.

The image is 116×128 pixels, and each frame is 29×32 pixels. The latter is what you’re setting the srcSize SpriteSheet parameter to. Flame will use these variables to create sprites from the different frames on your sprite sheet image.

Replace // TODO down animation with logic to initialize _runDownAnimation:

_runDownAnimation =
       spriteSheet.createAnimation(row: 0, stepTime: _animationSpeed, to: 4);

This code sets up an animation that loops across the first row of the player sprite sheet from the first frame until the fourth. It’s effectively a “while” loop that repeats from 0 until less than 4, where the sprite viewport moves in 32 pixel increments across 4 rows.

RayWorld player sprite sheet with boxes

Using this logic, initialize the rest of your animation variables.

_runLeftAnimation =
       spriteSheet.createAnimation(row: 1, stepTime: _animationSpeed, to: 4);
 
_runUpAnimation =
       spriteSheet.createAnimation(row: 2, stepTime: _animationSpeed, to: 4);
 
_runRightAnimation =
       spriteSheet.createAnimation(row: 3, stepTime: _animationSpeed, to: 4);
 
_standingAnimation =
       spriteSheet.createAnimation(row: 0, stepTime: _animationSpeed, to: 1);

Update your movePlayer function to assign the correct animations based on the player’s direction:

void movePlayer(double delta) {
   switch (direction) {
     case Direction.up:
       animation = _runUpAnimation;
       moveUp(delta);
       break;
     case Direction.down:
       animation = _runDownAnimation;
       moveDown(delta);
       break;
     case Direction.left:
       animation = _runLeftAnimation;
       moveLeft(delta);
       break;
     case Direction.right:
       animation = _runRightAnimation;
       moveRight(delta);
       break;
     case Direction.none:
       animation = _standingAnimation;
       break;
   }
 }

Build and run, and you’ll see your playable character has come to life as they run in each direction.

RayWorld player movement gif

At this point, you have the fundamentals of a game in place: a playable character with user input and movement. The next step is to add a world for your player to move around in.