Reference Verse compiles

Comments & Readability in Verse: Write Code Your Future Self Will Thank You For

Verse gives you three distinct comment styles — single-line `#`, inline block `<# … #>`, and multi-line block `<# … #>` — that let you document your intent, explain complex logic, and leave breadcrumbs for collaborators without affecting how your game runs. Good comments are the difference between a script you can maintain six months later and one you have to rewrite from scratch. This article covers every comment form Verse supports, shows you how to apply them in real game scenarios, and teach

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

Overview

When you're building a Fortnite island in UEFN, your Verse scripts can grow fast — trigger subscriptions, stat checks, AI focus calls, conditional unlocks. Comments are the tool that keeps that complexity navigable. They are completely ignored at compile time and runtime; they exist purely for humans.

Verse supports three comment forms:

Style Syntax Best for
Single-line # comment Quick notes, TODO markers, disabling a line
Inline block <# comment #> Labelling a value mid-expression without breaking the line
Multi-line block <# … #> spanning multiple lines Algorithm explanations, section headers, function contracts

Beyond syntax, readability is about naming, structure, and intent. A well-named variable (VaultIsUnlocked instead of B) often makes a comment unnecessary. A short comment above a subscription call explains why, not just what.

Use this article as your reference whenever you add a new device, subscribe to an event, or write logic that will be read by anyone other than you — including future-you.

API Reference

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

Walkthrough

Scenario: a vault door that tracks how many players have stepped on pressure plates. When the count reaches a threshold, the vault opens and a weapon is granted. Every meaningful decision in the script is documented with the appropriate comment style.

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

<#
  vault_controller — Verse Island example

  Tracks players stepping on two pressure plates.
  When both plates are active simultaneously the vault
  trigger fires and a weapon granter rewards the team.

  Devices to place in UEFN:
    - Two trigger_device props acting as pressure plates
    - One trigger_device for the vault door FX
    - One item_granter_device for the weapon reward
#>
vault_controller := class(creative_device):

    # ── Editable device references ──────────────────────────────────
    # Plate A: left pressure plate trigger
    @editable PlateA : trigger_device = trigger_device{}

    # Plate B: right pressure plate trigger
    @editable PlateB : trigger_device = trigger_device{}

    # Fires when the vault door should open (drives a door animation)
    @editable VaultTrigger : trigger_device = trigger_device{}

    # Grants the vault weapon to the activating player
    @editable WeaponGranter : item_granter_device = item_granter_device{}

    # ── Internal state ───────────────────────────────────────────────
    # Tracks which plates are currently pressed.
    # Both must be true at the same time to open the vault.
    var PlateAActive : logic = false
    var PlateBActive : logic = false

    # ── Lifecycle ────────────────────────────────────────────────────
    OnBegin<override>()<suspends> : void =
        <# Subscribe to both plates so we can track their independent states.
           We use named methods (not lambdas) to keep OnBegin readable. #>
        PlateA.TriggeredEvent.Subscribe(OnPlateATriggered)
        PlateB.TriggeredEvent.Subscribe(OnPlateBTriggered)

    # ── Event handlers ───────────────────────────────────────────────

    # Called when a player steps on Plate A.
    # Marks the plate active then checks the vault condition.
    OnPlateATriggered(Agent : ?agent) : void =
        set PlateAActive = true  # latch: stays true until reset
        CheckVaultCondition(Agent)

    # Called when a player steps on Plate B.
    OnPlateBTriggered(Agent : ?agent) : void =
        set PlateBActive = true
        CheckVaultCondition(Agent)

    # Evaluates whether both plates are active.
    # If so, fires the vault and grants the weapon to the triggering agent.
    CheckVaultCondition(MaybeAgent : ?agent) : void =
        if (PlateAActive? and PlateBActive?):
            <# Both plates are live — open the vault door.
               VaultTrigger drives a Sequencer cinematic via its
               TriggeredEvent, so we just call Trigger() here. #>
            VaultTrigger.Trigger()

            # Unwrap the optional agent before granting the weapon.
            # The granter requires a concrete agent, not an option type.
            if (A := MaybeAgent?):
                WeaponGranter.GrantItem(A)

            <# Reset both latches so the vault cannot be
               triggered a second time in the same round. #>
            set PlateAActive = false
            set PlateBActive = false

Line-by-line explanation

