Reference Verse compiles

spawn: Fire-and-Forget Async Tasks on Your Island

Sometimes you need a task to keep running long after the code that started it has moved on — a countdown ticking on the clifftop, a cinematic playing while the game loop continues, a player being teleported the moment they step ashore. Verse's `spawn` expression is your escape hatch: it launches any `<suspends>` function as a free-running task, decoupled from its parent scope. Pair it with `player_spawner_device` and `teleporter_device` and you can build rich, layered island moments without bloc

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

Overview

The spawn expression starts an independent async task. Unlike sync, race, or branch — which are structured concurrency expressions that must live inside an async (<suspends>) context and tie task lifetimes to a parent scope — spawn can be called anywhere: inside OnBegin, inside an event handler, even inside a non-suspending helper function. The spawned task becomes a free agent; it keeps running even after the scope that created it exits.

When to reach for spawn:

  • You want a looping background task (e.g. a tide timer that runs forever) without blocking your OnBegin.
  • You need to kick off per-player async logic inside a synchronous event handler (event handlers cannot themselves <suspends>).
  • You want to fire a cinematic or teleport sequence for one player while the rest of the game loop continues uninterrupted.

The rule: the function you pass to spawn must carry the <suspends> effect. You cannot spawn a <decides> function.

This article also covers two devices that shine in spawn-driven designs:

  • player_spawner_device — places a player at a specific island location (SpawnPlayer, SpawnedEvent).
  • teleporter_device — instantly moves a player to another location (Activate, Teleport, EnterEvent, TeleportedEvent).

API Reference

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

Walkthrough

Scenario: The Sunny Dock Arrival

Your cel-shaded island has a wooden dock jutting into a sparkling cove. When the round starts:

  1. Every player is spawned at the dock via a player_spawner_device.
  2. A cinematic plays the boat-arrival sequence.
  3. A background task starts a 60-second tide timer using a timer_device.
  4. When a player steps into the teleporter at the end of the dock, they are sent to the clifftop — and a per-player async task logs the teleport without blocking the main loop.

Each of those things happens concurrently thanks to spawn.

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

