Overview
The volume_device defines an invisible 3D region in your island and tells you exactly who and what is inside it. Unlike a trigger that fires on a single touch, a volume tracks continuous occupancy — it signals when an agent enters and again when they exit, and it can report the full list of agents currently standing inside at any moment.
Reach for the volume_device when you need to answer questions like:
- Is the player standing in the vault room right now?
- How many players are inside the capture zone (so I can score a team)?
- Did a physics prop get knocked into the lava pit?
- Has every defender left the safe area?
It exposes four events — AgentEntersEvent, AgentExitsEvent, PropEnterEvent, PropExitEvent — and two query methods — GetAgentsInVolume() and IsInVolume(). Because the events are listenable(agent) (and listenable(creative_prop)), your handler receives the thing that crossed the boundary, so you can act on that specific player or prop.
API Reference
volume_device
Used to track when agents enter and exit a volume.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
volume_device<public> := class<concrete><final>(creative_device_base):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
AgentEntersEvent |
AgentEntersEvent<public>:listenable(agent) |
Signaled when an agent enters the device volume. |
AgentExitsEvent |
AgentExitsEvent<public>:listenable(agent) |
Signaled when an agent exits the device volume. |
PropEnterEvent |
PropEnterEvent<public>:listenable(creative_prop) |
Signaled when an creative_prop entered the device volume. |
PropExitEvent |
PropExitEvent<public>:listenable(creative_prop) |
Signaled when an creative_prop exited the device volume. |
Methods (call these to make the device act):
| Method | Signature | Description |
|---|---|---|
GetAgentsInVolume |
GetAgentsInVolume<public>()<reads>:[]agent |
Returns an array of agents that are currently occupying the volume. |
IsInVolume |
IsInVolume<public>(Agent:agent)<transacts><decides>:void |
Succeeds when Agent is in the volume. |
Walkthrough
Let's build a vault room. A volume covers the room. The moment a player steps inside, we start a 10-second "alarm" countdown and grant them on-screen warnings. If they leave before the timer ends, the alarm cancels. We'll use AgentEntersEvent to start, AgentExitsEvent to cancel, and GetAgentsInVolume() to confirm the room is empty before resetting.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Colors }
using { /UnrealEngine.com/Temporary/UI }
vault_log := class(log_channel){}
# Helper turning a plain string into the localized `message` type the UI wants.
VaultText<localizes>(S:string):message = "{S}"
vault_room_device := class(creative_device):
Logger:log = log{Channel := vault_log}
# The volume covering the vault room. Drag your placed Volume here in the Details panel.
@editable
VaultVolume:volume_device = volume_device{}
# Track whether the alarm coroutine should keep running.
var AlarmActive:logic = false
OnBegin<override>()<suspends>:void =
# React when anyone crosses into or out of the room.
VaultVolume.AgentEntersEvent.Subscribe(OnAgentEntered)
VaultVolume.AgentExitsEvent.Subscribe(OnAgentExited)
OnAgentEntered(Agent:agent):void =
Logger.Print("An agent entered the vault.")
ShowMessage(Agent, VaultText("Vault breach detected! Alarm in 10s..."))
set AlarmActive = true
spawn{ RunAlarm() }
OnAgentExited(Agent:agent):void =
Logger.Print("An agent left the vault.")
# Only cancel the alarm if the room is now completely empty.
Occupants := VaultVolume.GetAgentsInVolume()
if (Occupants.Length = 0):
set AlarmActive = false
ShowMessage(Agent, VaultText("Vault secured. Alarm cancelled."))
RunAlarm()<suspends>:void =
Sleep(10.0)
# If nobody cancelled it, the heist succeeds.
if (AlarmActive?):
Logger.Print("ALARM! Vault stayed breached for 10 seconds.")
for (A : VaultVolume.GetAgentsInVolume()):
ShowMessage(A, VaultText("Heist complete!"))
ShowMessage(Agent:agent, Msg:message):void =
if (Player := player[Agent], PC := Player.GetControllerInterface[]):
# GetControllerInterface gives a playspace UI hook; fall back to logging.
Logger.Print("Message shown to a player.")
Line by line:
vault_log := class(log_channel){}declares a log channel so ourLogger.Printoutput is tagged.VaultText<localizes>(S:string):messageis the required pattern for producing amessagevalue — UI APIs never take a raw string. There is noStringToMessage.@editable VaultVolume:volume_deviceis the field that lets us call the placed device. Without an@editablefield inside acreative_deviceclass,VaultVolume.AgentEntersEventwould fail with "Unknown identifier."- In
OnBegin, weSubscribetwo handlers.AgentEntersEventislistenable(agent), so the handler signature is(Agent:agent):void— already unwrapped, no?needed here because the type is the non-optionalagent. OnAgentEnteredflipsAlarmActivetotrueandspawns the countdown coroutine so it runs concurrently without blocking the event handler.OnAgentExitedcallsGetAgentsInVolume()and checks.Length = 0. This is the key trick: the exit event fires per-agent, so we must re-query the volume to know if the room is truly empty before cancelling.RunAlarmsleeps 10 seconds, then re-checksAlarmActive?(thelogicunwrap) before declaring the heist a success.
Common patterns
Use IsInVolume to gate an interaction
IsInVolume(Agent) is a <decides> query — it succeeds only when that agent is inside. Perfect for "you must be standing in the zone to do X" checks.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
zone_check_log := class(log_channel){}
zone_gate_device := class(creative_device):
Logger:log = log{Channel := zone_check_log}
@editable
CaptureZone:volume_device = volume_device{}
@editable
ScoreButton:button_device = button_device{}
OnBegin<override>()<suspends>:void =
ScoreButton.InteractedWithEvent.Subscribe(OnButtonPressed)
OnButtonPressed(Agent:agent):void =
# Only award the point if the presser is actually inside the capture zone.
if (CaptureZone.IsInVolume[Agent]):
Logger.Print("Point scored — agent was in the zone.")
else:
Logger.Print("Press ignored — agent is outside the zone.")
Count occupants for team-scoring
GetAgentsInVolume() returns the live array of agents inside — ideal for periodically tallying how many players hold a point.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
tally_log := class(log_channel){}
zone_tally_device := class(creative_device):
Logger:log = log{Channel := tally_log}
@editable
PointVolume:volume_device = volume_device{}
OnBegin<override>()<suspends>:void =
# Every 5 seconds, count who's holding the point.
loop:
Sleep(5.0)
Occupants := PointVolume.GetAgentsInVolume()
Count := Occupants.Length
if (Count > 0):
Logger.Print("Point contested by some players.")
else:
Logger.Print("Point is empty.")
React to props entering a volume
PropEnterEvent and PropExitEvent track creative_props — useful for a "knock the barrel into the pit" puzzle. The handler receives the prop, so you can hide or dispose it.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
pit_log := class(log_channel){}
prop_pit_device := class(creative_device):
Logger:log = log{Channel := pit_log}
@editable
PitVolume:volume_device = volume_device{}
OnBegin<override>()<suspends>:void =
PitVolume.PropEnterEvent.Subscribe(OnPropFell)
PitVolume.PropExitEvent.Subscribe(OnPropLeft)
OnPropFell(Prop:creative_prop):void =
Logger.Print("A prop fell into the pit — removing it.")
# Make the prop vanish (disables collision) once it's in the pit.
Prop.Hide()
OnPropLeft(Prop:creative_prop):void =
Logger.Print("A prop left the pit volume.")
Gotchas
@editablefield is mandatory. You cannot callvolume_device{}.AgentEntersEventon a freshly-constructed value — that empty instance isn't your placed device. Declare@editable VaultVolume:volume_device = volume_device{}, then assign the placed device in the Details panel.- Exit fires per-agent, not per-room. When two players are inside and one leaves,
AgentExitsEventfires once. To know whether the room is empty, callGetAgentsInVolume()and check.Length = 0— don't assume an exit means empty. IsInVolumeis a<decides>function. Call it with square brackets inside anif(if (Vol.IsInVolume[Agent]):), not round parentheses. It returns no value — its success/failure is the answer.agenthandlers are already unwrapped.AgentEntersEventislistenable(agent)(non-optional), so the handler param isAgent:agentand you use it directly. You only need theif (A := Agent?)unwrap when an event hands you an optional?agent.messageneeds the localized helper. Any UI text param wants amessage, produced by a<localizes>function likeVaultText. Passing a raw"string"will not compile.- Props are
creative_prop, notagent. Don't try to feed a prop fromPropEnterEventintoGetAgentsInVolume-style agent logic — they're different types with different events.