Overview
The class_selector_ui_device presents a player with a menu of your island's configured Classes and lets them pick one. Think of a pre-game lobby where each player chooses Medic, Scout, or Tank before a round starts — that's exactly the job this device does.
You reach for it whenever a player needs to choose a class (as opposed to being force-assigned one). Three things make it useful from Verse:
- You can Show the UI to a specific agent on demand (e.g. when they step on a plate or press a button).
- You can Enable/Disable the device to gate when class-switching is allowed (open during lobby, locked once the round starts).
- You get events the instant a player opens the UI, selects a class, changes class (with the class index), or closes the UI — so you can grant gear, start a timer, or update a scoreboard.
The device is configured in UEFN (which classes appear, their order, the visuals). Your Verse code drives when it shows and what happens after a choice.
API Reference
class_selector_ui_device
Used to allow players to select their Class from a Class Selector UI.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
class_selector_ui_device<public> := class<concrete><final>(creative_device_base):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
ClassSelectedEvent |
ClassSelectedEvent<public>:listenable(agent) |
Signaled when an agent selects a class. Sends the agent that selected a class. |
ClassChangedEvent |
ClassChangedEvent<public>:listenable(tuple(agent, int)) |
Signaled when an agent changes a class. Sends the agent that selected a class and the int class that the player has changed to. |
UIClosedEvent |
UIClosedEvent<public>:listenable(agent) |
Signaled when an agent closes the UI. Sends the agent that closed the UI. |
UIOpenedEvent |
UIOpenedEvent<public>:listenable(agent) |
Signaled when the UI is opened. Sends the agent that is responsible for opening the UI. |
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. |
Show |
Show<public>(Agent:agent):void |
Show the Class Selector UI to Agent. |
Walkthrough
Scenario: A lobby room. When a player walks onto a trigger pad, we pop the class selector for them. When they pick a class, we confirm it on the HUD and remember which class index they chose; if they close the menu without committing, we just nudge them. Once a player has selected, we don't need to keep the device enabled for them, but we leave it enabled so others can still choose.
class_selector_lobby := class(creative_device):
@editable
ClassUI : class_selector_ui_device = class_selector_ui_device{}
@editable
EntryPad : trigger_device = trigger_device{}
@editable
StatusHud : hud_message_device = hud_message_device{}
# Localized text helper — message params need a localized value, not a raw string.
InfoText<localizes>(S : string) : message = "{S}"
OnBegin<override>()<suspends> : void =
# Make sure the selector is on so Show() and events work.
ClassUI.Enable()
# When a player steps on the pad, pop the class menu for THAT player.
EntryPad.TriggeredEvent.Subscribe(OnPadStepped)
# React to the four lifecycle events of the UI.
ClassUI.UIOpenedEvent.Subscribe(OnUIOpened)
ClassUI.ClassSelectedEvent.Subscribe(OnClassSelected)
ClassUI.ClassChangedEvent.Subscribe(OnClassChanged)
ClassUI.UIClosedEvent.Subscribe(OnUIClosed)
# TriggeredEvent hands us a ?agent — unwrap before using.
OnPadStepped(Agent : ?agent) : void =
if (Player := Agent?):
ClassUI.Show(Player)
# Fired when the menu appears for a player.
OnUIOpened(Player : agent) : void =
StatusHud.Show(Player)
# Fired when a player commits a class selection.
OnClassSelected(Player : agent) : void =
StatusHud.Show(Player)
# Fired when a player switches class — gives us the class index too.
OnClassChanged(Result : tuple(agent, int)) : void =
Player := Result(0)
ClassIndex := Result(1)
Print("Player changed to class index {ClassIndex}")
StatusHud.Show(Player)
# Fired when the player dismisses the menu.
OnUIClosed(Player : agent) : void =
Print("UI closed by a player")
Line by line:
@editable ClassUI : class_selector_ui_device = class_selector_ui_device{}— declares the field you bind to the placed device in UEFN. You must have this field; calling a bareclass_selector_ui_device.Show()fails with Unknown identifier.EntryPad,StatusHud— the trigger pad and HUD message device that drive and confirm the flow.InfoText<localizes>— a localized-text helper. Anymessageparameter (like HUD text in UEFN) needs a localized value; there is noStringToMessage.ClassUI.Enable()inOnBegin— turns the device on. A disabled device won't show or fire events.EntryPad.TriggeredEvent.Subscribe(OnPadStepped)— wires the pad to our handler.TriggeredEventis alistenable(?agent).- Each
ClassUI.<Event>.Subscribe(...)line hooks one of the device's four events to a method. OnPadStepped—TriggeredEventgives a?agent; we unwrap withif (Player := Agent?)and thenClassUI.Show(Player)to pop the menu for just that player.OnClassChanged(Result : tuple(agent, int))—ClassChangedEventsends a tuple, so we read the agent withResult(0)and the class index withResult(1).
Common patterns
Lock class switching once the round starts (Disable). Open the lobby during warmup, then disable the selector so no one can swap mid-match.
round_class_lock := class(creative_device):
@editable
ClassUI : class_selector_ui_device = class_selector_ui_device{}
@editable
StartButton : button_device = button_device{}
OnBegin<override>()<suspends> : void =
# Warmup: players may freely pick.
ClassUI.Enable()
# When the host presses the start button, lock in classes.
StartButton.InteractedWithEvent.Subscribe(OnRoundStart)
OnRoundStart(Agent : agent) : void =
# No more class swapping after this point.
ClassUI.Disable()
Print("Classes locked — round begins!")
Grant gear when a class is committed (ClassSelectedEvent). Hand the player a weapon the moment they confirm a class.
class_loadout_granter := class(creative_device):
@editable
ClassUI : class_selector_ui_device = class_selector_ui_device{}
@editable
WeaponGranter : item_granter_device = item_granter_device{}
OnBegin<override>()<suspends> : void =
ClassUI.Enable()
ClassUI.ClassSelectedEvent.Subscribe(OnSelected)
OnSelected(Player : agent) : void =
# Reward the player for choosing by handing them their kit.
WeaponGranter.GrantItem(Player)
Track which class each player runs (ClassChangedEvent index). The tuple's int is the class index — perfect for counting team composition.
class_tracker := class(creative_device):
@editable
ClassUI : class_selector_ui_device = class_selector_ui_device{}
OnBegin<override>()<suspends> : void =
ClassUI.Enable()
ClassUI.ClassChangedEvent.Subscribe(OnChanged)
OnChanged(Result : tuple(agent, int)) : void =
ClassIndex := Result(1)
if (ClassIndex = 0):
Print("A player joined the Tank class")
else if (ClassIndex = 1):
Print("A player joined the Medic class")
else:
Print("A player picked class {ClassIndex}")
Gotchas
- You must declare an
@editablefield and bind it in UEFN. A bareclass_selector_ui_device.Show()won't compile — the device only exists through your field. Showneeds a concreteagent, not a?agent. Events that hand you a?agent(like a trigger'sTriggeredEvent) must be unwrapped:if (Player := Agent?): ClassUI.Show(Player).ClassChangedEventis atuple(agent, int), not a plain agent. Read the agent withResult(0)and the class index withResult(1).ClassSelectedEvent,UIOpenedEvent, andUIClosedEventeach hand a plainagent.- A disabled device is silent.
Show()won't display and no events fire while the device is disabled. CallEnable()(typically inOnBegin) before relying on it. messageparameters need localized values. For HUD text or anymessagefield, declare a<localizes>helper likeInfoText<localizes>(S:string):message = "{S}"— there is noStringToMessage.- Verse doesn't auto-convert int↔float. The class index from
ClassChangedEventis anint; if you need it as a float for some math, convert explicitly. - Class definitions live in UEFN, not Verse. Which classes appear, their order, and the index numbers are set on the device in the editor — your code reacts to indices it doesn't itself define.