# dock_arrival_manager — place this creative_device on your island,
# then wire up the @editable fields in the Details panel.
dock_arrival_manager := class(creative_device):

    # The spawn pad sitting on the dock boards
    @editable DockSpawner : player_spawner_device = player_spawner_device{}

    # The teleporter at the far end of the dock (destination: clifftop)
    @editable DockTeleporter : teleporter_device = teleporter_device{}

    # Cinematic sequence: boat gliding into the cove
    @editable ArrivalCinematic : cinematic_sequence_device = cinematic_sequence_device{}

    # A timer device configured for 60 s countdown in the Details panel
    @editable TideTimer : timer_device = timer_device{}

    # ----------------------------------------------------------------
    # Entry point
    # ----------------------------------------------------------------
    OnBegin<override>()<suspends>:void =
        # 1. Subscribe to the dock teleporter so we react when anyone steps in
        DockTeleporter.EnterEvent.Subscribe(OnPlayerEntersTeleporter)

        # 2. Subscribe to the spawner so we know when each player lands on the dock
        DockSpawner.SpawnedEvent.Subscribe(OnPlayerSpawned)

        # 3. Play the boat-arrival cinematic for everyone (non-blocking — we spawn it)
        spawn { PlayArrivalCinematic() }

        # 4. Start the tide countdown in the background without blocking OnBegin
        spawn { RunTideCountdown() }

        # OnBegin can now return; both tasks keep running independently.

    # ----------------------------------------------------------------
    # Spawned task: plays the cinematic then stops cleanly
    # ----------------------------------------------------------------
    PlayArrivalCinematic()<suspends>:void =
        ArrivalCinematic.Play()
        # Wait until the sequence device signals it has stopped
        ArrivalCinematic.StoppedEvent.Await()
        # Cinematic finished — nothing else to do, task ends naturally

    # ----------------------------------------------------------------
    # Spawned task: starts the tide timer and waits for success/failure
    # ----------------------------------------------------------------
    RunTideCountdown()<suspends>:void =
        TideTimer.Start()     # kick off the 60-second countdown
        # Await whichever fires first
        race:
            block:
                TideTimer.SuccessEvent.Await()
                # Tide came in — could trigger a flood hazard here
            block:
                TideTimer.FailureEvent.Await()
                # Time ran out without success

    # ----------------------------------------------------------------
    # Synchronous event handler — called when a player spawns on the dock
    # (event handlers cannot suspend, so we spawn async work from here)
    # ----------------------------------------------------------------
    OnPlayerSpawned(Agent : agent):void =
        if (P := player[Agent]):
            # Spawn a per-player async task without blocking the handler
            spawn { WelcomePlayer(P) }

    # ----------------------------------------------------------------
    # Per-player async welcome — runs independently for each arrival
    # ----------------------------------------------------------------
    WelcomePlayer(P : player)<suspends>:void =
        # Re-spawn the player firmly at the dock position
        DockSpawner.SpawnPlayer(P)
        # Small pause so the cinematic has a beat before gameplay starts
        Sleep(2.0)
        # Enable the teleporter for this session
        DockTeleporter.Enable()

    # ----------------------------------------------------------------
    # Synchronous event handler — called when a player enters the teleporter
    # ----------------------------------------------------------------
    OnPlayerEntersTeleporter(Agent : agent):void =
        # Spawn a per-player async journey task
        spawn { SendPlayerToCliftop(Agent) }

    # ----------------------------------------------------------------
    # Async journey: teleport the player and wait for confirmation
    # ----------------------------------------------------------------
    SendPlayerToCliftop(Agent : agent)<suspends>:void =
        # Activate sends the agent through the teleporter to its linked destination
        DockTeleporter.Activate(Agent)
        # Await the TeleportedEvent to confirm the journey completed
        TeleportedAgent := DockTeleporter.TeleportedEvent.Await()
        # TeleportedEvent sends the agent that emerged — task ends here

Line-by-line highlights

Line What it teaches
spawn { PlayArrivalCinematic() } Launches the cinematic task without blocking OnBegin. OnBegin continues to the next line immediately.
spawn { RunTideCountdown() } Starts a forever-running background task. Because it is spawned, it outlives OnBegin's own execution.
OnPlayerSpawned(Agent : agent):void = Event handlers are synchronous (no <suspends>). You cannot Sleep or Await here directly — but you can call spawn.
spawn { WelcomePlayer(P) } Creates an independent task per player. Each player gets their own concurrent welcome sequence.
DockTeleporter.Activate(Agent) Sends the agent through the teleporter using the real teleporter_device.Activate API.
DockTeleporter.TeleportedEvent.Await() Suspends the task until the teleport completes, then the task exits cleanly.

Common patterns

Pattern 1 — Respawn a player at a named spawn pad

Use player_spawner_device.SpawnPlayer to programmatically place a player at a specific dock spawn pad (e.g. after they fall into the cove).

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

dock_respawn_manager := class(creative_device):

    @editable CoveSpawner : player_spawner_device = player_spawner_device{}
    @editable DamageZone  : damage_volume_device  = damage_volume_device{}

    OnBegin<override>()<suspends>:void =
        # When a player enters the cove's damage volume, respawn them at the dock
        DamageZone.AgentEntersEvent.Subscribe(OnAgentEntersCove)

    OnAgentEntersCove(Agent : agent):void =
        if (P := player[Agent]):
            # SpawnPlayer places the player at the CoveSpawner's location
            CoveSpawner.SpawnPlayer(P)

Pattern 2 — Enable/disable a teleporter on a timer

Use teleporter_device.Enable and teleporter_device.Disable to open the clifftop teleporter only during a time window, using Sleep inside a spawned task.

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

