Skip to content

Web HUD library

Source: SampleProject/Plugins/UnrealReactBridge/Source/UnrealReactBridge/Public/WebHUDLibrary.h · Private/WebHUDLibrary.cpp

Static Blueprint nodes under the Web HUD category. Use these to add and remove HUD layers at named anchors on the viewport. Every node is a BlueprintFunctionLibrary static, so they are callable from any Blueprint with a world context (Level BP, Player Controller, Game Mode, Widget BP, etc.).

Add Web HUD

cpp
static FWebHUDHandle AddWebHUD(
    UObject* WorldContextObject,
    FName Name,
    const FString& URL,
    EWebHUDAnchor Anchor = EWebHUDAnchor::TopLeft,
    FVector2D Padding = FVector2D(16, 16),
    FVector2D Size = FVector2D(0.3, 0.2),
    FVector2D MinPixelSize = FVector2D::ZeroVector,
    FVector2D MaxPixelSize = FVector2D::ZeroVector,
    bool bInteractive = true
);
ParamTypeDefaultRequiredNotes
WorldContextObjectUObject*yesAuto-supplied by BP context
NameFNameyesGlobally unique per world; also becomes BridgeName for Sender routing
URLFStringyesComplete URL; plugin does not prepend Default Dev URL
AnchorEWebHUDAnchorTopLeftnoOne of 9 anchor positions
PaddingFVector2D(16, 16) pxnoInset from anchor edge; sign auto-flipped for right/bottom; ignored for Center
SizeFVector2D(0.3, 0.2)noViewport ratio per axis; >1 clamped to 1, <=0 rejected
MinPixelSizeFVector2D(0, 0)noCSS-style floor (px) on the final rect after Viewport × Size. Axis value 0 = no floor
MaxPixelSizeFVector2D(0, 0)noCSS-style ceiling (px). Axis value 0 = no ceiling. If Max < Min, Min wins
bInteractivebooltruenofalse = display-only overlay (HitTestInvisible, non-focusable, no focus forwarding into CEF). Use for bars/badges/info panels with no <input>. Leave true for HUDs with React <input> fields. macOS caveat: false fixes the Slate layer only — a layer constructed at runtime still triggers a one-time native focus grab (Known Issues). To toggle a display-only layer at runtime on macOS, mount it once and flip Set Web HUD Visible instead of Add/Remove

Cross-resolution sizing (Min/MaxPixelSize)

Size is a viewport ratio so the widget scales as the viewport grows, but Padding is absolute pixels — mixing units makes 2K / 4K widgets look too cramped (Padding becomes a tiny fraction of a now-huge widget). MinPixelSize / MaxPixelSize give you CSS clamp() semantics on the final rect:

Size (0.32, 0.18) + MaxPixelSize (560, 280)
  → 720p   :  410 ×  130 (scales with viewport)
  → 1080p  :  614 ×  194 (scales with viewport)
  → 1440p  :  819 ×  259 → clamped → 560 × 259
  → 4K     : 1229 ×  389 → clamped → 560 × 280

The widget grows naturally up to a ceiling, then stops — keeping HUD proportions stable across resolutions. Leave both at zero (default) to preserve pre-v1.4 behavior.

Display-only overlays (bInteractive = false)

By default a Web HUD is interactive: it can take mouse and keyboard focus so React <input> fields work. For an overlay with no inputs (HP bars, buff badges, info panels), pass bInteractive = false.

A non-interactive layer is mounted HitTestInvisible (the whole subtree — including the embedded CEF browser — ignores the mouse) and never forwards keyboard focus into CEF. This is the right setting for any overlay with no <input>.

macOS: bInteractive = false is not enough for runtime mounts

On macOS the native web view (SAppleWebBrowserWidget) grabs Slate keyboard focus to itself once, on its first tick after construction ("to keep parity with CEF") — bypassing bInteractive = false, which only governs the Slate/UMG layer. That focus loss makes the OS cursor appear and flushes held movement keys (WASD go to the web layer, not the PlayerController). Layers added at BeginPlay are unaffected because pawn possession re-focuses the viewport right after; a layer constructed at runtime has no such re-focus. See Known Issues.

macOS-safe pattern for a runtime-toggled display-only layer: mount it once at BeginPlay (its one-time grab is absorbed under possession), then show/hide it with Set Web HUD Visible — never Add/Remove it at runtime. Toggling visibility reuses the existing browser, so the grab never re-fires.

You can flip interactivity on a live layer with UWebHUDWidget::SetInteractive(bool) (e.g. on a toggled-open chat panel) — it reapplies focusability + hit-test visibility immediately.

ReturnsFWebHUDHandle. Invalid (Name = None, Layer = nullptr) if validation failed; check with Handle.IsValid() on the C++ side or compare Name != None in BP.

Behavior

ConditionResultLog
Name reused for existing layerUpdates in place (LoadURL, anchor, padding, size)Warning: name exists — updated in place
Name = NAME_NoneReturns invalid handleError: Name is None
URL = ""Returns invalid handleError: URL is empty
Size.X <= 0 or Size.Y <= 0Returns invalid handleError: Size must be > 0 on both axes
Size.X > 1 or Size.Y > 1Clamped to 1.0 per axisWarning: Size clamped to (X, Y)
Anchor = Center with non-zero PaddingPadding ignoredVerbose: Padding ignored for Center anchor
Layout subsystem unavailableReturns invalid handleError: layout subsystem unavailable

