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.
Quick Quiz
5 quick questions. Pick an answer to see if you've got it.
-
1.
-
2.
-
3.
-
4.
-
5.
Recap
One step closer to Shell-Hunt β The 'Start Hunt' button on the galleon deck that kicks off a round
Sources
/guides/button-deviceΒ© Biloxi Studios Inc. β original Verse Island content.
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.