First Contact: the Button Device + Your First Verse Script

In this lesson you'll learn to

  • Student can add a creative_device Verse file, use @editable to reference a button_device, and Subscribe to InteractedWithEvent with an OnBegin handler that Prints.

Student can add a creative_device Verse file, use @editable to reference a button_device, and Subscribe to InteractedWithEvent with an OnBegin handler that Prints.

🧩 Your capstone piece: The 'Start Hunt' button on the galleon deck that kicks off a round

Walkthrough

Overview

The button_device represents a physical interactable button placed in your island. When a player walks up to it and holds the interact key for the configured duration, the button fires InteractedWithEvent and sends the agent who pressed it.

When to reach for it:

  • A vault door that only opens after a player presses a button for 3 seconds
  • A one-time-use switch that arms a trap and then permanently disables itself
  • A puzzle where the prompt text changes dynamically to hint at the next step
  • Any situation where you need player-initiated, hold-to-confirm input

Unlike a trigger plate (which fires automatically on contact), the button requires deliberate player action and supports configurable hold time β€” making it ideal for high-stakes or gated interactions.

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.

Walkthrough

Scenario: A Vault Door with a Timed Button and a Use Limit

You have a vault door made from a barrier device. Players must hold a button for 2 seconds to open it, but the button only works 3 times total (simulating a limited power supply). After each press, the prompt updates to show how many uses remain. On the final press, the button disables itself permanently.

Place a button_device, a barrier_device, and this Verse device in your island. Wire them up via the @editable fields in the Details panel.

vault_button_device := class(creative_device):

    # Wire this to the Button device placed in your level
    @editable
    VaultButton : button_device = button_device{}

    # Wire this to the Barrier device acting as the vault door
    @editable
    VaultDoor : barrier_device = barrier_device{}

    # Localized helper so we can pass dynamic strings as 'message'
    UpdatedPrompt<localizes>(S : string) : message = "{S}"

    OnBegin<override>()<suspends> : void =
        # Set a 2-second hold time β€” players must commit to opening the vault
        VaultButton.SetInteractionTime(2.0)

        # Cap the button at 3 total uses
        VaultButton.SetMaxTriggerCount(3)

        # Give players a clear initial prompt
        VaultButton.SetInteractionText(UpdatedPrompt("Open Vault (3 uses left)"))

        # Subscribe: every successful interaction calls OnVaultButtonPressed
        VaultButton.InteractedWithEvent.Subscribe(OnVaultButtonPressed)

    OnVaultButtonPressed(Interactor : agent) : void =
        # Disable the barrier so the vault door opens
        VaultDoor.Disable()

        # Check how many presses remain after this one
        Remaining := VaultButton.GetTriggerCountRemaining()

        if (Remaining > 0):
            # Update the prompt to reflect remaining uses
            VaultButton.SetInteractionText(UpdatedPrompt("Open Vault ({Remaining} uses left)"))
        else:
            # No uses left β€” disable the button so it can't be interacted with
            VaultButton.Disable()
            VaultButton.SetInteractionText(UpdatedPrompt("Vault Power Depleted"))

Line-by-line breakdown:

Line What it does
@editable VaultButton Declares the button reference so you can assign the placed device in the UEFN Details panel. Without @editable and the class field, the identifier is unknown at compile time.
UpdatedPrompt<localizes> Verse's SetInteractionText takes a message (a localized type), not a raw string. This helper wraps any string into a message.
SetInteractionTime(2.0) Players must hold the button for 2 full seconds β€” prevents accidental presses near the vault.
SetMaxTriggerCount(3) The button auto-disables after 3 successful interactions. Pass 0 for unlimited.
SetInteractionText(...) Updates the floating prompt players see when they look at the button.
InteractedWithEvent.Subscribe(OnVaultButtonPressed) Registers the handler. The event fires only after the full hold time completes.
GetTriggerCountRemaining() Returns how many presses are left. Returns 0 when GetMaxTriggerCount() is 0 (unlimited).
VaultButton.Disable() Prevents any further interaction once the power is gone.

Common patterns

Pattern 1 β€” Toggle a device on/off with a single button

A player presses a button to toggle a storm controller on and off. Demonstrates Enable / Disable and the InteractedWithEvent handler pattern.

storm_toggle_device := class(creative_device):

    @editable
    ToggleButton : button_device = button_device{}

    @editable
    StormController : storm_controller_device = storm_controller_device{}

    PromptOn<localizes>(S : string) : message = "{S}"

    var StormActive : logic = false

    OnBegin<override>()<suspends> : void =
        ToggleButton.SetInteractionText(PromptOn("Activate Storm"))
        ToggleButton.InteractedWithEvent.Subscribe(OnTogglePressed)

    OnTogglePressed(Interactor : agent) : void =
        if (StormActive?):
            StormController.Deactivate()
            set StormActive = false
            ToggleButton.SetInteractionText(PromptOn("Activate Storm"))
        else:
            StormController.Activate()
            set StormActive = true
            ToggleButton.SetInteractionText(PromptOn("Deactivate Storm"))

Pattern 2 β€” Read interaction time and max count for a HUD display

Before the round starts, query the button's configured values and print them to the log for debugging or to feed into a UI widget. Demonstrates GetInteractionTime and GetMaxTriggerCount.

