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.
Ready to learn more? Visit the Flame Documentation or explore Flame Examples for more game development patterns.