Reference Verse compiles

Type Conversion in Verse: Numbers, Strings, and the Pirate Ship HUD

Every Fortnite island eventually needs to show a number on screen, fire a trigger after a calculated delay, or pass a score into a display — and Verse's type system won't let you mix `int` and `float` freely. Type conversion is the bridge. In this article you'll learn the exact conversion functions Verse provides, why they exist, and how to wire them into a real pirate-ship island where cannon-fire counts and respawn delays are computed from live game data.

Updated Examples verified on the live UEFN compiler

Overview

Verse is a strongly typed language: an int is never silently promoted to a float, and a number is never automatically turned into a string. That strictness prevents whole classes of runtime bugs, but it means you must explicitly convert values whenever you cross a type boundary.

The three conversions you'll reach for most often are:

From To How
float string ToString(val:float):string
int float val * 1.0 or cast via expression
float int Int[val] (failable — use inside if)

These come up constantly when you:

  • Display a score or timer on a player's HUD.
  • Feed a computed delay into SetResetDelay(Time:float) on a trigger_device.
  • Build a vector3 from integer grid coordinates.

The sunny pirate-ship lagoon below is the perfect playground: cannon shots are counted as int, converted to float to drive trigger delays, and converted to string to show on the HUD.

API Reference

player

Full public surface, resolved verbatim from the live Epic digest (Verse.digest.verse). Inherited members are merged from agent.

player<native><public> := class<unique><persistent><module_scoped_var_weak_map_key><epic_internal>(agent):

trigger_device

Used to relay events to other linked devices.

Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse). Inherited members are merged from trigger_base_device.

trigger_device<public> := class<concrete><final>(trigger_base_device):

Events (subscribe a handler to react):

Event Signature Description
TriggeredEvent TriggeredEvent<public>:listenable(?agent) Signaled when an agent triggers this device. Sends the agent that used this device. Returns false if no agent triggered the action (ex: it was triggered through code).

Methods (call these to make the device act):

