Runtime Reticle UI Driven by a Tick Loop
What you'll learn
- Resolving players and their per-player UI through
GetPlayspace()andGetPlayerUI[]. - Building a
canvasoverlay and adding atext_blockreticle withAddWidget. - Subscribing to a per-frame update using the real
TickEvent.Subscribesignature (a callback taking afloatdelta). - Driving the widget's runtime state safely from a
<transacts>handler and cleaning up the subscription withcancelable.Cancel().
How it works
The device runs OnBegin when the level starts. It walks every player from GetPlayspace().GetPlayers(), and for each one it fetches that player's UI surface with GetPlayerUI[Player] — note the square brackets, because GetPlayerUI is a fallible (<decides>) lookup and MUST live inside an if.
Once we have the UI, we build a canvas and place a text_block inside a canvas_slot, then push the whole canvas onto the player's screen with AddWidget. The text_block is our reticle; we keep a reference so we can mutate it later.
For the live behavior we subscribe to the playspace TickEvent. Its real signature hands our callback a single float (the delta time between frames), and Subscribe returns a cancelable — we store it so we can Cancel() the loop when we're done. Each frame we accumulate elapsed time and rewrite the reticle text through SetText. Because updating widget state is a mutation, the handler is marked <transacts>, matching the effect requirements of the APIs it calls.
Let's build it
Create a Verse file named runtime_reticle.verse, paste the code below, then build (Ctrl+Shift+B) and drop the device into your level.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Verse }
# Device that attaches a live, per-frame reticle to each player's screen.
runtime_reticle := class<concrete>(creative_device):
# How long the encounter has been running, driven by the tick delta.
var ElapsedTime : float = 0.0
# The reticle text widget we mutate every frame.
var Reticle : text_block = text_block{}
# Stored so we can stop the per-frame loop later.
var TickHandle : ?cancelable = false
OnBegin<override>()<suspends>: void =
# Build the reticle UI for every player in the playspace.
for (Player : GetPlayspace().GetPlayers()):
# GetPlayerUI is fallible — it must be resolved inside an if.
if (PlayerUI := GetPlayerUI[Player]):
# Fresh text_block acts as our reticle glyph.
set Reticle = text_block{DefaultText := "( + )"}
# Canvas lets us position the widget on-screen.
ReticleCanvas := canvas:
Slots := array:
canvas_slot:
Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
Alignment := vector2{X := 0.5, Y := 0.5}
Widget := Reticle
# Push the canvas onto this player's screen.
PlayerUI.AddWidget(ReticleCanvas)
# Subscribe to the per-frame TickEvent. Its callback takes the
# frame delta (float); Subscribe returns a cancelable we keep.
set TickHandle = option{GetPlayspace().GetTickEvent().Subscribe(UpdateReticle)}
# Runs once per frame. <transacts> because it mutates widget state.
UpdateReticle(DeltaTime : float)<transacts>: void =
# Accumulate elapsed time from the per-frame delta.
set ElapsedTime = ElapsedTime + DeltaTime
# Rewrite the reticle text with live, interpolated data.
Reticle.SetText(StringToMessage("( + ) {Round[ElapsedTime]}s"))
# Helper: turn a string into the message SetText expects.
StringToMessage<localizes>(S : string)<computes> : message = "{S}"
GetPlayerUI[Player] uses [] because it is a <decides> lookup; calling it with () would not compile. The TickEvent.Subscribe callback signature is _(:float):void, so UpdateReticle takes exactly one float. We stash the returned cancelable in a ?cancelable so it can be cancelled later without crashing if the loop never started.
Try it yourself
- Add a
@editablefloatfor a pulse speed and useSinofElapsedTimeto make the reticle text flash between two glyphs. - Cancel the loop after a set duration: bind the option (
if (H := TickHandle?): H.Cancel()) onceElapsedTimepasses a threshold. - Swap the
text_blockfor a colored one and change its text based on how long the encounter has run. - Store one reticle per player in a
[player]text_blockmap instead of a single shared field so each screen is independent.
Recap
You attached a real widget to a player's screen with GetPlayerUI[] + AddWidget, drove it every frame through the genuine TickEvent.Subscribe API (whose callback receives a float delta), mutated widget state inside a <transacts> handler, and kept the returned cancelable so the loop can be cleanly stopped. These are the load-bearing pieces of any live, code-driven HUD in UEFN.
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 Implementing Camera Soft-Lock and Dynamic Target UI via Verse 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.