Veduta

Camera

Every scene has one camera, set in the scene file. Scripts read and change it while the game runs; a change takes effect from the frame drawn at the end of the tick.

2D: following the player

kinds.hero = {
  init = function(e)
    camera.follow(e, {bounds = "map", smooth = 0.2})
  end,
}

camera.follow(entity [, options]) moves the camera with the entity every tick, after the kinds, in the x, y plane; camera.follow(nil) stops it, and a scene load forgets it.

OptionMeaning
bounds"map" keeps the view inside the scene's map; {left, bottom, right, top} inside that rectangle (orthographic cameras; a level smaller than the view is centered)
smoothseconds to catch up; 0 or none is rigid
deadzone{w, h}: the entity moves that much around the center before the camera follows
offset{x, y} added to the entity's position: look ahead
snapfalse eases in from where the camera is instead of starting on the entity

A level without a map gives its size:

local LEVEL_W, LEVEL_H = 24, 14   -- tiles centered from x = 0 to 23, y = 0 down to -13

kinds.hero = {
  init = function(e)
    camera.follow(e, {bounds = {-0.5, -(LEVEL_H - 0.5), LEVEL_W - 0.5, 0.5}, deadzone = {2, 1}})
  end,
}

For a camera placed by hand, camera.follow2d(x, y, height) makes the orthographic camera of a 2D game: it looks down −Z at (x, y) and shows height units vertically (the width follows from the 4:3 panel, so 12 units high is 16 wide). It sits at z = 100, so everything from z = −100 to 99.9 is visible, and a larger z is nearer.

Pixel art stays sharp when a unit is a whole number of pixels: on the 240-pixel panel a height of 12 is 20 pixels per unit, 24 is 10, 15 is 16.

Any camera

camera.get() returns a table; camera.set{...} changes the fields it is given and keeps the others:

FieldMeaning
position{x, y, z} where the camera is
target{x, y, z} the point it looks at
orthotrue for orthographic, false for perspective
fovvertical field of view in degrees (perspective)
sizevisible height in units (orthographic)
near, farclip distances

A third-person camera behind a 3D hero:

kinds.walker = {
  update = function(e)
    -- ... move the hero ...
    camera.set{position = {e.x, e.y + 6, e.z + 8}, target = {e.x, e.y + 1, e.z}}
  end,
}

Shake

kinds.boulder = {
  on_touch = function(e, other)
    if other:has_tag("hero") then camera.shake(0.4, 0.3) end
  end,
}

camera.shake(strength [, seconds]) shakes the camera up to strength metres, fading over seconds (0.3). It works with follow, follow2d and set alike, and is undone before the next tick, so camera.get() never sees it. Like everything in a run it is deterministic: every replay of a scenario shakes the same.

From the world to the screen

camera.to_screen(x, y [, z]) returns the pixel of the frame where a point of the world is drawn, and whether it is on the frame: a name tag, a health bar, an arrow at the edge of the screen toward a goal.

function game.draw()
  local boss = scene.find("boss")
  if boss then
    local x, y, on = camera.to_screen(boss.x, boss.y + 1.5)
    if on then hud.text(x, y, "OGRE", {align = "center", shadow = "#000000"}) end
  end
end

Veduta v2 (Lua games, release candidates). This wiki is built from wiki/ in the engine's repository, where its examples are tested, and published with every engine release on https://veduta.roomve.it/docs/.