Skip to content

HUD reference

Everything you can put in a menu, panel, prompt or heads-up display.

A HUD is a table you hand to show_hud. It is drawn and driven on the player's own machine: changing pages, moving a slider and ticking a box are instant and never touch your script. Only a button carrying emit reports back.

luau
p:show_hud({
    id = "shop",
    anchor = "center",
    modal = true,
    vars = { coins = 40 },
    pages = {
        main = {
            { "text", "General store", size = 22, bold = true },
            { "text", "Coins: {coins}" },
            { "button", "Buy rope", emit = "buy", data = { item = "rope" } },
            { "button", "Close", close = true },
        },
    },
})

The outer table

KeyRequiredMeaning
idyesnames this HUD. Showing another with the same id replaces it
pagesyesnamed pages; each is a list of nodes
startwhich page opens first. Defaults to main
anchorwhere it sits: "center", "top", "bottom", "top_left", "top_right", "bottom_left", "bottom_right"
size{ w, h }. 0 = fit the content. "50%" = a fraction of the screen
modaltrue dims the world behind it
themecolours and shape — see Theme
stylesnamed style classes — see Classes
varsstarting values for {placeholders} and for widgets
worldhang it in the 3D world instead of on the screen — see World HUDs

Variables

vars is the HUD's own little memory. Any text node can interpolate one with braces, and widgets read and write them.

luau
vars = { coins = 40, sound_on = true },
pages = { main = {
    { "text", "Coins: {coins}" },
    { "toggle", "Sound", var = "sound_on" },
} },

Push new values from your script with update_hud — that is how health, a score or a timer stays live without rebuilding the menu.


Nodes

Every node is a list whose first entry is its kind. Text and labels come second, by position — not as a text = key.

luau
{ "text", "Hello" }                      -- correct
{ "text", text = "Hello" }               -- draws NOTHING

This is the single most common HUD mistake. A text = key on a text node is ignored, the node is created empty, it takes up space, and nothing appears. If a label is missing, check that it is in position two.

text

luau
{ "text", "Coins: {coins}", size = 18, bold = true, align = "center" }
KeyMeaning
sizefont size. Default 16, clamped 6–120
colortext colour
align"start" (default), "center", "end"
bold
wraplet long text run onto more lines

label is the same node under another name.

button

luau
{ "button", "Buy rope", emit = "buy", data = { item = "rope" } }
KeyMeaning
style"primary" (default), "ghost"
colorfill colour
text_color
heightdefault 42
enableda condition — the button greys out when it is false
tagnames the button, if you need to address it

What a button does — one or more of these:

KeyEffectReaches your script?
gotoopen another pageno
backreturn to the previous pageno
closeclose the HUDno
setwrite a variableno
addadd to a numeric variable: add = { "coins", -10 }no
emitreport to self:on_hudyes
datathe fields that go with emit

Navigation and variable changes happen on the player's machine, instantly. Only emit costs a message.

toggle

luau
{ "toggle", "Sound", var = "sound_on" }

A checkbox bound to a variable.

slider

luau
{ "slider", var = "volume", min = 0, max = 100, step = 5 }

progress

luau
{ "progress", var = "health", max = 100, color = "#39d98a" }

A horizontal bar.

ring

luau
{ "ring", var = "grab", max = 1, size = 92, thickness = 6,
  color = "#7dd3fc", track = "#1f2937", start = 0 }

The round counterpart of progress. start is where the arc begins, as a fraction of the circle.

hold

luau
{ "hold", key = "e", dur = 1.4, var = "grab", emit = "pick_up", data = {} }

Draws nothing. It drives var from 0 to 1 while the key is held, which is what a ring beside it shows.

KeyMeaning
keya single letter, or "space"
durseconds to complete
varthe variable it drives, 0 → 1
emit / goto / closewhat completion does
datafields for emit
on_start / on_complete / on_cancelextra actions at each moment

emit is the only part that reaches the server, and it arrives at on_hud with the player who did it.

icon

luau
{ "icon", "heart", size = 20, tint = "#ff5566" }

image

luau
{ "image", "coin", fit = "contain", w = 32, h = 32, rounding = 4 }

The second entry is either a built-in icon name or the id of a texture you uploaded.

Built-in icons: coin, heart, star, check, close, plus, minus, chevron_right, chevron_left, chevron_up, chevron_down, lock, play, pause, circle, ring, square.

KeyMeaning
fit"contain" (default), "cover", "stretch"
tintrecolours it — good for icons
w / hsize
roundingrounded corners

stage

A small 3D scene, drawn on this player's machine and nowhere else. This is how you build a lobby: a set behind the menu, with the player's character and their weapon standing in it.

luau
{ "stage", tag = "lobby", h = 420,
  show = { "world/sets/lobby" },
  look = { 0, 1.6, 0 }, dist = 6 },

It is a picture, not a place

