Overview
Recursion is a programming technique where a function calls itself until it reaches a stopping condition called the base case. Every recursive call works on a smaller version of the original problem, and when the base case is hit, the chain of calls unwinds and returns results back up.
In UEFN Verse, recursion is useful when:
- You need to count down (or up) a variable number of steps — e.g., awarding bonus score for each wave a player survives.
- You need to process a list item by item without a traditional
forloop. - You want to chain cinematic beats — play a sequence, wait, play the next, until none remain.
The island scenario for this article: A player reaches the clifftop overlooking the cove. A cinematic flare sequence fires — but first, a recursive countdown function ticks down from 5, awarding bonus score for each second the player held the clifftop. When the countdown reaches zero, the cinematic plays.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
Here is the complete, runnable device. It wires together a score_manager_device (bonus points per countdown tick), a cinematic_sequence_device (the clifftop flare), and a player_reference_device (to track which player earned the bonus). The recursive function CountdownAndScore calls itself, decrementing the counter each time, until it hits zero and fires the cinematic.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/UI }
# Place this device on your island near the clifftop.
# Wire: ScoreManager, ClifftopCinematic, PlayerRef in the editor.
clifftop_countdown_device := class(creative_device):
# The score manager that awards bonus points each countdown tick.
@editable
ScoreManager : score_manager_device = score_manager_device{}
# The cinematic sequence device that plays the flare cutscene.
@editable
ClifftopCinematic : cinematic_sequence_device = cinematic_sequence_device{}
# Tracks which player is on the clifftop.
@editable
PlayerRef : player_reference_device = player_reference_device{}
# How many seconds (ticks) to count down from.
StartCount : int = 5
OnBegin<override>()<suspends> : void =
# Wait for the PlayerRef to confirm a player is registered.
# ActivatedEvent fires when the device is activated (e.g. by a trigger).
Agent := PlayerRef.ActivatedEvent.Await()
# Begin the recursive countdown for that agent.
CountdownAndScore(Agent, StartCount)
# Recursive function: awards score each tick, then calls itself with Count - 1.
# Base case: when Count reaches 0, play the cinematic instead.
CountdownAndScore(Agent : agent, Count : int)<suspends> : void =
if (Count <= 0):
# Base case — countdown finished, fire the flare cinematic!
ClifftopCinematic.Play(Agent)
else:
# Award one tick of bonus score to the agent.
ScoreManager.Activate(Agent)
# Wait one second before the next tick.
Sleep(1.0)
# Recursive call: same function, smaller Count.
CountdownAndScore(Agent, Count - 1)
Line-by-line explanation
| Lines | What's happening |
|---|---|
@editable fields |
Lets you wire real placed devices in the UEFN editor. Without @editable, the device reference is a blank stub that does nothing. |
StartCount : int = 5 |
The number of recursive steps. Change this in the editor or in code. |
PlayerRef.ActivatedEvent.Await() |
Suspends until the player_reference_device fires — i.e., until a player is registered and the device is activated. Returns the agent. |
CountdownAndScore(Agent, StartCount) |
First call — kicks off the recursion with the full count. |
if (Count <= 0): |
Base case. When Count hits zero, stop recursing and do the final action. |
ClifftopCinematic.Play(Agent) |
Plays the clifftop flare sequence for the specific agent. |
ScoreManager.Activate(Agent) |
Awards the configured point value to the agent for this tick. |
Sleep(1.0) |
Suspends for one second — this is what makes each tick feel like a real countdown. |
CountdownAndScore(Agent, Count - 1) |
Recursive call. Same function, but Count is one smaller. Eventually reaches 0. |
The call stack for StartCount = 3 looks like this:
CountdownAndScore(Agent, 3) → Score, Sleep, calls →
CountdownAndScore(Agent, 2) → Score, Sleep, calls →
CountdownAndScore(Agent, 1) → Score, Sleep, calls →
CountdownAndScore(Agent, 0) → Play cinematic. Done.
Common patterns
Pattern 1 — Recursive score multiplier (Increment before Activate)
Each recursive level increments the score manager before activating, so deeper survivors earn exponentially more. This covers Increment and Activate — two different score_manager_device methods.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/UI }
# Awards escalating points: 1 pt on wave 1, 2 on wave 2, etc.
# Each recursive call increments the score THEN activates.
wave_bonus_device := class(creative_device):
@editable
ScoreManager : score_manager_device = score_manager_device{}
@editable
PlayerRef : player_reference_device = player_reference_device{}
WaveCount : int = 4
OnBegin<override>()<suspends> : void =
Agent := PlayerRef.ActivatedEvent.Await()
AwardWaveBonus(Agent, WaveCount)
# Recursive: increments score quantity each wave, then activates at the base.
AwardWaveBonus(Agent : agent, Wave : int)<suspends> : void =
if (Wave <= 0):
# Base case: activate once with the fully incremented score value.
ScoreManager.Activate(Agent)
else:
# Increment the score quantity for this wave level.
ScoreManager.Increment(Agent)
Sleep(0.5)
# Recurse into the next (smaller) wave.
AwardWaveBonus(Agent, Wave - 1)
Pattern 2 — Recursive cinematic chain (Play → StoppedEvent → next)
Play a sequence of cinematic beats one after another using recursion. Each call plays one cinematic, waits for it to stop, then recurses to the next index in an array.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/UI }
# Plays an array of cinematic sequences one after another, recursively.
cinematic_chain_device := class(creative_device):
# Add your cinematic sequence devices here in order.
@editable
Cinematics : []cinematic_sequence_device = array{}
OnBegin<override>()<suspends> : void =
# Start the chain from index 0.
PlayChain(0)
# Recursive: plays Cinematics[Index], waits for it to stop, then plays Index+1.
PlayChain(Index : int)<suspends> : void =
if (Index >= Cinematics.Length):
# Base case: no more cinematics to play.
return
else:
if (Cinematic := Cinematics[Index]):
# Play this cinematic for everyone.
Cinematic.Play()
# Await the StoppedEvent before moving on.
Cinematic.StoppedEvent.Await()
# Recurse: next cinematic in the chain.
PlayChain(Index + 1)
Pattern 3 — Recursive enable/disable toggle with PlayerRef tracking
Use recursion to alternate a player_reference_device between enabled and disabled states on a timed loop — useful for a "spotlight" effect that cycles through registered players.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/UI }
# Toggles the PlayerRef device on/off every N seconds, recursively, for MaxCycles cycles.
spotlight_toggle_device := class(creative_device):
@editable
PlayerRef : player_reference_device = player_reference_device{}
ToggleInterval : float = 2.0
MaxCycles : int = 6
OnBegin<override>()<suspends> : void =
ToggleCycle(MaxCycles, true)
# Recursive toggle: enables or disables PlayerRef each cycle.
ToggleCycle(CyclesLeft : int, EnableNow : logic)<suspends> : void =
if (CyclesLeft <= 0):
# Base case: all cycles done, leave device disabled.
PlayerRef.Disable()
else:
if (EnableNow?):
PlayerRef.Enable()
else:
PlayerRef.Disable()
Sleep(ToggleInterval)
# Recurse: flip the toggle, decrement cycles.
ToggleCycle(CyclesLeft - 1, not EnableNow)
Gotchas
1. Always have a base case — or you'll stack overflow
A recursive function must reach a stopping condition. If Count never reaches 0 (e.g., you pass a negative number and check Count = 0 exactly), the function recurses forever and crashes. Always use Count <= 0 rather than Count = 0 as your guard.
2. <suspends> is required when you call Sleep or Await inside recursion
If your recursive function calls Sleep() or .Await(), the function signature must include <suspends>. Forgetting this is a compile error. Every call site of a <suspends> function must also be in a suspending context.
3. int does not auto-convert to float for Sleep
Sleep takes a float. If your countdown counter is an int, you cannot pass it directly to Sleep. Use a literal like Sleep(1.0) or cast explicitly. Verse will not silently convert int to float.
4. Array index access is failable — use if to unwrap
In Pattern 2, Cinematics[Index] is a failable expression. Always unwrap it inside an if block:
if (Cinematic := Cinematics[Index]):
Cinematic.Play()
Without the if, the compiler rejects the bare index access outside a failure context.
5. @editable fields must be wired in the editor
Declaring @editable is only half the job. You must also drag the placed device into the slot in the UEFN Details panel. An unwired @editable device is a blank stub — calling .Play() or .Activate() on it silently does nothing, which makes bugs very hard to find.
6. Recursion depth has a practical limit
Verse runs on a game server with a call-stack limit. For gameplay purposes, keep recursive depth under ~100 levels. If you need to process thousands of items, prefer an iterative loop or for expression instead.