Skip to content

UI from Scripts

Scripts drive your in-game interface - labels, panels, buttons, images, and their animations - through api.ui, addressing UI elements by name, plus a quick api.setHud for one-line overlays.

Awaken has two ways to put text and controls on screen during Play: a structured UI built from UINode elements you lay out in the editor and manipulate by name, and a single HUD string for quick score/status text. Both are covered here.

The UIApi

api.ui targets UINode elements by their entity name - the name you gave the UI object in the Hierarchy. See Media: UI for building the elements themselves.

MemberSignatureDescription
ui.setText(name, text): voidSet a UI element's text content.
ui.setImage(name, imageId): voidSet an image element's sprite to an imported texture id.
ui.show(name): voidMake a UI element visible.
ui.hide(name): voidHide a UI element.
ui.onClick(name, cb): voidRegister a click handler for a button element.
ui.play(name, clip): voidPlay a UI animation clip on the widget (sets/adds its UIAnimator, restarts from 0).
ui.stop(name): voidFreeze the widget's UI animation on its current frame.

Each takes the element's name. A name that matches no UINode is a silent no-op, so a script never crashes because a label was renamed. setText, setImage, show, and hide write directly onto the named UINode; play and stop drive a UIAnimator on it; onClick wires the DOM overlay's click for that button back to your callback.

Driving text and visibility

setText, show, and hide write directly to the named UINode, which the UI overlay re-renders. Typical use is a HUD you update each frame or on events:

ts
export default class implements ScriptBehavior {
  onStart(api: ScriptApi) {
    api.state.lives ??= 3;
    api.ui.hide("gameOverPanel");
  }
  onUpdate(api: ScriptApi) {
    api.ui.setText("livesLabel", `Lives: ${api.state.lives}`);
    if (api.state.lives <= 0) api.ui.show("gameOverPanel");
  }
}

Buttons and onClick

onClick wires a callback to a UI button by name. When the player clicks that button in the rendered overlay, your callback fires. Register handlers in onStart:

ts
export default class implements ScriptBehavior {
  onStart(api: ScriptApi) {
    api.ui.onClick("startButton", () => api.loadScene("Level1"));
    api.ui.onClick("quitButton",  () => api.ui.show("confirmQuit"));
  }
}

Button handlers are cleared on Stop and re-registered on the next Play, so a re-run always binds fresh - no stale or duplicate handlers. Loading a scene also clears them.

Swapping images

A UINode with kind image draws a sprite scaled to its w×h box. api.ui.setImage(name, imageId) points that element at a different imported texture by its id - swap a HUD icon, a portrait, or a status indicator without a separate element per state:

ts
onStart(api: ScriptApi) {
  api.ui.setImage("weaponIcon", "icon_sword");
}
onCollisionEnter(api: ScriptApi) {
  api.ui.setImage("weaponIcon", "icon_bow"); // picked up a bow
}

The id is the texture's asset id (the same one the element's image field takes in the Inspector), not a file path or display name. The overlay resolves it to the sprite it draws. setImage on a non-image element is a no-op.

Playing UI animations

A UIClip animates a widget's x, y, w, h, opacity, rotation, scaleX/scaleY, and colour over time - a panel that slides in, a reticle that spins, a label that pulses. Clips are imported from Unity .anim files into the project's UI clip library. A UIAnimator component on the widget's root plays one; its tracks address the root and its named child elements.

api.ui.play(name, clip) gives the named widget a UIAnimator (or reuses the one it has), points it at the clip id, and restarts it from time 0. api.ui.stop(name) sets the animator's playing to false, freezing the widget on its current frame.

ts
onStart(api: ScriptApi) {
  api.ui.hide("gameOverPanel");
}
onUpdate(api: ScriptApi) {
  if (api.state.lives <= 0) api.ui.play("gameOverPanel", "slideIn");
}

play on a name that matches no UINode is a no-op. Exported games ship their UIClips and imported fonts with the scene (SceneFile.uiClips / fonts), so a widget that animates in the editor Game view animates the same way in game.html. See Media: UI for authoring the elements and importing clips.

setHud - the quick overlay

For simple status text you do not need a UINode at all. api.setHud(text) sets a single on-screen overlay string:

ts
onUpdate(api: ScriptApi) {
  api.setHud(`Score ${api.state.score ?? 0}   ·   Time ${api.time.toFixed(1)}s`);
}

setHud is the fastest path to on-screen feedback while prototyping. It is one string with no styling or layout - reach for UINode elements (api.ui) when you need positioned panels, multiple labels, or buttons. The HUD is cleared automatically when Play stops.

📸 Screenshot - save as img/scripting-ui-hud.png

A running game showing both UI layers: a setHud score/time line at the top, and a UINode "gameOverPanel" with a "Restart" button that a script shows via api.ui.show.

Play matches the shipped game

The UI service scripts talk to (button handlers, element text) is the same object the editor's Game view and the standalone Player both drive. So a menu you wire with api.ui.onClick behaves identically when you press Play and when a player opens your exported game.html.

See also

Awaken — browser-native WebGPU game engine.