Skip to content

Camera2D

2D camera for scrolling and viewport control.

Description

Camera2D controls viewport position and enables scrolling by shifting the renderer's display offset. It supports following targets, boundary constraints, and can be used for parallax effects.

The camera uses a dead-zone system: it only moves when the target is outside a central zone, creating smooth following behavior.

Namespace

namespace pixelroot32::graphics {
    class Camera2D {
        // ...
    };
}

Inheritance

  • Base class: None (standalone class)
  • Used by: Scenes (for scrolling and camera control)

Constructors

Camera2D(int viewportWidth, int viewportHeight)

Creates a new camera with specified viewport dimensions.

Parameters: - viewportWidth (int): Width of the viewport in pixels - viewportHeight (int): Height of the viewport in pixels

Notes: - Viewport size should match display size - Camera position starts at (0, 0) - No boundaries set by default (camera can move anywhere)

Example:

#include "graphics/Camera2D.h"

// Create camera matching display size
pixelroot32::graphics::Camera2D camera(128, 128);

// Or get from renderer
int width = renderer.getWidth();
int height = renderer.getHeight();
pixelroot32::graphics::Camera2D camera(width, height);

Public Methods

void setPosition(float x, float y)

Sets the camera position directly.

Parameters: - x (float): X position in world space - y (float): Y position in world space

Returns: - void

Notes: - Position is clamped to boundaries if set - Use for direct camera control or cutscenes - Overrides any following behavior

Example:

camera.setPosition(100.0f, 200.0f);

void setBounds(float minX, float maxX)

Sets horizontal boundaries for the camera.

Parameters: - minX (float): Minimum X position - maxX (float): Maximum X position

Returns: - void

Notes: - Camera position is clamped to these bounds - Use to prevent camera from going outside level bounds - Set both horizontal and vertical bounds for full constraint

Example:

// Level is 512 pixels wide, camera viewport is 128
// Prevent camera from showing outside level
camera.setBounds(0.0f, 512.0f - 128.0f);

void setVerticalBounds(float minY, float maxY)

Sets vertical boundaries for the camera.

Parameters: - minY (float): Minimum Y position - maxY (float): Maximum Y position

Returns: - void

Notes: - Camera position is clamped to these bounds - Use to prevent camera from going outside level bounds vertically

Example:

// Level is 512 pixels tall, camera viewport is 128
camera.setVerticalBounds(0.0f, 512.0f - 128.0f);

void followTarget(float targetX)

Makes the camera follow a target horizontally only.

Parameters: - targetX (float): X position of the target to follow

Returns: - void

Notes: - Camera follows target with dead-zone behavior - Only horizontal movement; vertical position unchanged - Useful for side-scrolling games

Example:

void update(unsigned long deltaTime) override {
    // Update player position
    player->update(deltaTime);

    // Camera follows player horizontally
    camera.followTarget(player->x);
    camera.apply(renderer);
}

void followTarget(float targetX, float targetY)

Makes the camera follow a target in both axes.

Parameters: - targetX (float): X position of the target to follow - targetY (float): Y position of the target to follow

Returns: - void

Notes: - Camera follows target with dead-zone behavior - Both horizontal and vertical following - Useful for top-down or platformer games

Example:

void update(unsigned long deltaTime) override {
    player->update(deltaTime);

    // Camera follows player in both axes
    camera.followTarget(player->x, player->y);
    camera.apply(renderer);
}

float getX() const

Gets the current X position of the camera.

Returns: - float: Current X position in world space

Example:

float cameraX = camera.getX();

float getY() const

Gets the current Y position of the camera.

Returns: - float: Current Y position in world space

Example:

float cameraY = camera.getY();

void apply(Renderer& renderer) const

Applies the camera's offset to the renderer.

Parameters: - renderer (Renderer&): The renderer to apply the camera offset to

Returns: - void

Notes: - Sets the renderer's display offset based on camera position - Should be called before drawing world elements - Negative offset is applied (camera moves right = world moves left)

Example:

void draw(pixelroot32::graphics::Renderer& renderer) override {
    // Apply camera offset
    camera.apply(renderer);

    // Draw world (offset applied automatically)
    renderer.drawTileMap(levelMap, 0, 0, Color::White);
    renderer.drawSprite(playerSprite, playerX, playerY, Color::White);

    // UI elements (not affected by camera)
    renderer.setDisplayOffset(0, 0);  // Reset for UI
    renderer.drawText("Score: 100", 10, 10, Color::White, 1);
}

Dead-Zone Following

The camera uses a dead-zone system for smooth following:

  • Dead zone: Central area where camera doesn't move
  • Following: Camera moves only when target leaves dead zone
  • Smooth: Creates natural, non-jarring camera movement

Example:

// Camera follows player with dead zone
void update(unsigned long deltaTime) override {
    player->update(deltaTime);

    // Camera follows (dead zone handled internally)
    camera.followTarget(player->x, player->y);
}

Usage Example

#include "graphics/Camera2D.h"

class GameScene : public pixelroot32::core::Scene {
private:
    pixelroot32::graphics::Camera2D camera;
    PlayerActor* player;
    TileMap levelMap;

public:
    void init() override {
        // Create camera matching display size
        auto& renderer = engine.getRenderer();
        camera = pixelroot32::graphics::Camera2D(
            renderer.getWidth(), 
            renderer.getHeight()
        );

        // Set level boundaries
        // Level is 512x512, viewport is 128x128
        camera.setBounds(0.0f, 512.0f - 128.0f);
        camera.setVerticalBounds(0.0f, 512.0f - 128.0f);

        // Create player
        player = new PlayerActor(64, 64);
        addEntity(player);
    }

    void update(unsigned long deltaTime) override {
        Scene::update(deltaTime);

        // Camera follows player
        camera.followTarget(player->x, player->y);
    }

    void draw(pixelroot32::graphics::Renderer& renderer) override {
        // Apply camera
        camera.apply(renderer);

        // Draw world (camera offset applied)
        renderer.drawTileMap(levelMap, 0, 0, Color::White);

        // Draw entities (Scene::draw handles this)
        Scene::draw(renderer);

        // Reset offset for UI
        renderer.setDisplayOffset(0, 0);
        renderer.drawText("Score: 100", 10, 10, Color::White, 1);
    }
};

Parallax Scrolling

Use multiple cameras or manual offset for parallax:

void draw(pixelroot32::graphics::Renderer& renderer) override {
    // Background layer (slow parallax)
    float bgOffsetX = camera.getX() * 0.5f;  // 50% speed
    renderer.setDisplayOffset(-bgOffsetX, 0);
    renderer.drawTileMap(backgroundMap, 0, 0, Color::White);

    // Midground layer (normal speed)
    camera.apply(renderer);
    renderer.drawTileMap(midgroundMap, 0, 0, Color::White);

    // Foreground (entities, normal speed)
    Scene::draw(renderer);

    // UI (no offset)
    renderer.setDisplayOffset(0, 0);
    renderer.drawText("Score: 100", 10, 10, Color::White, 1);
}

Performance Considerations

  • Apply frequency: apply() is fast; safe to call every frame
  • Boundary checks: Boundary clamping is efficient
  • Following: Dead-zone calculations are lightweight

ESP32 Considerations

  • Float math: Uses floating point; acceptable but integer math would be faster
  • Memory: Camera is small (few floats); minimal memory usage

See Also