Overview
The Vote Group device (vote_group_device) represents a single poll — the question. Think of a poll like “What map do we play next?” The group owns the overall state of the poll: when voting opens, when it closes, and which options belong to it.
The actual answers are separate Vote Option devices, each implementing vote_option_interface. Each option knows how to cast a vote for an agent, count its votes, and fire an event when a ballot lands on it.
Reach for this device when you want a structured, player-driven decision: choosing the next round’s game mode, letting a lobby pick a boss difficulty, or a quick “yes/no” to skip a cutscene. The group is the orchestrator; the options are the buttons players actually pick.
The key API split:
vote_group_device—BeginVote()opens voting;BeginVoteEventfires when it starts.vote_option_interface(the options) —CastVote[Agent]records a ballot,CastVoteEventfires per vote,GetVoteCount()reads the tally,GetOptionDescription()returns the displayed label.
API Reference
vote_group_device
Represents a poll. For example, in a poll “What to have for lunch?” the group represents the question and contains the possible answers. Owns the overall state of the poll and keeps track of available options. A group can have multiple options, connected via an internal ID.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
vote_group_device<public> := class<concrete><final>(creative_device_base):
Walkthrough
Let’s build a “What’s for lunch?” poll. We place one Vote Group device and two Vote Option devices (Pizza and Tacos) in the level and reference all three from a Verse device. When the match begins we open voting, then we log every ballot as it comes in and announce the winner when the trigger fires.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/UI }
lunch_poll := class(creative_device):
# The poll itself — the question "What's for lunch?"
@editable
VoteGroup : vote_group_device = vote_group_device{}
# The two answer options. These are Vote Option devices in the level.
@editable
PizzaOption : vote_option_device = vote_option_device{}
@editable
TacosOption : vote_option_device = vote_option_device{}
# A trigger a host steps on to close voting and announce the winner.
@editable
CloseTrigger : trigger_device = trigger_device{}
MyText<localizes>(S : string) : message = "{S}"
OnBegin<override>()<suspends> : void =
# Open the poll so options can accept ballots.
VoteGroup.BeginVote()
# React when each option receives a vote.
PizzaOption.CastVoteEvent.Subscribe(OnPizzaVote)
TacosOption.CastVoteEvent.Subscribe(OnTacosVote)
# Close + announce when the host triggers.
CloseTrigger.TriggeredEvent.Subscribe(OnCloseVote)
OnPizzaVote(Agent : agent) : void =
Print("A ballot landed on Pizza! Total: {PizzaOption.GetVoteCount()}")
OnTacosVote(Agent : agent) : void =
Print("A ballot landed on Tacos! Total: {TacosOption.GetVoteCount()}")
OnCloseVote(MaybeAgent : ?agent) : void =
Pizza := PizzaOption.GetVoteCount()
Tacos := TacosOption.GetVoteCount()
if (Pizza > Tacos):
Print("Pizza wins with {Pizza} votes!")
else if (Tacos > Pizza):
Print("Tacos win with {Tacos} votes!")
else:
Print("It's a tie at {Pizza} each!")
Line by line:
- The three
@editabledevice fields (VoteGroup,PizzaOption,TacosOption) plusCloseTriggerlet you drag the placed devices onto this Verse device in the Details panel. Without these fields you cannot call the devices at all. MyText<localizes>is the helper you need whenever an API wants amessageinstead of a rawstring. We keep it ready for HUD/UI use.- In
OnBegin,VoteGroup.BeginVote()opens the poll. Until this runs,CastVoteon any option fails — the group must have started voting. - We
Subscribeto each option’sCastVoteEvent. Alistenable(agent)hands the handler anagentdirectly, soOnPizzaVote(Agent : agent)matches the signature. GetVoteCount()returns aninttotal for that option — we print the running tally each time a ballot arrives.OnCloseVoteis aTriggeredEventhandler. Trigger events pass?agent(optional), so the parameter isMaybeAgent : ?agent. We don’t need the agent here, so we ignore it and just compare the two totals to declare a winner.
Common patterns
1. Manually casting a vote in code (CastVote)
Votes usually come from players interacting with the option in-game, but you can also cast one programmatically — for example, an NPC ally or a default fallback vote. CastVote is a <decides> function, so it can fail (no votes left, or voting not open) and must be called in a failure context like if.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
auto_voter := class(creative_device):
@editable
VoteGroup : vote_group_device = vote_group_device{}
@editable
DefaultOption : vote_option_device = vote_option_device{}
@editable
StartPlate : trigger_device = trigger_device{}
OnBegin<override>()<suspends> : void =
VoteGroup.BeginVote()
StartPlate.TriggeredEvent.Subscribe(OnPlate)
OnPlate(MaybeAgent : ?agent) : void =
if (Agent := MaybeAgent?):
# Try to record a ballot for this agent on the default option.
if (DefaultOption.CastVote[Agent]):
Print("Default vote cast successfully")
else:
Print("Could not cast vote (no votes left or poll closed)")
2. Showing each option’s label (GetOptionDescription)
GetOptionDescription() returns the message text shown to players for that option. Handy for building a summary HUD or logging what the choices are when voting opens.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
poll_announcer := class(creative_device):
@editable
VoteGroup : vote_group_device = vote_group_device{}
@editable
OptionA : vote_option_device = vote_option_device{}
@editable
OptionB : vote_option_device = vote_option_device{}
OnBegin<override>()<suspends> : void =
# Announce the choices, then open the poll.
Print("Vote now! Option A and Option B are available.")
VoteGroup.BeginVote()
# Read back each option's display label.
DescA : message = OptionA.GetOptionDescription()
DescB : message = OptionB.GetOptionDescription()
Print("Options ready to receive votes.")
3. Reacting when the poll opens (BeginVoteEvent)
The group fires BeginVoteEvent when voting starts. Subscribe to it to kick off a countdown, play a sound, or reveal the option buttons — instead of hard-coding logic right after your BeginVote() call.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
poll_opener := class(creative_device):
@editable
VoteGroup : vote_group_device = vote_group_device{}
@editable
StartTrigger : trigger_device = trigger_device{}
OnBegin<override>()<suspends> : void =
# Listen for the poll opening.
VoteGroup.BeginVoteEvent.Subscribe(OnVoteOpened)
# Let a host open the poll by stepping on a plate.
StartTrigger.TriggeredEvent.Subscribe(OnStart)
OnStart(MaybeAgent : ?agent) : void =
VoteGroup.BeginVote()
OnVoteOpened() : void =
Print("The poll is now open — cast your votes!")
Gotchas
- You must call
BeginVote()before any vote counts.CastVote[Agent]fails if the group hasn’t started voting yet. If players “vote” but nothing registers, check thatBeginVote()actually ran. CastVoteis<decides>— it can fail (agent out of votes, poll not open) and therefore MUST be called inside a failure context, e.g.if (Option.CastVote[Agent]):. Calling it bare won’t compile.- The group and the options are different devices.
vote_group_deviceowns the poll state but you cast/count votes on the Vote Option devices viavote_option_interface. Reference both in your Verse device. CastVoteEventislistenable(agent), buttrigger.TriggeredEventislistenable(?agent). The first hands your handler a plainagent; the second hands an?agentyou must unwrap withif (A := MaybeAgent?):. Mixing them up causes a signature mismatch.GetOptionDescription()returns amessage, not astring. Don’t try to concatenate it like a string — pass it to UI/HUD APIs that acceptmessage. To build your ownmessagefrom a string, use a<localizes>helper; there is noStringToMessage.GetVoteCount()returns anint. Verse does not auto-convert int↔float, so don’t mix it into float math without an explicit conversion.