Skip to content

HUD elements

Status: Implemented (API 1.21). Parts of the HUD the player moves, scales, hides and styles in the HUD editor and in Appearance > HUD, with an optional cached drawing.

Function table: hud. Related: HUD (layers), The UI Kit, Appearance Pages.

Layers or elements

HUD layer (cl_hud_layer_register, API 1.0) HUD element (cl_hud_element_register, API 1.21)
Where it draws Anywhere on the screen, you decide In its own box; the player places the box
HUD editor, Appearance > HUD No Yes: drag, scale, hide, Vanilla / Fancy style
Saved placement No Yes, anchored, survives window and GUI-scale changes
Cost per frame Your render callback every frame Every frame, or only when it changed (cached)

Use an element for anything the player looks at while playing (a compass, a counter, a timer). Use a layer for things that are not boxes (a full-screen tint, markers over the world).

Registering

local bar = cl.hud.element_register{
  id = "compass", label = "Compass bar", icon = "marker",
  width = 160, height = 14,
  anchor = "top", x = 0, y = 6,
  panel = true,           -- Fancy draws the HUD panel behind it
  cached = true,          -- drawn again only after bar:invalidate()
  visible = function() return cl.entity.client_player() ~= nil end,
  render = function(g, w, h)
    g:ui_text("N", w / 2 - 3, 2, "small", 0)
  end,
}
Field Meaning
id Unique in your mod: a-z, 0-9, _, -, .. The player’s placement is saved under mod.<mod id>.<id>; registering the same id again (a hot reload) replaces the element and keeps the placement.
label, icon The name and icon in the HUD editor and in Appearance > HUD.
width, height The box in GUI pixels. Change it later with e:set_size(w, h).
anchor, x, y The default position: one of the 9 screen points (top_left, top, top_right, left, center, right, bottom_left, bottom, bottom_right) and an offset inward from it.
panel In the Fancy style the HUD panel (with the opacity the player set) is drawn behind the element.
cached See below.
visible Optional; false skips the element this frame (and the editor shows a placeholder).
render(g, w, h) Draw in the element’s own box: (0, 0) is its top left. The player’s position and scale are applied around it.

Functions on the element: e:set_size(w, h), e:invalidate(), e:shown() (false while the player has hidden it), e:remove().

Cached elements

With cached = true the engine records what render drew and replays it every frame without calling your mod. Call e:invalidate() when what it shows changes:

cl.on("tick.client_post", function()
  local yaw = math.floor(cl.entity.client_player():rotation().yaw)
  if yaw ~= last_yaw then last_yaw = yaw; bar:invalidate() end
end)

The recording is also redrawn when the style, the theme, the fonts, the GUI scale or the placement change. A cached element costs well under a microsecond per frame; drawing that is not plain shapes and text (items, scissors) is drawn directly every frame.

The style

Inside render, g:panel, g:hud_panel, g:ui_text and g:icon follow the element’s own style (the player can set it per element in Appearance > HUD). cl.ui.hud_fancy() tells the HUD’s style for your own decisions.

C

static void draw_bar(cl_gui g, int32_t w, int32_t h, void* user) {
    cl_gui_ui_text(g, CL_STR("N"), (float)w / 2 - 3, 2, CL_UI_TEXT_SMALL, 0);
}

void setup_hud(void) {
    cl_hud_element_desc d = CL_HUD_ELEMENT_DESC_INIT;
    d.id = CL_STR("compass");
    d.label = CL_STR("Compass bar");
    d.width = 160;
    d.height = 14;
    d.anchor = CL_HUD_TOP;
    d.y = 6;
    d.panel = true;
    d.cached = true;
    d.render = draw_bar;
    cl_hud_element bar;
    cl_hud_element_register(&d, &bar);
}

C++: clight::hud::element_register(&d) returns Expected<clight::HudElement> with invalidate(), set_size(), shown(), remove().

Elements are client-only (CL_E_WRONG_SIDE on a dedicated server). Register them in lifecycle.client_setup. At most 64 per game, and the HUD layout holds 18 mod elements at a time.