I’ve always wanted to make video games. My first ever Android app that helped me to get my first job was a simple game, made with Android views. After that, there were a lot of attempts to create a more elaborate game using a game engine, but all of them failed due to a lack of time or the complexity of a framework. But when I first heard about Flame engine, based on Flutter, I was immediately attracted by its simplicity and cross-platform support, so I decided to try building a game with it. I wanted to start with something simple, but still challenging, to get a feel of the engine. This series of articles is my journey of learning Flame (and Flutter) and building a basic platformer game. I’ll try to make it quite detailed, so it should be useful to anyone who’s just dipping their toes into Flame or game dev in general. Over the course of 4 articles, I’m going to build a 2d side-scrolling game, that includes: A character that can run and jump A camera that follows the player Scrolling level map, with ground and platforms Parallax background Coins that the player can collect and HUD that displays the number of coins Win screen In the first part, we’re going to create a new Flame project, load all assets, add a player character, and teach him how to run. Project setup First, let’s create a new project. The official tutorial does a great job of describing all steps to do that, so just follow it. Bare Flame game One thing to add: when you’re setting up file, you can update libraries versions to the latest available, or leave it as is, because the caret sign (^) before a version will ensure your app uses the latest non-breaking version. ( ) pubspec.yaml caret syntax If you followed all steps, your file should look like this: main.dart import 'package:flame/game.dart'; import 'package:flutter/widgets.dart'; void main() { final game = FlameGame(); runApp(GameWidget(game: game)); } Assets Before we continue, we need to prepare assets that will be used for the game. Assets are images, animations, sounds, etc. For purposes of this series, we’ll use only images which are also called sprites in game dev. The simplest way to build a platformer level is to use tile maps and tile sprites. It means that the level is basically a grid, where each cell indicates what object / ground / platform it represents. Later, when the game is running, information from each cell is mapped to the corresponding tile sprite. Games graphics built using this technique could be really elaborate or very simple. For example, in Super Mario bros, you see that a lot of elements are repeating. That’s because, for each ground tile in the game grid, there’s only one ground image that represents it. We will follow the same approach, and prepare a single image for each static object we have. We also want some of the objects, such as the player character and coins to be animated. Animation is usually stored as a series of still images, each representing a single frame. When animation is playing, frames go one after another, creating the illusion of the object moving. Now the most important question is where to get the assets. Of course, you can draw them yourself, or commission them to an artist. Also, there are a lot of awesome artists who contributed game assets to open-source. I will be using by . Arcade Platformer Assets pack GrafxKid Typically, image assets come in two forms: sprite sheets and single sprites. The former is a large image, containing all game assets in one. Then game developers specify the exact position of the required sprite, and the game engine cuts it from the sheet. For this game, I will use single sprites (except animations, it’s easier to keep them as one image) because I don’t need all assets provided in the sprite sheet. Whether you’re creating sprites yourself or getting them from an artist, you might need to slice them to make them more suitable for the game engine. You can use tools created specifically for that purpose (like or any graphical editor. I used Adobe Photoshop, because, in this sprite sheet, sprites have unequal space between them, which made it hard for automatical tools to extract images, so I had to do it manually. texture packer) You also might want to increase the size of the assets, but if it’s not a vector image, the resulting sprite could become blurry. One workaround I found that works great for pixel art is to use resizing method in Photoshop (or Interpolation set to None in Gimp). But if your asset is more detailed, it probably won’t work. Nearest Neighbour (hard edges) With explanations out of the way, download or prepare your own and add them to folder of your project. the assets I prepared assets/images Whenever you add new assets, you need to register them in file like this: pubspec.yaml flutter: assets: - assets/images/ And the tip for the future: if you are updating already registered assets you need to restart the game to see the changes. Now let’s actually load the assets into the game. I like to have all asset names in one place, which works great for a small game, as it’s easier to keep track of everything and modify if needed. So, let’s create a new file in the directory: lib assets.dart const String THE_BOY = "theboy.png"; const String GROUND = "ground.png"; const String PLATFORM = "platform.png"; const String MIST = "mist.png"; const String CLOUDS = "clouds.png"; const String HILLS = "hills.png"; const String COIN = "coin.png"; const String HUD = "hud.png"; const List<String> SPRITES = [THE_BOY, GROUND, PLATFORM, MIST, CLOUDS, HILLS, COIN, HUD]; And then create another file, which will contain all game logic in the future: game.dart import 'package:flame/game.dart'; import 'assets.dart' as Assets; class PlatformerGame extends FlameGame { @override Future<void> onLoad() async { await images.loadAll(Assets.SPRITES); } } is the main class that represents our game, it extends , the base game class used in the Flame engine. Which in turn extends - the basic building block of Flame. Everything in your game, including images, interface or effects are Components. Each has an async method , which is called on component initialization. Usually, all component setup logic goes there. PlatformerGame FlameGame Component Component onLoad Finally, we imported our file we created earlier and added to explicitly declare where our assets constants are coming from. And used method to load all assets listed in the list to the game images cache. assets.dart as Assets images.loadAll SPRITES Then, we need to create our new from . Modify the file as follows: PlatformerGame main.dart import 'package:flame/game.dart'; import 'package:flutter/widgets.dart'; import 'game.dart'; void main() { runApp( const GameWidget<PlatformerGame>.controlled( gameFactory: PlatformerGame.new, ), ); } All preparation is done, and the fun part begins. Adding player character Create a new folder and a new file inside of it. This is gonna be the component that represents the player character: The Boy. lib/actors/ theboy.dart import '../game.dart'; import '../assets.dart' as Assets; import 'package:flame/components.dart'; class TheBoy extends SpriteAnimationComponent with HasGameRef<PlatformerGame> { TheBoy({ required super.position, // Position on the screen }) : super( size: Vector2.all(48), // Size of the component anchor: Anchor.bottomCenter // ); @override Future<void> onLoad() async { animation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 1, // For now we only need idle animation, so we load only 1 frame textureSize: Vector2.all(20), // Size of a single sprite in the sprite sheet stepTime: 0.12, // Time between frames, since it's a single frame not that important ), ); } } The class extends which is a component used for animated sprites and has a mixin which allows us to reference the game object to load images from the game cache or get global variables later. SpriteAnimationComponent HasGameRef In our method we create a new from the sprite sheet we declared in the file. onLoad SpriteAnimation THE_BOY assets.dart Now let’s add our player to the game! Return to the file and add following to the bottom of method: game.dart onLoad final theBoy = TheBoy(position: Vector2(size.x / 2, size.y / 2)); add(theBoy); If you run the game now, we should be able to meet The Boy! Player movement First, we need to add the ability to control The Boy from the keyboard. Let’s add mixin to the file. HasKeyboardHandlerComponents game.dart class PlatformerGame extends FlameGame with HasKeyboardHandlerComponents Next, let’s return to and mixin: theboy.dart KeyboardHandler class TheBoy extends SpriteAnimationComponent with KeyboardHandler, HasGameRef<PlatformerGame> Then, add some new class variables to component: TheBoy final double _moveSpeed = 300; // Max player's move speed int _horizontalDirection = 0; // Current direction the player is facing final Vector2 _velocity = Vector2.zero(); // Current player's speed Finally, let’s override the method which allows listening for keyboard inputs: onKeyEvent @override bool onKeyEvent(RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed) { _horizontalDirection = 0; _horizontalDirection += (keysPressed.contains(LogicalKeyboardKey.keyA) || keysPressed.contains(LogicalKeyboardKey.arrowLeft)) ? -1 : 0; _horizontalDirection += (keysPressed.contains(LogicalKeyboardKey.keyD) || keysPressed.contains(LogicalKeyboardKey.arrowRight)) ? 1 : 0; return true; } Now equals 1 if the player moves to the right, -1 if the player moves to the left, and 0 if the player doesn’t move. However, we cannot yet see it on the screen, because the position of the player isn’t changed yet. Let’s fix that by adding the method. _horizontalDirection update Now I need to explain what the game loop is. Basically, it means that the game is being run in an endless loop. In each iteration, the current state is rendered in method and then a new state is calculated in the method . The parameter in the method’s signature is time in milliseconds since the last state update. With that in mind, add the following to : Component's render update dt theboy.dart @override void update(double dt) { super.update(dt); _velocity.x = _horizontalDirection * _moveSpeed; position += _velocity * dt; } For each game loop cycle, we update horizontal velocity, using the current direction and max speed. Then we change the sprite position with the updated value multiplied by . dt Why do we need the last part? Well, if you update the position with just velocity, then the sprite will fly away into space. But can we just use the smaller speed value, you may ask? We can, but the way the player moves will be different with different frames per second (FPS) rate. The number of frames (or game loops) per second depends on the game performance and hardware it’s run on. The better the device performance, the higher the FPS, and the faster the player moves. In order to avoid that, we make the speed depend on the time passed from the last frame. That way the sprite will move similarly on any FPS. Okay, if we run the game now, we should see this: Awesome, now let’s make the boy turn around when he goes to the left. Add this to the bottom of the method: update if ((_horizontalDirection < 0 && scale.x > 0) || (_horizontalDirection > 0 && scale.x < 0)) { flipHorizontally(); } Fairly easy logic: we check if the current direction (the arrow the user is pressing) is different from the direction of the sprite, then we flip the sprite along the horizontal axis. Now let’s also add running animation. First define two new class variables: late final SpriteAnimation _runAnimation; late final SpriteAnimation _idleAnimation; Then update like this: onLoad @override Future<void> onLoad() async { _idleAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 1, textureSize: Vector2.all(20), stepTime: 0.12, ), ); _runAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 4, textureSize: Vector2.all(20), stepTime: 0.12, ), ); animation = _idleAnimation; } Here we extracted previously added idle animation to the class variable and defined a new run animation variable. Next, let’s add a new method: updateAnimation void updateAnimation() { if (_horizontalDirection == 0) { animation = _idleAnimation; } else { animation = _runAnimation; } } And finally, invoke this method at the bottom of the method and run the game. update Conclusion That’s it for the first part. We learned how to set up a Flame game, where to find assets, how to load them into your game, and how to create an awesome animated character and make it move based on keyboard inputs. The code for this part could be found . on my github In the next article, I’ll cover how to create a game level using Tiled, how to control the Flame camera, and add a parallax background. Stay tuned! Resources At the end of each part, I’ll be adding a list of awesome creators and resources I learned from. GrafxKid’s Arcade platformer assets https://opengameart.org/content/arcade-platformer-assets DevKage Flame Game Development Series: https://youtu.be/mSPalRqZQS8 Craig Oda channel https://youtu.be/hwQpBuZoV9s Ember Quest Game Tutorial https://github.com/flame-engine/flame/blob/main/doc/tutorials/platformer/platformer.md Flame Engine documentation https://docs.flame-engine.org/1.6.0/flame/flame.html