Reference Devices compiles

water_device: Rising Tides, Sinking Vaults, and Swimmable Arenas

The water_device gives you a programmable volume of water you can swim in, fish in, or drive boats across — and crucially, one you can raise and lower on cue. This article shows how to detect agents entering and exiting the water and how to flood or drain a room as a real gameplay mechanic.

Updated Examples verified on the live UEFN compiler
Watch the Knotwater_device in ~90 seconds.

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 a creative_device subclass.
  • The @editable fields (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 a message. Even though this example only uses Print, keep this pattern handy: any device API that wants a message needs a <localizes> function, never a raw string.
  • OnBegin<override>()<suspends> : void = runs once when the game starts. All our Subscribe calls 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 — because AgentEntersWaterEvent is listenable(agent), the handler receives the agent directly (no ?agent unwrap needed here).
  • VerticalFillingCompletedEvent is listenable(tuple()), so its handler takes no parameters. When filling finishes we immediately call BeginVerticalEmptying() 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. AgentEntersWaterEvent and AgentExitsWaterEvent are listenable(agent), so their handlers take (Agent : agent). But VerticalFillingCompletedEvent and VerticalEmptyingCompletedEvent are listenable(tuple()), so their handlers take no parameters at all. Writing OnFillComplete(Agent : agent) will fail to compile.
  • Subscribe inside OnBegin, not the field declaration. Event subscriptions must happen at runtime in OnBegin (or another method). You can't subscribe where you declare the field.
  • You must declare the device as an @editable field. Calling water_device.BeginVerticalFilling() on a bare type name fails with 'Unknown identifier'. Always go through a placed instance you've referenced in the Details panel.
  • Resume only works after Stop. ResumeVerticalMovement continues 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. BeginVerticalFilling raises 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.
  • message is not string. If you call into a device API that wants a message, build one with a <localizes> helper function. There is no StringToMessage.
  • Int and float don't auto-convert. If you ever compute timings or distances around the flood, remember Verse will not silently convert between int and float — convert explicitly.

Device Settings & Options

The water_device User Options panel in the UEFN editor — every setting you can tune.

Water Device settings and options panel in the UEFN editor — Zone Width, Zone Depth, Zone Height, Default Vertical Water Percentage, Vertical Filling Speed TPM, Vertical Emptying Speed TPM
Water Device — User Options in the UEFN editor: Zone Width, Zone Depth, Zone Height, Default Vertical Water Percentage, Vertical Filling Speed TPM, Vertical Emptying Speed TPM
⚙️ Settings on this device (6)

Setting names read from the panel above — may be partial; the screenshots are the source of truth.

Zone Width
Zone Depth
Zone Height
Default Vertical Water Percentage
Vertical Filling Speed TPM
Vertical Emptying Speed TPM

Guides & scripts that use water_device

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

Build your own lesson with water_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 →