Modular Verse: Functions, Parameters, and Player UI
What you'll learn
You'll learn how to break UEFN logic into clean, reusable functions: declaring them with the := operator, giving them typed parameters and a return type, and marking them with the correct effect specifiers (<decides>, <transacts>, <suspends>). We then apply this to a practical task — a trigger that increments a counter and pushes the value onto a real on-screen canvas widget for the player who triggered it.
How it works
A Verse function is declared as Name(Params):ReturnType = body. Two things trip up almost everyone:
()vs[]. Parentheses call normal functions. Square brackets call fallible (<decides>) functions, and they may ONLY appear inside a failure context — anif (...)/for (...)condition, another<decides>body, or anot/oroperand. Getting a player's UI can fail (the UI may not exist yet), so that call uses[]inside anif.- Effect specifiers. If your function modifies state or the world it must be
<transacts>. If it needs toSleepor await, it must be<suspends>. A function that calls a<transacts>function must itself be<transacts>(or<suspends>, which includes transaction safety).
Also note: comparison is = (not ==), reassignment uses set X = ..., and the boolean type is logic, not bool.
For this build we:
- Define
MakeCounterCanvas— a helper that creates a canvas holding a text block, returning both so we can update the text later. - Define
UpdateText— a<transacts>function that takes anintand atext_blockand sets its message. - Subscribe to a
trigger_deviceevent, get the triggering player's UI viaGetPlayerUI[], add our canvas withAddWidget, and update it each time.
Let's build it
Place a Trigger device in your level and bind it to the @editable field. Each time a player steps on the trigger, the counter increments and the on-screen number updates.
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# A trigger that builds a per-player UI counter and updates it on each activation.
counter_ui_device := class<concrete>(creative_device):
# Editable reference to the Trigger device placed in the level.
@editable Trigger : trigger_device = trigger_device{}
# Mutable counter shared across triggers. 'set' is required to reassign.
var Counter : int = 0
# Remember the text block we created so we can update it later.
var CounterText : ?text_block = false
# HELPER: build a canvas containing a text block and return the text block.
MakeCounterCanvas(StartValue:int): tuple(canvas, text_block) =
Label := text_block{DefaultText := StringToMessage("Count: {StartValue}")}
Root := canvas:
Slots := array:
canvas_slot:
Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.1}, Maximum := vector2{X := 0.5, Y := 0.1}}
Offsets := margin{Top := 0.0, Left := 0.0}
Widget := Label
(Root, Label)
# HELPER: build a message from a string. Interpolation turns the int into text.
StringToMessage<localizes>(Value:string)<computes>: message = "{Value}"
# FUNCTION: update the text block's message.
UpdateText(Value:int, Text:text_block): void =
Text.SetText(StringToMessage("Count: {Value}"))
Print("Updated UI to show: {Value}")
OnBegin<override>()<suspends>: void =
# Device events do NOT need () before .Subscribe.
Trigger.TriggeredEvent.Subscribe(OnTriggered)
# Handler receives an ?agent payload from the trigger.
OnTriggered(MaybeAgent:?agent): void =
if (Agent := MaybeAgent?, Player := player[Agent]):
# GetPlayerUI[] is fallible, so it must be bound inside an if.
if (UI := GetPlayerUI[Player]):
set Counter = Counter + 1
# First trigger: build the canvas and remember the text block.
if (Existing := CounterText?):
UpdateText(Counter, Existing)
else:
Built := MakeCounterCanvas(Counter)
UI.AddWidget(Built(0))
set CounterText = option{Built(1)}```
## Try it yourself
- Add a **cooldown**: make `OnTriggered` set a `var CanTrigger : logic = true`, flip it off, then `Sleep(1.0)` and flip it back on (you'll need to make the handler `<suspends>` or spawn the work).
- Change the anchor/offset values on the `canvas_slot` to reposition the counter on screen.
- Reset the counter to zero when it reaches 10 using an `if (Counter >= 10):` check before updating the text.
- Give each player their own counter by storing values in a `map` keyed by `player`.
## Recap
You defined typed functions, chose the correct effect specifiers (`<transacts>` for widget mutation, `<computes>`/`<localizes>` for the message builder, `<suspends>` for the entry point), and used `[]` only inside failure contexts for the fallible `GetPlayerUI[]` and `player[Agent]` calls. You returned multiple values with a `tuple`, stored the widget in an `?option`, and updated it with `SetText` — a genuinely modular, DRY pattern you can reuse across devices.
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 Defining and Calling Functions in 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.