Overview
The vote option device represents a single choice in a poll — for example the "Desert Map" option in a "Where do we play next?" vote. It never works alone: every option belongs to a vote_group_device, which represents the question itself and owns the overall state of the poll (when voting starts, the time limit, who's tied for first).
Reach for these two devices whenever you want players to collectively pick an outcome: choosing the next mini-game, voting a player off, deciding the difficulty, or running an in-game survey. The pattern is always:
- The group calls
BeginVote()to open the poll. - Each option lets agents
CastVote(Agent). - You listen to
CastVoteEventto update UI, and readGetVoteCount()to tally. - When the group fires
EndVoteEvent(orVoteTiedEvent), you act on the winner.
Because vote_option_device implements vote_option_interface, the casting/counting methods live on the OPTION, while the poll lifecycle (begin/end/tie) lives on the GROUP. You'll usually have one editable vote_group_device field and several editable vote_option_device fields in the same script.
API Reference
vote_option_device
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
vote_option_device<public> := class(vote_option_interface, creative_device_base):
Walkthrough
Let's build a "Pick the next round" poll. We place one vote_group_device and two vote_option_devices (Desert and Snow) in the level, plus a trigger that opens the poll when a host steps on a plate. When a player interacts with an option device in the world it normally casts the vote automatically, but here we also expose a manual CastVote path and react to every ballot.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/Diagnostics }
next_round_vote := class(creative_device):
# The poll itself — owns BeginVote / EndVoteEvent / VoteTiedEvent.
@editable
VoteGroup : vote_group_device = vote_group_device{}
# Each option is a separate placed device.
@editable
DesertOption : vote_option_device = vote_option_device{}
@editable
SnowOption : vote_option_device = vote_option_device{}
# A plate the host steps on to open the poll.
@editable
StartPlate : trigger_device = trigger_device{}
# Localized helper so we can pass strings as `message`.
Banner<localizes>(S : string) : message = "{S}"
OnBegin<override>()<suspends>:void =
# Open the poll when the host steps on the start plate.
StartPlate.TriggeredEvent.Subscribe(OnStartPressed)
# React every time ANY ballot lands on either option.
DesertOption.CastVoteEvent.Subscribe(OnDesertVote)
SnowOption.CastVoteEvent.Subscribe(OnSnowVote)
# The group tells us when voting closes or ties.
VoteGroup.EndVoteEvent.Subscribe(OnVoteEnded)
VoteGroup.VoteTiedEvent.Subscribe(OnVoteTied)
OnStartPressed(Agent : ?agent) : void =
# BeginVote opens the poll and starts the timer if a time limit is set.
VoteGroup.BeginVote()
Print("Voting is now OPEN!")
OnDesertVote(Agent : agent) : void =
Print("A vote landed on Desert. Total now: {DesertOption.GetVoteCount()}")
OnSnowVote(Agent : agent) : void =
Print("A vote landed on Snow. Total now: {SnowOption.GetVoteCount()}")
OnVoteEnded(Result : tuple()) : void =
DesertCount := DesertOption.GetVoteCount()
SnowCount := SnowOption.GetVoteCount()
if (DesertCount > SnowCount):
Print("Winner: DESERT with {DesertCount} votes")
else if (SnowCount > DesertCount):
Print("Winner: SNOW with {SnowCount} votes")
else:
Print("It's a tie!")
OnVoteTied(Tie : tuple()) : void =
Print("The poll ended in a tie — re-running.")
VoteGroup.BeginVote()
Line by line:
- The three device fields (
VoteGroup,DesertOption,SnowOption) MUST be@editableand you bind each to a placed device in the Details panel — a barevote_option_device{}.CastVote()would fail with Unknown identifier. - In
OnBeginwe subscribe handlers. NoticeCastVoteEventis alistenable(agent), so its handler takes(Agent : agent)directly (not?agent). TheTriggeredEventon the plate hands a?agent, so that handler unwraps differently. OnStartPressedcallsVoteGroup.BeginVote()— this is what actually opens the poll. UntilBeginVoteruns,CastVoteon any option will fail.OnDesertVote/OnSnowVotefire once per cast. We callGetVoteCount()on the option to read the running tally.OnVoteEndedreceives the group'sEndVoteEvent(atuple()payload) and compares the twoGetVoteCount()totals to declare a winner.OnVoteTiedre-opens the poll by callingBeginVote()again.
Common patterns
Casting a vote manually with CastVote
CastVote is <decides> — it fails if the agent has no votes left or the poll hasn't started. Always call it inside an if.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
force_vote_device := class(creative_device):
@editable
VoteGroup : vote_group_device = vote_group_device{}
@editable
Option : vote_option_device = vote_option_device{}
@editable
VoteButton : button_device = button_device{}
OnBegin<override>()<suspends>:void =
VoteGroup.BeginVote()
VoteButton.InteractedWithEvent.Subscribe(OnPressed)
OnPressed(Agent : agent) : void =
# CastVote can fail, so guard it.
if (Option.CastVote[Agent]):
Print("Vote accepted!")
else:
Print("Vote rejected — no votes left or poll not open.")
Showing the option's description text
GetOptionDescription() returns a message you can drop straight onto a player's HUD widget.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/UI }
show_option_label := class(creative_device):
@editable
Option : vote_option_device = vote_option_device{}
@editable
RevealButton : button_device = button_device{}
OnBegin<override>()<suspends>:void =
RevealButton.InteractedWithEvent.Subscribe(OnReveal)
OnReveal(Agent : agent) : void =
if (PlayerUI := GetPlayerUI[player[Agent]]):
Label := text_block{DefaultText := Option.GetOptionDescription()}
PlayerUI.AddWidget(Label)
Listing every option in a group with GetVotingOptions
The group can enumerate its options as a []vote_option_interface, so you can tally the whole poll generically.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
tally_all_options := class(creative_device):
@editable
VoteGroup : vote_group_device = vote_group_device{}
OnBegin<override>()<suspends>:void =
VoteGroup.BeginVote()
VoteGroup.EndVoteEvent.Subscribe(OnEnded)
OnEnded(Result : tuple()) : void =
Options := VoteGroup.GetVotingOptions()
var Best : int = -1
for (Opt : Options):
Count := Opt.GetVoteCount()
if (Count > Best):
set Best = Count
Print("Highest vote total in this poll: {Best}")
Gotchas
- The option counts; the group controls the poll.
CastVote,CastVoteEvent,GetVoteCount, andGetOptionDescriptionlive on the OPTION.BeginVote,EndVoteEvent,VoteTiedEvent, andGetVotingOptionslive on the GROUP. Mixing them up is the #1 mistake. CastVoteis<decides>— guard it. CallingOption.CastVote(Agent)directly is a compile error; useif (Option.CastVote[Agent]):. It fails (not crashes) when the agent is out of votes orBeginVotehasn't been called yet.- You must call
BeginVotefirst. No votes can be cast — andCastVoteEventnever fires — until the group's poll is open. CastVoteEventhands a plainagent, not?agent. Alistenable(agent)handler takes(Agent : agent)directly. Only events like the trigger'sTriggeredEventgive you a?agentyou must unwrap withif (A := Agent?):.GetOptionDescriptionreturnsmessage, notstring. Pass it straight to widgets or HUD message fields. If you want a custom literal, build one with a<localizes>helper — there is noStringToMessage.EndVoteEvent/VoteTiedEventpayloads aretuple(). They carry no useful data; read the winner yourself viaGetVoteCount()on each option.- Bind every editable device. An unbound
vote_option_device{}field will not point at your placed device and your votes silently go nowhere.