Reference Verse compiles

implement interface: Contracts That Power Your Island Devices

Interfaces in Verse let you define a *contract* — a set of methods any class can promise to fulfill — so your game logic stays clean and extensible as your island grows. On a cel-shaded pirate cove dock, a rusty lever button, a hidden trigger plate, and a glowing HUD banner all need to cooperate: interfaces are the glue that makes that possible without tangling your code. Master `implement interface` and you'll write Verse that scales from one dock puzzle to a whole archipelago of challenges.

Updated Examples verified on the live UEFN compiler

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/else type 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. messagestring — 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.SetMaxTriggerCount accepts 010000 (0 = unlimited).
  • trigger_device.SetMaxTriggerCount clamps 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}).

Guides & scripts that use button_device

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

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