button_device: Interactive Buttons That Drive Game Logic
The `button_device` is one of the most versatile tools in UEFN — it lets players physically interact with a prop to trigger anything from opening a vault door to activating a boss encounter. In Verse you can subscribe to its `InteractedWithEvent`, dynamically change its prompt text, tune how long a player must hold it, and cap how many times it can be used before it locks out. This article covers every method and event on `button_device` with real, compilable examples.
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)
Guides & scripts that use button_device
- Spawning Dynamic Text Widgets on the Player UI with VerseBring your UEFN island to life by adding live text widgets directly to a player's UI at runtime. In this guide you'll subscribe to a button interaction, safely resolve the player's UI layer, add a canvas-based text displ…
- Toggle Prop Visibility with a Verse Trigger VolumeUnlock dynamic environmental storytelling by hiding and revealing a prop on command. This tutorial wires a Verse device to a trigger volume so that each time a player enters, a target prop flips between hidden and visibl…
- Minimize UEFN Verse Memory Allocations and Cell UsageHitting the UEFN memory cell limit can freeze your editor and block publishing. The fix isn't magic—it's disciplined Verse: allocate objects once, reuse mutable variables, and handle fallible calls safely. This guide sho…
- Toggle Rig State and Show Live HUD Feedback in VerseWant to script dynamic animation state and keep the player informed? This guide shows how to model an IK/FK rig toggle in Verse and reflect that state live on the player's HUD. You'll wire a button to a toggle handler, t…
- Verse Field Event Parameters: Wiring Sliders & Buttons to VerseThis tutorial demonstrates how to build a dynamic UI system in UEFN using Verse Field Event Parameters. The creator walks through creating a Widget Blueprint with a slider and numbered buttons, configuring event paramete…
- QUESTS API in Fortnite Creative/UEFN! (Experimental Tutorial)This tutorial demonstrates how to build a complete Quest Management system using Fortnite's experimental Verse Quests API. The creator walks through importing assets, defining custom quest classes inheriting from basic_q…
Build your own lesson with button_device
Generate a personalized, compile-checked lesson grounded in this exact reference.
Build a lesson →