Example

text
Event BeginPlay
  └─ Add Web HUD
        Name:    "HpBar"
        URL:     "http://localhost:5173/hp"
        Anchor:  Top Left
        Padding: (16, 16)
        Size:    (0.3, 0.1)
     → Handle (FWebHUDHandle)

TIP

For dev iteration, point URL at your local Vite server (http://localhost:5173). For shipping, package your React build under Content/Web/ and use file:///... — see /guide/dev-server.

Remove Web HUD By Name

cpp
static bool RemoveWebHUDByName(UObject* WorldContextObject, FName Name);
ParamTypeRequiredNotes
WorldContextObjectUObject*yesAuto-supplied by BP context
NameFNameyesThe name passed to Add Web HUD

Returnstrue if a matching layer was found and removed; false otherwise.

Side effects — Unmounts the underlying widget from the viewport stack, fires NativeDestruct on the layer widget, unregisters from UUnrealReactBridgeSubsystem, and removes the layout stack itself if no layers remain.

Example

text
Input Action "Esc" (pressed)
  └─ Remove Web HUD By Name (Name: "HpBar")  → bRemoved

Remove Web HUD

cpp
static bool RemoveWebHUD(const FWebHUDHandle& Handle);
ParamTypeRequiredNotes
HandleFWebHUDHandleyesThe handle returned by Add Web HUD

Returnstrue if the handle was valid and the layer was removed; false if the handle was already invalid or the layer was already gone.

Side effects — Identical to Remove Web HUD By Name.

Example

text
Add Web HUD → HudHandle (stored on actor)
...
Event End Play
  └─ Remove Web HUD (Handle: HudHandle)

Remove All Web HUDs

cpp
static void RemoveAllWebHUDs(UObject* WorldContextObject);
ParamTypeRequiredNotes
WorldContextObjectUObject*yesAuto-supplied by BP context

Returnsvoid.

Side effects — Removes every registered layer in the current world; layout stack is torn down.

Example

text
Event "Return To Main Menu"
  └─ Remove All Web HUDs

Is Web HUD Active

cpp
static bool IsWebHUDActive(UObject* WorldContextObject, FName Name);

BlueprintPure — safe to call from arbitrary expressions and Tick branches.

ParamTypeRequiredNotes
WorldContextObjectUObject*yesAuto-supplied by BP context
NameFNameyesThe name to look up

Returnstrue if a layer with that Name is currently registered with the layout subsystem.

Example

text
Branch
  Condition: Is Web HUD Active (Name: "PauseMenu")
  True  → Remove Web HUD By Name ("PauseMenu")
  False → Add Web HUD ("PauseMenu", "http://localhost:5173/pause", Center, (0,0), (0.5, 0.6))

Set Web HUD Visible

cpp
static bool SetWebHUDVisible(UObject* WorldContextObject, FName Name, bool bVisible);

Show or hide an already-mounted layer without unmounting it (toggles Slate Collapsed). Prefer this over Remove Web HUD + Add Web HUD for a display-only overlay you toggle at runtime.

ParamTypeRequiredNotes
WorldContextObjectUObject*yesAuto-supplied by BP context
NameFNameyesThe name passed to Add Web HUD
bVisibleboolyestrue = show (the layer's interactive-resolved visibility); false = Collapsed

Returnstrue if a layer with that Name is mounted; false otherwise.

Why it exists (macOS): re-Adding a layer reconstructs its browser, which on macOS re-triggers a one-time native keyboard-focus grab that drops held game input (WASD) and shows the cursor — see Known Issues. Set Web HUD Visible keeps the browser alive, so toggling never re-fires the grab.

Example — runtime-toggled display-only panel (macOS-safe)

text
Event BeginPlay
  └─ Add Web HUD ("BuffPanel", URL, TopLeft, (12,56), (0.22,0.07), bInteractive = false)
  └─ Set Web HUD Visible ("BuffPanel", false)            // mount once, start hidden

Input Action "B" (pressed)
  └─ Set Web HUD Visible ("BuffPanel", NOT bPanelOpen)   // toggle — no Add/Remove, no focus grab

Get Bundled Demo URL

cpp
static FString GetBundledDemoURL();

BlueprintPure — safe to call from arbitrary expressions.

ParamTypeRequiredNotes
No parameters

Returns — A file:///<plugin>/Resources/WebDemo/index.html URL pointing at the bundled offline demo HTML. Empty string if the plugin can't be located (shouldn't happen at runtime since this function lives inside the plugin).

Use it for — the no-Node review path (L_OfflineDemo), or any local "does the bridge work at all?" smoke check.

Don't use it for — shipped HUDs. The path is absolute and embeds the plugin's install location; cooked builds and end-user installs will move that path.

Example

text
Event BeginPlay
  └─ Add Web HUD
        Name:   "OfflineDemo"
        URL:    Get Bundled Demo URL
        Anchor: Center
        Padding: (0, 0)
        Size:   (1.0, 1.0)

See also

Released under the MIT License.