HUD document structure, variables, and themes.
HUD files
A HUD (heads-up display) is the interface drawn on top of the game: health bars, score, menus, touch controls. In DemiEngine it is a separate JSON document — a tree of interface nodes — saved as *.hud.json. Scenes attach one; the file itself never contains gameplay objects. This page walks through every part of a HUD document.
What a HUD document is
Think of the HUD as a page layout, not a level. Its nodes are containers, labels, buttons, and similar controls arranged in screen space. Nodes can be interactive (buttons respond to clicks and to input actions) or decorative (labels and images). The document describes structure, appearance, and which input actions the controls feed.
Like every DemiEngine document, a HUD file starts with format_version.
Canvas size and units
{ "format_version": 1, "canvas_size": [1280, 720], "children": [] }
canvas_sizeis[width, height]in interface units — the design resolution the layout is authored against.- Positions, sizes, gaps, and padding in the file are in these units. On a real screen the whole canvas is scaled to fit, so a layout designed at 1280×720 works on other resolutions without rewriting numbers.
- Pick the resolution you design at and stay consistent across your HUD documents.
Because everything is in canvas units, a label at [100, 40] is 100 units from the left and 40 from the top of that design rectangle, whatever the player's screen is.
A minimal menu, field by field
This HUD is a centered title and one button:
{
"format_version": 1,
"canvas_size": [1280, 720],
"root": {
"id": "menu",
"type": "container",
"dock": "fill",
"children": [
{
"id": "title",
"type": "label",
"text": "Skyforge",
"font_size": 48,
"text_alignment": "center"
},
{
"id": "start_button",
"type": "button",
"text": "Start",
"action": "confirm",
"size": [240, 64]
}
]
}
}
What each field does:
| Field | Meaning |
|---|---|
format_version | Always required. Documents without it are rejected. |
canvas_size | Design resolution [width, height] in interface units. |
root | The single top node of the tree. Everything else is nested under it. |
id | Stable node identity — the only required node field. Tests and scripts address nodes by ID (for example, to press start_button without knowing screen coordinates). |
type | Optional. What kind of node this is; defaults to container. Here container (layout only), label (text display), and button (pressable control). |
dock: "fill" | Stretch this node across its parent's available space. Other values: top, bottom, left, right, center. |
children | Nested nodes, laid out by this node's layout settings. |
text | The string a label or button shows. |
font_size | Text size in canvas units. |
text_alignment | Horizontal text alignment: start, center, or end. alignment instead arranges a container's children on its cross axis. |
action | UI action name emitted when the control is activated; the project input map can bind a keyboard/gamepad action to it. |
size | Explicit [width, height] when the node should not auto-size. |
Node types
The optional type field selects the node's behavior. Omit it and the node is a container:
| Type | Role |
|---|---|
container | Pure layout box. Invisible; holds children. |
panel | Styled background box that can hold children. |
label | Static text display. |
text | Text block (longer copy than a short label). |
image | A picture from a texture asset. |
button | Pressable control with text. |
toggle | On/off switch. |
slider | Value dragged along a range. |
text_input | Editable text field with a placeholder. |
scroll | Scrollable region. |
list | Repeated rows container. |
progress | Progress bar (health, loading). |
modal | Overlay dialog that takes focus. |
virtual_button | Touch button that feeds an input action. |
virtual_stick | Touch stick that feeds a 2D input action. |
Interactive types (button, toggle, slider, text_input, virtual_button, virtual_stick) connect to named actions. See Input bindings for how actions and virtual: inputs are declared.
Layout fields
| Field | Meaning |
|---|---|
dock | Attach to an edge of the parent: fill, top, bottom, left, right, center. |
stack | Arrange children in a line: row, column, or grid. |
columns | For stack: "grid", how many columns the grid has. |
gap | Space between stacked children, in canvas units. |
pad | Shorthand: uniform inner spacing on all sides. |
padding | Inner spacing, per side when one number is not enough. |
margin | Outer spacing around the node. |
at | Place the node at an explicit point instead of docking/stacking. |
position / size | Explicit rectangle in canvas units. |
anchor_min / anchor_max | Anchor points (0…1 of the parent) that position and size are relative to. |
min_size / max_size | Clamp the computed size into a range. |
alignment | Where content sits in the node: start, center, end, stretch. |
Prefer dock, stack, pad, and at before falling back to raw anchors and pixel rectangles — the shorthand stays readable when the canvas changes.
Appearance fields
| Field | Meaning |
|---|---|
color | RGBA array with components 0…1 ([1, 0.5, 0, 1]) or a hex string (#FF8000, #FF8000FF). |
font | Font to draw text with, as an asset:// URI (for example asset://fonts/ui). |
font_size | Text size in canvas units. |
text | String shown by labels, text blocks, and text buttons. |
placeholder | Hint string shown by text_input while empty. |
texture | Image asset for image nodes and some controls, as asset://…. |
style | Named visual style applied to the node, layered with theme defaults. |
visible | Whether the node is shown. |
disabled | Whether the control accepts input. |
focusable | Whether keyboard/gamepad focus can land on this node. |
control | A string naming the virtual input for virtual_button / virtual_stick nodes. The node feeds the project input bound to virtual:<control>. |
minimum / maximum | Slider range endpoints (defaults 0 and 1). |
accessibility_label / accessibility_description / accessibility_hidden | Screen-reader text and visibility. |
Variables: ${name}
Text fields can contain placeholders written as ${name}. At run time each placeholder is replaced with the value of the HUD variable name. Declare the names the HUD uses:
{
"format_version": 1,
"variables": ["score", "player_name"],
"root": {
"id": "hud_root",
"children": [
{
"id": "score_label",
"type": "label",
"text": "${player_name} — score ${score}"
}
]
}
}
variablesis an array of names. It declares which runtime variables the HUD uses — for values set by scripts or by runtime-created UI — and never holds default values or translated text.- Writing
${name}in a text field self-declares that name; thevariableslist is for names that runtime-created UI will use before any text references them. - The label above displays whatever the runtime has set for
player_nameandscore. When a script updatesscore, the label shows the new value. - Use variables for anything that changes during play: counters, names, status words. Keep fixed copy as plain text.
Theme and localization
Two optional file references, both resolved relative to the HUD file:
{
"format_version": 1,
"theme": "../ui/default.theme.json",
"localization_file": "../ui/en.localization.json",
"children": []
}
theme— a path to a theme JSON file relative to the HUD file (for exampleexamples/ui_showcase/scenes/main.hud.jsonpoints at../ui/default.theme.json). It supplies styles and HUD navigation mappings; supported style fields include colors, padding, gap, font size, minimum size, and control/row height.localization_file— a path to a localization file relative to the HUD file, one per locale (for exampleen.localization.json,de.localization.json). The file holds a flat key→string map underlocalization:
{
"format_version": 1,
"localization": {
"title": "Demi UI",
"play": "Play"
}
}
Nodes pick their strings by key, and the localized value replaces the node's text at load:
{ "id": "play_button", "type": "button", "localization_key": "play" }
localization_key— replaces the node'stextwith the localized string for that key.placeholder_localization_key— the same for atext_inputnode'splaceholder.- Keys that do not resolve leave the authored text untouched.
A HUD document can also carry embedded translations. locales is an object keyed by locale whose values are key→string maps, and locale names the default:
{
"format_version": 1,
"locale": "en",
"locales": {
"en": { "play": "Play" },
"de": { "play": "Spielen" }
},
"children": [
{ "id": "play_button", "type": "button", "localization_key": "play" }
]
}
Hud.set_locale("de") switches languages at run time and reapplies every node's localization key; an invalid locale keeps the last valid one. Inline localization values declared directly in the HUD document take precedence over strings pulled from the localization file.
${name} variables are a separate mechanism (native values set with Hud.set_variable); they are not localized.
action_map and action_effects
Controls emit UI actions and can optionally trigger HUD effects when an action fires.
{
"format_version": 1,
"action_map": { "submit": "confirm" },
"action_effects": {
"menu_accept": { "focus": "start" }
},
"children": [
{ "id": "start", "type": "button", "text": "Start", "action": "menu_accept" }
]
}
action_map— navigation slot (next,previous,submit,cancel) → project input action name. Here, the project'sconfirminput activates the currently focused control through thesubmitslot.action_effects— keyed by UI action name (not node ID). Each value may setshow(array of node IDs to reveal),hide(array of node IDs to hide), and/orfocus(one node ID to focus). Here, a control with"action": "menu_accept"focuses thestartnode when activated.- The
actionfield on a node names the UI action that node emits; it does not add anaction_mapentry.
Values in action_map must match project input declarations. See Input bindings and demi.project.json.
root versus top-level children
There are two equivalent ways to write the top of the tree:
{
"format_version": 1,
"root": {
"id": "screen",
"type": "container",
"children": [
{ "id": "title", "type": "label", "text": "Skyforge" }
]
}
}
or, without naming a root node:
{
"format_version": 1,
"children": [
{ "id": "title", "type": "label", "text": "Skyforge" },
{ "id": "start_button", "type": "button", "text": "Start" }
]
}
- With
root, you own the outermost node: give it an ID, a type, and layout fields that govern the whole screen. - With top-level
children, the engine supplies an implicit fill root and your nodes become its children. - Use
rootwhen the screen needs one named outer container (for modal management or full-screen styling); use top-levelchildrenfor small, simple screens.
A node position may use id, prefab, arguments, and overrides to place a reusable UI prefab. Each override directly replaces one field on the expanded root; id, children, and prefab are reserved. See UI prefabs.
Safe areas
A safe area is the part of the screen guaranteed to be unobstructed — cutouts, rounded corners, and system bars on phones sit outside it.
respect_safe_areais a per-node field and defaults totrue. Readable text and interactive controls are then laid out inside the safe region.- Set
respect_safe_area: falseon a node only for intentional edge-to-edge work: full-screen backgrounds, immersive art, or compositions that are meant to run to the glass. - When you go edge-to-edge, keep important buttons and readable text clear of cutouts yourself — the node is promising the engine you have handled that.
Node field quick reference
{
"id": "health_bar",
"type": "progress",
"visible": true,
"disabled": false,
"focusable": false,
"color": "#3CFF6A",
"dock": "top",
"size": [400, 16],
"margin": 12,
"min_size": [120, 8],
"max_size": [800, 24],
"accessibility_label": "Health",
"respect_safe_area": true,
"children": []
}
That node is a progress bar pinned to the top of its parent, 400×16 design units, with outer margin 12, clamped between 120×8 and 800×24, drawn in the given green, named for screen readers, and kept inside the safe area. Not every field is required — omit anything you do not need.
Saves and live game data are separate from HUD files; runtime state is written to platform user-data, never into the HUD document. See Game data & saves.