Gessa Docs
Recipes

Recipe

UI HUD panels widgets and world nameplates

The player needs on-screen information (score, health, timer) or floating labels over entities (nameplates, damage numbers, quest markers). Screen UI is a UiPanel; world labels are a TextRenderableComponent.
engine v1.0.234since action-catalog.v1.0.232, script-semantic-patch-ops.v1, ui.v1, rendering.v1Copy for LLM

Use this for

score and health HUD; on-screen panels bound to game state; floating nameplates and damage numbers over entities; menus opened from scripts

Not for

durable data itself (see saves-and-persistence); global camera post (see camera-feel)

Pairs with: Game loop assembly entities scripts HUD win shippable loop, Saves persistence player progress and data stores, Collectibles pickups scoring and win condition, Combat damage health and respawn scripting

Wire the whole HUD in one pass

Create the panel ONCE, then bind every widget in a SINGLE project_apply_script_semantic_patch pass rather than one call per widget. Each wireHudStateBinding op adds one widget-to-state binding; put them all in the same patch so the HUD is wired as one decisive edit, not a drip of updates. A single field update to one widget is the precision tool, for a targeted repair.

The recipe

Screen HUD:

  1. Create a panel with project_create_ui_panel (project.ui_panel.create) holding the widgets you want (a score readout, a bar).
  2. Bind a widget to durable state from a script with project_apply_script_semantic_patch op wireHudStateBinding { "scope":{"kind":"handler","handlerRef":<key>}, "panelKey": <panel key>, "widgetKey": <widget key>, "storeKey":"progress", "recordKey":"player", "field":"score", "valueType":"number", "defaultValue": 0 }. It reads the warehouse field and pushes it through ctx.ui.setWidgetValue. Add one such op per widget in the same patch.
  3. Open and close panels from a GessaScript body with ctx.ui.openPanel and ctx.ui.closePanel (for a pause menu, a shop, a death screen).

World-space labels:

A player's own name over their character is NOT a TextRenderableComponent. The game's character policy nameplate setting (everyone, team, or none) makes the runtime stamp a server-written NameplateComponent from the account, so you never author a player name plate by hand (see player-characters-and-spawns). Use TextRenderableComponent for non-player world labels: enemy nameplates, item names, damage numbers, and quest markers.

  1. Attach TextRenderableComponent to the labeled entity { "type":"TextRenderableComponent", "enabled": true, "text":"Enemy", "textSource":"static", "color":"#ffffff", "fontSize": 18, "anchor":"bottom", "billboardMode":"camera", "offset":{"x":0,"y":1.8,"z":0}, "maxDistance": 80, "fadeDistance": 60 }.
  2. Drive the label from live data with textSource: static (fixed text), entityName (the entity's name), projection (a projectionKey), or stat (a statKey). Use stat for a floating value that updates from state.

Pitfalls

  • wireHudStateBinding reads the SAME store/record/field a save uses; wire the HUD to the field you already persist (see saves-and-persistence), not a separate copy.
  • TextRenderableComponent.text is capped at 256 chars; fontSize is 6..96; fontFamily is sans, mono, or serif. Out-of-range values fail.
  • A TextRenderableComponent with textSource:"stat" needs a statKey; with projection it needs a projectionKey. The discriminator drives which field is required.
  • HUD widget values are a uiEffect; they display state, they do not persist it.

Verify

  • engine_get_component_schema on TextRenderableComponent for text-source enums and bounds.
  • qa_capture_renderer_viewport (qa.renderer.viewport.capture) to confirm the HUD and nameplates render.
  • simulation_run to confirm the bound widget updates when the state changes.
Was this helpful?Report an issueContact support

On this page