Reference Verse compiles

spawn_wave_pattern: Pirate Cove Creature Waves

The spawn wave pattern combines `creature_spawner_device` and `trigger_device` to send enemies at players in timed, escalating waves — perfect for a sun-drenched pirate cove where skeleton crews defend buried treasure. Instead of one flat mob rush, you get distinct waves that players can feel, react to, and survive. This article shows you exactly how to wire those two devices together in Verse so your cove comes alive with cel-shaded chaos.

Updated Examples verified on the live UEFN compiler

Overview

The Spawn Wave Pattern describes the technique of using a timer_device as a metronome that drives repeated cycles of spawning, scoring, and cinematics. Each time the timer fires its SuccessEvent, your Verse device advances the wave counter, awards points to surviving players, plays a cinematic sting, and re-arms the timer for the next wave.

Use this pattern when you need:

  • Escalating difficulty (more spawns, less time between waves)
  • Per-wave score rewards tied to survival
  • Cinematic punctuation (a horn blast, a wave crash) between rounds
  • A clean way to end the game after a fixed number of waves

The four devices you'll place on your cove island:

Device Role
timer_device Counts down each wave interval
player_spawner_device Respawns eliminated players at the dock
score_manager_device Awards points for surviving each wave
cinematic_sequence_device Plays a cel-shaded wave-crash sting

API Reference

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

Walkthrough

Scenario: A 2D cel-shaded cove island. Players defend the dock. Every 30 seconds a new wave begins. Survivors earn 100 points. After 5 waves the cinematic plays one final time and the game ends. Eliminated players respawn at the shore spawner.

Place these devices in UEFN and wire them to the Verse device via the @editable fields:

  • One timer_device (set to 30 s countdown, Does Not Auto-Start)
  • One player_spawner_device at the dock
  • One score_manager_device (set to award 100 points per activation)
  • One cinematic_sequence_device (your wave-crash sequence)
using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/Patchwork }
using { /Verse.org/Simulation }
using { /Verse.org/Native }

# Localized message helper
WaveMessage<localizes>(S : string) : message = "{S}"

cove_wave_manager := class(creative_device):

    # Wire these in the UEFN Details panel
    @editable WaveTimer      : timer_device              = timer_device{}
    @editable DockSpawner    : player_spawner_device     = player_spawner_device{}
    @editable ScoreManager   : score_manager_device      = score_manager_device{}
    @editable WaveCinematic  : cinematic_sequence_device = cinematic_sequence_device{}

    # Mutable wave state
    var CurrentWave  : int = 0
    MaxWaves         : int = 5

    OnBegin<override>()<suspends> : void =
        # Subscribe to the timer's SuccessEvent — fires when the countdown hits zero
        WaveTimer.SuccessEvent.Subscribe(OnWaveTimerSuccess)

        # Subscribe to the cinematic's StoppedEvent so we know when the sting ends
        WaveCinematic.StoppedEvent.Subscribe(OnCinematicStopped)

        # Play the opening cinematic (the cove sunrise reveal) then start wave 1
        WaveCinematic.Play()
        # OnCinematicStopped will kick off the first timer

    # Called each time the 30-second wave countdown completes
    OnWaveTimerSuccess(MaybeAgent : ?agent) : void =
        set CurrentWave = CurrentWave + 1

        if (CurrentWave >= MaxWaves):
            # Final wave survived — play the victory cinematic and end
            WaveCinematic.Play()
            # OnCinematicStopped handles the game-over path
        else:
            # Award points to the agent who triggered the timer (if any)
            if (A := MaybeAgent?):
                ScoreManager.Activate(A)
            else:
                # No specific agent — award global points
                ScoreManager.Activate()

            # Play the between-wave cinematic sting
            WaveCinematic.Play()
            # OnCinematicStopped will restart the timer for the next wave

    # Called when any cinematic sequence finishes
    OnCinematicStopped(Ignored : tuple()) : void =
        if (CurrentWave >= MaxWaves):
            # Game over — disable the timer so nothing else fires
            WaveTimer.Disable()
        else:
            # Restart the 30-second countdown for the next wave
            WaveTimer.Reset()
            WaveTimer.Start()

Line-by-line explanation

Lines What's happening
@editable fields Expose the four devices to the UEFN Details panel so you can drag-and-drop the placed instances. Without @editable the Verse device can't see them.
WaveTimer.SuccessEvent.Subscribe(OnWaveTimerSuccess) Hooks into the timer's completion event. The handler receives ?agent (the player who started the timer, or false if nobody did).
WaveCinematic.StoppedEvent.Subscribe(OnCinematicStopped) StoppedEvent sends tuple() — no agent payload. We use it as a gate: the next wave only starts after the sting finishes.
if (A := MaybeAgent?) Safe unwrap of the ?agent option. If a real player is present, award them points directly; otherwise fall back to the no-agent overload.
ScoreManager.Activate(A) / ScoreManager.Activate() The two overloads of Activate — per-agent and global. The per-agent version credits the specific survivor.
WaveTimer.Reset() then WaveTimer.Start() Reset snaps the countdown back to 30 s; Start begins it again. Calling Start without Reset would resume from wherever it paused.
WaveTimer.Disable() Prevents any further SuccessEvent firings after the final wave.

