Fonts, variables, localization, and styles.
Text, theme & locale
This page covers HUD fonts, variables, theme styles, and localization. The text module can parse rich-text markup for scripts, but HUD nodes currently render plain text.
Fonts
A font is a Font2D asset. Reference it by URI on any text-bearing node:
{
"id": "title",
"type": "label",
"text": "Skyforge",
"font": "asset://fonts/head",
"font_size": 48
}
font— anasset://URI naming aFont2Dasset. Optional; the default is the embedded Inter Variable face.font_size— height in canvas units. Scales with the canvas like every other dimension.
Change them at runtime with Hud.set_font(id, uri) and Hud.set_font_size(id, n); see HUD from Lua.
Wrapping, alignment, overflow
Long text needs a policy: how it breaks, where it sits in its box, and what happens when it still does not fit.
{
"id": "objective",
"type": "text",
"text": "Hold the forge until the gate seals.",
"text_wrap": "word",
"text_alignment": "start",
"text_vertical_alignment": "center",
"line_spacing": 4,
"max_lines": 3,
"text_overflow": "ellipsis",
"size": [420, 96]
}
| Field | Values | Meaning |
|---|---|---|
text_wrap | none, word, grapheme | Never break; break at word boundaries; break between any two characters (for narrow CJK columns). |
text_alignment | start, center, end | Horizontal placement of each line in the box. |
text_vertical_alignment | start, center, end | Where the text block sits vertically in the box. |
line_spacing | number | Extra canvas units between lines. |
max_lines | number | Caps how many lines are drawn. |
text_overflow | visible, clip, ellipsis | Draw past the box; cut at the box edge; cut with “…”. |
The example renders up to three wrapped lines, centered vertically, left-aligned, and elides with “…” if the objective text still overflows.
${variables} interpolation
Text can reference named variables. At draw time each ${name} is replaced with the variable’s current value.
{
"format_version": 1,
"variables": ["score"],
"children": [
{ "id": "score", "type": "label", "text": "Score: ${score}" }
]
}
- Top-level
variablesin the HUD document is an array of variable names — never a name→value map. Values are runtime strings set from Lua withHud.set_variable/Hud.set_variables; unresolved names stay visible as${name}. - Node
textmay mix literal words and${name}markers anywhere in the string. - Interpolation happens when text is displayed, so one node definition can show changing values.
local Hud = require("demi.hud")
Hud.set_variable("score", "1200")
Hud.set_variables({ score = "1300", game_title = "Skyforge II" })
local has = Hud.has_variable("score") -- boolean
local v = Hud.get_variable("score") -- string or nil
Hud.reset_variables() -- clear runtime values
Hud.set_variable(name, value)sets one value (a string). Returnstruewhen something changed, plus an error string.Hud.set_variables(table)sets several at once. Same return pair.Hud.has_variable(name)returns a boolean.Hud.get_variable(name)returns the current string, ornilwhen the name is unknown.Hud.reset_variables()clears every runtime value; unresolved names render as${name}. No return value.
The score label redraws with the new number — no need to rebuild the node.
Rich text tags
Text.parse_rich(markup, strict?) parses supported tags into plain text, spans, and diagnostics for scripts. HUD labels do not consume those spans, so putting tags directly in a node's text does not style it.
local Text = require("demi.text")
local parsed = Text.parse_rich("[strong]Forge tip:[/strong] hold guard")
-- parsed.text, parsed.spans, parsed.diagnostics
| Tag | Parsed metadata |
|---|---|
[color=…][/color] | A color span with a value such as #ffd27a. |
[em][/em] | An emphasis span. |
[strong][/strong] | A strong span. |
[link=…][/link] | A link span with a value. |
[icon=…] | A zero-length icon span with a value. |
The parser only describes spans; it does not make HUD text bold, italic, linked, or insert an image.
Theme styles
A theme is a JSON document of reusable styles. Reference one from the HUD and apply it per node with style.
{
"format_version": 1,
"styles": {
"menu_button": {
"font_size": 28,
"color": "#f2f4ff",
"background_color": "#2a2f45ff",
"padding": [24, 12, 24, 12],
"min_size": [280, 52]
},
"title": {
"font_size": 48,
"color": "#ffffff"
}
},
"action_map": { "submit": "ui_ok" }
}
styles— map of style name to supported fields:color,background_color,text_color,padding,gap,font_size,min_size,control_height, androw_height. Setfontdirectly on the node.action_map— optional map from HUD navigation slots (next,previous,submit,cancel) to project input action names.
Applying a style in a HUD document:
{
"format_version": 1,
"theme": "../ui/themes/dark.json",
"children": [
{ "id": "title", "type": "label", "text": "${game_title}", "style": "title" },
{ "id": "start", "type": "button", "text": "Start", "style": "menu_button", "action": "ui_ok" }
]
}
- Top-level
themenames the theme file path, relative to the HUD file. styleon a node copies that style’s fields onto the node.- Explicit node
text_color,font_size, andmin_sizetake precedence. Styles can still replace nodecolorandbackground_color; they fill padding and gap when those values are zero.
Why themes: one change to menu_button restyles every menu button in Skyforge. Individual exceptions stay readable on the node.
Localization
A localization file supplies initially loaded strings. The HUD references one file; each node pulls its string by key when the document loads. Runtime locale switching uses the HUD's embedded locales map.
{
"format_version": 1,
"localization": {
"play": "Spielen",
"quit": "Beenden"
}
}
{
"format_version": 1,
"localization_file": "../ui/de.localization.json",
"children": [
{ "id": "start", "type": "button", "text": "Start", "localization_key": "play", "style": "menu_button" },
{ "id": "seed", "type": "text_input", "placeholder_localization_key": "seed_hint" }
]
}
- The file holds a flat
localizationobject mapping string keys to translated text. A HUD loads only the file named bylocalization_file. localization_fileon the HUD names that file, relative to the HUD file.- A node’s
localization_key(andplaceholder_localization_keyfor text inputs) selects the string; its value replaces the node’s text (or placeholder) when the document loads. - For runtime switching, embed a
localesobject plus a defaultlocalein the HUD:
{
"format_version": 1,
"locale": "en",
"locales": {
"en": { "play": "Start", "quit": "Quit" },
"de": { "play": "Spielen", "quit": "Beenden" }
},
"children": [
{ "id": "start", "type": "button", "text": "Start", "localization_key": "play" }
]
}
local Hud = require("demi.hud")
Hud.set_locale("de") -- reapply every localization key in German
Hud.set_locale(code)selects a loaded entry inlocalesand reapplies localization keys. It cannot load anotherlocalization_file. Returnstrueon success, plus an error string; the last valid locale is kept on failure.${name}variables are a separate native-variable mechanism — they are interpolated, not localized.
Pseudo-locale QA
Translated text is often longer than English. Pseudo-localization brackets strings and repeats vowels so some layout bugs show up before real translations arrive.
Hud.set_pseudo_locale(true) -- stress-test layout with fake translations
Hud.set_pseudo_locale(enabled)takes a boolean and switches displayed strings to a pseudo-locale build of the current strings. No return value.- Localized strings render with doubled vowels inside brackets (English “Start” becomes “[Staart]”), making some truncation and wrapping problems easier to see.
- Run Skyforge’s menu in pseudo-locale before each release; fix anything that clips or overflows.
Next
- HUD nodes — every node type and its fields.
- HUD from Lua — setters and events.
- Layout — dock, stack, anchors, safe areas.