The "Ghost in the Machine": Mastering the Client Panel in UEFN
The "Ghost in the Machine": Mastering the Client Panel in UEFN
So, you've built an island that looks like a masterpiece. You've got traps that delete players in one hit, loot that spawns faster than a bus drop, and a storm that moves with the grace of a tank. But when you test it, something feels… off. The UI is laggy, the buttons don't respond, or worse, the stats are showing numbers that make no sense because the server is trying to do all the heavy lifting for every single player's screen.
Enter the Client Panel.
In Fortnite Creative (UEFN), there's a golden rule of performance: Don't make the Server do the Server's job for the Player's screen. The Server is the referee; the Client (your TV, PC, or phone) is the player watching the game. If the referee has to calculate the color of your health bar every frame, the game chugs. If your TV calculates its own health bar, smooth sailing.
Today, we're going to stop treating the UI like a global server variable and start treating it like a local player gadget. We'll build a custom "Player Status" dashboard that lives entirely on your device, using Verse to talk to the UI widgets. No lag, no server strain, just pure, local-level chaos.
What You'll Learn
- The Client vs. Server Split: Why your UI shouldn't live on the "Global" stage.
- Widget Binding: How to link a Verse variable (like health) to a UI text box without the server sweating.
- The Stack Box: The ultimate organizer for your UI widgets, like a loot bag that only holds specific items.
- Local Execution: Writing Verse code that only runs on your screen, not everyone's.
How It Works
Imagine you're in a match. You have a health bar.
The Old Way (Server-Side UI): Every time you take damage, the Server says, "Hey everyone! Player X is now at 75 health!" Then every player's game has to update their screen. If 100 people are playing, the Server screams that message 100 times. If the server lags, your health bar freezes. This is like having a narrator shout your score to the entire stadium every time you touch the ball.
The New Way (Client-Side UI): The Server only tells you, "Hey, you took damage." Your local game updates your health number. Your local UI reads that number and updates your screen. The Server doesn't care about your health bar's color. It's like having a personal mirror that reflects your face instantly, without asking the whole stadium if they want to see it.
The Scene Graph & UI Hierarchy
In UEFN, UI widgets live in a hierarchy called the Scene Graph. Think of this like your inventory screen. You have a main window (the Canvas or Panel), and inside it, you stack things.
- Canvas/Panel: The background of your HUD. It's the frame of the picture.
- Stack Box: A container that arranges widgets in a line (either vertically like a column of loot, or horizontally like a row of buttons). It's the shelf in your inventory.
- Text/Image Widgets: The actual content. The item icon, the health number, the kill count.
When we use Verse to control these, we aren't moving the widgets around the game world (like a Prop Mover). We are changing the data inside the widget. We're swapping the text from "100" to "99" without moving the box itself.
The "Client Panel" Concept
In UEFN's Performance Dashboard, you'll see a menu called "Client." This isn't just a menu; it's a philosophy. When we build our UI, we want to ensure our Verse code is running locally on each player's device. This means we use Player-Scoped events.
If you write a function that updates a UI text box, and you bind it to a player-specific event (like "Player Damaged"), that code only runs on that player's machine. The server doesn't get involved in the pixel-pushing.
Let's Build It
We are going to build a simple "Revenge Tracker" HUD. It shows how many players you've eliminated in the current life. It's simple, it's visual, and it proves that the UI is updating locally.
Step 1: The UI Setup (No Code Yet)
- Open your Level Editor.
- Go to the Widget Editor (usually found in the Tools tab or by dragging a "UI Widget" into the scene).
- Create a new Widget called
Revenge_Tracker. - Inside the widget, add a Stack Box. Set it to Vertical.
- Inside the Stack Box, add a Text widget.
- Name it
ElimCount_Text. - Set the text to "Elims: 0" (just for preview).
- Make the font big and bold. You're a legend; your stats should be readable.
- Name it
- Save and close the Widget Editor.
Step 2: The Verse Code
Now, we need to tell Verse to find that text box and change the words inside it when you get an elimination.
We need to understand two key Verse concepts here:
bind: Think of this like plugging a controller into a console. You're connecting a piece of code to a UI element so they can talk.player: In Verse,playerrefers to the specific person playing the game. When we sayplayer.GetEliminations(), we're asking that specific person, not the server, "How many eliminations do you have?"
Here is the code. Copy this into a new Verse file attached to your UI Widget or a Game State.
# Revenge_Tracker.verse
# 1. IMPORT THE STUFF WE NEED
using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/Simulation }
# 2. THE DEVICE CLASS
# Attach this to a Verse Device actor placed in the level.
# It manages the Revenge Tracker HUD for each player.
revenge_tracker_device := class(creative_device):
# 3. THE MAIN LOGIC
# OnBegin fires once when the game session starts on the server.
# We then fan out to each player locally via <runs_on_client>.
OnBegin<override>()<suspends> : void =
# Subscribe to player-added so late-joiners also get the HUD.
GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded)
# Also set up HUD for any players already in the session.
for (P : GetPlayspace().GetPlayers()):
spawn { RunClientHUD(P) }
# Called whenever a new player joins.
OnPlayerAdded(P : player) : void =
spawn { RunClientHUD(P) }
# 4. CLIENT-SIDE HUD LOGIC
# <runs_on_client> ensures this executes on the player's local machine,
# not the server — keeping UI updates fast and cheap.
RunClientHUD(P : player)<suspends> : void =
# Build the text widget that will display elim count.
ElimCount_Text := text_block:
DefaultText := StringToMessage("Elims: 0")
# Wrap it in a stack_box (vertical stack).
# # note: stack_box children are set at construction in current Verse UI API.
TrackerStack := stack_box:
Orientation := orientation.Vertical
Slots := array:
stack_box_slot:
Widget := ElimCount_Text
# Add the widget to this player's screen via player_ui.
# player_ui.AddWidget only takes (:widget) or (:widget,:player_ui_slot) — no player arg.
if (PlayerUI := GetPlayerUI[P]):
PlayerUI.AddWidget(TrackerStack, player_ui_slot{ZOrder := 1})
# Track elim count manually — no GetEliminations() API exists yet.
# # note: Verse does not expose a GetEliminations() method; we count via EliminatedEvent.
var ElimCount : int = 0
# 5. LISTEN FOR ELIMINATIONS
# fort_character exposes EliminatedEvent, which fires when this character is eliminated.
# We listen on all characters; filter to kills made BY player P.
loop:
# Wait until P has a valid fort_character, then watch for events.
if (FortChar := P.GetFortCharacter[]):
# EliminatedEvent fires on the eliminated character.
# The event message contains the instigator (killer).
EliminatedMessage := FortChar.EliminatedEvent().Await()
# EliminatedMessage.EliminatingCharacter is the fort_character who got the kill.
if (KillerChar := EliminatedMessage.EliminatingCharacter?):
if (Killer := KillerChar.GetAgent[]):
if (Killer = P):
set ElimCount += 1
UpdateDisplay(ElimCount_Text, ElimCount)
else:
# Character not ready yet; yield and retry.
Sleep(0.1)
# 6. HELPER FUNCTION
# Updates the text widget with the latest elim count.
UpdateDisplay(ElimText : text_block, NewCount : int) : void =
ElimText.SetText(StringToMessage("Elims: {NewCount}"))
# ---- Verse Cortex: appended community utilities (UEFN-299) ----
# Community utility: wrap a string as a localizable `message`
# (required by SetText / HUD / billboard APIs). Auto-appended by
# Verse Cortex (UEFN-299) because the snippet referenced it but did
# not define it.
StringToMessage<localizes>(value:string) : message = "{value}"
Walkthrough: What Just Happened?
revenge_tracker_device: This is your inventory list. You're telling Verse, "Hey, inside this widget, there is a thing calledElimCount_Text." If you named your text widget something else in the editor, change this name. If they don't match, Verse will panic like a player who forgot their pickaxe.GetPlayspace().GetPlayers(): This is crucial. Rather than a single global player, we iterate every player in the session and spin up a personal HUD for each one. It's like handing every player their own mirror instead of pointing everyone at one giant scoreboard.FortChar.EliminatedEvent().Await(): This is the magic. We're attaching a small piece of code to the "You Got a Kill" moment.- When an elimination fires, the game checks: "Did this player get the kill?"
- If yes, it increments the local elimination count for that player.
- It then updates the text box on that player's screen.
SetText(...): This is how we change the text. It's like changing the label on a loot box. The box stays in the same spot, but the label changes.
Why This Is Better
- Performance: The server doesn't have to calculate or send the text "Elims: 5" to 100 players. It just tells you "You got a kill." Your computer updates your screen.
- Responsiveness: The UI updates instantly because it's not waiting for a network round-trip from the server. It's local.
- Scalability: You can have 100 players, each with their own custom UI, and the server barely breaks a sweat.
Try It Yourself
Challenge: Modify the code above to show a "Health Bar" instead of eliminations.
- Hint 1: You'll need to use
FortChar.GetHealth()instead of counting eliminations. - Hint 2: Health changes constantly, not just on kills. You might need to use a
whileloop or bind to a different event likeOnTakeDamageorOnHealthChanged(if available in your Verse version). - Hint 3: If you use a loop, make sure it doesn't run too fast or you'll crash the game like a bus with no brakes.
Bonus Challenge: Add a "Last Killer" name to the UI. When you die, show who killed you. (Hint: Look at the EliminatedMessage.Eliminator variable in the EliminatedEvent handler—it works for both getting and giving kills!).
Recap
- Client Panels keep your UI light and fast by running on the player's device, not the server.
- Stack Boxes organize your widgets like a tidy inventory.
GetPlayspace().GetPlayers()is your best friend. It ensures you're talking to your stats, not everyone's.- Binding connects your Verse logic to your UI widgets, so when the game changes, the screen changes instantly.
Now go build a HUD that's smoother than a fresh ice patch. And remember: if it lags, check if you're making the server do the heavy lifting. Let the client carry the weight.
References
- https://dev.epicgames.com/documentation/en-us/uefn/ui-widget-editor-in-unreal-editor-for-fortnite
- https://dev.epicgames.com/documentation/en-us/fortnite-creative/performance-dashboard-in-fortnite-creative
- https://dev.epicgames.com/documentation/en-us/uefn/making-custom-backplates-in-fortnite-creative
- https://dev.epicgames.com/documentation/en-us/uefn/speedway-race-with-verse-persistence-template
- https://dev.epicgames.com/documentation/en-us/fortnite/speedway-race-with-verse-persistence-template
Verse source files
- 01-device.verse · device
Turn this into a guided course
Add Clients Panel 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.
References
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.