Reference Verse compiles

Comparison Operators: Race Timers That React to Your Score

Comparison operators are the heartbeat of game logic — they let your island ask questions like 'did the player beat the record?' or 'is time running out?' Every branching decision in Verse flows through `=`, `<>`, `<`, `>`, `<=`, and `>=`. In this article you'll wire them directly to a `timer_device` on a sun-drenched pirate-ship dock, pausing, resuming, and completing the clock based on live numeric comparisons.

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

Overview

Comparison operators in Verse are failable expressions — they either succeed (and execution continues) or fail (and the branch is skipped). This makes them a natural fit for if expressions, for filters, and any other failure context. Because they are failable, you cannot use them outside a failure context; trying to write X < Y as a standalone statement is a compile error.

Verse ships six comparison operators:

Operator Meaning Supported Types
< Less than int, float
<= Less than or equal int, float
> Greater than int, float
>= Greater than or equal int, float
= Equal int, float, string, arrays/tuples of comparable elements
<> Not equal int, float, string

Reach for comparison operators whenever you need to:

  • Gate a game event behind a score or count threshold (open a door when kills ≥ 5)
  • Validate a timer (trigger an alarm when seconds remaining < 10)
  • Check whether a player chose the correct answer in a quiz minigame
  • Compare string identifiers to route logic

API Reference

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

Walkthrough

Scenario: The Score-Gated Vault Door

A player steps on a pressure plate. Your Verse device checks their elimination count (stored in a simple counter). If they have 5 or more eliminations, the vault barrier lowers and they hear a success sound. If not, nothing happens — they need to keep playing.

This example wires up a trigger_device (the pressure plate), a barrier_device (the vault door), and uses >= to gate the unlock.

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

# Tracks how many eliminations a player has earned.
# In a real project this would come from a tracker_device;
# here we keep a simple module-level counter for clarity.
vault_door_manager := class<concrete>(creative_device):

    # The pressure plate the player steps on
    @editable
    Plate : trigger_device = trigger_device{}

    # The barrier that acts as the vault door
    @editable
    VaultDoor : barrier_device = barrier_device{}

    # Simulated elimination count — wire a real tracker in production
    var PlayerEliminations : int = 0

    # Required eliminations to open the vault
    RequiredEliminations : int = 5

    OnBegin<override>()<suspends> : void =
        # Subscribe to the pressure plate
        Plate.TriggeredEvent.Subscribe(OnPlateTriggered)

        # Simulate earning eliminations over time so the example is self-contained
        loop:
            Sleep(3.0)
            set PlayerEliminations += 1
            if (PlayerEliminations >= 10):
                break  # stop accumulating after 10

    # Called every time a player steps on the plate
    OnPlateTriggered(Agent : ?agent) : void =
        # Check the threshold — >= is failable, so it lives inside if
        if (PlayerEliminations >= RequiredEliminations):
            VaultDoor.Disable()   # lower the barrier — vault is open!
        else:
            # Not enough eliminations yet — door stays up
            VaultDoor.Enable()

Line-by-line breakdown:

  • @editable fields let you drag the real trigger_device and barrier_device from the UEFN outliner into the device's property panel — without this, the device can't see them.
  • Plate.TriggeredEvent.Subscribe(OnPlateTriggered) — registers the handler. The event fires every time a player steps on the plate.
  • OnPlateTriggered(Agent : ?agent) — the handler signature matches listenable(?agent). We don't need the agent here, but the parameter must still be declared.
  • if (PlayerEliminations >= RequiredEliminations):>= is failable; the if provides the failure context. If the comparison fails, the else branch runs.
  • VaultDoor.Disable() / VaultDoor.Enable() — real barrier_device API calls that lower and raise the barrier.
  • The loop at the bottom simulates earning eliminations every 3 seconds so you can test the door opening without a full elimination system.

Common patterns

Pattern 1 — Timer Alarm with < (less than)

Fire a cinematic trigger when the match clock drops below 10 seconds.

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

