Reference Verse compiles

Type Annotations in Verse: Clarity at the Cove

Verse is a strongly-typed language: every value has a type, and the compiler checks those types before your island ever loads. Type annotations let you declare exactly what kind of data a variable, parameter, or return value holds — catching bugs at compile time instead of during a live game. In this article you'll learn how to use Verse's core types (`player`, `vector3`, `color`, and friends) to build a cel-shaded pirate cove where a lookout tower tracks approaching ships, colors the sky at daw

Updated Examples verified on the live UEFN compiler

Overview

A type annotation is the : TypeName you write after an identifier to tell Verse (and every future reader) exactly what kind of value lives there. Because Verse is strongly typed, the compiler rejects any mismatch — you cannot accidentally pass a float where an int is expected, or hand a raw string to a function that wants a message.

Three of the most important built-in types you'll annotate constantly are:

Type What it is Typical use
player A unique, persistent handle to one human in the session Tracking who stepped on a plate, awarding XP, etc.
vector3 A 3-D point or direction with float X, Y, Z components Spawn positions, velocities, distances
color An RGB triple in the ACES 2065-1 linear color space Tinting lights, UI, VFX

Reach for explicit type annotations when:

  • A variable's type would be ambiguous from context alone.
  • You want self-documenting code that reads like a design doc.
  • You're defining function parameters and return types (always annotate these).
  • You're storing values in arrays, maps, or class fields.

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):

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>:

color

Represents colors as RGB triples in the ACES 2065-1 color space. Component values are linear (i.e. *gamma* = 1.0).

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

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

Walkthrough

Scene: The Pirate Cove Lookout

A cel-shaded pirate cove has a lookout tower. When the round begins the device:

  1. Records the spawn position of the tower as a vector3.
  2. Picks a dawn color (color) to describe the sky.
  3. Stores the first player who joins as a player reference.
  4. Computes the horizontal distance from the tower to that player's character.

Every variable is explicitly annotated so the article can point at each annotation and explain it.

using { /Fortnite.com/Characters }
using { /Fortnite.com/Devices }
using { /Fortnite.com/Game }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/Colors }
using { /Fortnite.com/Playspaces }
using { /Verse.org/Verse }

