User interface
A game’s menus and HUD are GUI documents: .xml files of markup that say what is on a screen,
much like a web page. GUI style sheets, plain .css, say how it looks. Scripts show documents
as screens, fill in their text, and answer their buttons.
GUI documents
Section titled “GUI documents”Make one in the Assets panel: right-click the background and choose New > GUI > GUI Document (the File menu has the same list). Its page is a text editor with a live preview beside it. The game’s own GUI draws the preview with the project’s fonts, theme and sheets, so the preview shows what the game will show. While the markup does not build, the preview keeps the last version that did, and the problems list under the code says what is wrong. Save the document to use it in the game.

You can also import an .xml file you wrote elsewhere.
A pause menu:
<Screen class="fades" nav-default="resume-btn" back="none"> <Panel class="centre"> <Panel class="card"> <Label class="title" text="Paused"/> <Button id="resume-btn" text="Resume"/> <Button id="restart-btn" text="Start the level over"/> <Button id="title-btn" text="Leave to the title"/> </Panel> </Panel></Screen>Elements nest. A Panel holds other elements and lays them out, and controls are what the
player sees and uses. An element’s attributes are its settings:
idnames it, so a script can find it, andclassgives it classes for the style sheets.- An attribute that is one of the element’s own properties (
text,value,min,max,src) sets that property. - Any other attribute is a CSS property for that element alone, such as
gap="8",flex-direction="row"orfont-size="24px". It takes precedence over the style sheets. {{name}}in a value follows the GUI’s data value of that name, which a script sets withgui.set(below):text="Score: {{score}}",value="{{health}}". A slider, check box or text field bound this way writes the player’s changes back to the value.
An element or attribute the GUI does not know is left out, and the page’s problems list warns about it.
Screens
Section titled “Screens”A document that a script shows as a menu or HUD starts with <Screen>. Its attributes say how
it sits over the screens below it:
| Attribute | Effect |
|---|---|
covers="true" |
The screens below are hidden. Use it for a title screen. Off by default. |
blocks="false" |
The screens below, and the game, still take input. Use it for a HUD. Blocking is on by default. |
pauses="true" |
The screen stack reports the game as paused while the screen is up; the script decides what pausing means. Off by default. |
back="none" |
Escape and the pad’s East button do not pop it, so the script handles going back. By default they pop the top screen, unless it is the only one. |
nav-default |
The id of the element a keyboard or gamepad starts on, so menus work without a mouse. |
A button activates on a click, Return, Space or the pad’s South button. The arrow keys and the pad move the focus between controls.
Elements
Section titled “Elements”| Containers | Panel, ScrollView, ScrollBar, Splitter, Expander |
| Text | Label, RichText, EditableLabel |
| Text input | TextField, TextArea, NumberField |
| Buttons | Button, CheckBox, RadioButton, ToggleSwitch |
| Values | Slider, ProgressBar, ComboBox, ColorSwatch, ColorPicker |
| Pictures | Image |
| Lists | ListBox, ListView, TreeView, TreeItem, TableView, TableItem |
| Tabs | TabView, TabBar, Tab, TabPage |
| Menus and windows | MenuBar, Menu, MenuItem, MenuSeparator, Window, MessageBox, ToastHost |
| Spacing | Separator, Spacer |
| Game screens | Screens, Screen, Prompt |
An Image shows a texture or a vector image (an imported .svg) by its ID:
<Image src="{6faf9764-c33a-444f-8126-91cc8eade4f5}"/>. A script can set it instead
(Image.of(node).src = "{...}"). In that case leave src out of the markup, because a markup
attribute acts as a style that the element keeps returning to. A picture that is still loading
when it is set appears once it is ready.
A Prompt shows the control for one of the input map’s actions, such as
<Prompt action="Pause"/>. It shows a pad button while a pad is in use and a key otherwise, and
follows the player’s rebinding. It shows the control’s name as text, and tells the style sheet
which control it is, so a sheet can draw a picture for each. Its binding attribute is the
control’s name in lower case with dashes (pad-south, pad-lb, pad-ls for the left stick
pressed, space, w-a-s-d), and its kind is pad, keyboard or pointer:
prompt[kind="pad"] { color: rgba(0, 0, 0, 0); width: 38px; height: 38px; background-size: 38px 38px; }prompt[binding="pad-south"] { background-image: url("{a-texture-of-the-A-button}"); }Style sheets
Section titled “Style sheets”A GUI style sheet (New > GUI > GUI Style Sheet) is CSS. Rules pick elements and set how they look:
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }screen.fades:entering { animation: fade-in 0.3s ease-out; }
.centre { flex-grow: 1; justify-content: center; align-items: center; padding: 32px; }.card { padding: 24px; gap: 10px; width: 400px; background-color: rgba(12, 9, 7, 0.91); border-radius: 16px; }.title { font-size: 36px; color: #F0D8A8; }
button { height: 44px; font-size: 22px; color: #F0E0C0; background-color: #1E1610; border-radius: 10px; }button:hover { background-color: #2C2016; }button:focus-visible { border: 4px solid #F0C070; }button:disabled { color: #6A5A48; }- Selectors: an element’s type in lower case (
button,label,slider,screen; aRadioButtonisradioand aToggleSwitchisswitch),.class,#id,[attribute]and[attribute=value], and the combinators (space,>,+,~). States are pseudo-classes::hover,:active,:focus,:focus-visible,:focus-within,:disabled,:checked,:selected, and the structural ones (:first-child,:nth-child()and the rest), with:not(),:is()and:where(). A screen is:enteringwhile it arrives and:leavingwhile it goes. - Layout is flexbox by default: every container is a column unless
flex-directionsaysrow, withgap,justify-content,align-items,flex-growand the rest.display: gridlays out a grid, anddisplay: noneremoves an element from the layout. - Appearance: sizes,
margin,padding,background-color,background-image,border,border-radius,box-shadow,opacity,transform,filter,backdrop-filter, the font and text properties, andtext-shadow,text-outlineandtext-glowfor text that has to stay readable over a busy scene. - Motion:
transition, andanimationwith@keyframes. - Variables:
--namecustom properties, read withvar(--name). The GUI’s built-in theme is written in them (--accent,--surface,--textand more), so a sheet that sets them on:rootrestyles every control at once. - At-rules:
@media,@importand@font-facebesides@keyframes.
A sheet’s page edits the sheet beside a preview. The preview draws markup that belongs to the page, shown and editable in the Previewed Over pane above the preview. It starts with a little of every control. Preview Over a Document in the page’s toolbar replaces it with a copy of one of your documents; the document itself is not changed.
Set the project’s sheets in Project > Project Settings…, in the UI section, as GUI sheets. They apply in order over the built-in theme, each over those before it, to everything the game shows.
Import .ttf, .otf or .ttc fonts. The UI section of Project Settings has the Default
UI font, used where nothing names another (Roboto when it is empty), and Other UI fonts for
more families. A sheet picks one with font-family, by the Family set on the font asset:
.title { font-family: "Lilita One"; font-size: 60px; }Showing screens from a script
Section titled “Showing screens from a script”The gui facade reaches the game’s GUI. gui.root() is the screen layer, which survives scene
changes, and inside it, with the id screens, is the game’s screen stack. Push a document by
its ID as a string, with or without braces (right-click the document in the Assets panel and
choose Copy GUID):
local PauseDoc = "{f5faae8a-fa1f-4c68-b2af-1cc8c1707afc}" -- a GUI document's ID
local function screens() return ScreenStack.of(gui.root():find("screens"))end
function Game:pause() run.setTimeScale(0) local menu = screens():push(PauseDoc) local this = self menu:find("resume-btn"):on("click", function(e) this:resume() return true end)end
function Game:resume() screens():pop() run.setTimeScale(1)end
function Game:showLoot(loot) Label.of(gui.root():find("hud-loot")).text = "Loot " .. loot ProgressBar.of(gui.root():find("hud-light")).value = 0.75endconst string kPauseDoc = "{f5faae8a-fa1f-4c68-b2af-1cc8c1707afc}"; // a GUI document's ID
funcdef bool EventHandler(GuiEvent e); // a button's click
ScreenStack@ screens() { return ScreenStack::of(gui::root().find("screens")); }
class Game{ void pause() { run::setTimeScale(0.0f); Screen@ menu = screens().push(kPauseDoc); menu.find("resume-btn").on("click", EventHandler(this.onResume)); }
private bool onResume(GuiEvent e) { screens().pop(); run::setTimeScale(1.0f); return true; }
void showLoot(int loot) { Label::of(gui::root().find("hud-loot")).text = "Loot " + loot; ProgressBar::of(gui::root().find("hud-light")).value = 0.75f; }}- The stack’s
push(document)shows a document on top and returns its screen.pop()removes the top screen,replace(document)swaps it for another,back()goes back as Escape does, andclear()empties the stack.top()is the top screen andcount()how many there are;isPaused()is true while a screen withpausesis up. A document is loaded fresh on each push, so connect its buttons each time. find(id)finds an element in a screen, or anywhere undergui.root(). It returnsnil(Luau) ornull(AngelScript) when there is none. A screen that was popped stays in the tree while its exit animation plays, so search the new top screen first when ids repeat. Reach a control’s own properties through its type:Label.of(node).text,Slider.of(node).value,Button.of(node)(Label::ofand so on in AngelScript).- Every element has
visible,enabledandopacity, anddisplayed, which takes it out of the layout when false (a hidden element keeps its place).UIWidget.of(node)addsaddClass,removeClass,hasClassandtoggleClass, so a sheet can restyle it. on(event, handler)calls the handler on eachclick(a button, from the mouse, keyboard or pad),input(a slider as it moves),change,focusand other events. A handler that returns true takes the event, so it goes no further.tween(property, to, seconds, easing)animates a property, named as a sheet names it:node:tween("opacity", "0", 0.5, "ease").gui.set(name, value)sets a data value that markup follows with{{name}}, andgui.getNumber,gui.getTextandgui.getBoolread one back.
The GUI runs on real time, so menus still animate while setTimeScale(0) holds the game still.
GUI in the scene
Section titled “GUI in the scene”Three components show a document from an entity instead. Each has a Document, and an optional Sheet that applies over the project’s sheets to that component alone.
GUI Canvas shows its document while its entity is in the scene: a HUD that belongs to a level.
Order puts one canvas above another (higher on top), and with Interactive off it takes no
input. Scaler Mode sizes it: ConstantPixel (the default) keeps one GUI pixel to one screen
pixel, and ReferenceResolution lays the document out at the Reference Resolution (1920 by
1080 by default) and scales it to fit, so it looks the same on every display. With Render Mode
RenderTexture it draws into a texture of Render Texture Width by Render Texture Height
(512 by 512 by default) instead, which a Sprite or Decal on the same entity shows, for example a
computer screen in the world.
GUI Billboard floats a document over the entity, such as a name or a health bar, hung by its
bottom centre and facing the camera. Offset moves it from the entity: with Orientation
Cylindrical (the default) the offset is in world space, and with Screen it is in the entity’s
own space, so it turns with the entity. With Scale Mode Distance it shrinks as the entity
gets further away; it is full size at Reference Distance (10) and kept between Min Scale
(0.3) and Max Scale (2). The default, Fixed, keeps one size at any distance.
GUI World Panel puts a document on a flat panel in the entity’s plane, Size Meters across (1.6 by 0.9 by default), drawn at Pixels Per Meter (200). The panel is shown through a Sprite on the entity, which is added if the entity has none. With Interactive on (the default), the player can click it.
Each has Visible. A script reaches a component’s document through its root(), which is
nil (Luau) or null (AngelScript) until the document is built:
local board = GuiBillboardComponent.of(self.entity)board.visible = truelocal panel = board:root()if panel then ProgressBar.of(panel:find("meter")).value = 0.5 Label.of(panel:find("mark")).text = "?"endGuiBillboardComponent@ board = GuiBillboardComponent::of(self);board.visible = true;Node@ panel = board.root();if (panel !is null){ ProgressBar::of(panel.find("meter")).value = 0.5f; Label::of(panel.find("mark")).text = "?";}GuiCanvasComponent and GuiWorldPanelComponent work the same way.
The loading screen
Section titled “The loading screen”A document set as Loading screen in the Startup section of Project Settings shows while
the game loads its first scene. The GUI’s data value bootProgress goes from 0 to 1 as it loads, so a
bar can follow it:
<Panel class="loading"> <Label text="Loading"/> <ProgressBar value="{{bootProgress}}"/></Panel>If it is left empty, the engine shows its own: the engine’s logo over a progress bar and the word “Loading”.