Camera centers automatically?

I’m just trying to understand the camera’s default render position;

In this screenshot of the collection, I have the camera selected (blue rectangle) and the main stage area is set to 1920x1080 (white rectangle). The Defold logo is in the center of the stage (960,540), and some test artwork is centered around 0,0 on the bottom left.

When I run the game, and set the camera zoom to 4, I would expect to see the artwork at 0,0. Instead I see the Defold logo centered on the screen. And this centering behaviour continues if I change the Display setting to 960x540, the camera will center within that instead, and the Defold logo appears near the top right.

Also when I set the camera position to 0,0 in the script, the behaviour stays the same, and if I set it to 100,0, the camera seems to be 100 pixels right of center.

Can someone explain this “automatic centering” behaviour?
image

Sure, let’s look at the default render script to see what’s going on. I’m assuming you’re using the default render script, however the following logic is the same across third-party libraries as well.

--
-- projection that centers content with maintained aspect ratio and optional zoom
--
local function get_fixed_projection(camera, state)
    camera.zoom = camera.zoom or DEFAULT_ZOOM
    local projected_width = state.window_width / camera.zoom
    local projected_height = state.window_height / camera.zoom
    local left = -(projected_width - state.width) / 2
    local bottom = -(projected_height - state.height) / 2
    local right = left + projected_width
    local top = bottom + projected_height
    return vmath.matrix4_orthographic(left, right, bottom, top, camera.near, camera.far)
end
--
-- projection that centers and fits content with maintained aspect ratio
--
local function get_fixed_fit_projection(camera, state)
    camera.zoom = math.min(state.window_width / state.width, state.window_height / state.height)
    return get_fixed_projection(camera, state)
end
--
-- projection that stretches content
--
local function get_stretch_projection(camera, state)
    return vmath.matrix4_orthographic(0, state.width, 0, state.height, camera.near, camera.far)
end
--
-- projection for gui
--
local function get_gui_projection(camera, state)
    return vmath.matrix4_orthographic(0, state.window_width, 0, state.window_height, camera.near, camera.far)
end

The above code snippet contains all of the built-in camera projection matrix calculations. The comments above each of these functions states if the content is centered, which is true for the fixed_fit and fixed projections, but not for the stretch and gui projections.

So what exactly makes these projections center the content? Take a look at the vmath.matrix4_orthographic() function, which is called at the end of each of these projection functions.

The left, right, bottom, and top arguments specify the x_min left side of your view, the x_max right side of your view, the y_min bottom side of your view, and the y_max top side of your view, which of course creates a rectangle on the screen where graphics are drawn.

Take a look at the get_fixed_projection() function. It claims to center content. It does this by shifting those left, right, bottom, and top coordinates leftward by half the width of the window, and downward by half the height of the window:

local left = -(projected_width - state.width) / 2
local bottom = -(projected_height - state.height) / 2
local right = left + projected_width
local top = bottom + projected_height

Therefore, when the camera game object is centered at (0, 0), and the sprite is centered at (0, 0), the sprite will appear centered on the window.

On the other hand, if you’re using a projection that sets left and bottom to 0 and right and top to the window width and height, then a camera game object at (0, 0) and a sprite at (0, 0) will result in the sprite appearing at the bottom-left of the window.

You can make your own projection functions like the ones in the default render script to change this behavior and make your own custom behaviors.

Thank you, that helped me understand where to look.

In my main.script, I was using msg.post("@render:", "use_fixed_projection", { zoom = 4 }) from a tutorial

To get the behaviour I wanted, I replaced this with msg.post("@render:", "use_camera_projection") and changed the camera component properties to Orthographic, and Zoom 4 :slight_smile:

2 Likes