Nothing on a stage exists in the world. It is not replicated, it has no physics, and no other player sees a trace of it — so a menu costs the server exactly nothing, however many people are looking at one.

That is what makes a lobby possible. Built out of world objects, a lobby forces you to choose between everybody standing in the same room and building one room per player. A stage removes the question: there is no room.

What it is not is somewhere the player can walk. A place people move around in is a world, not a stage.

What stands on it

show is a list. A bare name is a piece at the origin; a table places it.

KeyMeaning
modela path on the world's library (world/…). Can be the first entry instead
whoa person stands here, by their player.id. Not with model
posewhat that person is doing: an animation asset. Only with who
speedhow fast pose plays. Default 1, and it may be negative
pos{ x, y, z } in the stage's own space. Default { 0, 0, 0 }
rot{ x, y, z } in degrees, like everywhere else
scale{ x, y, z }, or one number for all three. Default 1

A piece is a model or a who — never both. The two readings of { model = …, who = … } are equally sensible (a person in a costume, scenery labelled with an owner), so writing both is refused instead of guessed at.

A path may name either kind of thing on the shelf:

  • a model you uploaded — one piece, placed where you put it;
  • a set you built — anything you assembled in the editor, took, and published to the shelf. It arrives with all its parts, each one where you left it, and its root lands on the piece's pos. This is the ordinary way to make a lobby: build it, take it, name it.

Blocks in a set are drawn from their own shape and colour, so a set made in the editor needs nothing uploaded at all.

Where the camera stands

Without look the camera frames whatever the pieces add up to and fills the rectangle — a backdrop, not a photograph of a model. With look you place it yourself, which is what a lobby usually wants: standing inside the room, aimed at where the character is.

KeyMeaning
look{ x, y, z } the camera aims at, in the stage's space. Default: the middle of the set
disthow far back it stands, in metres. Default: far enough to fill the frame
yaw / pitchthe angle it stands at, in degrees. Default 35 and 12
zoommultiplies whichever distance is in force. Below 1 = closer
spinmay the player drag to turn it? Default true, or false once you set look — a shot you framed is not a turntable, and dragging one walks the camera through your own walls

Find the numbers by looking, not by guessing

look is a point in the set's own space, which is a space you have never seen a coordinate of — so typing numbers, saving, and looking again is a slow way to find a shot, and the usual first result is a camera standing outside the building, framing the roof.

Open Dev → Stage framing in the dock while the menu is on screen. It takes the stage in hand: drag it to turn it (even with spin off), move the aim and the distance while watching, set the light, and press Copy for the script — what lands on the clipboard is these exact lines, ready to paste into your stage{ … }. Closing the window hands the stage straight back to the script; nothing it does outlives it.

What it looks like

A stage has no background of its own: whatever is behind it in the HUD — your panel, its gradient, an image — shows through, so put the stage at the bottom of a stack and lay the menu over it.

It is drawn with the same materials the world draws: base colour, normal, metallic-roughness and emissive maps, cut-outs and blended surfaces, plus contact occlusion in the corners and a glow around anything bright enough to be a light. A set looks in the menu the way it looks standing in it.

It is lit like a place, not a studio: one key light fixed in the set (so turning it does not swing the sun around), parts casting shadows on each other, and no studio backdrop or floor disc. It is deliberately not lit by the world's sun — a menu should not go dark when night falls in the match.

The light

Four numbers, and leaving them out is the default rig.

KeyMeaning
light.yawwhere the key comes from, in degrees around the set
light.pitchand how high above the horizon. 90 is straight overhead, which lights every vertical surface the same and leaves the set with no shape
light.brightthe key's strength. 1 = the default
light.ambienthow much of the environment reaches the set. Turn it down for night: what is left is the key and whatever the set itself emits
luau
{ "stage", show = { "world/sets/lobby" },
  look = { 0, 1.6, 0 }, dist = 6,
  light = { yaw = 120, pitch = 30, bright = 1.4, ambient = 0.35 } },

A set is a mood, and which mood is yours: the same warehouse is a morning depot or a night staging area depending on these four.

When something cannot be drawn

A stage says so in its own rectangle, naming the path: a name the shelf does not hold, a sound or a texture where a set was meant, a model whose file did not download. It never sits on a loading plate for something that is not coming.

People on a stage

who puts somebody's avatar in the set — the player, a teammate, whoever your script names. They stand in the scene rather than over it: lit by the stage's own light, hidden behind anything in front of them, and casting onto its floor.

lua
{ "stage", tag = "party", look = { 0, 1.2, 0 }, dist = 3.5,
  show = {
      "world/sets/lobby",
      { who = me.id,      pos = { -0.6, 0, 0 }, rot = { 0, 160, 0 } },
      { who = friend.id,  pos = {  0.6, 0, 0 }, rot = { 0, 200, 0 }, pose = CHEER },
  } }

