Flutter Intermediate Lesson 110 of 135 3 min read
Camera and World in Flame: Follow the Player, Fixed Resolution and HUD
Learn the Flame camera and world: follow the player, set bounds, use a fixed resolution for every screen, and keep a HUD on screen. Runnable example.
On this page
So far the whole game fitted on one screen. Real games have worlds bigger than the screen: a long level, a large map. Flame separates the two:
- World: where the game objects live, in world coordinates.
- Camera: looks at part of the world and shows it on the screen. It can move, follow the player and zoom.
- Viewport: the area of the screen the camera draws into. Things added to the viewport (the HUD: score, lives, buttons) do not move with the world.
FlameGame
├── world ← player, enemies, trees (moves under the camera)
└── camera
├── viewfinder ← position, zoom, angle of the camera
└── viewport ← HUD: score, health bar (fixed on screen)
A camera that follows the player
import 'package:flame/components.dart';
import 'package:flame/experimental.dart' show Rectangle;
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
const worldSize = 2000.0;
void main() => runApp(GameWidget(game: ExploreGame()));
class ExploreGame extends FlameGame with HasKeyboardHandlerComponents {
late final Explorer player;
@override
Future<void> onLoad() async {
// Some landmarks so you can see the world move.
for (var x = 0.0; x <= worldSize; x += 200) {
for (var y = 0.0; y <= worldSize; y += 200) {
world.add(CircleComponent(
radius: 20,
position: Vector2(x, y),
anchor: Anchor.center,
paint: Paint()..color = Colors.green.withValues(alpha: 0.6),
));
}
}
// The edges of the world
world.add(RectangleComponent(
size: Vector2.all(worldSize),
paint: Paint()
..color = Colors.white24
..style = PaintingStyle.stroke
..strokeWidth = 4,
));
player = Explorer()..position = Vector2.all(worldSize / 2);
world.add(player);
camera.follow(player); // keep the player in the centre
camera.setBounds(Rectangle.fromLTRB(0, 0, worldSize, worldSize), considerViewport: true);
// HUD: added to the viewport, so it never moves.
camera.viewport.add(TextComponent(
text: 'Arrow keys / WASD to explore',
position: Vector2(16, 16),
));
}
}
class Explorer extends RectangleComponent with KeyboardHandler {
Explorer() : super(size: Vector2.all(40), anchor: Anchor.center, paint: Paint()..color = Colors.orange);
final direction = Vector2.zero();
@override
bool onKeyEvent(KeyEvent event, Set<LogicalKeyboardKey> keys) {
direction
..x = (keys.contains(LogicalKeyboardKey.arrowRight) || keys.contains(LogicalKeyboardKey.keyD) ? 1.0 : 0.0) -
(keys.contains(LogicalKeyboardKey.arrowLeft) || keys.contains(LogicalKeyboardKey.keyA) ? 1.0 : 0.0)
..y = (keys.contains(LogicalKeyboardKey.arrowDown) || keys.contains(LogicalKeyboardKey.keyS) ? 1.0 : 0.0) -
(keys.contains(LogicalKeyboardKey.arrowUp) || keys.contains(LogicalKeyboardKey.keyW) ? 1.0 : 0.0);
return true;
}
@override
void update(double dt) {
super.update(dt);
if (!direction.isZero()) position += direction.normalized() * 300 * dt;
position.clamp(size / 2, Vector2.all(worldSize) - size / 2);
}
}
Click the game, then use the arrow keys. The player stays in the middle and the world scrolls. At the edges the camera stops, because of setBounds.
Fixed resolution: the same game on every screen
Phones, tablets and monitors all have different sizes. A fixed resolution camera lets you design for one size (say 400×800) and scales it to fit any screen, adding bars if needed:
class MyGame extends FlameGame {
MyGame()
: super(
camera: CameraComponent.withFixedResolution(width: 400, height: 800),
);
@override
Future<void> onLoad() async {
camera.viewfinder.anchor = Anchor.topLeft; // (0,0) top-left of the 400×800 area
}
}
Now 400 is always the full width, whatever the device. Most of the game projects use this.
Zoom and shake
camera.viewfinder.zoom = 2; // twice as close
camera.viewfinder.angle = 0.1; // tilt
camera.moveTo(Vector2(500, 300)); // jump to a point
A simple screen shake: move the viewfinder a few pixels back and forth with a MoveEffect (see Effects and particles).
World vs screen coordinates
- A component in the world uses world coordinates.
- A component in the viewport uses screen coordinates.
- Convert between them with
camera.globalToLocal(screenPoint)andcamera.localToGlobal(worldPoint).
Try it yourself
Add a “minimap” text in the HUD that shows the player’s position rounded to whole numbers, updated every frame. (Tip: give the TextComponent a reference to the player and set text in its update.)