Camera System
The Camera System manages the 2D camera view for rendering, allowing control over position, zoom, rotation, and offset. It works in conjunction with the CameraComponent component to provide flexible camera control for the game.
Architecture
The camera system consists of two main parts:
- CameraComponent Component - Stores camera properties (position, zoom, rotation, offset)
- CameraSystem - Manages sf::View transformations and applies camera properties to the render window
CameraComponent Component
The CameraComponent component stores all camera-related properties:
struct CameraComponent {
float x = 0.0F; // Camera X position
float y = 0.0F; // Camera Y position
float zoom = 1.0F; // Zoom level (1.0 = normal, 2.0 = 2x zoom in)
float offsetX = 0.0F; // Offset X (for screen shake, etc.)
float offsetY = 0.0F; // Offset Y (for screen shake, etc.)
float rotation = 0.0F; // Rotation in degrees
bool active = true; // Whether this camera is active
// Target following
EntityId targetEntity = 0; // Entity to follow (0 = none)
float followSmoothness = 5.0F; // How fast camera follows (higher = faster)
bool followEnabled = false; // Whether following is active
};
Factory Method
CameraComponent camera = CameraComponent::create(100.0F, 200.0F, 2.0F);
// Creates camera at (100, 200) with 2x zoom
Position Control
camera.setPosition(400.0F, 300.0F); // Absolute positioning
camera.move(10.0F, 5.0F); // Relative movement
Zoom Control
camera.setZoom(2.0F); // 2x zoom (shows half the view area)
camera.clampZoom(0.5F, 4.0F); // Ensure zoom stays within bounds
Zoom behavior:
- zoom = 1.0 - Normal view (shows full window size)
- zoom = 2.0 - Zoomed in 2x (shows half the area)
- zoom = 0.5 - Zoomed out 2x (shows double the area)
Offset (Screen Shake)
The offset is added to the camera position when calculating the final view center. This is useful for: - Screen shake effects - Camera wobble - Temporary view adjustments
Rotation
camera.setRotation(45.0F); // Set rotation to 45 degrees
camera.rotate(10.0F); // Rotate by additional 10 degrees
Reset
Target Following
// Set entity to follow with smoothness factor
camera.setTarget(playerEntity, 10.0F); // Follow player with smoothness 10
// Clear target (stop following)
camera.clearTarget();
Following behavior:
- followSmoothness controls how fast the camera converges to the target (higher = faster)
- The camera smoothly interpolates to the target position using deltaTime
- Following is automatically disabled if the target entity dies
- Following works with world bounds clamping
CameraSystem
The CameraSystem applies CameraComponent properties to the SFML render window's view.
Initialization
Update Loop
void gameLoop(float deltaTime) {
// Update camera based on active CameraComponent component
// deltaTime is required for smooth following
cameraSystem.update(registry, deltaTime);
// Render using the updated view
// (view is automatically set on the window)
}
Active Camera Selection
The system automatically finds the first active CameraComponent component:
EntityId camera1 = registry.createEntity();
EntityId camera2 = registry.createEntity();
auto& cam1 = registry.emplace<CameraComponent>(camera1);
auto& cam2 = registry.emplace<CameraComponent>(camera2);
cam1.active = true; // This camera will be used
cam2.active = false; // This camera is ignored
To switch cameras, simply toggle the active flag:
World Bounds Clamping
Prevent the camera from moving outside defined world boundaries:
// Define world bounds: left, top, width, height
cameraSystem.setWorldBounds(0.0F, 0.0F, 1920.0F, 1080.0F);
// Enable clamping
cameraSystem.setWorldBoundsEnabled(true);
// Camera will now be clamped to stay within bounds
Clamping behavior: - Takes zoom level into account (prevents showing area outside bounds) - If world is smaller than view, camera centers on world - Can be enabled/disabled at runtime
Accessing the View
const sf::View& view = cameraSystem.getView();
sf::Vector2f center = view.getCenter();
sf::Vector2f size = view.getSize();
Getting Active Camera
EntityId activeCameraId = cameraSystem.getActiveCamera();
if (activeCameraId != 0) {
auto& camera = registry.get<CameraComponent>(activeCameraId);
// Modify camera properties...
}
Usage Examples
Automatic Smooth Camera Follow (Recommended)
// Set up camera to automatically follow player
EntityId player = getPlayerEntity();
EntityId camera = getCameraEntity();
auto& cam = registry.get<CameraComponent>(camera);
// Camera will smoothly follow the player entity
cam.setTarget(player, 5.0F); // 5.0 = smoothness factor
// In game loop, just call update with deltaTime
// The camera will automatically follow the target
cameraSystem.update(registry, deltaTime);
Manual Camera Follow
// Manual control: Update camera to follow player
EntityId player = getPlayerEntity();
EntityId camera = getCameraEntity();
const auto& playerTransform = registry.get<TransformComponent>(player);
auto& cam = registry.get<CameraComponent>(camera);
cam.setPosition(playerTransform.x, playerTransform.y);
Custom Smooth Following Logic
// Custom smooth camera interpolation
const auto& playerPos = registry.get<TransformComponent>(player);
auto& cam = registry.get<CameraComponent>(camera);
float lerpFactor = 0.1F;
float dx = (playerPos.x - cam.x) * lerpFactor;
float dy = (playerPos.y - cam.y) * lerpFactor;
cam.move(dx, dy);
Screen Shake Effect
// Simple screen shake on impact
void applyScreenShake(CameraComponent& camera, float intensity) {
float shakeX = (rand() / (float)RAND_MAX - 0.5F) * intensity * 2.0F;
float shakeY = (rand() / (float)RAND_MAX - 0.5F) * intensity * 2.0F;
camera.setOffset(shakeX, shakeY);
}
// Gradually reduce shake over time
void updateScreenShake(CameraComponent& camera, float deltaTime) {
camera.offsetX *= (1.0F - deltaTime * 5.0F); // Decay
camera.offsetY *= (1.0F - deltaTime * 5.0F);
if (std::abs(camera.offsetX) < 0.1F) camera.offsetX = 0.0F;
if (std::abs(camera.offsetY) < 0.1F) camera.offsetY = 0.0F;
}
Zoom Control
// Zoom in/out based on input
void handleZoomInput(CameraComponent& camera) {
if (zoomInPressed) {
camera.setZoom(camera.zoom * 1.1F);
}
if (zoomOutPressed) {
camera.setZoom(camera.zoom / 1.1F);
}
// Keep zoom in reasonable range
camera.clampZoom(0.5F, 4.0F);
}
Multiple Camera Setup
// Main game camera
EntityId mainCamera = registry.createEntity();
auto& mainCam = registry.emplace<CameraComponent>(mainCamera);
mainCam.active = true;
// Minimap camera (inactive by default)
EntityId minimapCamera = registry.createEntity();
auto& miniCam = registry.emplace<CameraComponent>(minimapCamera);
miniCam.active = false;
miniCam.setZoom(0.25F); // Zoomed out for overview
// Switch to minimap
mainCam.active = false;
miniCam.active = true;
Integration with RenderSystem
The CameraSystem should be updated before the RenderSystem:
// In game loop
cameraSystem.update(registry); // Apply camera transformations
renderSystem.update(registry); // Render sprites using current view
The RenderSystem automatically uses the window's current view, which was set by the CameraSystem.
Best Practices
- Single Active Camera - Only one camera should be active at a time
- Update Before Render - Always update CameraSystem before RenderSystem
- Component Reference Safety - Don't hold references to CameraComponent across system updates (registry may reallocate)
- Zoom Validation - Use
clampZoom()to prevent extreme zoom values - World Bounds - Enable world bounds clamping for bounded levels
- Screen Shake Decay - Always decay screen shake offset back to zero
- Smooth Following - Use interpolation for smooth camera movement
Common Patterns
Camera Controller System
Create a dedicated system for camera control logic:
class CameraControllerSystem : public ISystem {
public:
void update(Registry& registry, float deltaTime) override {
// Find player
EntityId player = findPlayer(registry);
if (player == 0) return;
// Find active camera
EntityId cameraId = findActiveCamera(registry);
if (cameraId == 0) return;
const auto& playerTransform = registry.get<TransformComponent>(player);
auto& camera = registry.get<CameraComponent>(cameraId);
// Smooth follow
float lerpSpeed = 5.0F;
float dx = (playerTransform.x - camera.x) * lerpSpeed * deltaTime;
float dy = (playerTransform.y - camera.y) * lerpSpeed * deltaTime;
camera.move(dx, dy);
// Update screen shake
updateScreenShake(camera, deltaTime);
}
};
Camera Zones
Define different camera behaviors for different areas:
struct CameraZone {
float left, top, width, height;
float zoom;
bool constrainToBounds;
};
// When player enters zone, adjust camera settings
void onPlayerEnterZone(CameraComponent& camera, const CameraZone& zone) {
camera.setZoom(zone.zoom);
if (zone.constrainToBounds) {
cameraSystem.setWorldBounds(
zone.left, zone.top,
zone.width, zone.height
);
cameraSystem.setWorldBoundsEnabled(true);
}
}
Implementation Notes
- The system stores the base window size for zoom calculations
- Component references may be invalidated by world bounds clamping
- View is automatically set on the window after each update
- Rotation uses degrees (converted to sf::Angle internally)
- No camera active means default view is used
- Dead entities with CameraComponent are automatically skipped