Skip to content
shmellyorc edited this page Sep 14, 2026 · 2 revisions

Camera

VOID's standard Camera is a matrix-driven 2D camera with position, zoom, rotation, bounds, coordinate conversion, and conservative visible bounds for rendering.

The camera types live in:

using Void.Engine.Cameras;

Normal drawing still goes through the regular batchers. You pass a camera to Begin, and the batcher carries its view-projection matrix through VOID's renderer-neutral pipeline.

Basic Camera

var camera = new Camera
{
    Position = new Vect2(500, 300),
    Zoom = 2f,
    Rotation = 0f,
    Bounds = new Rect2(0, 0, 2000, 1200)
};

Position is the world-space point shown at the center of the logical viewport.

A new camera starts:

  • centered on GameSettings.Instance.Viewport
  • at Zoom = 1
  • with zero rotation
  • with no world bounds

Drawing With a Camera

Pass it to a sprite or primitive batch:

_batcher.Begin(
    SortMode.BackToFront,
    camera: camera);

_batcher.Draw(
    playerTexture,
    playerPosition,
    Color.White);

_batcher.End();

The same camera can be used with PrimitiveBatcher and render-target drawing.

Passing the camera to Begin(...) also triggers its automatic once-per-rendered-frame update before the batcher reads ViewProjection.

See Rendering for normal batcher usage.

Position

camera.Position =
    new Vect2(640, 360);

This is the world position that appears at the center of the viewport.

A simple follow camera can therefore be:

camera.Position =
    player.Position + player.Size * 0.5f;

When a camera is passed to a batcher's Begin(...), VOID updates that camera automatically before reading its view-projection matrix.

A camera used by multiple batchers in the same rendered frame still updates only once for that frame. Game code does not manually call a camera update method.

Zoom

camera.Zoom = 2f;

Higher values show less world space and make the scene appear closer.

VOID clamps zoom values below 0.1f:

camera.Zoom = 0f;

// Effective zoom is 0.1f.

Reset to normal zoom:

camera.ResetZoom();

Rotation

Rotation is measured in radians:

camera.Rotation =
    MathHelper.HalfPI;

Positive values follow VOID's positive-Y-down 2D rotation convention.

Rotation is included when VOID calculates camera bounds and visible extents.

World Bounds

Set a rectangle to keep the visible camera area inside a world region:

camera.Bounds =
    new Rect2(0, 0, 2000, 1200);

An empty rectangle disables clamping:

camera.Bounds = default;

Clamping accounts for zoom and rotation, so the rotated viewport corners remain inside the configured world bounds.

If the bounded region is smaller than the visible camera area on an axis, VOID centers the camera on that axis instead of trying to clamp to an impossible range.

Screen and World Conversion

ScreenToWorld converts logical viewport coordinates into world space:

Vect2 worldPosition =
    camera.ScreenToWorld(viewportPosition);

WorldToScreen does the reverse:

Vect2 viewportPosition =
    camera.WorldToScreen(worldPosition);

These methods use GameSettings.Instance.Viewport.

MouseState.Position, however, is window-relative. If the native window and logical viewport differ because of Fit, PixelPerfect, Fill, supersampling, or another presentation setup, account for that presentation mapping before treating a raw mouse position as viewport coordinates.

See Input → Mouse and Window & Displays → Window Scale Modes.

View Bounds and Culling

Every BaseCamera exposes:

Rect2 visibleWorld =
    camera.ViewBounds;

ViewBounds is a conservative world-space axis-aligned rectangle derived from the four inverse-transformed viewport corners.

That matters for rotated cameras: the returned rectangle is intentionally large enough to contain the rotated visible area.

SpriteBatcher uses camera view bounds for coarse destination-rectangle culling when a camera is supplied to Begin.

Matrix-Based Camera Pipeline

A camera exposes its current view-projection matrix:

Matrix viewProjection =
    camera.ViewProjection;

The matrix is cached and rebuilt only after camera state invalidates it.

For the standard camera, the pipeline is:

translate world by -Position
        ↓
apply inverse Camera.Rotation
        ↓
apply Zoom
        ↓
project the logical viewport into clip space

VOID uses its own Matrix type rather than System.Numerics.Matrix4x4.

See Matrix for composition rules and common transforms.

Custom Cameras

Derive from BaseCamera when the standard Camera is not enough:

public sealed class OffsetCamera : BaseCamera
{
    private Vect2 _offset;

    public Vect2 Offset
    {
        get => _offset;
        set
        {
            _offset = value;
            Invalidate();
        }
    }

    protected override Matrix CreateViewProjection()
    {
        Vect2 viewport =
            GameSettings.Instance.Viewport;

        return
            Matrix.CreateTranslation(-_offset) *
            Matrix.CreateOrthographic(
                viewport.X,
                viewport.Y);
    }
}

Whenever derived camera state changes the matrix, call Invalidate() so the cached view-projection and inverse matrices are rebuilt on demand.

BaseCamera also provides the cached inverse matrix to derived cameras and throws if an operation requires an inverse but the current matrix is not invertible.

Override the protected OnUpdate(FrameTime) hook for follow behavior, shake, smoothing, or transitions:

protected override void OnUpdate(
    FrameTime frameTime)
{
    // Update custom camera state.
    // Call Invalidate() if the matrix changed.
}

OnUpdate is invoked automatically when the camera is first used by a batcher during a rendered frame. In fixed timestep mode, the FrameTime seen here is in the render phase, so DeltaTime and ElapsedTime represent the actual elapsed rendered-frame time rather than the fixed simulation step.

Post-Processing

PostProcessor.Apply can also receive a BaseCamera:

postProcessor.Apply(
    sceneTarget,
    camera);

See Rendering → Post-Processing.


Back to Rendering

Clone this wiki locally