Method Signature Description
Trigger Trigger<public>(Agent:agent):void Triggers this device with Agent being passed as the agent that triggered the action. Use an agent reference when this device is setup to require one (for instance, you want to trigger the device only with a particular agent.
Trigger Trigger<public>():void Triggers this device, causing it to activate its TriggeredEvent event.
Enable Enable<public>():void Enables this device.
Disable Disable<public>():void Disables this device.
SetMaxTriggerCount SetMaxTriggerCount<public>(MaxCount:int):void Sets the maximum amount of times this device can trigger. * 0 can be used to indicate no limit on trigger count. * MaxCount is clamped between [0,20].
GetMaxTriggerCount GetMaxTriggerCount<public>()<transacts>:int Gets the maximum amount of times this device can trigger. * 0 indicates no limit on trigger count.
GetTriggerCountRemaining GetTriggerCountRemaining<public>()<transacts>:int Returns the number of times that this device can still be triggered before hitting GetMaxTriggerCount. Returns 0 if GetMaxTriggerCount is unlimited.
SetResetDelay SetResetDelay<public>(Time:float):void Sets the time (in seconds) after triggering, before the device can be triggered again (if MaxTrigger count allows).
GetResetDelay GetResetDelay<public>()<transacts>:float Gets the time (in seconds) before the device can be triggered again (if MaxTrigger count allows).
SetTransmitDelay SetTransmitDelay<public>(Time:float):void Sets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered.
GetTransmitDelay GetTransmitDelay<public>()<transacts>:float Gets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered.

vector3

3-dimensional vector with float components.

Full public surface, resolved verbatim from the live Epic digest (Verse.digest.verse).

vector3<native><public> := struct<concrete><computes><persistable><uht_comparable>:

Walkthrough

Scenario — The Cannon-Fire Scoreboard

A player stands on the deck of a cel-shaded pirate ship anchored in a turquoise lagoon. Every time they step on a cannon plate (a trigger_device), a cannon fires. The number of shots taken is tracked as an int, converted to float to set an ever-growing reset delay (making each subsequent shot harder to spam), and converted to string to display on the player's HUD via a text_block widget.

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

# Localisation helper — Verse requires `message` not raw string
CannonMessage<localizes>(S : string) : message = "{S}"

cannon_score_device := class(creative_device):

    # ── Editable device references ──────────────────────────────
    # Place a Trigger Device on the cannon plate in your island.
    @editable
    CannonTrigger : trigger_device = trigger_device{}

    # A second trigger that fires a "reload complete" signal to
    # linked devices (e.g. a sound cue or particle burst).
    @editable
    ReloadTrigger : trigger_device = trigger_device{}

    # ── Runtime state ────────────────────────────────────────────
    var ShotCount : int = 0

    # ── Entry point ──────────────────────────────────────────────
    OnBegin<override>()<suspends> : void =
        # Start with a 1-shot limit so the player must wait between shots.
        CannonTrigger.SetMaxTriggerCount(1)
        CannonTrigger.SetResetDelay(2.0)   # 2-second base cooldown

        # Subscribe to the cannon plate
        CannonTrigger.TriggeredEvent.Subscribe(OnCannonFired)

    # ── Event handler ────────────────────────────────────────────
    OnCannonFired(MaybeAgent : ?agent) : void =
        # Increment shot counter (int)
        set ShotCount += 1

        # ── Conversion 1: int → float ────────────────────────────
        # Verse will NOT auto-promote int to float.
        # Multiply by 1.0 to produce a float literal.
        ShotCountFloat : float = ShotCount * 1.0

        # Use the float to make each successive shot take longer to reload.
        # Formula: 2 seconds + 0.5 seconds per previous shot.
        NewDelay : float = 2.0 + (ShotCountFloat * 0.5)
        CannonTrigger.SetResetDelay(NewDelay)

        # ── Conversion 2: float → string ─────────────────────────
        # ToString() is the ONLY built-in that converts float → string.
        DelayText : string = ToString(NewDelay)

        # ── Conversion 3: int → string (via float path) ──────────
        ShotText : string = ToString(ShotCountFloat)

        # Build a HUD message for the agent who fired the cannon.
        if (A := MaybeAgent?):
            if (P := player[A]):
                ShowHUD(P, ShotText, DelayText)

        # Fire the reload trigger after the delay so linked devices
        # (sound, particle) know the cannon is ready again.
        ReloadTrigger.SetTransmitDelay(NewDelay)
        ReloadTrigger.Trigger()

    # ── HUD helper ───────────────────────────────────────────────
    ShowHUD(P : player, Shots : string, Delay : string) : void =
        HUDText : string = "Shots: {Shots}  |  Next reload: {Delay}s"
        Msg : message = CannonMessage(HUDText)
        TB := text_block{DefaultText := Msg}
        if (UI := GetPlayerUI[P]):
            UI.AddWidget(TB)

Line-by-line explanation

Lines What's happening
CannonTrigger.SetMaxTriggerCount(1) Limits the trigger to 1 fire before it must reset — forces the delay to matter.
CannonTrigger.SetResetDelay(2.0) Sets the initial 2-second cooldown. SetResetDelay takes a float — if you pass an int the compiler rejects it.
CannonTrigger.TriggeredEvent.Subscribe(OnCannonFired) Wires the plate to our handler. The event sends ?agent (optional agent).
ShotCountFloat : float = ShotCount * 1.0 int → float: multiplying by the float literal 1.0 widens the result to float.
NewDelay : float = 2.0 + (ShotCountFloat * 0.5) Pure float arithmetic — no conversion needed once both sides are float.
ToString(NewDelay) float → string: the only built-in for this direction.
if (A := MaybeAgent?) Unwraps the ?agent option before use — required by the type system.
if (P := player[A]) Casts agent to player — this is a failable cast, so it lives inside if.
ReloadTrigger.SetTransmitDelay(NewDelay) Delays the signal sent to linked devices by the same float duration.
ReloadTrigger.Trigger() Fires the reload trigger immediately (the transmit delay handles the timing).

Common patterns

Pattern 1 — Reading back a float and converting to int for display

You stored a delay as float via GetResetDelay(). You want to show it as a whole-second countdown — so you need float → int.

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

delay_reader_device := class(creative_device):

    @editable
    WatchedTrigger : trigger_device = trigger_device{}

    OnBegin<override>()<suspends> : void =
        # GetResetDelay returns float
        CurrentDelayFloat : float = WatchedTrigger.GetResetDelay()

        # float → int: Int[x] is FAILABLE — use inside if
        if (WholeSeconds := Int[CurrentDelayFloat]):
            # WholeSeconds is now a plain int
            AsFloat2 : float = WholeSeconds * 1.0          # int → float again
            AsString : string = ToString(AsFloat2)         # float → string
            Log("Reload is {AsString} whole seconds")
        else:
            Log("Could not convert delay to int")

Key point: Int[x] is a failable conversion — it truncates toward zero but fails if the value is out of the valid integer range. Always wrap it in if.


Pattern 2 — Configuring trigger limits from a computed float score

A leaderboard score (float) drives how many times a treasure-chest trigger can fire. SetMaxTriggerCount takes int, so you must convert.

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

treasure_gate_device := class(creative_device):

    @editable
    ChestTrigger : trigger_device = trigger_device{}

    # Simulated score arriving as float (e.g. from a stat system)
    var PlayerScore : float = 7.5

    OnBegin<override>()<suspends> : void =
        # Divide score by 2.5 to get allowed chest opens
        AllowedOpensFloat : float = PlayerScore / 2.5

        # float → int (failable)
        if (AllowedOpensInt := Int[AllowedOpensFloat]):
            # Clamp to [0,20] — SetMaxTriggerCount clamps internally but
            # being explicit is good practice.
            Clamped : int = if (AllowedOpensInt > 20) then 20
                            else if (AllowedOpensInt < 0) then 0
                            else AllowedOpensInt
            ChestTrigger.SetMaxTriggerCount(Clamped)

            # Confirm: read it back and display
            Confirmed : int = ChestTrigger.GetMaxTriggerCount()
            ConfirmedFloat : float = Confirmed * 1.0
            Log("Chest opens allowed: {ToString(ConfirmedFloat)}")
        else:
            # Fallback: unlimited
            ChestTrigger.SetMaxTriggerCount(0)
            Log("Score out of range — chest opens unlimited")

Pattern 3 — Building a vector3 from int grid coordinates

A pirate-ship grid uses integer tile coordinates. You need a vector3 (all float fields) to position a prop or pass to an API.

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

grid_position_device := class(creative_device):

    @editable
    SignalTrigger : trigger_device = trigger_device{}

    # Integer tile coordinates from a game-design grid
    TileX : int = 3
    TileY : int = 7
    TileSize : float = 256.0   # centimetres per tile

    OnBegin<override>()<suspends> : void =
        SignalTrigger.TriggeredEvent.Subscribe(OnSignal)

    OnSignal(MaybeAgent : ?agent) : void =
        # int → float for each axis before building vector3
        WorldX : float = TileX * 1.0 * TileSize
        WorldY : float = TileY * 1.0 * TileSize
        WorldZ : float = 0.0

        TilePos : vector3 = vector3{X := WorldX, Y := WorldY, Z := WorldZ}

        # Convert components back to string for a debug log
        PosText : string = "({ToString(TilePos.X)}, {ToString(TilePos.Y)})"
        Log("Cannon tile world pos: {PosText}")

        # Trigger with no agent to signal linked devices
        SignalTrigger.Trigger()

Gotchas

1. int and float are NEVER interchangeable

Verse will not silently widen int to float. Passing ShotCount (an int) directly to SetResetDelay() is a compile error. Always write ShotCount * 1.0 or declare the value as a float literal from the start.

2. Int[x] is failable — it MUST live inside if

# ❌ Compile error — Int[] can fail, you can't use it bare
MyInt : int = Int[SomeFloat]

# ✅ Correct
if (MyInt := Int[SomeFloat]):
    # use MyInt here

3. ToString only accepts float, not int

There is no ToString(Val:int) overload. Convert to float first:

Count : int = 5
# ❌ ToString(Count)  — no int overload
# ✅
CountStr : string = ToString(Count * 1.0)

4. messagestring — you cannot pass a raw string to a widget

text_block{DefaultText := "hello"} is a compile error. Declare a localised helper:

MyMsg<localizes>(S : string) : message = "{S}"
# Then:
text_block{DefaultText := MyMsg("hello")}

5. Unwrap ?agent before casting to player

The TriggeredEvent sends ?agent (an optional). You need two steps:

OnFired(MaybeAgent : ?agent) : void =
    if (A := MaybeAgent?):       # unwrap option
        if (P := player[A]):     # failable cast agent → player
            # use P

Skipping either step is a compile error.

6. SetMaxTriggerCount clamps to [0, 20]

Values above 20 are silently clamped. 0 means unlimited, not zero shots. Plan your int arithmetic accordingly.

7. GetTriggerCountRemaining returns 0 when the limit is unlimited

Don't use GetTriggerCountRemaining() = 0 as a "no shots left" check — it also returns 0 when the trigger is set to unlimited (MaxCount = 0). Check GetMaxTriggerCount() first.

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 →