Overview
A Verse interface is a named contract: it declares method signatures (and optionally default implementations) that any class can agree to honour by writing class(my_interface). When a class implements an interface, the compiler guarantees it provides every required method — so you can write code that works with any implementor, not just one specific class.
When to reach for it:
- You have several devices or manager classes that share a logical role (e.g. "things that can be activated by a player") but differ in what they actually do.
- You want a single handler to drive multiple device types without a chain of
if/elsetype checks. - You're building a reusable system (puzzle manager, quest tracker, score board) that shouldn't care which concrete device sits behind it.
The pirate-cove scenario used throughout this article: A sun-drenched dock has two activation sources — a barnacled button bolted to a sea-chest and a pressure trigger hidden under a plank. Both should unlock the same treasure vault door and flash a HUD message to the interacting pirate. An interface called activatable gives both sources a common Activate(Agent:agent):void method, and a single vault_manager device drives the whole thing.
API Reference
button_device
Used to create a button which can trigger other devices when an agent interacts with it.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
button_device<public> := class<concrete><final>(creative_device_base):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
InteractedWithEvent |
InteractedWithEvent<public>:listenable(agent) |
Signaled when an agent successfully interacts with the button for GetInteractionTime seconds. Sends the agent that interacted with the button. |
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. |
SetInteractionText |
SetInteractionText<public>(Text:message):void |
Sets the text that displays when an agent is close to this button and looks at it. Text is limited to 64 characters. |
SetInteractionTime |
SetInteractionTime<public>(Time:float):void |
Sets the duration of the interaction required to activate this device (in seconds). |
GetInteractionTime |
GetInteractionTime<public>()<transacts>:float |
Returns the duration of the interaction required to activate this device (in seconds). |
SetMaxTriggerCount |
SetMaxTriggerCount<public>(MaxCount:int):void |
Sets the maximum amount of times this button can be interacted with before it will be disabled. * MaxCount must be between 0 and 10000. * 0 indicates no limit on trigger count. |
GetMaxTriggerCount |
GetMaxTriggerCount<public>()<transacts>:int |
Returns the maximum amount of times this button can be interacted with before it will be disabled. * GetTriggerMaxCount will be between 0 and 10000. * 0 indicates no limit on trigger count. |
GetTriggerCountRemaining |
GetTriggerCountRemaining<public>()<transacts>:int |
Returns the number of times that this button can still be interacted with before it will be disabled. Will return 0 if GetMaxTriggerCount is unlimited. |
hud_message_device
Used to show custom HUD messages to one or more
agents.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
hud_message_device<public> := class<concrete><final>(creative_device_base):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
ShowMessageEvent |
ShowMessageEvent<public>:listenable(agent) |
Called when a Message has been Shown on-screen. Returns an Agent if it was Shown on a specified Agent's screen. |
HideMessageEvent |
HideMessageEvent<public>:listenable(agent) |
Called when a Message has been Hidden on-screen. Returns an Agent if it was Hidden from a specified Agent's screen. |
ClearAllMessagesEvent |
ClearAllMessagesEvent<public>:listenable(agent) |
Called when all queued Messages from all players that are affected by this HUD Message Device have been cleared. |
Methods (call these to make the device act):
| Method | Signature | Description |
|---|---|---|
Show |
Show<public>(Agent:agent):void |
Shows the currently set HUD Message on Agents screen. Will replace any previously active message. Use this when the device is setup to target specific agents. |
Show |
Show<public>():void |
Shows the currently set Message HUD message on screen. Will replace any previously active message. |
Hide |
Hide<public>():void |
Hides the HUD message. |
Hide |
Hide<public>(Agent:agent):void |
Hides the currently set HUD Message on Agents screen. Use this when the device is setup to target specific agents. |
Show |
Show<public>(Agent:agent, Message:message, ?DisplayTime:float |
Displays a Custom message to a specific Agent that you define.Setting DisplayTime to 0.0 will display the HUD message persistently.If not defined, or less than 0.0 the message will show for the time set on the device. |
Show |
Show<public>(Message:message, ?DisplayTime:float |
Displays a Custom message that you define for all PlayersSetting DisplayTime to 0.0 will display the HUD message persistently.If not defined, or less than 0.0 the message will show for the time set on the device. |
SetDisplayTime |
SetDisplayTime<public>(Time:float):void |
Sets the time (in seconds) the HUD message will be displayed. 0.0 will display the HUD message persistently. |
GetDisplayTime |
GetDisplayTime<public>()<transacts>:float |
Returns the time (in seconds) for which the HUD message will be displayed. 0.0 means the message is displayed persistently. |
SetText |
SetText<public>(Text:message):void |
Sets the Message to be displayed when the HUD message is activated. Text is clamped to 150 characters. |
ClearAllMessages |
ClearAllMessages<public>():void |
Clears all queued Messages from all players that are affected by this HUD Message Device. |
trigger_device
Used to relay events to other linked devices.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse). Inherited members are merged from trigger_base_device.
trigger_device<public> := class<concrete><final>(trigger_base_device):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
TriggeredEvent |
TriggeredEvent<public>:listenable(?agent) |
Signaled when an agent triggers this device. Sends the agent that used this device. Returns false if no agent triggered the action (ex: it was triggered through code). |
Methods (call these to make the device act):
| Method | Signature | Description |
|---|---|---|
Trigger |
Trigger<public>(Agent:agent):void |
Triggers this device with Agent being passed as the agent that triggered the action. Use an agent reference when this device is setup to require one (for instance, you want to trigger the device only with a particular agent. |
Trigger |
Trigger<public>():void |
Triggers this device, causing it to activate its TriggeredEvent event. |
Enable |
Enable<public>():void |
Enables this device. |
Disable |
Disable<public>():void |
Disables this device. |
SetMaxTriggerCount |
SetMaxTriggerCount<public>(MaxCount:int):void |
Sets the maximum amount of times this device can trigger. * 0 can be used to indicate no limit on trigger count. * MaxCount is clamped between [0,20]. |
GetMaxTriggerCount |
GetMaxTriggerCount<public>()<transacts>:int |
Gets the maximum amount of times this device can trigger. * 0 indicates no limit on trigger count. |
GetTriggerCountRemaining |
GetTriggerCountRemaining<public>()<transacts>:int |
Returns the number of times that this device can still be triggered before hitting GetMaxTriggerCount. Returns 0 if GetMaxTriggerCount is unlimited. |
SetResetDelay |
SetResetDelay<public>(Time:float):void |
Sets the time (in seconds) after triggering, before the device can be triggered again (if MaxTrigger count allows). |
GetResetDelay |
GetResetDelay<public>()<transacts>:float |
Gets the time (in seconds) before the device can be triggered again (if MaxTrigger count allows). |
SetTransmitDelay |
SetTransmitDelay<public>(Time:float):void |
Sets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered. |
GetTransmitDelay |
GetTransmitDelay<public>()<transacts>:float |
Gets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered. |
Walkthrough
Below is the complete, compilable example. Read the line-by-line explanation that follows.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# ─── 1. Define the interface ───────────────────────────────────────────────
# Any class that implements `activatable` must provide Activate().
# We also supply a default label so simple implementors need not override it.
activatable := interface:
# Concrete classes must implement this.
Activate(Agent : agent) : void
# Default implementation — classes may override.
GetLabel() : string = "Activate"
# ─── 2. Implement the interface on a button wrapper ────────────────────────
# This thin wrapper class wires a button_device into the activatable contract.
button_activatable := class(activatable):
Button : button_device
Activate(Agent : agent) : void =
# Re-enable the button so it can be used again after a reset
Button.Enable()
GetLabel() : override string = "Press to Unlock Vault"
# ─── 3. Implement the interface on a trigger wrapper ──────────────────────
trigger_activatable := class(activatable):
Trigger : trigger_device
Activate(Agent : agent) : void =
# Fire the trigger programmatically so linked devices react
Trigger.Trigger(Agent)
GetLabel() : override string = "Step on the Plank"
# ─── 4. The vault manager device — the only creative_device placed in UEFN ─
vault_manager := class(creative_device):
# ── Editable device references (drag-and-drop in UEFN Details panel) ──
@editable
DockButton : button_device = button_device{}
@editable
PlankTrigger : trigger_device = trigger_device{}
@editable
VaultUnlockTrigger : trigger_device = trigger_device{}
@editable
HudMessage : hud_message_device = hud_message_device{}
# ── Localized text helpers ─────────────────────────────────────────────
UnlockText<localizes>(S : string) : message = "{S}"
HintText<localizes>(S : string) : message = "{S}"
# ── Runtime state ──────────────────────────────────────────────────────
var VaultOpen : logic = false
# ── Entry point ────────────────────────────────────────────────────────
OnBegin<override>()<suspends> : void =
# Configure the button: 3-second hold, max 1 use (one-shot unlock)
DockButton.SetInteractionText(HintText("Hold to Unlock the Vault"))
DockButton.SetInteractionTime(3.0)
DockButton.SetMaxTriggerCount(1)
# Configure the trigger: allow only 1 activation, 2-second reset
PlankTrigger.SetMaxTriggerCount(1)
PlankTrigger.SetResetDelay(2.0)
# Configure the HUD: show for 4 seconds
HudMessage.SetDisplayTime(4.0)
# Subscribe to both input sources
DockButton.InteractedWithEvent.Subscribe(OnButtonPressed)
PlankTrigger.TriggeredEvent.Subscribe(OnPlankTriggered)
# ── Button handler ─────────────────────────────────────────────────────
# InteractedWithEvent sends a plain `agent` (not ?agent)
OnButtonPressed(Agent : agent) : void =
# Build a button_activatable and call through the interface
Source := button_activatable{Button := DockButton}
HandleActivation(Agent, Source)
# ── Trigger handler ────────────────────────────────────────────────────
# TriggeredEvent sends `?agent` — unwrap before use
OnPlankTriggered(MaybeAgent : ?agent) : void =
if (Agent := MaybeAgent?):
Source := trigger_activatable{Trigger := PlankTrigger}
HandleActivation(Agent, Source)
# ── Shared logic driven by the interface ──────────────────────────────
# This method doesn't know or care whether Source is a button or trigger.
HandleActivation(Agent : agent, Source : activatable) : void =
if (not VaultOpen):
set VaultOpen = true
# Call the interface method — works for any activatable
Source.Activate(Agent)
# Fire the vault unlock trigger so the door prop opens
VaultUnlockTrigger.Trigger(Agent)
# Show a personalised HUD message to the activating pirate
HudMessage.Show(Agent, UnlockText("⚓ Vault Unlocked! Claim your treasure!"))
Line-by-line explanation
| Lines | What's happening |
|---|---|
activatable := interface: |
Declares the contract. No class keyword — this is a pure interface. |
Activate(Agent:agent):void |
Abstract method — every implementor must provide a body. |
GetLabel():string = "Activate" |
Default implementation — implementors may override it. |
button_activatable := class(activatable): |
Opts in to the contract. The compiler will error if Activate is missing. |
Button.Enable() |
Calls a real button_device method from the API surface. |
GetLabel():override string |
Overrides the default with a dock-specific label. |
trigger_activatable := class(activatable): |
Second implementor — same interface, different device underneath. |
Trigger.Trigger(Agent) |
Calls trigger_device.Trigger(Agent:agent):void — fires the trigger with the player as instigator. |
DockButton.SetInteractionText(...) |
Sets the hold-prompt text (requires a message, hence the <localizes> helper). |
DockButton.SetInteractionTime(3.0) |
Players must hold for 3 seconds — adds tension on the dock. |
DockButton.SetMaxTriggerCount(1) |
One-shot: the button disables itself after the vault opens. |
PlankTrigger.SetMaxTriggerCount(1) |
Same one-shot logic for the hidden plank trigger. |
PlankTrigger.SetResetDelay(2.0) |
2-second cooldown before the trigger could fire again (irrelevant here since max is 1, but good practice). |
HudMessage.SetDisplayTime(4.0) |
Banner stays on screen for 4 seconds. |
HandleActivation(Agent, Source) |
Accepts any activatable — the interface makes this polymorphism possible. |
Source.Activate(Agent) |
Calls whichever concrete Activate the implementor provides. |
VaultUnlockTrigger.Trigger(Agent) |
Fires the vault-door trigger so linked prop animators react. |
HudMessage.Show(Agent, UnlockText(...)) |
Shows a custom message to the specific pirate who opened the vault. |
Common patterns
Pattern 1 — Query trigger state before firing
Before firing the vault trigger, check how many uses remain so you can show a different message on the last activation.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
remaining_checker := class(creative_device):
@editable
ChestTrigger : trigger_device = trigger_device{}
@editable
HudMessage : hud_message_device = hud_message_device{}
LastChanceText<localizes>(S : string) : message = "{S}"
NormalText<localizes>(S : string) : message = "{S}"
OnBegin<override>()<suspends> : void =
# Allow exactly 3 activations, then the chest is empty
ChestTrigger.SetMaxTriggerCount(3)
ChestTrigger.SetResetDelay(1.0)
ChestTrigger.TriggeredEvent.Subscribe(OnChestTriggered)
OnChestTriggered(MaybeAgent : ?agent) : void =
if (Agent := MaybeAgent?):
Remaining := ChestTrigger.GetTriggerCountRemaining()
if (Remaining = 1):
# Last loot drop — warn the crew!
HudMessage.Show(Agent, LastChanceText("⚠ Last treasure in this chest!"))
else:
HudMessage.Show(Agent, NormalText("💰 Loot grabbed!"))
# Fire the trigger so linked item-spawner devices react
ChestTrigger.Trigger(Agent)
Pattern 2 — Button with dynamic interaction time and disable/enable cycle
A cannon-loading button on the pirate ship takes longer to "load" each round, and is temporarily disabled while the cannon fires.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
cannon_loader := class(creative_device):
@editable
CannonButton : button_device = button_device{}
@editable
CannonTrigger : trigger_device = trigger_device{}
@editable
HudMessage : hud_message_device = hud_message_device{}
LoadingText<localizes>(S : string) : message = "{S}"
FiringText<localizes>(S : string) : message = "{S}"
var RoundNumber : int = 1
OnBegin<override>()<suspends> : void =
CannonButton.SetInteractionText(LoadingText("Hold to Load Cannon"))
CannonButton.SetInteractionTime(2.0)
# Unlimited uses — the cannon can fire all match
CannonButton.SetMaxTriggerCount(0)
CannonButton.InteractedWithEvent.Subscribe(OnCannonLoaded)
OnCannonLoaded(Agent : agent) : void =
# Disable button while cannon fires so crew can't double-load
CannonButton.Disable()
HudMessage.Show(Agent, FiringText("💥 FIRE!"))
CannonTrigger.Trigger(Agent)
# Increase load time each round (harder as game progresses)
set RoundNumber = RoundNumber + 1
NewTime := 2.0 + (RoundNumber * 0.5)
CannonButton.SetInteractionTime(NewTime)
# Re-enable after the cannon's transmit delay clears
CurrentDelay := CannonTrigger.GetTransmitDelay()
Sleep(CurrentDelay + 1.0)
CannonButton.Enable()
# Sleep requires a suspends context — spawn a task for the handler
OnBegin<override>()<suspends> : void =
CannonButton.SetInteractionText(LoadingText("Hold to Load Cannon"))
CannonButton.SetInteractionTime(2.0)
CannonButton.SetMaxTriggerCount(0)
CannonButton.InteractedWithEvent.Subscribe(OnCannonLoadedSpawn)
OnCannonLoadedSpawn(Agent : agent) : void =
spawn { ReloadSequence(Agent) }
ReloadSequence(Agent : agent)<suspends> : void =
CannonButton.Disable()
HudMessage.Show(Agent, FiringText("💥 FIRE!"))
CannonTrigger.Trigger(Agent)
set RoundNumber = RoundNumber + 1
NewTime := 2.0 + (RoundNumber * 0.5)
CannonButton.SetInteractionTime(NewTime)
CurrentDelay := CannonTrigger.GetTransmitDelay()
Sleep(CurrentDelay + 1.0)
CannonButton.Enable()
Pattern 3 — HUD message lifecycle: Show, SetText, ClearAllMessages
A lighthouse beacon on the clifftop cycles through three announcements, then clears everything when the round ends.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
lighthouse_announcer := class(creative_device):
@editable
RoundTrigger : trigger_device = trigger_device{}
@editable
Announcement : hud_message_device = hud_message_device{}
Msg<localizes>(S : string) : message = "{S}"
OnBegin<override>()<suspends> : void =
# Persist each message until we swap it
Announcement.SetDisplayTime(0.0)
RoundTrigger.TriggeredEvent.Subscribe(OnRoundEvent)
# Kick off the announcement cycle
spawn { AnnounceCycle() }
OnRoundEvent(MaybeAgent : ?agent) : void =
# When the round-end trigger fires, wipe all queued messages
Announcement.ClearAllMessages()
AnnounceCycle()<suspends> : void =
# Phase 1 — boarding
Announcement.SetText(Msg("🏴☠️ All pirates to the dock!"))
Announcement.Show()
Sleep(5.0)
# Phase 2 — treasure hunt begins
Announcement.SetText(Msg("⚓ The hunt for the Golden Anchor begins!"))
Announcement.Show()
Sleep(5.0)
# Phase 3 — warning
Announcement.SetText(Msg("⏳ 60 seconds remaining — find the vault!"))
Announcement.Show()
Sleep(60.0)
# Round over — hide and clear
Announcement.Hide()
Announcement.ClearAllMessages()
Gotchas
1. message ≠ string — always use a <localizes> helper
Methods like SetInteractionText, SetText, and HudMessage.Show(Agent, Message, ...) take a message type, not a raw string. There is no StringToMessage function. Declare a localizes helper:
MyText<localizes>(S : string) : message = "{S}"
// then pass:
Button.SetInteractionText(MyText("Hold to Open"))
Passing a bare string literal will produce a compile error.
2. TriggeredEvent sends ?agent, InteractedWithEvent sends agent
trigger_device.TriggeredEvent is listenable(?agent) — the agent is optional because the trigger can fire from code with no player. Always unwrap:
OnTriggered(MaybeAgent : ?agent) : void =
if (Agent := MaybeAgent?):
# safe to use Agent here
button_device.InteractedWithEvent is listenable(agent) — a player must interact, so no unwrap needed.
3. Interface methods need no override keyword — wrapper class methods do
When you provide a default implementation in an interface and a class wants to replace it, use <override>:
GetLabel() : override string = "My Custom Label"
Forgetting <override> when a default exists, or adding it when there's no default, both cause compile errors.
4. SetMaxTriggerCount clamps differently per device
button_device.SetMaxTriggerCountaccepts0–10000(0= unlimited).trigger_device.SetMaxTriggerCountclamps to[0, 20](0= unlimited).
Passing 50 to a trigger's SetMaxTriggerCount silently clamps to 20 — always check GetMaxTriggerCount() after setting if the exact value matters.
5. Handlers that need Sleep must be <suspends> — use spawn {}
Event handler callbacks (e.g. OnButtonPressed) are not <suspends> by default. If you need to Sleep inside a handler, spawn a separate async task:
OnButtonPressed(Agent : agent) : void =
spawn { DelayedReset(Agent) }
DelayedReset(Agent : agent)<suspends> : void =
Sleep(3.0)
DockButton.Enable()
6. @editable fields must be declared inside the class(creative_device) body
You cannot reference a placed device from a bare variable or from inside an interface. Only creative_device subclasses support @editable fields that bind to UEFN-placed instances. Interface implementors that need device references must receive them as constructor fields (as shown in button_activatable{Button := DockButton}).