Common patterns

Pattern 1 — Respawn a player at the cove dock on elimination

Wire a second player_spawner_device (the respawn pad at the water's edge) and listen for players who need to re-enter the fight.

using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/Patchwork }
using { /Verse.org/Simulation }
using { /Verse.org/Native }

cove_respawn_handler := class(creative_device):

    @editable ShoreSpawner : player_spawner_device = player_spawner_device{}

    OnBegin<override>()<suspends> : void =
        # Listen for any agent that spawns from this pad
        ShoreSpawner.SpawnedEvent.Subscribe(OnPlayerSpawned)

    OnPlayerSpawned(Agent : agent) : void =
        # Agent is a concrete agent — no unwrap needed here
        # Re-enable the spawner in case it was disabled between waves
        ShoreSpawner.Enable()
        # Spawn the specific player at the dock location
        if (P := player[Agent]):
            ShoreSpawner.SpawnPlayer(P)

Pattern 2 — Increment score each time a wave starts (not just on timer success)

Use score_manager_device.Increment() to ramp up the point value as waves escalate, then Activate() to cash it out.

using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/Patchwork }
using { /Verse.org/Simulation }
using { /Verse.org/Native }

cove_escalating_score := class(creative_device):

    @editable ScoreManager : score_manager_device = score_manager_device{}
    @editable WaveTimer    : timer_device         = timer_device{}

    var WaveCount : int = 0

    OnBegin<override>()<suspends> : void =
        WaveTimer.SuccessEvent.Subscribe(OnWaveDone)
        WaveTimer.Start()

    OnWaveDone(MaybeAgent : ?agent) : void =
        set WaveCount = WaveCount + 1
        # Add one bonus point to the pool for every wave completed
        ScoreManager.Increment()
        # Pay out the accumulated score to the surviving agent
        if (A := MaybeAgent?):
            ScoreManager.Activate(A)
        else:
            ScoreManager.Activate()
        # Reset and restart for the next wave
        WaveTimer.Reset()
        WaveTimer.Start()

Pattern 3 — Play a cinematic in reverse for a "wave retreats" moment

When the final wave is cleared, play the wave-crash cinematic backwards so the water visually pulls back from the dock.

using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/Patchwork }
using { /Verse.org/Simulation }
using { /Verse.org/Native }

cove_wave_retreat := class(creative_device):

    @editable WaveCinematic : cinematic_sequence_device = cinematic_sequence_device{}
    @editable WaveTimer     : timer_device              = timer_device{}

    var WaveCount : int = 0
    MaxWaves      : int = 3

    OnBegin<override>()<suspends> : void =
        WaveTimer.SuccessEvent.Subscribe(OnWaveDone)
        WaveCinematic.StoppedEvent.Subscribe(OnCinematicStopped)
        WaveTimer.Start()

    OnWaveDone(MaybeAgent : ?agent) : void =
        set WaveCount = WaveCount + 1
        if (WaveCount >= MaxWaves):
            # Play the cinematic BACKWARDS — water retreats from the dock
            WaveCinematic.PlayReverse()
        else:
            WaveCinematic.Play()

    OnCinematicStopped(Ignored : tuple()) : void =
        if (WaveCount < MaxWaves):
            WaveTimer.Reset()
            WaveTimer.Start()
        else:
            WaveTimer.Disable()

Gotchas

1. ?agent must be unwrapped before use

timer_device.SuccessEvent sends ?agent, not agent. You cannot pass a ?agent directly to ScoreManager.Activate(Agent:agent). Always unwrap first:

if (A := MaybeAgent?):
    ScoreManager.Activate(A)

Skipping this causes a type-mismatch compile error.

2. timer_device.Start() does NOT reset the clock

If you call Start() after the timer has already fired, it resumes from 0 (already expired). Always call Reset() before Start() when re-arming a wave timer.

3. cinematic_sequence_device.StoppedEvent sends tuple(), not agent

The handler signature must be (Ignored : tuple()) : void. Writing (Agent : agent) will compile-fail. There is no agent payload on this event.

4. score_manager_device.Activate() vs Activate(Agent)

The no-argument overload awards points globally (useful for team scores). The Agent overload credits a specific player. Make sure your Activating Team setting in the device properties matches which overload you call — a mismatch silently does nothing.

5. player_spawner_device.SpawnPlayer requires a player, not an agent

The SpawnedEvent sends agent. You must downcast: if (P := player[Agent]): before calling ShoreSpawner.SpawnPlayer(P). The cast is failable, so it must live inside an if.

6. Localized message parameters

If you ever pass text to a UI or HUD device, raw strings are not accepted. Declare a localizes helper:

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

There is no StringToMessage function in Verse.

7. @editable is mandatory for placed devices

Declaring var MyTimer : timer_device = timer_device{} at class scope creates a default-constructed device that is not connected to anything on your island. Only @editable fields can be wired to real placed instances in the UEFN Details panel.

Guides & scripts that use creature_spawner_device

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

Build your own lesson with creature_spawner_device

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 →