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.
| Option | Meaning |
|---|---|
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) |
smooth | seconds 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 |
snap | false 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:
| Field | Meaning |
|---|---|
position | {x, y, z} where the camera is |
target | {x, y, z} the point it looks at |
ortho | true for orthographic, false for perspective |
fov | vertical field of view in degrees (perspective) |
size | visible height in units (orthographic) |
near, far | clip 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