Overview
The water_device creates a volume of water on your island. Out of the box players can swim in it, fish, or pilot boats across it. Where it becomes a game mechanic is the Verse API: you can fill the volume up to a configured level, empty it back out, pause and resume that vertical movement, and react the moment an agent enters or exits the water.
Reach for it when you want:
- A rising-water escape room where the flood starts when a timer or trigger fires and players must climb out before it's full.
- A drainable moat that empties to reveal a hidden path once an objective is complete.
- Reward or damage logic that fires when a player swims into (or out of) a danger zone.
- A boss-arena flood phase you can stop and resume to control the difficulty curve.
The device is configured in UEFN (its size, its Default Vertical Water Percentage, etc.), and you drive its behavior from Verse by calling the methods below and subscribing to its events.
API Reference
water_device
Used to create and manipulate volumes of water where players can swim, fish, or drive boats.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
water_device<public> := class<concrete><final>(creative_device_base):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
AgentEntersWaterEvent |
AgentEntersWaterEvent<public>:listenable(agent) |
Signaled when an agent enters the water. Sends agent that entered the water. |
AgentExitsWaterEvent |
AgentExitsWaterEvent<public>:listenable(agent) |
Signaled when an agent exits the water. Sends agent that exited the water. |
VerticalFillingCompletedEvent |
VerticalFillingCompletedEvent<public>:listenable(tuple()) |
Signals when the volume is filled to the water level set in the Default Vertical Water Percentage option. |
VerticalEmptyingCompletedEvent |
VerticalEmptyingCompletedEvent<public>:listenable(tuple()) |
Signals when the water volume is completely empty. |
Methods (call these to make the device act):
| Method | Signature | Description |
|---|---|---|
Enable |
Enable<public>():void |
Enables this device. |
Disable |
Disable<public>():void |
Disables this device. |
BeginVerticalEmptying |
BeginVerticalEmptying<public>():void |
Starts vertically emptying the water in the volume. |
BeginVerticalFilling |
BeginVerticalFilling<public>():void |
Starts vertically filling the water in the volume. |
StopVerticalMovement |
StopVerticalMovement<public>():void |
Stops filling or emptying the volume. |
ResumeVerticalMovement |
ResumeVerticalMovement<public>():void |
Resumes either filling or emptying the volume. |
Walkthrough
Let's build a rising-water escape challenge. When a player steps on a pressure plate (a button_device), the room starts flooding. We track who is in the water, and when the volume finishes filling we drain it back out so the room can reset. We also award nothing if the player escapes — they just need to get out before the fill completes.
This example uses four real members of the device: BeginVerticalFilling, BeginVerticalEmptying, AgentEntersWaterEvent, and VerticalFillingCompletedEvent.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Fortnite.com/Characters }
# A rising-water escape challenge driven by a water_device.
flood_room_device := class(creative_device):
# The water volume we flood and drain.
@editable
Water : water_device = water_device{}
# The plate the player steps on to start the flood.
@editable
StartButton : button_device = button_device{}
# Localized helper so we can show messages (message <> string).
MakeMessage<localizes>(S : string) : message = "{S}"
OnBegin<override>()<suspends> : void =
# When the button is pressed, start flooding the room.
StartButton.InteractedWithEvent.Subscribe(OnStartPressed)
# React whenever someone enters the water.
Water.AgentEntersWaterEvent.Subscribe(OnEnterWater)
# React whenever someone exits the water.
Water.AgentExitsWaterEvent.Subscribe(OnExitWater)
# When the volume is full, drain it back out to reset the room.
Water.VerticalFillingCompletedEvent.Subscribe(OnFillComplete)
# When fully drained, the room is ready for another run.
Water.VerticalEmptyingCompletedEvent.Subscribe(OnEmptyComplete)
# button_device hands us the interacting agent.
OnStartPressed(Agent : agent) : void =
Print("Flood started!")
Water.BeginVerticalFilling()
# AgentEntersWaterEvent is listenable(agent) -> handler gets the agent directly.
OnEnterWater(Agent : agent) : void =
Print("An agent entered the water!")
OnExitWater(Agent : agent) : void =
Print("An agent escaped the water!")
# VerticalFillingCompletedEvent is listenable(tuple()) -> no payload.
OnFillComplete() : void =
Print("Room fully flooded — draining now.")
Water.BeginVerticalEmptying()
OnEmptyComplete() : void =
Print("Room drained — ready for the next run.")
Line by line:
flood_room_device := class(creative_device):— every script that controls placed devices must be acreative_devicesubclass.- The
@editablefields (Water,StartButton) are how you connect this script to the actual devices you dragged into the level. You assign them in the device's Details panel in UEFN. MakeMessage<localizes>(S : string) : message— the canonical way to turn a string into amessage. Even though this example only usesPrint, keep this pattern handy: any device API that wants amessageneeds a<localizes>function, never a raw string.OnBegin<override>()<suspends> : void =runs once when the game starts. All ourSubscribecalls live here.StartButton.InteractedWithEvent.Subscribe(OnStartPressed)wires the plate press to our handler.Water.BeginVerticalFilling()kicks off the flood — the water rises toward the Default Vertical Water Percentage configured on the device.OnEnterWater(Agent : agent)/OnExitWater— becauseAgentEntersWaterEventislistenable(agent), the handler receives the agent directly (no?agentunwrap needed here).VerticalFillingCompletedEventislistenable(tuple()), so its handler takes no parameters. When filling finishes we immediately callBeginVerticalEmptying()to reset.
Common patterns
Pause and resume the flood mid-rise
Use StopVerticalMovement to freeze the water at its current level (e.g. during a boss intermission) and ResumeVerticalMovement to continue exactly where it left off.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Toggle the flood on/off with two buttons.
flood_pause_device := class(creative_device):
@editable
Water : water_device = water_device{}
@editable
PauseButton : button_device = button_device{}
@editable
ResumeButton : button_device = button_device{}
OnBegin<override>()<suspends> : void =
# Start the water moving up.
Water.BeginVerticalFilling()
PauseButton.InteractedWithEvent.Subscribe(OnPause)
ResumeButton.InteractedWithEvent.Subscribe(OnResume)
OnPause(Agent : agent) : void =
Water.StopVerticalMovement()
OnResume(Agent : agent) : void =
Water.ResumeVerticalMovement()
Enable/disable the whole volume
Disable switches the device off entirely (no swimming, no fill logic), and Enable turns it back on — handy for gating a water hazard until a phase begins.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# The water hazard is off until the round actually starts.
hazard_gate_device := class(creative_device):
@editable
Water : water_device = water_device{}
@editable
RoundStartButton : button_device = button_device{}
OnBegin<override>()<suspends> : void =
# Keep the water inert until the round begins.
Water.Disable()
RoundStartButton.InteractedWithEvent.Subscribe(OnRoundStart)
OnRoundStart(Agent : agent) : void =
Water.Enable()
Water.BeginVerticalFilling()
Reward a player for surviving in the water
Subscribe to AgentExitsWaterEvent to fire logic the instant a player climbs out — here we grant an item via a linked item_granter_device.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Hand out a reward when a player exits the water.
swim_reward_device := class(creative_device):
@editable
Water : water_device = water_device{}
@editable
Granter : item_granter_device = item_granter_device{}
OnBegin<override>()<suspends> : void =
Water.AgentExitsWaterEvent.Subscribe(OnExit)
OnExit(Agent : agent) : void =
# Grant the configured item to the agent who left the water.
Granter.GrantItem(Agent)
Gotchas
- Two different event payloads.
AgentEntersWaterEventandAgentExitsWaterEventarelistenable(agent), so their handlers take(Agent : agent). ButVerticalFillingCompletedEventandVerticalEmptyingCompletedEventarelistenable(tuple()), so their handlers take no parameters at all. WritingOnFillComplete(Agent : agent)will fail to compile. - Subscribe inside
OnBegin, not the field declaration. Event subscriptions must happen at runtime inOnBegin(or another method). You can't subscribe where you declare the field. - You must declare the device as an
@editablefield. Callingwater_device.BeginVerticalFilling()on a bare type name fails with 'Unknown identifier'. Always go through a placed instance you've referenced in the Details panel. Resumeonly works afterStop.ResumeVerticalMovementcontinues whatever direction was previously in progress (filling or emptying). If movement was never started, it has nothing to resume.- Fill level is set in UEFN, not Verse.
BeginVerticalFillingraises the water to the Default Vertical Water Percentage option configured on the device — there's no Verse method to set an arbitrary target percentage, so configure it in the Details panel. messageis notstring. If you call into a device API that wants amessage, build one with a<localizes>helper function. There is noStringToMessage.- Int and float don't auto-convert. If you ever compute timings or distances around the flood, remember Verse will not silently convert between
intandfloat— convert explicitly.