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
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
);| Param | Type | Default | Required | Notes |
|---|---|---|---|---|
WorldContextObject | UObject* | — | yes | Auto-supplied by BP context |
Name | FName | — | yes | Globally unique per world; also becomes BridgeName for Sender routing |
URL | FString | — | yes | Complete URL; plugin does not prepend Default Dev URL |
Anchor | EWebHUDAnchor | TopLeft | no | One of 9 anchor positions |
Padding | FVector2D | (16, 16) px | no | Inset from anchor edge; sign auto-flipped for right/bottom; ignored for Center |
Size | FVector2D | (0.3, 0.2) | no | Viewport ratio per axis; >1 clamped to 1, <=0 rejected |
MinPixelSize | FVector2D | (0, 0) | no | CSS-style floor (px) on the final rect after Viewport × Size. Axis value 0 = no floor |
MaxPixelSize | FVector2D | (0, 0) | no | CSS-style ceiling (px). Axis value 0 = no ceiling. If Max < Min, Min wins |
bInteractive | bool | true | no | false = 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 × 280The 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.
Returns — FWebHUDHandle. Invalid (Name = None, Layer = nullptr) if validation failed; check with Handle.IsValid() on the C++ side or compare Name != None in BP.
Behavior
| Condition | Result | Log |
|---|---|---|
Name reused for existing layer | Updates in place (LoadURL, anchor, padding, size) | Warning: name exists — updated in place |
Name = NAME_None | Returns invalid handle | Error: Name is None |
URL = "" | Returns invalid handle | Error: URL is empty |
Size.X <= 0 or Size.Y <= 0 | Returns invalid handle | Error: Size must be > 0 on both axes |
Size.X > 1 or Size.Y > 1 | Clamped to 1.0 per axis | Warning: Size clamped to (X, Y) |
Anchor = Center with non-zero Padding | Padding ignored | Verbose: Padding ignored for Center anchor |
| Layout subsystem unavailable | Returns invalid handle | Error: layout subsystem unavailable |
Example
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
static bool RemoveWebHUDByName(UObject* WorldContextObject, FName Name);| Param | Type | Required | Notes |
|---|---|---|---|
WorldContextObject | UObject* | yes | Auto-supplied by BP context |
Name | FName | yes | The name passed to Add Web HUD |
Returns — true 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
Input Action "Esc" (pressed)
└─ Remove Web HUD By Name (Name: "HpBar") → bRemovedRemove Web HUD
static bool RemoveWebHUD(const FWebHUDHandle& Handle);| Param | Type | Required | Notes |
|---|---|---|---|
Handle | FWebHUDHandle | yes | The handle returned by Add Web HUD |
Returns — true 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
Add Web HUD → HudHandle (stored on actor)
...
Event End Play
└─ Remove Web HUD (Handle: HudHandle)Remove All Web HUDs
static void RemoveAllWebHUDs(UObject* WorldContextObject);| Param | Type | Required | Notes |
|---|---|---|---|
WorldContextObject | UObject* | yes | Auto-supplied by BP context |
Returns — void.
Side effects — Removes every registered layer in the current world; layout stack is torn down.
Example
Event "Return To Main Menu"
└─ Remove All Web HUDsIs Web HUD Active
static bool IsWebHUDActive(UObject* WorldContextObject, FName Name);BlueprintPure — safe to call from arbitrary expressions and Tick branches.
| Param | Type | Required | Notes |
|---|---|---|---|
WorldContextObject | UObject* | yes | Auto-supplied by BP context |
Name | FName | yes | The name to look up |
Returns — true if a layer with that Name is currently registered with the layout subsystem.
Example
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
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.
| Param | Type | Required | Notes |
|---|---|---|---|
WorldContextObject | UObject* | yes | Auto-supplied by BP context |
Name | FName | yes | The name passed to Add Web HUD |
bVisible | bool | yes | true = show (the layer's interactive-resolved visibility); false = Collapsed |
Returns — true 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)
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 grabGet Bundled Demo URL
static FString GetBundledDemoURL();BlueprintPure — safe to call from arbitrary expressions.
| Param | Type | Required | Notes |
|---|---|---|---|
| — | — | — | 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
Event BeginPlay
└─ Add Web HUD
Name: "OfflineDemo"
URL: Get Bundled Demo URL
Anchor: Center
Padding: (0, 0)
Size: (1.0, 1.0)See also
/guide/multi-hud— multi-layer HUD design walkthrough/reference/types—EWebHUDAnchor,FWebHUDHandle,FBridgeErrorInfo