Multi-line block comment at the top (<# … #>) — acts as a file header. It names the device, describes the gameplay intent, and lists the UEFN devices a designer must place. This is the most important comment in any file: it answers what does this do and how do I set it up?

Section-divider single-line comments (# ── Editable device references ──) — visually group fields. Verse has no region keyword, so comment dividers are the standard way to create scannable sections.

Per-field single-line comments — each @editable field gets one line explaining its role. Short and specific beats long and vague.

Inline block comment (<# original amount #>) — used inside CheckVaultCondition to annotate the vault trigger call without breaking the expression onto a new line.

Intent comment on the latch (# latch: stays true until reset) — explains why the value is set to true and not toggled, preventing a future editor from "fixing" it.

Unwrap comment — the comment before if (A := MaybeAgent?) explains why we must unwrap: the granter API requires a concrete agent. This is the kind of comment that prevents a subtle bug.

Common patterns

Pattern 1 — Inline block comments to annotate a multi-part expression

Use <# … #> when you want to label the parts of a compound calculation without splitting it across lines. Here a stat tracker drives a score multiplier.

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

<# score_display — shows how inline block comments document a formula #>
score_display := class(creative_device):

    @editable StatTracker : tracker_device = tracker_device{}

    OnBegin<override>()<suspends> : void =
        # Read the raw stat value from the tracker device
        RawScore : int = StatTracker.GetStatValue()

        <# Multiply by 10 to convert "eliminations" into display points.
           The tracker returns an int so no float conversion is needed. #>
        DisplayPoints : int =
            RawScore          <# raw elimination count #>
            * 10              <# points per elimination #>

        # TODO: pipe DisplayPoints into a HUD element when UI API is available
        _ = DisplayPoints  # suppress unused-variable warning during prototyping

Pattern 2 — TODO and FIXME markers with single-line comments

Single-line # comments are the standard place for TODO, FIXME, and NOTE markers. They show up in text-editor searches and make tech-debt visible.

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

<#
  respawn_gate — demonstrates TODO / FIXME comment conventions.
  Place a trigger_device as the respawn zone boundary.
#>
respawn_gate := class(creative_device):

    # TODO: replace with a player_spawner_device once the API is wired up
    @editable BoundaryTrigger : trigger_device = trigger_device{}

    # FIXME: cooldown is hardcoded — expose as @editable float when time allows
    RespawnCooldownSeconds : float = 3.0  # seconds before player can respawn again

    OnBegin<override>()<suspends> : void =
        # NOTE: TriggeredEvent fires for ANY agent, including NPCs — filter if needed
        BoundaryTrigger.TriggeredEvent.Subscribe(OnBoundaryEntered)

    OnBoundaryEntered(Agent : ?agent) : void =
        # Unwrap the optional agent; bail silently if no agent is present
        if (A := Agent?):
            # NOTE: actual respawn logic goes here once PlayerSpawner API is available
            _ = A  # placeholder — prevents unused-variable compile error

Pattern 3 — Block comment as a function contract

For complex helper methods, a multi-line block comment above the method acts as a contract: inputs, outputs, side-effects, and assumptions.

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

<# elimination_bonus — awards bonus items on multi-kill streaks #>
elimination_bonus := class(creative_device):

    @editable Granter : item_granter_device = item_granter_device{}
    @editable EliminationTracker : tracker_device = tracker_device{}

    OnBegin<override>()<suspends> : void =
        # Poll the tracker once at start to establish baseline
        _ = CheckStreakBonus()

    <#
      CheckStreakBonus
      ────────────────
      Reads the current elimination count from EliminationTracker.
      Returns true if the count qualifies for a streak bonus (>= 3),
      false otherwise.

      Side-effects: none — does NOT call Granter directly.
      Caller is responsible for acting on the return value.

      Assumptions:
        - EliminationTracker is configured to track eliminations.
        - GetStatValue() returns 0 if no eliminations have occurred yet.
    #>
    CheckStreakBonus() : logic =
        Count : int = EliminationTracker.GetStatValue()
        # A streak requires at least 3 eliminations
        if (Count >= 3):
            return true
        return false

Gotchas

1. # comments are NOT block comments — they end at the newline

A common mistake from C/C++ backgrounds: # only comments out the rest of that line. If you write:

# This is line one
  This is NOT a comment — it will cause a compile error

Use <# … #> any time you need a comment to span more than one line.

2. Inline block comments inside expressions must be balanced

Every <# must have a matching #>. An unclosed inline comment will consume everything after it to the end of the file, producing cryptic parse errors far from the actual mistake. Always close your block comments before saving.

3. Comments do not substitute for correct types

Verse is strongly typed. Writing # this is a float above an int variable does not change the type. If GetStatValue() returns int and you need a float, you must write Float := Float(StatTracker.GetStatValue()) — a comment cannot bridge the gap.

4. message parameters require <localizes>, not a comment workaround

Some device APIs accept a message type, not a plain string. You cannot pass a raw string literal or use a comment to "cast" it. Declare a localizer function:

MyText<localizes>(S : string) : message = "{S}"

Then pass MyText("Vault Opened!"). No amount of commenting will make a string satisfy a message parameter.

5. Commented-out code accumulates debt

It is tempting to comment out old logic with # instead of deleting it. In a version-controlled project (UEFN uses source control), deleted code is recoverable from history. Commented-out blocks confuse readers about what is actually active. Prefer deletion over commenting out, and use # TODO: markers for planned code.

6. Over-commenting is noise too

Avoid restating what the code already says clearly:

# BAD: set PlateAActive to true
set PlateAActive = true

# GOOD: latch stays true until both plates reset at round end
set PlateAActive = true

The best comment explains why, not what.

Build your own lesson with comments_and_readability

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 →