Reference Verse

Named Arguments in Verse: Calling Devices with Clarity

Named arguments let you label the values you pass into a function or device call — turning a cryptic `Configure(3.0, true, 5)` into a self-documenting `Configure(Delay := 3.0, Loop := true, Count := 5)`. In this guide we'll wire up a barrier and a timer for a box-fight round and use named arguments (plus default parameters) to keep the code crystal clear.

Updated
The code on this reference page is provided as-is and did not pass the latest compile check — treat the examples as a starting point and verify in your project.
Watch the Knotnamed_arguments in ~90 seconds.

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 @editable fields 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 := true gives 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. Notice StartCountdown isn't passed — the default true kicks in.
  • RoundTimer.SuccessEvent.Subscribe(OnTimeUp) subscribes our handler in OnBegin.
  • OnTimeUp(Agent : ?agent) is the handler for a listenable(?agent) event; we unwrap the optional with if (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 write Enable(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 := default in 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.
  • logic is not bool and must be unwrapped with ?. To branch on a logic parameter, write if (ShowWall?):. Passing it works with true/false, but testing it needs the ?.
  • listenable(?agent) handlers receive (Agent : ?agent). Always unwrap with if (A := Agent?): before using the agent — the timer may fire with no activating agent.
  • timer_device has overloadsEnable() and Enable(Agent:agent), Reset() and Reset(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 a message, build one with a <localizes> function; raw strings won't compile. (Not needed above since we only used Print on a log.)

Build your own lesson with named_arguments

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →