Overview
A named argument is when you pass a value to a function or method by writing the parameter's name, an assignment :=, and the value — instead of relying purely on positional order. Verse supports this for your own functions, and it's also how you set fields when constructing devices and log values (e.g. log{Channel := box_fight_log}).
Why does this matter for a UEFN creator? Device setup functions often take several numbers and flags in a row. Round(30.0, 3, true) is impossible to read six months later. Round(TimeLimit := 30.0, Players := 3, EnableMidBarrier := true) reads like a sentence. Named arguments also pair with default parameters, so callers can omit anything they're happy with the default for.
Reach for named arguments whenever:
- A function has more than one parameter of the same type (two floats, two logics) — order mistakes there are silent and dangerous.
- You have optional configuration that most callers won't change.
- You want your round-setup or device-config code to be legible to teammates.
There is no special "named-arguments device" — this is a core Verse language feature you apply when calling the real devices like barrier_device and timer_device. The walkthrough below builds a real box-fight round manager to show it in action.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
We'll build a small box-fight round manager. When the round starts, we enable the arena barriers and start a countdown timer. When the timer succeeds (time runs out) we drop the mid-barrier so players can rush each other. All of the configuration is done through a helper function called with named arguments, so the setup reads clearly.
box_fight_manager := class(creative_device):
Logger:log = log{Channel := box_fight_log}
@editable
Barrier1:barrier_device = barrier_device{}
@editable
Barrier2:barrier_device = barrier_device{}
@editable
MidBarrier:barrier_device = barrier_device{}
@editable
RoundTimer:timer_device = timer_device{}
# A helper with NAMED + DEFAULT parameters.
# Callers pass values by name, and can skip ones with defaults.
ConfigureRound(EnableSideBarriers:logic, EnableMidBarrier:logic, StartCountdown:logic := true):void =
if (EnableSideBarriers?):
Barrier1.Enable()
Barrier2.Enable()
else:
Barrier1.Disable()
Barrier2.Disable()
if (EnableMidBarrier?):
MidBarrier.Enable()
else:
MidBarrier.Disable()
if (StartCountdown?):
RoundTimer.Enable()
RoundTimer.Start()
OnBegin<override>()<suspends>:void =
# Called with NAMED arguments — reads like a description of the round.
# StartCountdown is omitted, so its default (true) is used.
ConfigureRound(EnableSideBarriers := true, EnableMidBarrier := true)
# Listen for the timer finishing.
RoundTimer.SuccessEvent.Subscribe(OnTimeUp)
# Event handler: a listenable(?agent) hands us (Agent : ?agent).
OnTimeUp(Agent : ?agent):void =
Logger.Print("Time up — dropping the mid barrier!")
# Open the arena so players can rush.
MidBarrier.Disable()
if (A := Agent?):
Logger.Print("Timer was activated by an agent.")
Line by line:
Logger:log = log{Channel := box_fight_log}— even constructing the logger uses a named field assignment (Channel := ...). Same idea as named arguments.- The four
@editablefields are the real placed devices we control. You MUST declare them as fields to call their methods. ConfigureRound(...)is our own function with three parameters.StartCountdown:logic := truegives that parameter a default value — callers can leave it off.- Inside, we call the real device methods:
Barrier1.Enable(),MidBarrier.Disable(),RoundTimer.Enable(),RoundTimer.Start(). - In
OnBegin,ConfigureRound(EnableSideBarriers := true, EnableMidBarrier := true)uses named arguments. NoticeStartCountdownisn't passed — the defaulttruekicks in. RoundTimer.SuccessEvent.Subscribe(OnTimeUp)subscribes our handler inOnBegin.OnTimeUp(Agent : ?agent)is the handler for alistenable(?agent)event; we unwrap the optional withif (A := Agent?):before treating it as a real agent.
Common patterns
Pattern 1 — Named arguments make same-typed flags unmistakable
When several parameters share a type, positional calls are a trap. Naming them removes all doubt. Here we start a timer and enable a barrier through a helper.
trap_room_device := class(creative_device):
@editable
Wall:barrier_device = barrier_device{}
@editable
CountdownTimer:timer_device = timer_device{}
Arm(ShowWall:logic, RunTimer:logic):void =
if (ShowWall?):
Wall.Enable()
if (RunTimer?):
CountdownTimer.Enable()
CountdownTimer.Start()
OnBegin<override>()<suspends>:void =
# No guessing which logic is which:
Arm(ShowWall := true, RunTimer := true)
Pattern 2 — Default parameters let callers opt in
A helper that resets the timer, with an optional flag to also reset the barriers' ignore lists. Most callers won't need the second behavior, so it defaults to false.
round_resetter_device := class(creative_device):
@editable
SideBarrier:barrier_device = barrier_device{}
@editable
RoundTimer:timer_device = timer_device{}
ResetRound(StopTimer:logic := true, ClearIgnores:logic := false):void =
if (StopTimer?):
RoundTimer.Reset()
if (ClearIgnores?):
SideBarrier.RemoveAllFromIgnoreList()
OnBegin<override>()<suspends>:void =
# Reset the timer using the default; also clear ignore lists by naming it.
ResetRound(ClearIgnores := true)
Pattern 3 — Named arguments when subscribing and reacting
Here we use the timer's FailureEvent and add the activating agent to a barrier's ignore list, so that player can walk out. The named-argument helper decides which barrier to affect.
escape_gate_device := class(creative_device):
Logger:log = log{Channel := escape_log}
@editable
GateBarrier:barrier_device = barrier_device{}
@editable
FailTimer:timer_device = failtimer
LetThrough(TargetBarrier:barrier_device, ForAgent:agent):void =
TargetBarrier.AddToIgnoreList(ForAgent)
OnBegin<override>()<suspends>:void =
FailTimer.Enable()
FailTimer.Start()
FailTimer.FailureEvent.Subscribe(OnFail)
OnFail(Agent : ?agent):void =
if (A := Agent?):
# Named arguments spell out what each value is for:
LetThrough(TargetBarrier := GateBarrier, ForAgent := A)
Logger.Print("Player let through the gate.")
Gotchas
- Named arguments use
:=, not=. Inside a call you writeEnable(Agent := SomePlayer)— the assignment operator is:=. A single=is a comparison/definition and won't parse here. - You still need to satisfy every parameter without a default. Named-ness doesn't make a required parameter optional; only a
:= defaultin the declaration does. - Default parameters are declared in the function signature, e.g.
StartCountdown:logic := true. The default is what's used when the caller omits that argument entirely. logicis notbooland must be unwrapped with?. To branch on alogicparameter, writeif (ShowWall?):. Passing it works withtrue/false, but testing it needs the?.listenable(?agent)handlers receive(Agent : ?agent). Always unwrap withif (A := Agent?):before using the agent — the timer may fire with no activating agent.timer_devicehas overloads —Enable()andEnable(Agent:agent),Reset()andReset(Agent:agent). If you call an overloaded method, the arguments you pass (and whether you name them) select which overload runs; don't accidentally pass an agent to the whole-game version.- There is no
StringToMessage. If a device method takes amessage, build one with a<localizes>function; raw strings won't compile. (Not needed above since we only usedPrinton alog.)