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.
📽 Video version available on YouTube and Odysee
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';
class Basic2DMovement extends FlameGame with KeyboardEvents {
static const _size = 100.0;
final paint = Paint()..color = Colors.lightGreen;
double _x = 0.0;
double _y = 0.0;
@override
void render(Canvas canvas) {
super.render(canvas);
final rect = Rect.fromLTWH(_x, _y, _size, _size);
canvas.drawRect(rect, paint);
}
@override
KeyEventResult onKeyEvent(
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
final isKeyDown = event is RawKeyDownEvent;
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 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;
static const double _speed = 100.0;
static const double _friction = 0.9;
Vector2 _position = Vector2.zero();
Vector2 _movementVector = Vector2.zero();
bool _isPressingLeft = false;
bool _isPressingRight = false;
bool _isPressingUp = false;
bool _isPressingDown = false;
@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
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';
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;
@override
Future<void> onLoad() async {
await super.onLoad();
size = Vector2(_size, _size);
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 {
late final Player _player;
@override
Color backgroundColor() => const Color(0xFF353935);
@override
Future<void> onLoad() async {
await super.onLoad();
_player = Player();
await add(_player);
}
}