button_info_logger := class(creative_device):

    @editable
    InfoButton : button_device = button_device{}

    OnBegin<override>()<suspends> : void =
        HoldTime := InfoButton.GetInteractionTime()
        MaxUses  := InfoButton.GetMaxTriggerCount()

        # MaxUses == 0 means unlimited
        if (MaxUses = 0):
            Print("Button hold time: {HoldTime}s | Uses: unlimited")
        else:
            Print("Button hold time: {HoldTime}s | Max uses: {MaxUses}")

        InfoButton.InteractedWithEvent.Subscribe(OnInfoButtonPressed)

    OnInfoButtonPressed(Interactor : agent) : void =
        Remaining := InfoButton.GetTriggerCountRemaining()
        Print("Button pressed. Uses remaining: {Remaining}")

Pattern 3 β€” Race two buttons: first player to press wins

Two teams compete to press their respective buttons first. Uses race so whichever button fires first wins and the other is immediately disabled. Demonstrates Disable and concurrent event awaiting.

first_press_wins_device := class(creative_device):

    @editable
    RedButton  : button_device = button_device{}

    @editable
    BlueButton : button_device = button_device{}

    @editable
    RedReward  : item_granter_device = item_granter_device{}

    @editable
    BlueReward : item_granter_device = item_granter_device{}

    WinText<localizes>(S : string) : message = "{S}"

    OnBegin<override>()<suspends> : void =
        RedButton.SetInteractionText(WinText("Press for Red Team!"))
        BlueButton.SetInteractionText(WinText("Press for Blue Team!"))

        # Race: whichever branch completes first wins; the other is cancelled
        race:
            block:
                RedWinner := RedButton.InteractedWithEvent.Await()
                BlueButton.Disable()
                RedReward.GrantItem(RedWinner)
                RedButton.SetInteractionText(WinText("Red Team Wins!"))
            block:
                BlueWinner := BlueButton.InteractedWithEvent.Await()
                RedButton.Disable()
                BlueReward.GrantItem(BlueWinner)
                BlueButton.SetInteractionText(WinText("Blue Team Wins!"))

Gotchas

1. @editable is mandatory β€” you cannot construct a live device in code

You must declare button_device as an @editable field inside your class(creative_device). Writing MyButton := button_device{} inside OnBegin gives you a disconnected default object β€” it is not the device placed in your level and none of its events will fire.

# WRONG β€” this is a blank object, not your placed device
OnBegin<override>()<suspends> : void =
    MyButton := button_device{}
    MyButton.InteractedWithEvent.Subscribe(OnPressed)  # never fires

# RIGHT β€” declare at class scope
@editable
MyButton : button_device = button_device{}

2. SetInteractionText requires a message, not a string

The parameter type is message (a localized type). Passing a raw string literal causes a compile error. Always wrap with a <localizes> helper:

# WRONG
MyButton.SetInteractionText("Open Door")  # compile error: string β‰  message

# RIGHT
DoorPrompt<localizes>(S : string) : message = "{S}"
MyButton.SetInteractionText(DoorPrompt("Open Door"))

3. GetTriggerCountRemaining returns 0 for unlimited buttons

If GetMaxTriggerCount() returns 0 (meaning no limit), then GetTriggerCountRemaining() also returns 0 β€” not a large number. Always check GetMaxTriggerCount() first before interpreting the remaining count:

if (MyButton.GetMaxTriggerCount() = 0):
    # Unlimited β€” remaining count is meaningless
else:
    Remaining := MyButton.GetTriggerCountRemaining()
    # Safe to use Remaining here

4. InteractedWithEvent fires AFTER the full hold time

The event does not fire on button-down β€” it fires only after the player holds for the full GetInteractionTime() duration. If you call SetInteractionTime(5.0), your handler won't run until 5 seconds of uninterrupted holding. Design your UX accordingly β€” if the time is too long, players will assume the button is broken.

5. SetMaxTriggerCount clamps to 0–10000

Passing a value outside [0, 10000] is clamped. Pass 0 explicitly for unlimited. There is no -1 sentinel β€” using negative values is undefined behavior.

6. Event handlers are class-scope methods, not lambdas

Subscribe takes a method reference. Define your handler as a method on the class (not inside OnBegin) and pass it by name:

# WRONG β€” anonymous function inside OnBegin won't compile as a subscriber
MyButton.InteractedWithEvent.Subscribe((A : agent) => { ... })

# RIGHT β€” class-scope method
OnPressed(Interactor : agent) : void =
    # your logic here

OnBegin<override>()<suspends> : void =
    MyButton.InteractedWithEvent.Subscribe(OnPressed)
πŸ΄β€β˜ οΈ

Take on the Challenge

Pass the 5-question challenge to master this lesson and chart the quest in your journal.

Recap

One step closer to Shell-Hunt β€” The 'Start Hunt' button on the galleon deck that kicks off a round

Pass the quiz above to chart this quest in your Journal.
Source
Verse Island

Β© Biloxi Studios Inc. β€” original Verse Island content.

Sources & licensing

Turn this into a guided course

Add First Contact: the Button Device + Your First Verse Script to your free study plan β€” we'll suggest related pages and stitch the lot into one compile-checked, self-guided lesson with worked examples and quizzes.

🧭 The Keeper's log

Quest complete? Chart your next heading from the 🦩 South Shores (Free) expedition.