Overview
The radio_device is a Creative device that plays audio — music, ambient sound, or sound effects — either globally or targeted at specific registered agents. You place it in your UEFN level, configure the audio clip and volume in its User Options panel, then drive it entirely from Verse at runtime.
When to reach for it:
- Background music that starts/stops based on game phase (lobby → combat → victory)
- Per-player audio cues (a heartbeat that only the hiding player hears)
- A hidden radio prop that appears and starts playing when a player finds a secret area
- Cutting all music on elimination or round end
The key distinction from a static audio actor: Register(Agent) makes the radio play from that agent's perspective, letting you deliver personalised audio without extra devices.
API Reference
radio_device
Used to play curated music from the device or one or more registered
agents.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
radio_device<public> := class<concrete><final>(creative_device_base):
Methods (call these to make the device act):
| Method | Signature | Description |
|---|---|---|
Play |
Play<public>():void |
Starts playing audio from this device. |
Stop |
Stop<public>():void |
Stops playing audio from this device. |
Register |
Register<public>(Agent:agent):void |
Adds the specified agent as a target for the Radio to play audio from |
Unregister |
Unregister<public>(Agent:agent):void |
Removes the specified agent as a target for the Radio to play audio from if previously Registered |
UnregisterAll |
UnregisterAll<public>():void |
Removes all previously registered agents as targets for the Radio to play audio from |
Show |
Show<public>()<transacts>:void |
Shows this device in the world. |
Hide |
Hide<public>()<transacts>:void |
Hides this device from the world. |
Walkthrough
Scenario: A prop-hunt map. When a prop player stands still for 5 seconds, a heartbeat radio starts playing — registered only to that player — so hunters nearby can hear it. When the player moves (steps on a trigger), they are unregistered and the heartbeat stops for them. A second ambient radio plays global background music from the start and is hidden so players can't see it in the world.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Fortnite.com/Playspaces }
using { /Fortnite.com/Characters }
# Converts a plain string into a localizable message for UI/log use.
HeartbeatLog<localizes>(S : string) : message = "{S}"
prop_hunt_radio_manager := class(creative_device):
# The heartbeat radio — configured in UEFN with a looping heartbeat audio clip.
@editable
HeartbeatRadio : radio_device = radio_device{}
# Ambient background music radio — always playing, hidden from players.
@editable
AmbientRadio : radio_device = radio_device{}
# A trigger the prop player steps on when they move (e.g. a movement sensor trigger).
@editable
MovementTrigger : trigger_device = trigger_device{}
# Tracks which agents currently have the heartbeat registered.
var RegisteredAgents : []agent = array{}
OnBegin<override>()<suspends> : void =
# Hide the ambient radio prop so players can't see the device in the world.
AmbientRadio.Hide()
# Start the ambient background music immediately — heard by everyone.
AmbientRadio.Play()
# Subscribe to the movement trigger so we can unregister movers.
MovementTrigger.TriggeredEvent.Subscribe(OnPlayerMoved)
# Simulate registering all players to the heartbeat after 5 seconds of game start.
# In a real map you'd hook this to a "player stood still" event.
Sleep(5.0)
RegisterAllPlayersToHeartbeat()
# Registers every current player to the heartbeat radio.
RegisterAllPlayersToHeartbeat() : void =
Playspace := GetPlayspace()
Players := Playspace.GetPlayers()
for (P : Players):
HeartbeatRadio.Register(P)
set RegisteredAgents = RegisteredAgents + array{P}
# Start the heartbeat — now each registered player hears it.
HeartbeatRadio.Play()
# Called when a player steps on the movement trigger — unregister them from heartbeat.
OnPlayerMoved(Agent : ?agent) : void =
if (A := Agent?):
HeartbeatRadio.Unregister(A)
# Remove from our local tracking array.
set RegisteredAgents = for (R : RegisteredAgents, R <> A) { R }
# If nobody is registered any more, stop the heartbeat entirely.
if (RegisteredAgents.Length = 0):
HeartbeatRadio.Stop()
Line-by-line explanation:
| Lines | What's happening |
|---|---|
@editable HeartbeatRadio |
Wires the Verse class to the placed radio_device in the level. Without @editable the device can't be referenced. |
AmbientRadio.Hide() |
Hides the device mesh so players don't see a floating radio prop — audio still plays. |
AmbientRadio.Play() |
Starts global background music. No agents registered = everyone hears it. |
MovementTrigger.TriggeredEvent.Subscribe(OnPlayerMoved) |
Hooks the movement trigger to our handler. |
Sleep(5.0) |
Waits 5 seconds before registering players — simulates "stood still" detection. |
HeartbeatRadio.Register(P) |
Targets the heartbeat at each player individually. |
HeartbeatRadio.Play() |
Starts the heartbeat; only registered agents hear it. |
if (A := Agent?) |
Safely unwraps the ?agent the trigger event sends. |
HeartbeatRadio.Unregister(A) |
Removes the moving player — they no longer hear the heartbeat. |
HeartbeatRadio.Stop() |
Stops the device entirely once no one is registered. |
Common patterns
Pattern 1 — Stop all audio and clear all registered agents at round end
Use Stop and UnregisterAll together to cleanly reset audio state when a round finishes.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
round_end_audio_cleaner := class(creative_device):
@editable
BattleMusic : radio_device = radio_device{}
@editable
RoundEndTrigger : trigger_device = trigger_device{}
OnBegin<override>()<suspends> : void =
# Start battle music at round begin.
BattleMusic.Play()
# Wait for the round-end trigger (e.g. all enemies eliminated).
RoundEndTrigger.TriggeredEvent.Subscribe(OnRoundEnded)
OnRoundEnded(Agent : ?agent) : void =
# Remove every registered agent so no one hears stale audio next round.
BattleMusic.UnregisterAll()
# Stop the music immediately.
BattleMusic.Stop()
Pattern 2 — Reveal a hidden radio as a collectible secret
Use Show and Play together to make a radio appear and start playing when a player finds a secret area.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
secret_radio_revealer := class(creative_device):
# Radio starts hidden in UEFN (set Visible = false in device settings as default).
@editable
SecretRadio : radio_device = radio_device{}
# A trigger placed inside the secret room.
@editable
SecretRoomTrigger : trigger_device = trigger_device{}
OnBegin<override>()<suspends> : void =
# Make sure the radio is hidden at game start.
SecretRadio.Hide()
SecretRoomTrigger.TriggeredEvent.Subscribe(OnSecretFound)
OnSecretFound(Agent : ?agent) : void =
# Reveal the radio prop in the world.
SecretRadio.Show()
# Start playing — the discoverer and anyone nearby hears it.
SecretRadio.Play()
# Optionally register only the discovering player for a personal audio reward.
if (A := Agent?):
SecretRadio.Register(A)
Pattern 3 — Per-player audio: register one player, unregister another on elimination
Register a VIP player to a special theme, then unregister them when they are eliminated.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Fortnite.com/Playspaces }
using { /Fortnite.com/Characters }
vip_theme_manager := class(creative_device):
@editable
VIPThemeRadio : radio_device = radio_device{}
# Trigger fired by a game-manager device when the VIP is chosen.
@editable
VIPChosenTrigger : trigger_device = trigger_device{}
# Trigger fired when the VIP is eliminated.
@editable
VIPEliminatedTrigger : trigger_device = trigger_device{}
var CurrentVIP : ?agent = false
OnBegin<override>()<suspends> : void =
VIPThemeRadio.Hide()
VIPChosenTrigger.TriggeredEvent.Subscribe(OnVIPChosen)
VIPEliminatedTrigger.TriggeredEvent.Subscribe(OnVIPEliminated)
OnVIPChosen(Agent : ?agent) : void =
if (A := Agent?):
set CurrentVIP = option{A}
VIPThemeRadio.Register(A)
VIPThemeRadio.Play()
OnVIPEliminated(Agent : ?agent) : void =
if (VIP := CurrentVIP?):
VIPThemeRadio.Unregister(VIP)
set CurrentVIP = false
VIPThemeRadio.Stop()
Gotchas
1. @editable is mandatory — you cannot construct a radio_device in code.
Every radio_device you want to control must be placed in the UEFN level and wired via an @editable field. Writing MyRadio := radio_device{} and calling methods on it will compile but will have no effect because there is no placed device behind it.
2. Register targets audio at an agent, but Play must still be called.
Calling Register(Agent) alone does nothing audible. You must also call Play() to start the audio. Conversely, calling Play() before any agents are registered broadcasts to everyone — decide which behaviour you want before writing the sequence.
3. Show and Hide are <transacts> — they participate in Verse's speculative execution.
If you call Show() or Hide() inside a failing if branch that gets rolled back, the visibility change is also rolled back. This is usually what you want, but be aware when mixing them with <decides> expressions.
4. ?agent unwrap is required in every event handler.
Trigger events send ?agent (an optional agent). Always unwrap with if (A := Agent?): before passing to Register or Unregister — passing a ?agent directly where agent is expected is a compile error.
5. Audio clip is set in the editor, not in Verse.
You cannot change which audio clip a radio_device plays from Verse. The clip, volume, and looping behaviour are all configured in the device's User Options panel in UEFN. Verse only controls playback state and registration.
6. UnregisterAll then Stop — order matters for clean resets.
Call UnregisterAll() before Stop() when resetting between rounds. If you call Stop() first and then immediately Play() in the next round, any lingering registrations from the previous round will still be active.