Camera tutorial

How viewports and cameras work

An important thing to understand about cameras is that they need to be inserted in the "scene" that we want to render. A simple "scene" could be a layer containing a couple of sprites. More complicated scenes may involve several layers nested together in a tree-like structure. The branches of this structure are always layers and the leaf nodes are sprites. Cameras that are not inserted in a scene will not output anything at all.

The output of camera is rendered using a viewport. A viewport is simply a clipped rectangular section of the window. The global display object contains a default viewport which is the same size as the window. When we assign a camera to this viewport, the viewport will render whatever is in the camera's viewing range. Our scene may contain multiple cameras but only one can be assigned per viewport. Viewports may come in handy if you plan on using split screens or mini maps.

Dealing with coordinate systems

Working with cameras can be a little tricky due to the difference in projections. Looking at the following diagram, in green and red we have the layer and the camera coordinate systems respectively. Think of the layer coordinate system (green) as an infinite world space, whereas the camera (red) is a rectangular region within it. Notice that the camera is a child node of the layer and only covers a finite area of it. The size of this area (usually called the viewing frustum) is determined by both the zoom of the camera and the dimensions of the associated viewport(s). If you remember from the input tutorial, the center of the default viewport is also the origin of the cursor position. Therefore for this example, the mouse cursor position happens to be in local camera coordinates.

Figure 1: An example rotated camera (red) over a layer (green) containing a sprite (Mario). The rotated red rectangle is the section captured by the camera, sprites outside of it will be culled

Example script

So how do we convert the mouse cursor position to "scene" coordinates? Luckily, you don't have to calculate this by hand, thanks to a utility function called "get_world_point". This functions is found in all scene nodes (sprites, layers and cameras). It converts any point located on a node to the coordinate system of its top-most parent. In the following example, the camera's oldest descendent is the "scene" layer.

Figure 2: Mouse cursor positions in an 800 by 600 window

Let's look at the actual script that puts what we have discussed so far into action. The script allows you to move the camera around the scene and place sprite objects by pressing the mouse. The top part of the script initializes the scene and inserts a camera in it. Next, we have two callback functions. The first one translates the camera while the user is holding the right mouse button. The second callback function creates a new sprite and draws a circle on it whenever the user presses the left mouse button. The sprite is then inserted in the scene at world coordinates.

display:create ( "Camera example", 800, 600, 32, true )

camera = Camera ( )
scene = Layer ( )
scene:add_child ( camera ) = camera

-- move the camera using the right button
mouse.on_move = function ( mouse, dx, dy )
  if mouse:is_down ( MBUTTON_RIGHT ) then
    camera.x = camera.x - dx
    camera.y = camera.y - dy

-- draw a circle on left button click
mouse.on_press = function ( mouse, button )
  if button == MBUTTON_LEFT then
    local ptx, pty = camera:get_world_point ( mouse.xaxis, mouse.yaxis )
    local s = Sprite ( ptx, pty )
    s.canvas:circle ( 10 )
    s.canvas:fill ( )
    scene:add_child ( s )

The neat part here is how the mouse cursor position (xaxis and yaxis) is converted to scene coords. Since the camera is assigned to the default viewport (which covers the entire window), the mouse cursor position can be treated as a point on the camera's coordinate system.

Download:  camera.lua