# ─────────────────────────────────────────────
# cove_lookout_device
# Demonstrates type annotations with player,
# vector3, and color in a pirate-cove scenario.
# ─────────────────────────────────────────────
cove_lookout_device := class(creative_device):

    # @editable fields — placed in the UEFN details panel.
    # The type annotation `: player_spawner_device` tells Verse
    # (and the editor) exactly what kind of device to expect.
    @editable
    TowerSpawner : player_spawner_device = player_spawner_device{}

    # ── Localised message helper ──────────────────────────────
    # `message` is a distinct type from `string`; we need this
    # wrapper to pass text to functions that accept `message`.
    WelcomeText<localizes>(Name : string) : message = "Ahoy, {Name}! The cove is yours."

    # ── Lifecycle ─────────────────────────────────────────────
    OnBegin<override>()<suspends> : void =

        # ── vector3 annotation ───────────────────────────────
        # vector3 holds three float components: X, Y, Z.
        # We annotate TowerPos explicitly so readers know the
        # coordinate type at a glance.
        TowerPos : vector3 = vector3{X := 0.0, Y := 0.0, Z := 800.0}

        # ── color annotation ─────────────────────────────────
        # color stores linear RGB floats (ACES 2065-1 space).
        # DawnSky is a warm orange — R>G>B, all in [0..1+].
        DawnSky : color = color{R := 1.0, G := 0.45, B := 0.1}

        # Print the dawn color components to the log so we can
        # verify the values during playtesting.
        # Removed the ambiguous `Log` variable to avoid compiler error 3588
        # and the `Channel` initialization requirement.
        Print("Dawn R={DawnSky.R} G={DawnSky.G} B={DawnSky.B}")

        # ── Waiting for a player ──────────────────────────────
        # GetPlayspace() returns the island's playspace; we ask
        # it for the current player list.
        Playspace : fort_playspace = GetPlayspace()
        Players : []player = Playspace.GetPlayers()

        if (Players.Length > 0):
            # ── player annotation ────────────────────────────
            # `player` is a class (extends `agent`) that
            # represents exactly one human in the session.
            # Array indexing is fallible, so bind it inside an `if`.
            if (FirstSailor : player := Players[0]):
                # Resolve the player to a fort_character so we can
                # read their world position.
                if (Character : fort_character := FirstSailor.GetFortCharacter[]):
                    # GetTransform() returns a transform; .Translation
                    # is a vector3 — we annotate it explicitly.
                    SailorPos : vector3 = Character.GetTransform().Translation

                    # ── Arithmetic on vector3 components ─────────
                    # vector3 components are floats; we compute the
                    # 2-D (XY) distance from the tower to the sailor.
                    DX : float = SailorPos.X - TowerPos.X
                    DY : float = SailorPos.Y - TowerPos.Y
                    # Verse has no auto int↔float conversion, so we
                    # keep everything as float throughout.
                    DistXY : float = Sqrt(DX * DX + DY * DY)

                    Print("Sailor is {DistXY} cm from the tower.")

                    # Greet the sailor with a localised message.
                    # GetFortCharacter() gives us a display name via
                    # the agent interface.
                    AgentName : string = "Sailor"
                    # player has no SendMessage method; use Print which accepts `message`.
                    Print(WelcomeText(AgentName))```

### Line-by-line highlights

| Line | Annotation | Why it matters |
|---|---|---|
| `TowerPos : vector3` | `vector3` | Locks the type; you can't accidentally assign a scalar. |
| `DawnSky : color` | `color` | Makes it clear this is a linear-RGB struct, not a hex string. |
| `FirstSailor : player` | `player` | Distinguishes a human player from a generic `agent` or NPC. |
| `SailorPos : vector3` | `vector3` | Documents that `GetTransform().Translation` is positional. |
| `DX : float` | `float` | Verse won't silently coerce; explicit float keeps math correct. |
| `AgentName : string` | `string` | Needed before passing to the `<localizes>` wrapper. |

## Common patterns

### Pattern 1 — Annotating a function that returns `vector3`

A helper that computes the midpoint between two cove landmarks. Explicit parameter and return-type annotations make the contract crystal-clear.

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

cove_midpoint_device := class(creative_device):

    # Returns the midpoint vector3 between two positions.
    # Both params and the return type are annotated.
    Midpoint(A : vector3, B : vector3) : vector3 =
        vector3{
            X := (A.X + B.X) / 2.0
            Y := (A.Y + B.Y) / 2.0
            Z := (A.Z + B.Z) / 2.0
        }

    OnBegin<override>()<suspends> : void =
        # Dock and cliff positions on the cove.
        DockPos   : vector3 = vector3{X := 500.0,  Y := 0.0,   Z := 0.0}
        CliffPos  : vector3 = vector3{X := -300.0, Y := 1200.0, Z := 400.0}

        # The return type is vector3 — inferred here, but
        # annotating it makes refactoring safer.
        MeetPoint : vector3 = Midpoint(DockPos, CliffPos)

        Log := log{}
        Log.Print("Meet at X={MeetPoint.X} Y={MeetPoint.Y} Z={MeetPoint.Z}")

Pattern 2 — Annotating a color field and using it as a tint

A device that stores named color constants as class fields. Annotating them as color prevents accidental reassignment to an incompatible type.

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

cove_palette_device := class(creative_device):

    # Named color constants — annotated as `color` so the
    # compiler rejects any non-color assignment.
    OceanBlue  : color = color{R := 0.05, G := 0.35, B := 0.8}
    SandGold   : color = color{R := 0.9,  G := 0.75, B := 0.3}
    CoralRed   : color = color{R := 1.0,  G := 0.2,  B := 0.15}

    # Picks a color by time-of-day index (0=dawn,1=noon,2=dusk).
    GetSkyColor(TimeIndex : int) : color =
        if (TimeIndex = 0):
            color{R := 1.0, G := 0.45, B := 0.1}   # dawn orange
        else if (TimeIndex = 1):
            color{R := 0.6, G := 0.8,  B := 1.0}   # noon sky
        else:
            color{R := 0.5, G := 0.1,  B := 0.4}   # dusk purple

    OnBegin<override>()<suspends> : void =
        NoonSky : color = GetSkyColor(1)
        Log := log{}
        Log.Print("Noon sky R={NoonSky.R} G={NoonSky.G} B={NoonSky.B}")
        Log.Print("Ocean R={OceanBlue.R} Sand R={SandGold.R} Coral R={CoralRed.R}")

Pattern 3 — Annotating an array of player and iterating it

A device that counts how many players are standing on the cove dock and logs each one. Annotating []player makes the intent unambiguous.

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

cove_roster_device := class(creative_device):

    OnBegin<override>()<suspends> : void =
        Playspace : fort_playspace = GetPlayspace()

        # Explicit []player annotation — an ordered list of
        # player references, one per human in the session.
        Sailors : []player = Playspace.GetPlayers()

        Log := log{}
        Log.Print("Sailors at the cove: {Sailors.Length}")

        # Iterate with a for-expression; Index and Sailor are
        # inferred, but we could annotate them too.
        for (Index -> Sailor : Sailors):
            # Annotating CharOpt as ?fort_character documents
            # that GetFortCharacter[] is failable.
            if (Character : fort_character = Sailor.GetFortCharacter[]):
                Pos : vector3 = Character.GetTransform().Translation
                Log.Print("Sailor {Index} is at Z={Pos.Z}")

Gotchas

1. int and float are never auto-converted

Verse will not silently promote an int to a float or vice-versa. If you write DX : float = SomeInt the compiler errors. Use Float(SomeInt) to convert, or keep your literals consistent (0.0 not 0).

# ✗ Compile error — int literal where float expected
# BadZ : float = 800

# ✓ Correct
GoodZ : float = 800.0

2. stringmessage — always wrap for localised text

Functions that display text to players (like SendMessage) require a message, not a bare string. Declare a <localizes> wrapper:

Greeting<localizes>(Name : string) : message = "Welcome, {Name}!"

There is no StringToMessage function — the wrapper is the only way.

3. player is a subtype of agent — don't confuse them

player extends agent, so a player can go anywhere an agent is expected, but not the reverse. If a function returns agent you must cast down with if (P : player = SomeAgent) before using player-specific APIs.

4. color components are linear floats, not 0–255 bytes

color{R := 1.0, G := 1.0, B := 1.0} is white. Values above 1.0 are valid (HDR). Never pass integer byte values — color{R := 255.0, ...} will produce a blindingly over-exposed result.

5. vector3 components are float — arithmetic must stay float

All three fields (X, Y, Z) are float. Mixed-type expressions like Pos.X + 5 (where 5 is an int literal) will fail. Write Pos.X + 5.0.

6. Annotating @editable fields requires a default value

Every @editable class field must have a default initialiser even when the creator will override it in the details panel. Omitting the default causes a compile error:

# ✓ Correct — default provided
@editable
MySpawner : player_spawner_device = player_spawner_device{}

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 →