clifftop_gate_manager := class(creative_device):

    @editable CliftopTeleporter : teleporter_device = teleporter_device{}

    OnBegin<override>()<suspends>:void =
        # Start the gating loop as a fire-and-forget background task
        spawn { GateTeleporterLoop() }

    # Cycles the clifftop teleporter: open 20 s, closed 10 s, forever
    GateTeleporterLoop()<suspends>:void =
        loop:
            CliftopTeleporter.Enable()
            Sleep(20.0)          # open window
            CliftopTeleporter.Disable()
            Sleep(10.0)          # closed window

Pattern 3 — React to SpawnedEvent and start a per-player async task

Subscribe to player_spawner_device.SpawnedEvent and use spawn to kick off a per-player async sequence from the synchronous handler.

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

shore_greeter := class(creative_device):

    @editable ShoreSpawner   : player_spawner_device    = player_spawner_device{}
    @editable ShoreTeleporter: teleporter_device        = teleporter_device{}
    @editable ArrivalSeq     : cinematic_sequence_device = cinematic_sequence_device{}

    OnBegin<override>()<suspends>:void =
        ShoreSpawner.SpawnedEvent.Subscribe(OnAgentSpawnedAtShore)

    # Synchronous handler — must NOT suspend
    OnAgentSpawnedAtShore(Agent : agent):void =
        # Spawn an independent async greeting task for this specific agent
        spawn { GreetAgent(Agent) }

    # Async per-agent greeting: play cinematic, then open the teleporter
    GreetAgent(Agent : agent)<suspends>:void =
        ArrivalSeq.Play(Agent)              # play cinematic for this agent
        ArrivalSeq.StoppedEvent.Await()     # wait until it finishes
        ShoreTeleporter.Enable()            # now let them through
        Sleep(0.5)
        ShoreTeleporter.Activate(Agent)     # send them on their way

Gotchas

1. spawn is fire-and-forget — there is no automatic cleanup

The spawned task keeps running even if the device that created it is garbage-collected or the round ends unexpectedly. If your task loops forever (loop:), make sure it has a natural exit condition or it will run until the session ends. Use race with a sentinel event if you need cancellation.

2. Event handlers are synchronous — you cannot Await or Sleep in them directly

If you try to call Sleep(5.0) inside OnPlayerSpawned(Agent:agent):void, the compiler will reject it because the function lacks <suspends>. The fix is always: spawn { MyAsyncWork(Agent) } from inside the handler.

3. The function passed to spawn must have <suspends>

# WRONG — DoThing has no <suspends> effect
DoThing():void = Print("hello")
spawn { DoThing() }   # compile error

# RIGHT
DoThingAsync()<suspends>:void = Sleep(1.0)
spawn { DoThingAsync() }   # fine

4. You cannot spawn a <decides> function

Failable (<decides>) functions cannot be spawned. If you need to run a failable check asynchronously, wrap it in a <suspends> function that uses if to call the failable expression.

5. player_spawner_device.SpawnPlayer respects the device's ShouldRespawnAlivePlayers setting

If ShouldRespawnAlivePlayers is false in the Details panel and the player is still alive, calling SpawnPlayer will fall back to another valid spawn or skydive. Always configure the device in the editor to match your intent.

6. teleporter_device.Activate(Agent) vs Teleport(Agent)

Activate sends the agent to the teleporter's linked destination group (respects the device's channel/group settings). Teleport sends the agent directly to this device's location. Use Activate for normal portal gameplay; use Teleport when you want to pull a player to a specific teleporter pad regardless of linking.

7. listenable(agent) events send a plain agent, not ?agent

SpawnedEvent and EnterEvent hand your handler a concrete agent. You only need if (A := Agent?) unwrapping for events typed listenable(?agent) (like timer_device.SuccessEvent). Mixing these up causes a type error.

Guides & scripts that use spawn

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

Build your own lesson with spawn

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 →