Flame is the usual 2D path in Flutter. If you already know MonoGame or XNA, the game loop, sprites, input, and content pipeline are the same jobs with different names. This guide maps those APIs side by side so you can port a mental model rather than start from a Flutter-first tutorial.
Game Engine Overview
Flutter uses the Flame engine for 2D game development, which provides similar functionality to MonoGame/XNA. Both are component-based and handle rendering, input, and audio.
MonoGame/XNA
// MonoGame Game class
public class MyGame : Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
public MyGame()
{
graphics = new
GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
base.Initialize();
}
}
Flutter/Flame
// Flame game class
import 'package:flame/game.dart';
class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
// Initialize game here
// Load assets, setup world
await super.onLoad();
}
@override
void update(double dt) {
super.update(dt);
// Update game logic
}
}
// Run the game
void main() {
runApp(GameWidget(game: MyGame()));
}
Key differences
- Flame extends
FlameGameinstead ofGame. onLoad()is likeLoadContent()andInitialize().- No separate
Draw()method; components render themselves.
Game Loop & Update Cycle
Both engines use a fixed update loop with delta time for frame-independent movement.
MonoGame/XNA
// Game loop methods
protected override void Update(
GameTime gameTime
)
{
float deltaTime = (float)gameTime
.ElapsedGameTime.TotalSeconds;
// Update player position
playerPosition.X +=
velocity.X * deltaTime;
playerPosition.Y +=
velocity.Y * deltaTime;
base.Update(gameTime);
}
protected override void Draw(
GameTime gameTime
)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
spriteBatch.Draw(
playerTexture,
playerPosition,
Color.White
);
spriteBatch.End();
base.Draw(gameTime);
}
Flutter/Flame
// Game loop in Flame
class MyGame extends FlameGame {
late Player player;
@override
Future<void> onLoad() async {
player = Player();
add(player);
}
@override
void update(double dt) {
super.update(dt);
// Additional game logic
// Components update themselves
}
}
// Component with update and render
class Player extends SpriteComponent {
Vector2 velocity = Vector2(100, 0);
@override
void update(double dt) {
super.update(dt);
position.x += velocity.x * dt;
position.y += velocity.y * dt;
}
// render() is handled by SpriteComponent
}
Key differences
- Components handle their own update and rendering in Flame.
dt(delta time) is in seconds in both frameworks.- No explicit
Draw()call; Flame renders components automatically. - Use
add()to add components to the game world.
Sprites & Textures
Both frameworks load and render 2D sprites. Flame uses components while MonoGame uses textures directly.
MonoGame/XNA
// Loading and drawing sprites
Texture2D playerTexture;
Vector2 position;
Rectangle sourceRect;
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(
GraphicsDevice
);
playerTexture = Content.Load<Texture2D>(
"player"
);
position = new Vector2(100, 100);
}
protected override void Draw(
GameTime gameTime
)
{
spriteBatch.Begin();
// Simple draw
spriteBatch.Draw(
playerTexture,
position,
Color.White
);
// With source rectangle (sprite sheet)
spriteBatch.Draw(
playerTexture,
position,
sourceRect,
Color.White,
rotation,
origin,
scale,
SpriteEffects.None,
0f
);
spriteBatch.End();
}
Flutter/Flame
// Loading and rendering sprites
class Player extends SpriteComponent
with HasGameRef<MyGame> {
@override
Future<void> onLoad() async {
sprite = await gameRef.loadSprite(
'player.png'
);
size = Vector2(64, 64);
position = Vector2(100, 100);
anchor = Anchor.center;
}
}
// Sprite sheet animation
class AnimatedPlayer extends
SpriteAnimationComponent {
@override
Future<void> onLoad() async {
animation = await gameRef.loadSpriteAnimation(
'player_sheet.png',
SpriteAnimationData.sequenced(
amount: 4,
stepTime: 0.2,
textureSize: Vector2(64, 64),
),
);
size = Vector2(64, 64);
position = Vector2(100, 100);
}
}
Key differences
- Flame uses
SpriteComponentinstead of drawing textures manually. - Assets go in
assets/images/folder (configured inpubspec.yaml). SpriteAnimationComponenthandles sprite sheet animations automatically.- Anchor point determines sprite origin (like MonoGame's origin parameter).
Input Handling
Both frameworks provide keyboard, mouse, and touch input. Flame uses mixins for input handling.
MonoGame/XNA
// Input handling in MonoGame
KeyboardState keyboardState;
MouseState mouseState;
protected override void Update(
GameTime gameTime
)
{
keyboardState = Keyboard.GetState();
mouseState = Mouse.GetState();
// Keyboard input
if (keyboardState.IsKeyDown(Keys.Right))
{
playerPosition.X += speed *
deltaTime;
}
if (keyboardState.IsKeyDown(Keys.Space))
{
Jump();
}
// Mouse input
if (mouseState.LeftButton ==
ButtonState.Pressed)
{
Shoot(new Vector2(
mouseState.X,
mouseState.Y
));
}
base.Update(gameTime);
}
Flutter/Flame
// Input handling in Flame
class MyGame extends FlameGame
with KeyboardEvents, TapDetector {
// Keyboard input
@override
KeyEventResult onKeyEvent(
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
if (keysPressed.contains(
LogicalKeyboardKey.arrowRight
)) {
player.position.x += speed * dt;
}
if (event is RawKeyDownEvent &&
event.logicalKey ==
LogicalKeyboardKey.space) {
player.jump();
}
return KeyEventResult.handled;
}
// Touch/Mouse input
@override
void onTapDown(TapDownInfo info) {
shoot(info.eventPosition.game);
}
}
// Per-component input
class Button extends SpriteComponent
with TapCallbacks {
@override
void onTapDown(TapDownEvent event) {
// Handle button press
}
}
Key differences
- Flame uses mixins (
KeyboardEvents,TapDetector) for input. - Individual components can handle their own input with
TapCallbacks. - Touch and mouse input use the same API in Flame.
- Flame supports multi-touch natively.
Collision Detection
Both frameworks provide collision detection systems. Flame has built-in collision detection with mixins.
MonoGame/XNA
// Collision detection in MonoGame
Rectangle playerBounds;
Rectangle enemyBounds;
protected override void Update(
GameTime gameTime
)
{
playerBounds = new Rectangle(
(int)playerPosition.X,
(int)playerPosition.Y,
playerTexture.Width,
playerTexture.Height
);
enemyBounds = new Rectangle(
(int)enemyPosition.X,
(int)enemyPosition.Y,
enemyTexture.Width,
enemyTexture.Height
);
// Check collision
if (playerBounds.Intersects(
enemyBounds))
{
OnCollision();
}
// Circle collision
float distance = Vector2.Distance(
playerPosition,
enemyPosition
);
if (distance < playerRadius +
enemyRadius)
{
OnCollision();
}
}
Flutter/Flame
// Collision detection in Flame
class MyGame extends FlameGame
with HasCollisionDetection {
@override
Future<void> onLoad() async {
add(Player());
add(Enemy());
}
}
class Player extends SpriteComponent
with CollisionCallbacks {
@override
Future<void> onLoad() async {
await super.onLoad();
// Rectangle hitbox
add(RectangleHitbox());
// Or circle hitbox
// add(CircleHitbox());
}
@override
void onCollision(
Set<Vector2> intersectionPoints,
PositionComponent other,
) {
super.onCollision(intersectionPoints, other);
if (other is Enemy) {
// Handle collision with enemy
onHitEnemy();
}
}
}
class Enemy extends SpriteComponent
with CollisionCallbacks {
@override
Future<void> onLoad() async {
await super.onLoad();
add(RectangleHitbox());
}
}
Key differences
- Flame's collision system is automatic with
HasCollisionDetectionmixin. - Add hitboxes (
RectangleHitbox,CircleHitbox) to components. - Override
onCollision()to handle collision responses. - Supports polygon hitboxes and custom shapes.
Camera & Viewport
Both frameworks provide camera systems for scrolling and zooming. Flame has a built-in camera with follow behavior.
MonoGame/XNA
// Camera in MonoGame
Matrix cameraTransform;
Vector2 cameraPosition;
float zoom = 1.0f;
protected override void Update(
GameTime gameTime
)
{
// Follow player
cameraPosition = playerPosition;
// Create transform matrix
cameraTransform = Matrix.CreateTranslation(
-cameraPosition.X,
-cameraPosition.Y,
0
) * Matrix.CreateScale(zoom);
}
protected override void Draw(
GameTime gameTime
)
{
spriteBatch.Begin(
transformMatrix: cameraTransform
);
// Draw game objects
spriteBatch.Draw(
playerTexture,
playerPosition,
Color.White
);
spriteBatch.End();
}
Flutter/Flame
// Camera in Flame
class MyGame extends FlameGame {
late Player player;
@override
Future<void> onLoad() async {
// Setup camera
camera.followComponent(
player,
worldBounds: Rect.fromLTRB(
0, 0, 2000, 2000
),
);
// Set zoom
camera.zoom = 2.0;
// Camera shake effect
camera.shake(
intensity: 10,
duration: 0.5,
);
}
}
// Using CameraComponent (advanced)
class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
final world = World();
final camera = CameraComponent(world: world);
camera.viewfinder.anchor = Anchor.center;
camera.viewport = FixedResolutionViewport(
Vector2(800, 600)
);
addAll([camera, world]);
}
}
Key differences
- Flame's camera automatically follows components with
followComponent(). - No manual transform matrices needed in Flame.
- Built-in camera effects: shake, zoom, pan.
CameraComponentfor advanced multi-camera setups.
Particle Systems
Both frameworks support particle effects for explosions, fire, smoke, etc.
MonoGame/XNA
// Simple particle system
class Particle
{
public Vector2 Position;
public Vector2 Velocity;
public float Life;
public Color Color;
}
List<Particle> particles =
new List<Particle>();
void CreateExplosion(Vector2 position)
{
for (int i = 0; i < 50; i++)
{
particles.Add(new Particle
{
Position = position,
Velocity = new Vector2(
Random(-100, 100),
Random(-100, 100)
),
Life = 1.0f,
Color = Color.Orange
});
}
}
void UpdateParticles(float deltaTime)
{
foreach (var p in particles)
{
p.Position += p.Velocity * deltaTime;
p.Life -= deltaTime;
}
particles.RemoveAll(p => p.Life <= 0);
}
Flutter/Flame
// Particle system in Flame
import 'package:flame/particles.dart';
void createExplosion(Vector2 position) {
final particle = ParticleSystemComponent(
particle: Particle.generate(
count: 50,
lifespan: 1.0,
generator: (i) => AcceleratedParticle(
acceleration: Vector2(0, 100),
speed: Vector2.random() * 200,
position: position,
child: CircleParticle(
radius: 2.0,
paint: Paint()
..color = Colors.orange,
),
),
),
);
add(particle);
}
// Advanced particle with sprites
void createSmokeEffect(Vector2 position) {
add(
ParticleSystemComponent(
particle: Particle.generate(
count: 20,
generator: (i) => MovingParticle(
from: position,
to: position + Vector2(0, -100),
curve: Curves.easeOut,
child: ScalingParticle(
to: 2.0,
child: SpriteParticle(
sprite: smokeSprite,
),
),
),
),
),
);
}
Key differences
- Flame has a built-in particle system with composable particles.
- Multiple particle types:
CircleParticle,SpriteParticle,ComponentParticle. - Particles can be nested for complex effects.
- Automatic cleanup when lifespan expires.
Audio & Sound Effects
Both frameworks provide audio playback for music and sound effects.
MonoGame/XNA
// Audio in MonoGame
SoundEffect jumpSound;
SoundEffectInstance musicInstance;
Song backgroundMusic;
protected override void LoadContent()
{
jumpSound = Content.Load<SoundEffect>(
"jump"
);
backgroundMusic = Content.Load<Song>(
"background"
);
// Play music
MediaPlayer.Play(backgroundMusic);
MediaPlayer.IsRepeating = true;
MediaPlayer.Volume = 0.5f;
}
void PlayJumpSound()
{
// Simple playback
jumpSound.Play();
// With volume and pitch
jumpSound.Play(
volume: 0.8f,
pitch: 0.0f,
pan: 0.0f
);
}
void StopMusic()
{
MediaPlayer.Stop();
}
Flutter/Flame
// Audio in Flame
import 'package:flame_audio/flame_audio.dart';
class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
// Preload audio
await FlameAudio.audioCache.loadAll([
'jump.mp3',
'background.mp3',
]);
// Play background music
FlameAudio.bgm.initialize();
await FlameAudio.bgm.play(
'background.mp3',
volume: 0.5,
);
}
void playJumpSound() {
// Simple playback
FlameAudio.play('jump.mp3');
// With volume
FlameAudio.play(
'jump.mp3',
volume: 0.8,
);
}
void stopMusic() {
FlameAudio.bgm.stop();
}
@override
void onRemove() {
FlameAudio.bgm.dispose();
super.onRemove();
}
}
Key differences
- Flame uses
flame_audiopackage for audio support. FlameAudio.play()for sound effects,FlameAudio.bgmfor music.- Audio files go in
assets/audio/folder. - Background music automatically loops by default.
Tilemaps & Level Design
Both frameworks support tilemap rendering for 2D level design.
MonoGame/XNA
// Tilemap in MonoGame
Texture2D tilesheet;
int[,] mapData;
int tileWidth = 32;
int tileHeight = 32;
protected override void LoadContent()
{
tilesheet = Content.Load<Texture2D>(
"tilesheet"
);
// Load map data
mapData = new int[,] {
{ 1, 1, 1, 1, 1 },
{ 1, 0, 0, 0, 1 },
{ 1, 0, 2, 0, 1 },
{ 1, 1, 1, 1, 1 },
};
}
protected override void Draw(
GameTime gameTime
)
{
spriteBatch.Begin();
for (int y = 0; y < mapData.GetLength(0); y++)
{
for (int x = 0; x < mapData.GetLength(1); x++)
{
int tileId = mapData[y, x];
Rectangle source = new Rectangle(
(tileId % 8) * tileWidth,
(tileId / 8) * tileHeight,
tileWidth,
tileHeight
);
spriteBatch.Draw(
tilesheet,
new Vector2(x * tileWidth,
y * tileHeight),
source,
Color.White
);
}
}
spriteBatch.End();
}
Flutter/Flame
// Tilemap with Tiled in Flame
import 'package:flame_tiled/flame_tiled.dart';
class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
// Load Tiled map
final tiledMap = await TiledComponent.load(
'map.tmx',
Vector2.all(32),
);
add(tiledMap);
}
}
// Manual tilemap rendering
class TileMap extends Component {
late Sprite tileSprite;
final List<List<int>> mapData = [
[1, 1, 1, 1, 1],
[1, 0, 0, 0, 1],
[1, 0, 2, 0, 1],
[1, 1, 1, 1, 1],
];
@override
Future<void> onLoad() async {
tileSprite = await Sprite.load(
'tilesheet.png'
);
}
@override
void render(Canvas canvas) {
for (int y = 0; y < mapData.length; y++) {
for (int x = 0; x < mapData[y].length; x++) {
final tileId = mapData[y][x];
final srcX = (tileId % 8) * 32.0;
final srcY = (tileId ~/ 8) * 32.0;
tileSprite.renderRect(
canvas,
Rect.fromLTWH(
x * 32.0, y * 32.0, 32, 32
),
srcPosition: Vector2(srcX, srcY),
srcSize: Vector2(32, 32),
);
}
}
}
}
Key differences
- Flame has built-in Tiled editor support via
flame_tiledpackage. - Load
.tmxfiles directly withTiledComponent. - Tiled supports layers, collision objects, and custom properties.
- Manual tilemap rendering similar to MonoGame is also possible.
Physics Simulation
Both frameworks support physics engines. Flame integrates with Forge2D (Box2D port) for advanced physics.
MonoGame/XNA
// Using Farseer Physics (Box2D port)
World physicsWorld;
Body playerBody;
void Initialize()
{
physicsWorld = new World(
new Vector2(0, 9.8f)
);
// Create player body
playerBody = BodyFactory.CreateRectangle(
physicsWorld,
1.0f, // width
2.0f, // height
1.0f, // density
new Vector2(100, 100)
);
playerBody.BodyType = BodyType.Dynamic;
playerBody.Restitution = 0.3f;
playerBody.Friction = 0.5f;
}
void Update(GameTime gameTime)
{
float deltaTime = (float)gameTime
.ElapsedGameTime.TotalSeconds;
// Step physics simulation
physicsWorld.Step(deltaTime);
// Apply forces
playerBody.ApplyForce(
new Vector2(100, 0)
);
// Sync graphics with physics
playerPosition = playerBody.Position;
}
Flutter/Flame
// Using Flame Forge2D
import 'package:flame_forge2d/flame_forge2d.dart';
class MyGame extends Forge2DGame {
@override
Future<void> onLoad() async {
world.gravity = Vector2(0, 9.8);
add(Player());
}
}
class Player extends BodyComponent {
@override
Body createBody() {
final shape = PolygonShape()
..setAsBoxXY(0.5, 1.0);
final fixtureDef = FixtureDef(shape)
..restitution = 0.3
..friction = 0.5
..density = 1.0;
final bodyDef = BodyDef()
..position = Vector2(5, 5)
..type = BodyType.dynamic;
return world.createBody(bodyDef)
..createFixture(fixtureDef);
}
void jump() {
body.applyLinearImpulse(
Vector2(0, -300)
);
}
void moveRight() {
body.applyForce(Vector2(100, 0));
}
}
Key differences
- Flame uses
flame_forge2d(Box2D) for physics. - Extend
Forge2DGameinstead ofFlameGame. - Components extend
BodyComponentfor physics bodies. - Physics world is automatically stepped by the engine.
Related reading
Official docs: Flame Documentation and Flame Examples.