Three things worth knowing before you write it:

  • Only somebody in this world can be shown. A person who is not here is not drawn, and the stage says so in its own rectangle. Their avatar simply does not exist on this machine.
  • The figure is a picture, not a second view of them. Posing it does not touch their body in the world, and nobody else sees what you did — which is why it needs no permission at all, unlike players:play_animation.
  • pose, not anim. anim is already the entrance animation a node plays when it appears. One name, one concept.

A stage with people on it redraws every frame, because a body is never still. One without them costs nothing when nothing changes.

Limits

At most 4 stages in one HUD, 64 pieces on one stage, and 4 people on one stage — a set counts as the one piece you wrote, however many parts it has. Over any of them, the HUD is refused with a reason rather than quietly trimmed. See limits.

row / column

Containers. Their children are the second entry, as a list.

luau
{ "row", {
    { "button", "−10", add = { "hp", -10 }, style = "ghost" },
    { "button", "+10", add = { "hp", 10 } },
}, height = 40, gap = 8 },
KeyMeaning
gapspace between children. Default 10
padspace inside the container
heightrow only. Default 42
justifyrow only: "start", "center", "end", "between"
aligncolumn only

col is short for column.

separator / spacer

luau
{ "separator" }
{ "spacer", h = 12 }

Styling

Universal keys

Any node accepts these:

KeyMeaning
classone or more named styles from styles
w / hpixels, "50%", or "2fr" (a share of a row)
marginn, {vertical, horizontal}, or {top, right, bottom, left}
opacity0 – 1
place"start", "center", "end"
visiblea condition — see Conditions

On leaf nodes and containers:

KeyMeaning
bgbackground colour
bg2second colour, making a vertical gradient
bordera colour, or { colour, width }
roundingcorner radius
shadow
bg_imagean icon name or texture id, drawn behind the content
bg_fit"cover" (default), "contain", "stretch"

Classes

luau
styles = {
    title = { size = 22, bold = true },
    card  = { bg = "#1b2330", rounding = 12, shadow = true },
},
pages = { main = {
    { "text", "Shop", class = "title" },
    { "column", { ... }, class = "card" },
} },

class = "a b" applies several, left to right. A node's own key always wins over its classes.

Theme

luau
theme = {
    bg = "#141a24",
    bg2 = "#1b2330",
    text = "#e8edf5",
    accent = "#6d5dfc",
    border = "#2a3345",
    rounding = 12,
    pad = 14,
    shadow = true,
    bg_image = "…",
},

A page may carry its own theme, which inherits from the HUD's and overrides what it names.

Colours

Four forms, all accepted anywhere a colour is:

luau
"#141a24"        -- what every colour picker gives you
"#141a24cc"      -- with transparency
"navy"           -- a name: red, white, transparent…
{ 20, 26, 36 }   -- r, g, b from 0 to 255

Motion

Pure client-side, costing nothing:

KeyMeaning
animentry animation: "rise", "fade", "slide_left", "slide_right", "slide_down", "pop"
anim_durseconds
anim_delayseconds — stagger a list by giving each row a bigger delay
effectcontinuous: "float", "pulse"
effect_amount
effect_speed

Conditions

visible and a button's enabled take the same notation:

luau
visible = "open"                        -- is the variable truthy?
visible = { "not", "open" }
visible = { "==", "mode", "stopped" }
visible = { ">", "coins", 10 }
visible = { "<", "hp", 25 }

World HUDs

Set world and the panel stops being a screen corner and becomes a billboard hanging on an object in the 3D scene — a "hold E to pick up" prompt, a shop sign, a nameplate.

It always faces the player, is scaled by distance, and disappears when the anchor is out of range, behind the camera, or not being looked at.

luau
p:show_hud({
    id = "pick_up",
    world = {
        height = 1.2,
        distance = 3,
        fade = 9,
        gaze = 20,
    },
    size = { 100, 100 },
    vars = { grab = 0 },
    pages = { main = {
        { "ring", var = "grab", size = 30, thickness = 2 },
        { "text", "Hold E", size = 10, align = "center" },
        { "hold", key = "e", dur = 1.4, var = "grab", emit = "take" },
    } },
})

world = true takes every default. A table overrides what it names:

KeyMeaning
objectwhich object it hangs on. Defaults to the one running the script
heightmetres above the object
offset{x, y, z} instead of height, for full control
distancestop drawing past here
fadestart dissolving here
gazeonly while looked at, within this many degrees. 0 = any angle
pixels_atthe distance at which it draws at its authored pixel size. 0 = never scale
scale{ min, max }

Common mistakes

SymptomCause
a label is invisiblethe text was given as text = instead of in position two
the HUD never appearsid or pages missing, or show_hud called outside a handler
a button does nothingit has no emit, goto, back, close, set or add
the number never changesyou rebuilt with show_hud instead of pushing with update_hud
a world HUD never showsthe player is outside distance, or outside the gaze cone

See also

Hungrit scripting documentation.