Veduta

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.

FunctionDraws
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
OptionMeaning
src{x, y, w, h}: the part of the texture, in texels (default: all of it)
w, hthe size drawn, in pixels (default: the part's size)
colormultiplies the texels: "#ff8080" tints, "#ffffff80" is half transparent
flip_x, flip_ymirror 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

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/.