Popups, dialogs, sheets, cards, HUD panels, alerts and snackbars draw their face at 80% opacity. The fight carries on behind a confirm dialog; the map shows under an inventory sheet. In a game this is not decoration — it is what keeps the player oriented. A fully solid panel erases where they were standing the moment a window opens.
modulate.a.
Five layers, most specific first
| Order | Where | Unit | Reach |
|---|---|---|---|
| ① | the argument at that call — surface.alpha, GoStyle.card(…, alpha) | ratio 0.0–1.0 | one window |
| ② | GoConfig.container_alpha_overrides[kind] | ratio 0.0–1.0 | one kind of panel, project-wide |
| ③ | GoConfig.metric_overrides[<kind>_alpha] | percent | projects that keep every measurement in one place |
| ④ | GoConfig.container_alpha | ratio 0.0–1.0 | every panel at once |
| ⑤ | the theme's GoHud/constants/<kind>_alpha | percent | what the look decides — the source of truth |
With none of them set the value is 100, so nothing about an older theme changes.
alpha field and
argument takes 0.0–1.0, the same units as Color.a and modulate.a, and a
negative value means "not set". The one exception is the theme's constants, because a
Theme constant cannot hold a float: those and the channel that overrides them
(metric_overrides, which shares the theme's names and integer type) are written as
80. Nothing else is.
# One window only — a confirm dialog that must not hide the fight behind it
surface.alpha = 0.6
sheet.alpha = 0.7
dialogs.alpha = 0.9 # @export fields are ratios too
drawer.alpha = 0.7
GoPopover.open(slot, body, {"alpha": 0.9})
var glass := GoStyle.card(Color.TRANSPARENT, -1.0, -1.0, -1.0, 0.5)
# Per kind — keep the HUD nearly solid, because its text sits straight over the world
GoUi.config.container_alpha_overrides = {
GoTheme.BOX_PANEL: 0.7, # dialogs and sheets can breathe
GoTheme.BOX_HUD: 0.95,
}
GoUi.refresh() # redraws widgets that are already open
# The whole project — a busy world wants its panels back to solid
GoUi.config.container_alpha = 1.0
GoUi.refresh()
GoUi.surface_alpha(GoTheme.BOX_PANEL) # the resolved ratio, e.g. 0.8
What follows the value, and what does not
| Follows it — containers | Ignores it — things you press, and markers |
|---|---|
GoSurface (the one shell behind dialogs, sheets and dropdowns) · GoSheet · GoDialogs · GoDrawer · GoPopover · GoNotice · GoSnackbar · GoPromptCard · GoCoachMark · GoConsole |
every button · GoSlot (quick slots) · GoBadge · segmented controls · choice cells · chips · discs and avatars |
GoStyle.card() · hud_panel() · overlay_panel() · alert() · plate() · edge_card_panel() · style_notice_panel() · floating() · box() · surface() |
all text and icons |
A button whose state is washed out no longer says what it is, and a badge exists to make two characters legible — neither is a container, so neither follows the value.
popup_alpha 100). The engine may host a
PopupMenu in its own Window, and there the OS does not composite it with the
game: translucency comes out black rather than see-through. Lower it only in projects that embed their
subwindows (gui_embed_subwindows).
Three details that come from real screens
-
The value multiplies, it does not replace. The default HUD face is already 0.92 alpha, so
hud_alpha80 lands at 0.736 — a theme that deliberately made a face translucent keeps that decision. The flip side: apply it twice and the panel fades twice, which is why the alpha is multiplied in exactly one place (GoSkin.surface_box()) and every other face function forwards the argument instead of fading its own result. -
A colour you passed is a colour you get.
GoStyle.plate(…, fill)keeps an explicit fill exactly as given:Color(ink, 0.14)already states its alpha, and multiplying panel opacity into it would cut the caller's intent twice.GoStyle.floating(…, opaque = true)ignores the value for the same kind of reason — filling the face solid is that flag's whole purpose. -
Panels gohud did not build get the same rule.
GoStyle.fade_panel(node)— after the node is in the tree, so it reads the theme it inherits. It is idempotent: the original face is recorded and every call recomputes from that, so repeated redraws never stack. After swapping themes,GoStyle.forget_face(node)drops that memory so the next call picks up the new face.
Custom faces fade too — and that is the part that silently breaks
The cut-corner panel (GoStyleBoxCut) and the forged frame
(GoStyleBoxMedieval) are not StyleBoxFlat; they draw themselves. Put the
opacity only in the flat branch and these themes quietly ignore it while every value-based test still
passes. Both fade their background and keep glow, rivets, corner engraving and bevels at full strength —
and the medieval face's grain and bevel were always proportional to its background alpha, so they thin
out along with the panel.
Which is the wider point: bg_color.a == 0.8 is a number. The world shows through and
the text is still legible is the requirement. gohud checks the first in its headless suite and the
second by drawing panels over diagonal stripes on a virtual monitor
(tests/gohud_alpha_shot.gd) — on the flat, the cut and the forged face, in both the dark
and the light theme.