Position objects with a camera

A camera supplies projection and view matrices; the game supplies object transforms and chooses which camera each pass uses. Start from First Game’s sprite walkthrough. This task separates moving an object in world space from changing the view of that world.

Track drawable dimensions

Reuse First Game’s simulation-owned CE::Rendering::Camera2D camera_. During initialization on platform, it reads engine_.window().framebuffer_size(). Keep its existing update:

camera_.set_framebuffer_size(tick.framebuffer_size);

Camera2D projects Y-up framebuffer-pixel bounds from zero to width/height. Logical window dimensions can differ on scaled displays. Negative pixel sizes reject; zero sizes are retained while projection clamps each axis to at least one. The camera does not turn logical input coordinates into pixels for you.

Move the object

First Game retains the position in actor_->position and already includes <glm/gtc/matrix_transform.hpp>. Its frame preparation places the sprite through the model matrix:

image_style.model_matrix = glm::translate(glm::mat4(1.0f), glm::vec3(actor_->position, 0.0f));

The asset’s pivot defines which local point reaches actor_->position. Style scale changes local geometry; it does not change the meaning of your world position. Select your game’s coordinate convention explicitly when extending beyond this pixel-oriented example.

Change the view

Add a glm::vec2 view_origin_{}; game member for the world-space origin of the view. For a visible first step, set it to (80, 0) during update() and then update the camera there:

view_origin_ = glm::vec2(80.0f, 0.0f);
camera_.set_view_matrix(glm::translate(glm::mat4(1.0f), glm::vec3(-view_origin_, 0.0f)));

The actor should appear 80 pixels farther left than in the unchanged view, while its stored position stays the same. After that checkpoint, replace the fixed assignment with the view origin chosen by your game’s movement policy.

Moving the origin right shifts the displayed world left without changing any entity’s authoritative position. This is explicit positioning; a following-camera behavior would choose that state according to its own game policy.

Copy matrices into passes

Keep First Game’s pass setup during frame preparation:

auto pass = frame.begin_pass(camera_.projection_matrix(), camera_.view_matrix());

Its context and draws still use this pass. Move direct screen text to First Game’s separate HUD pass so world scrolling does not move it. Both writers borrow the current frame; retain neither them nor camera matrix references beyond preparation.

Camera revisions count changed values; they are not atomic publication. Runtime transfers the copied matrices with the completed frame and renderer playback does not read your live camera.

Observe the result

Resizing should update visible pixel bounds. Changing actor_->position moves just the actor; changing view_origin_ moves the world view. A separate HUD pass remains anchored to its own projection. After initialization, keep camera mutations in update and release the game’s resource handles during ordinary cleanup; cameras own CPU values and need no backend shutdown call.

Read camera contracts, display dimensions and frame ownership. Perspective parameters belong to Camera3D; they do not automatically select depth policy or a scene system.