Tuples: Return Lap AND Time Together

In this lesson you'll learn to

  • βœ“Return and destructure tuples for multi-value results.

Return and destructure tuples for multi-value results.

πŸ“– Reference & full walkthrough: Tuples: Return Lap AND Time Together

🧩 Your capstone piece: GetBestLap():(int, float) result


What you'll learn

  • How Verse expresses "multiple return values" with tuples like tuple(logic, logic) and destructuring via .0 / .1.
  • How <decides> calls such as Tracker.IsActive[Agent] and Tracker.HasReachedTarget[Agent] "return" a success/failure that you read in an if β€” they do NOT return logic, so you never call them bare.
  • How overloaded device methods (GetValue() vs GetValue(Agent)) let you pick which value to "return".
  • How to wire a trigger event to read all of these at once.

How it works

Verse gives you two complementary tools:

  1. Tuples pack several values into one. A function typed tuple(logic, logic) returns two values; you build one by stating (A, B) as the final expression and read it back with Result(0) / Result(1).

  2. <decides> functions are fallible. CoinTracker.IsActive[Agent] uses [] brackets because it may fail. It never hands back a logic β€” it either succeeds (the then: branch runs) or fails (the else: runs). To turn that success/failure INTO a logic you can pass around, wrap it in an if and set a flag.

⚠️ Common mistake: writing CoinTracker.IsActive[A] as a bare statement. [] fallible calls may ONLY appear in a failure context β€” an if/for condition, a <decides> body, or a not/or operand.

  1. Overloads: GetValue() returns the whole-match value, GetValue(Agent:agent) returns that player's value, and GetValue(TeamIndex:int) returns the team's value. These use () parens because they simply return an int β€” they are not fallible. Choosing the overload is how you "return" the value you actually want.

We combine IsActive and HasReachedTarget into ONE helper that returns both as a tuple(logic, logic), so the caller gets two answers from one call, then reads the numeric value with a GetValue overload.

Let's build it

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

# Finish-line challenge: read three answers from one trigger check-in.
finish_line_challenge := class<concrete>(creative_device):

    # The plate a player steps on to "check in" at the finish line.
    @editable
    CheckPlate : trigger_device = trigger_device{}

    # The Tracker counting each player's coins.
    @editable
    CoinTracker : tracker_device = tracker_device{}

    # Returns TWO answers at once as a tuple:
    #   result(0) = is the tracker active for this agent?
    #   result(1) = has this agent reached the target?
    # Both come from <decides> calls folded into plain logic values.
    ReadStatus(A : agent) : tuple(logic, logic) =
        var WasActive : logic = false
        # IsActive[A] is fallible β€” read its success inside an if.
        if (CoinTracker.IsActive[A]):
            set WasActive = true
        var WasDone : logic = false
        if (CoinTracker.HasReachedTarget[A]):
            set WasDone = true
        # State the tuple as the final expression to "return" both.
        (WasActive, WasDone)

    OnBegin<override>()<suspends> : void =
        # Device events subscribe WITHOUT () after the event name.
        CheckPlate.TriggeredEvent.Subscribe(OnCheckIn)

    OnCheckIn(Agent : ?agent) : void =
        # TriggeredEvent hands us ?agent β€” unwrap it before use.
        if (A := Agent?):
            # One call, two answers via tuple destructuring.
            Result := ReadStatus(A)
            WasActive := Result(0)
            WasDone := Result(1)
            # Overloaded GetValue(Agent) returns THIS player's value (int).
            PlayerValue := CoinTracker.GetValue(A)
            # GetValue() with no args returns the whole-match value.
            MatchValue := CoinTracker.GetValue()
            if (WasDone?):
                Print("Finished! Player coins = {PlayerValue}, match total = {MatchValue}")
            else if (WasActive?):
                Print("Still tracking. Player coins = {PlayerValue}")
            else:
                Print("Tracker not active for this player yet.")

Try it yourself

  1. Place a Tracker device and a Trigger device in your level.
  2. Add this Verse device, then assign CoinTracker and CheckPlate in its Details panel.
  3. Configure the Tracker to count a stat and set a Target Value.
  4. Step on the trigger before and after reaching the target β€” watch the log print the correct branch each time.
  5. Extend it: add a GetValue(TeamIndex:int) read for the player's team total, or use SetValue(A, 0) to reset a player's counter after they finish.

Recap

  • Verse has no return a, b, c; use a tuple return (tuple(logic, logic)) and read fields with Result(0) / Result(1).
  • <decides> calls like IsActive[A] and HasReachedTarget[A] use [] and may ONLY appear in a failure context β€” read their success in an if, never call them bare.
  • Overloaded GetValue() / GetValue(Agent) / GetValue(TeamIndex) use () and return an int directly β€” pick the overload to choose which value you "return".
  • Device events (TriggeredEvent) subscribe without (); unwrap ?agent with if (A := Agent?): before using it.
πŸ”’

Keep going β€” free

You've read the intro. The rest of this lesson is free for members. Sign in to continue and track your progress.

Sign in free to continue this lesson
Pass the quiz above to chart this quest in your Journal.

Sources

/guides/multiple-return-values
Source

Biloxi Studios original lesson

Guided course

Add this lesson to your free study plan.

🧭 The Keeper’s log

Quest complete? Chart your next heading from the 🐻 North Jungle expedition.

β›΅ Take me there