Mastering Local HUD Widgets with @Client in Verse
Tutorial intermediate

Mastering Local HUD Widgets with @Client in Verse

Updated intermediate

What you'll learn

You'll discover how to use @Client functions to run UI logic directly on the player's device. You'll learn to safely fetch each player's UI container, construct a canvas widget, position it, and drive dynamic updates like text changes and visibility toggles—all without triggering network replication errors.

How it works

When a creative_device begins, its OnBegin runs on the server. To affect the client's screen, you must either mark your device as local or call a function annotated with @Client. The @Client pattern is ideal here: the server iterates over players and dispatches a setup task that executes on each machine.

Inside that client task, you use GetPlayerUI[] to grab the player_ui handle. Because GetPlayerUI is a fallible <decides> function, you must bind it in a failure context like if (UI := GetPlayerUI(Player)):. Once you have the UI, you can construct a widget (like a canvas), configure its anchors and offsets, and call AddWidget to render it. From there, you can enter a suspension loop to update the widget using SetText and SetVisibility.

Let's build it

This device demonstrates the full pipeline. It defines integer math helpers to avoid the forbidden % operator, marks the update function with @Client and <transacts>, and safely wires up the player UI.

using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Progression }
using { /Verse.org/SceneGraph/CollisionProfiles }
using { /Verse.org/Simulation }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Conversations }
using { /Fortnite.com/Devices }

my_hud_device := class<concrete>(creative_device):

    # Total integer helpers prevent the "no modulo" compiler error.
    VC_IntMod(A:int, B:int):int =
        if (R := Mod[A, B]): R else: 0

    VC_IntDiv(A:int, B:int):int =
        if (B > 0, R := Mod[A, B], Q := Floor[(A - R) * 1.0 / (B * 1.0)]): Q else: 0

    # @Client ensures this runs on the player's machine.
    # <transacts> is required because we mutate state via SetText/SetVisibility.
    SetupLocalHUD<client><transacts>(Player:player)<suspends>: void =
        # GetPlayerUI is fallible; bind it safely.
        if (UI := GetPlayerUI(Player)):
            # Construct a canvas widget for positioning.
            HUDCanvas := canvas{
                Anchors: anchors{
                    Minimum: vector2{X:0.5, Y:0.5},
                    Maximum: vector2{X:0.5, Y:0.5}
                },
                Offsets: vector2{X:0.0, Y:0.0},
                Alignment: vector2{X:0.5, Y:0.5},
                ZOrder: 1,
                SizeToContent: false
            }
            
            # Add the widget to the player's UI layer.
            UI.AddWidget(HUDCanvas)
            
            # Dynamic update loop.
            Counter := 0
            while (true):
                set Counter += 1
                
                # Interpolate text using Message interpolation.
                HUDCanvas.SetText(Message{"Reticle Count: {Counter}"})
                
                # Use VC_IntMod instead of %. Compare with =.
                if (VC_IntMod(Counter, 6) = 0):
                    # Toggle visibility safely.
                    if (Vis := HUDCanvas.GetVisibility[]):
                        if (Vis = widget_visibility::Visible):
                            HUDCanvas.SetVisibility(widget_visibility::Hidden)
                        else:
                            HUDCanvas.SetVisibility(widget_visibility::Visible)
                    end
                end
                
                # Yield to other coroutines.
                Sleep(0.1)
            end

    OnBegin<override>()<transacts><suspends>: void =
        # Iterate all players in the playspace.
        for (Player : GetPlayspace().GetPlayers()):
            # Dispatch the client-side setup.
            SetupLocalHUD(Player)
        end

Try it yourself

  1. Place my_hud_device in your island and compile.
  2. Test in preview mode; notice the reticle updates instantly for each player.
  3. Experiment: Change the Offsets to move the widget to a corner, or adjust ZOrder to draw behind other UI.
  4. Challenge: Add a second widget that tracks the player's health by reading a device value and updating both widgets simultaneously.

Recap

  • Use @Client to execute UI logic locally and avoid replication lag.
  • Always bind <decides> results like GetPlayerUI[] in an if statement.
  • Call AddWidget to render your constructed widget into the player_ui.
  • Avoid % and ==; use VC_IntMod and = respectively.
  • Mark functions that touch UI state as <transacts> and ensure callers match.

Check your understanding

Test yourself with an interactive quiz and track your progress + earn XP — free for members.

Turn this into a guided course

Add Local HUD Widget Creation with @Client Functions to your free study plan — we'll suggest related pages and stitch the lot into one compile-checked, self-guided lesson with worked examples and quizzes.

Original tutorial generated by Verse Island from the Verse/UEFN knowledge base, with references to the Epic Games sources above. Code is validated against the knowledge base.

Comments

    Sign in to vote, comment, or suggest an edit. Sign in