Flutter, a modern UI toolkit with which you can build cross-platform beautiful UIs. But, do you know that it can also be used to develop games?
Introducing Flame Engine, a 2D game framework built on top of Flutter with which you can create any kind of 2D game and run it in all Flutter-supported platforms like mobile, the web and desktop.
In this article I will show you 3 quick samples to get started with Flame. The samples can be found here.
Simple 2D movement
To start, add the flame dependency to pubspec.yaml:
dependencies:
flame: 1.1.0
Use the following snippet (in-code comments):
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// Create a class that extends from FlameGame, use the
// KeyboardEvents mixin to receive keyboard input events
class Basic2DMovement extends FlameGame with KeyboardEvents {
// This will be the size of the moving object
static const _size = 100.0;
// With this paint we will give it a color
final paint = Paint()..color = Colors.lightGreen;
// Use this variables to store the position of the object
double _x = 0.0;
double _y = 0.0;
// This render function will be called in
// each frame to paint the object
@override
void render(Canvas canvas) {
super.render(canvas);
// This rect represents our object (a square)
final rect = Rect.fromLTWH(_x, _y, _size, _size);
// Draw the object with the provided Canvas
canvas.drawRect(rect, paint);
}
@override
KeyEventResult onKeyEvent(
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
// Store if the key is down
final isKeyDown = event is RawKeyDownEvent;
// Alter the x, y values according to the current keys pressed
if (keysPressed.contains(LogicalKeyboardKey.arrowLeft) && isKeyDown) {
_x -= 10.0;
} else if (keysPressed.contains(LogicalKeyboardKey.arrowRight) &&
isKeyDown) {
_x += 10.0;
}
if (keysPressed.contains(LogicalKeyboardKey.arrowUp) && isKeyDown) {
_y -= 10.0;
} else if (keysPressed.contains(LogicalKeyboardKey.arrowDown) &&
isKeyDown) {
_y += 10.0;
}
// Return this value to acknowledge that the input
// has been managed
return KeyEventResult.handled;
}
}
To execute this sample, this class must be wrapped within a GameObject called in runApp()
:
void main() {
runApp(GameWidget(game: Basic2DMovement()));
}
Advanced 2D movement
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Advanced2DMovement extends FlameGame with KeyboardEvents {
static const _size = 100.0;
final paint = Paint()..color = Colors.lightGreen;
// This will be the speed of the object
static const double _speed = 100.0;
// This friction will apply if there are no input so the object
// does not stop immediately
static const double _friction = 0.9;
// Store the position in a Vector2
Vector2 _position = Vector2.zero();
// This vector is the current momentum of the object
Vector2 _movementVector = Vector2.zero();
// This booleans will indicate us the current pressed keys
bool _isPressingLeft = false;
bool _isPressingRight = false;
bool _isPressingUp = false;
bool _isPressingDown = false;
// The update function will be called in each frame
// with the time from the last frame as the delta value,
// this way we can ensure a framerate
// independent movement
@override
void update(double delta) {
super.update(delta);
// Create a vector to store the current input
final Vector2 inputVector = Vector2.zero();
// Alter the input vector according to the current pressed keys
if (_isPressingLeft) {
inputVector.x -= 1.0;
} else if (_isPressingRight) {
inputVector.x += 1.0;
}
if (_isPressingUp) {
inputVector.y -= 1.0;
} else if (_isPressingDown) {
inputVector.y += 1.0;
}
// If there are some input...
if (!inputVector.isZero()) {
// Assign the input vector to the movement vector
_movementVector = inputVector;
// Normalize the movement vector so the speed
// will be always the same in all directions
_movementVector.normalize();
// Apply the speed and the delta time for a
// framerate independent movement
_movementVector *= _speed * delta;
} else {
// If no keys are pressed, apply a friction to the vector to make
// the object stop gradually
_movementVector *= _friction;
}
// Apply movement vector to the current position
_position += _movementVector;
}
@override
void render(Canvas canvas) {
super.render(canvas);
final rect = Rect.fromLTWH(_position.x, _position.y, _size, _size);
canvas.drawRect(rect, paint);
}
@override
KeyEventResult onKeyEvent(
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
if (keysPressed.contains(LogicalKeyboardKey.arrowLeft) &&
event is RawKeyDownEvent) {
_isPressingLeft = true;
} else if (event is RawKeyUpEvent &&
event.data.logicalKey == LogicalKeyboardKey.arrowLeft) {
_isPressingLeft = false;
}
if (keysPressed.contains(LogicalKeyboardKey.arrowRight) &&
event is RawKeyDownEvent) {
_isPressingRight = true;
} else if (event is RawKeyUpEvent &&
event.data.logicalKey == LogicalKeyboardKey.arrowRight) {
_isPressingRight = false;
}
if (keysPressed.contains(LogicalKeyboardKey.arrowUp) &&
event is RawKeyDownEvent) {
_isPressingUp = true;
} else if (event is RawKeyUpEvent &&
event.data.logicalKey == LogicalKeyboardKey.arrowUp) {
_isPressingUp = false;
}
if (keysPressed.contains(LogicalKeyboardKey.arrowDown) &&
event is RawKeyDownEvent) {
_isPressingDown = true;
} else if (event is RawKeyUpEvent &&
event.data.logicalKey == LogicalKeyboardKey.arrowDown) {
_isPressingDown = false;
}
return KeyEventResult.handled;
}
}
Add a sprite and move the logic to a Player class
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// We are going to move all the logic regarding
// the player to this class, the KeyboardHandler
// will allow us to capture keyboard events from within.
// All the previous logic has been moved to this class
// almost without changes.
//
// This time we are going to extend from SpriteComponent,
// which will allow us to add a sprite to this element.
class Player extends SpriteComponent with KeyboardHandler {
static const _size = 128.0;
static const double _speed = 100.0;
static const double _friction = 0.9;
Vector2 _movementVector = Vector2.zero();
bool _isPressingLeft = false;
bool _isPressingRight = false;
bool _isPressingUp = false;
bool _isPressingDown = false;
// The onLoad() function is called at the beginning
// to make an initial load of resources
@override
Future<void> onLoad() async {
await super.onLoad();
size = Vector2(_size, _size);
// Calling Sprite.load() we can asign to this
// component the given image
sprite = await Sprite.load('flutter.png');
}
@override
void update(double delta) {
super.update(delta);
final Vector2 inputVector = Vector2.zero();
if (_isPressingLeft) {
inputVector.x -= 1.0;
} else if (_isPressingRight) {
inputVector.x += 1.0;
}
if (_isPressingUp) {
inputVector.y -= 1.0;
} else if (_isPressingDown) {
inputVector.y += 1.0;
}
if (!inputVector.isZero()) {
_movementVector = inputVector;
_movementVector.normalize();
_movementVector *= _speed * delta;
} else {
_movementVector *= _friction;
}
position += _movementVector;
}
@override
bool onKeyEvent(
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
if (keysPressed.contains(LogicalKeyboardKey.arrowLeft) &&
event is RawKeyDownEvent) {
_isPressingLeft = true;
} else if (event is RawKeyUpEvent &&
event.data.logicalKey == LogicalKeyboardKey.arrowLeft) {
_isPressingLeft = false;
}
if (keysPressed.contains(LogicalKeyboardKey.arrowRight) &&
event is RawKeyDownEvent) {
_isPressingRight = true;
} else if (event is RawKeyUpEvent &&
event.data.logicalKey == LogicalKeyboardKey.arrowRight) {
_isPressingRight = false;
}
if (keysPressed.contains(LogicalKeyboardKey.arrowUp) &&
event is RawKeyDownEvent) {
_isPressingUp = true;
} else if (event is RawKeyUpEvent &&
event.data.logicalKey == LogicalKeyboardKey.arrowUp) {
_isPressingUp = false;
}
if (keysPressed.contains(LogicalKeyboardKey.arrowDown) &&
event is RawKeyDownEvent) {
_isPressingDown = true;
} else if (event is RawKeyUpEvent &&
event.data.logicalKey == LogicalKeyboardKey.arrowDown) {
_isPressingDown = false;
}
return true;
}
}
class SpriteExample extends FlameGame with HasKeyboardHandlerComponents {
// Declare the player as a variable
late final Player _player;
// Let's modify the background color
@override
Color backgroundColor() => const Color(0xFF353935);
@override
Future<void> onLoad() async {
await super.onLoad();
// Add the player to this game
_player = Player();
await add(_player);
}
}
Top comments (3)
i like the promises Flutter brings (native performance on any platform, write-once-run-everywhere, made for browsers and just recently will allow some desktop export without relying on electron, and Flame has a lot of potential if it can harness all these low level performance tweaks, but the 2d-only aspect makes it lag behind good game frameworks on almost every language.
Python, that's interpreted instead of compiled has 3d engines and frameworks (pygame 2 onwards).
Even javascript that's an ungodly mess to explore, have 3d gpu accelerated game engines or frameworks (three.js, babylon.js, etc.)
Wish google gave the flutter devs a money grant or some other kind of sponsorship to advance flame to be closer to the maturity of Raylib, LibGDX or other frameworks.
A 3D renderer would be awesome, indeed. But the problem is that Flutter is designed to create conventional 2D user interfaces, so Flame is constrained to that. Personally, I think that can be an interesting option if you plan to develop a 2D game, but if you are going after 3D you should look to other tools that can run on 3D canvases (Flutter uses Skia, which is optimized for 2D).
At some point we'll most likely be able to make it a 3D engine, but right now we are lacking proper support for shaders from Flutter (it is underway though) and access to a Z-buffer.