timer_alarm_device := class<concrete>(creative_device):

    @editable
    AlarmTrigger : trigger_device = trigger_device{}

    # Countdown in whole seconds
    var SecondsRemaining : int = 60

    # Has the alarm already fired this round?
    var AlarmFired : logic = false

    OnBegin<override>()<suspends> : void =
        loop:
            Sleep(1.0)
            if (SecondsRemaining > 0):
                set SecondsRemaining -= 1
            # Fire the alarm exactly once when time drops below 10
            if (SecondsRemaining < 10, not AlarmFired):
                set AlarmFired = true
                AlarmTrigger.Trigger()  # kicks off the cinematic / alarm VFX
            if (SecondsRemaining = 0):
                break

Key point: SecondsRemaining < 10 is failable — chaining it with , not AlarmFired inside the same if means both conditions must succeed. The comma acts as a logical AND in a failure context.


Pattern 2 — String equality = for a quiz answer check

A player interacts with a button labelled with an answer. Compare their choice to the correct answer string.

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

quiz_checker_device := class<concrete>(creative_device):

    # Button the player presses to submit answer "Paris"
    @editable
    AnswerButton : button_device = button_device{}

    # The barrier that opens on a correct answer
    @editable
    RewardDoor : barrier_device = barrier_device{}

    CorrectAnswer : string = "Paris"
    PlayerAnswer  : string = "Paris"   # In production, set this from UI input

    OnBegin<override>()<suspends> : void =
        AnswerButton.InteractedWithEvent.Subscribe(OnAnswerSubmitted)

    OnAnswerSubmitted(Agent : agent) : void =
        # String equality is failable — must be inside a failure context
        if (PlayerAnswer = CorrectAnswer):
            RewardDoor.Disable()   # correct — open the door
        else:
            RewardDoor.Enable()    # wrong — keep it shut

Key point: String comparison with = is case-sensitive and code-point exact. "paris""Paris". Normalise your strings before comparing if players type free text.


Pattern 3 — Inequality <> to detect a state change

A score tracker updates every few seconds. Broadcast a special event only when the new score is different from the previous score (i.e., something actually changed).

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

score_change_watcher := class<concrete>(creative_device):

    @editable
    ScoreChangedTrigger : trigger_device = trigger_device{}

    var LastScore : int = 0
    var CurrentScore : int = 0

    OnBegin<override>()<suspends> : void =
        loop:
            Sleep(2.0)
            # Simulate a score update (replace with real tracker polling)
            set CurrentScore += 3

            # <> (not equal) is failable — fires only when score actually changed
            if (CurrentScore <> LastScore):
                set LastScore = CurrentScore
                ScoreChangedTrigger.Trigger()  # notify other devices

Key point: <> is the not-equal operator. It succeeds when the two operands differ, so it's perfect for change-detection guards.

Gotchas

1. Comparisons are failable — they MUST live in a failure context

Writing X < Y as a bare statement is a compile error. Always wrap comparisons in if, for, or another failure context:

# ❌ WRONG — bare comparison, compile error
X < Y

# ✅ CORRECT
if (X < Y):
    DoSomething()

2. No automatic int ↔ float conversion

Verse does not coerce types. Comparing an int to a float literal fails to compile:

var Count : int = 5
# ❌ WRONG — 5.0 is float, Count is int
if (Count < 5.0): ...

# ✅ CORRECT — keep types consistent
if (Count < 5): ...

If you genuinely need to compare across types, convert explicitly: Float(Count) < 5.0 (using the built-in Float() conversion).

3. = on strings is code-point exact and case-sensitive

"Paris" = "paris" fails. If you're comparing player-typed strings, normalise case before comparing or use a known canonical form.

4. Chaining comparisons with commas (AND logic)

Verse has no && operator. To require multiple comparisons to all succeed, chain them with commas inside the same if:

if (Score >= 10, Score < 20):
    # Score is in [10, 20)
    DoSomething()

Each clause is evaluated left-to-right; if any fails, the whole if fails.

5. not negates a whole failable expression, not just comparisons

To invert a comparison, wrap it with not:

if (not (Score >= 10)):
    # Score is less than 10
    DoSomething()

This is equivalent to Score < 10 but useful when the original expression is complex.

6. Array/tuple equality with =

Verse supports element-wise equality for arrays and tuples whose element types are comparable. Two arrays are equal only if they have the same length and every element pair is equal. This is handy for checking ordered answer sequences in puzzle games.

Guides & scripts that use player

Step-by-step tutorials that put this object to work.

Build your own lesson with player

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 →