HUD
The HUD is what is drawn over the scene: score, lives, menus, dialogue. It is drawn in
game.draw, which runs once per frame after the scene, and in the draw of
screens, which follow it; only there.
Coordinates are pixels from the top-left corner of the frame, which is 320 × 240 on the
console. engine.width and engine.height give the frame's size inside game.draw.
| Function | Draws |
|---|---|
hud.text(x, y, text [, color [, scale]]) | text in the built-in 8 × 8 font; returns the width drawn in pixels |
hud.text(x, y, text, {color =, scale =, align =, shadow =}) | the same, aligned "center" or "right" on x, with a shadow color one pixel down-right |
hud.text_box(x, y, w, h, text [, options]) | text wrapped to a box and cut to the lines that fit |
hud.rect(x, y, w, h [, color]) | a filled rectangle |
hud.outline(x, y, w, h [, color [, thickness]]) | a rectangle's border |
hud.line(x1, y1, x2, y2 [, color [, width]]) | a line of whole pixels |
hud.circle(x, y, r [, color [, fill]]) | a circle, or a disc when fill |
hud.bar(x, y, w, h, value, max [, options]) | a health or progress bar |
hud.text_width(text [, scale]) | the width hud.text would draw; works outside draw too |
hud.wrap(text, width [, scale]) | the text broken into lines of at most width pixels, and the line count |
hud.image(texture, x, y [, options]) | a texture, or a part of it, with sharp texels |
hud.panel(texture, x, y, w, h, border [, options]) | a frame that stretches to any size |
hud.image_size(texture) | a texture's width and height in texels |
A colour is "#rrggbb", "#rrggbbaa" (with alpha, for translucent panels) or an integer
0xrrggbb. The default is white. scale is a whole number: 2 draws 16 × 16 characters.
local score, lives = 0, 3
function game.draw()
-- a translucent bar across the top
hud.rect(0, 0, engine.width, 14, "#000000b0")
hud.text(4, 3, "SCORE " .. score, "#f4f0e0")
-- lives as small squares on the right
for i = 1, lives do
hud.rect(engine.width - 12 * i, 3, 8, 8, "#ff6060")
end
end
Centering and wrapping text
function game.draw()
hud.text(engine.width // 2, 100, "È già notte", {align = "center", scale = 2, shadow = "#000000"})
hud.text(engine.width - 4, 4, "x3", {align = "right", color = "#f4c542"})
local shown, total = hud.text_box(60, 130, 200, 40,
"Il ponte è crollato. Prendi il sentiero della grotta.",
{color = "#a0a0b0", align = "center", valign = "middle", spacing = 2})
end
align works line by line on multi-line text. hud.text_box wraps at spaces, keeps the
lines that fit its height and returns the lines drawn and the lines the text has: more
than it drew means another page. Its options: color, scale, align, valign
("top", "middle", "bottom"), spacing (extra pixels between lines), shadow.
hud.wrap(text, width) gives the broken lines without drawing.
The font has ASCII, the accented letters of Latin-1 (à è é ì ò ù ç ñ ä ö ü ß …) and the
typographic characters of Windows-1252 (€ ‘ ’ “ ” – — …); anything else draws as ?. Write
scripts in UTF-8, as VS Code does. Measure with hud.text_width, not #text: # counts
bytes, and è is two.
Bars and shapes
local hp, max_hp = 3, 5
function game.draw()
hud.bar(4, 4, 60, 6, hp, max_hp, {color = "#40d060", low = "#ff4040", border = "#ffffff"})
hud.bar(310, 40, 6, 80, 0.7, 1, {vertical = true, color = "#60a0ff"})
hud.outline(100, 60, 120, 80, "#f4c542", 2)
hud.line(0, 20, engine.width, 20, "#ffffff40")
hud.circle(160, 120, 30, "#ff8080") -- a ring
hud.circle(160, 120, 4, "#ff8080", true) -- a dot
-- a bar over each enemy, where the camera shows it
for _, enemy in ipairs(scene.tagged("enemy")) do
local x, y, on = camera.to_screen(enemy.x, enemy.y + 0.8)
if on then hud.bar(x - 8, y, 16, 2, enemy.state.hp, 3, {back = false}) end
end
end
hud.bar options: color (green), back (the empty part; false for none), border,
low (the color at low_at, 0.25, or less), vertical (fills up). camera.to_screen
turns a point of the world into the pixel where it is drawn, and whether it is on the frame.
What to keep out of draw
game.draw runs once per frame shown: never change the game there. Everything that
changes state belongs in game.update or a kind, so tests (which may draw fewer frames, or
none) play the same game as the player. Read variables in draw, write them in update.
Images and icons
hud.image draws a texture asset (see Graphics Assets), whole or
a part of it. Keep the icons of a game in one sheet and pick each with src, the part's
{x, y, w, h} in texels:
local ICON = 16 -- the sheet is a grid of 16 × 16 icons
local function icon(col, row, x, y)
hud.image("icons", x, y, {src = {col * ICON, row * ICON, ICON, ICON}})
end
function game.draw()
icon(0, 0, 4, 4) -- a heart
hud.image("portrait_mira", 8, 160, {w = 48, h = 48}) -- a 24 × 24 portrait, twice as large
hud.image("arrow", 300, 220, {flip_x = true, color = "#ffffff80"})
end
| Option | Meaning |
|---|---|
src | {x, y, w, h}: the part of the texture, in texels (default: all of it) |
w, h | the size drawn, in pixels (default: the part's size) |
color | multiplies the texels: "#ff8080" tints, "#ffffff80" is half transparent |
flip_x, flip_y | mirror the image |
Texels stay sharp at any size. For pixel art, draw at whole multiples of the texture's size.
Panels and dialogue boxes
For conversations, ui.dialog draws a box, types the text, pages it and offers choices on
its own (see Game Toolkit); ui.style.panel gives it
a nine-slice texture like the one below. Draw your own when the game needs a different
layout.
hud.panel draws a nine-slice panel: a small frame texture whose corners keep their size
while the edges and the middle stretch, so one 12 × 12 texture makes boxes of any size.
border is the corners' width in texels, one number or {left, top, right, bottom}:
local function dialogue(speaker, text)
hud.panel("frame", 8, 172, 304, 60, 4)
hud.image("portrait_" .. speaker, 16, 180, {w = 44, h = 44})
hud.text(68, 184, text, "#f4f0e0")
if engine.tick % 20 < 10 then
hud.text(292, 216, ">", "#f4c542") -- a blinking "more" arrow
end
end