overlay layout¶
a HUD is a panel that floats over the whole screen, on top of the game: a compass strip, a kill feed, a crosshair.
an overlay is the root a panel hangs from. two things make a root an overlay: it fills the viewport, and it lets mouse input fall through to the game behind it instead of swallowing every click.
this article assumes you have read the model, arranging children for flex layout, and styles.
the overlay root¶
Goo.Hud.Overlay() returns the canonical overlay container. you use it as the root your Build() returns:
var root = Hud.Overlay();
Overlay() bakes in four properties so you do not reinvent them per HUD:
Position = PositionMode.Absolute,Top = 0,Left = 0,Width = 100%,Height = 100%. the root covers the viewport regardless of where the host panel sits in layout.FlexDirection = FlexDirection.Column. yoga and CSS default a flex container toRow, which is right for prose but wrong for almost every HUD. an overlay stacks top-to-bottom, soOverlay()flips the default for you.PointerEvents = PointerEvents.None. without this, the invisible full-screen root sits in front of the game and eats every click. this is the single property that separates a HUD from a modal. more on it below.
Overlay() is a factory, so compose extra fields with a C# with expression. it must be called from inside Build(), the same rule as new Container().
anchoring to a corner¶
most HUDs pin their content to a screen region: a feed in the bottom-left, a score in the top-right, a compass at top-center. Goo.Layout.ResolveAnchor turns an anchor corner into the flex triple that places content there.
// from KeystrokeVisualizerUI
var r = Layout.ResolveAnchor( Anchor ); // Anchor is a Layout.Anchor enum value
var root = Hud.Overlay() with
{
Padding = AnchorMargin,
JustifyContent = r.JustifyContent,
AlignItems = r.AlignItems,
};
var column = new Container { FlexDirection = r.FlexDirection, Gap = ChipGap };
Layout.Anchor has seven values: TopLeft, TopCenter, TopRight, BottomLeft, BottomCenter, BottomRight, and Center. ResolveAnchor returns an AnchorResolution carrying JustifyContent, AlignItems, and FlexDirection. set the first two on the overlay root and the third on the content column.
when you do not need the resolution pieces individually, Hud.Anchored does the wiring for you: it wraps your content in a full-bleed, pointer-through cell that pins it to the anchor corner.
root.Children.Add( Hud.Anchored( Layout.Anchor.TopCenter, content, key: "compass" ) );
the wrapper is always FlexDirection.Column; when you want the ColumnReverse stack-up behavior described above, put it on an inner stacking column, never on the anchored wrapper itself (top anchors would render at the bottom).
the FlexDirection is the subtle one. top-anchored rows use ColumnReverse and bottom-anchored rows use Column, so in both cases a freshly added child appears closest to the anchored edge. AnchorResolution.StacksUp is true when the direction is Column. a slide-in animation reads it to know whether new entries rise from below or drop from above.
a worked example: the compass HUD¶
the blessed demo for this article is Code/Demos/Compass/CompassUI.cs, a heading strip pinned to the top-center of the screen. it shows the standard overlay pattern: OnEnabled sizes the host panel, and Build() calls Hud.Overlay() and adds an anchored child.
GooPanel<T> exposes the engine panel as Panel. size it once in OnEnabled so the host covers the viewport:
// from CompassUI
protected override void OnEnabled()
{
base.OnEnabled();
Panel.Style.Width = Length.Percent( 100 );
Panel.Style.Height = Length.Percent( 100 );
}
Build() returns an Overlay() root and adds the compass band as an anchored child at TopCenter:
// from CompassUI
protected override Container Build()
{
var root = Hud.Overlay();
root.Children.Add( Hud.Anchored( Layout.Anchor.TopCenter, _view.Build(), key: "compass" ) );
return root;
}
Hud.Anchored wraps the content in a full-screen absolute cell that resolves JustifyContent and AlignItems from Layout.ResolveAnchor, so the content lands at the chosen corner without you wiring up those properties by hand. (the shipped demo carries its own HudLayout.Anchored helper that predates the library factory and does the same thing.)
the band redraws as the player turns. the demo uses a CompassView helper that tracks heading state. OnUpdate ticks the view and rebuilds only when it reports a change:
// from CompassUI
protected override void OnUpdate()
{
if ( _view.Tick( Scene, Time.Delta ) ) Rebuild();
base.OnUpdate();
}
the condition gate is the point worth keeping: an overlay that calls Rebuild() every frame rebuilds its whole subtree every frame. gate the rebuild on a meaningful change and the HUD costs nothing while the player holds still.
the rest of the Hud family¶
Overlay() and Anchored() have five siblings on Goo.Hud, all returning a plain Container:
| factory | what it bakes in |
|---|---|
Fill() |
absolute, 100% x 100%, pointer-through. the standard full-bleed layer for shader overlays. resolves inside the nearest positioned ancestor; add Position = Relative to that ancestor if needed. |
Scrim( color ) |
absolute, all four edges pinned to zero, optional background color. it does not set PointerEvents: assign it yourself, since scrims typically toggle between None and All on open and close. |
Wallpaper( texture, path ) |
a Fill() carrying a full-bleed Image child. pass texture: null to load by path; path is ignored when texture is non-null. must be called from within Build(). |
Spacer() |
sets only FlexGrow = 1. pushes siblings apart; everything else is caller-controlled. |
Divider( color ) |
a 1px full-width horizontal rule. defaults to a low-alpha white line; pass a color to override. |
one composition caveat: the style fields a factory sets follow the first-declared-wins rule, so Fill() and Wallpaper() cannot have their baked-in Position, size, or PointerEvents overridden with a with expression. build the Container directly when you need an interactive full-bleed layer. Scrim deliberately leaves PointerEvents unset so that one stays overridable.
pointer events: the one rule that matters¶
an overlay is a full-screen panel sitting in front of the game. if it accepts pointer events, it intercepts every click, and the game stops responding to the mouse even though the overlay looks empty. PointerEvents = PointerEvents.None is what makes the overlay see-through to input.
Hud.Overlay() sets it on the root. when you build the overlay by hand, set it yourself, on the root and on any purely-decorative child that should not catch the cursor. a HUD element that does need clicks (a button baked into the overlay) sets PointerEvents = PointerEvents.All on just that element to opt back in.
see also¶
- arranging children - flex direction, justify, and align, the layout surface anchoring builds on
- container-reference - the full property surface, including
Position,PointerEvents, andOverflow - styles - init-only style properties and how to keep them dry
- animations - the rebuild loop and the animators a live HUD drives its motion with