Reference Devices compiles

vote_option_device: Player-Driven Polls in Verse

Want your players to decide which map loads next, what the boss does, or where the loot drops? The vote_option_device is one answer in a poll managed by a vote_group_device. In this article you'll wire up a real 'What's for the next round?' vote, count the ballots, and react to the winner — all in Verse.

Updated Examples verified on the live UEFN compiler
Watch the Knotvote_option_device in ~90 seconds.

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:

  1. The group calls BeginVote() to open the poll.
  2. Each option lets agents CastVote(Agent).
  3. You listen to CastVoteEvent to update UI, and read GetVoteCount() to tally.
  4. When the group fires EndVoteEvent (or VoteTiedEvent), 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 @editable and you bind each to a placed device in the Details panel — a bare vote_option_device{}.CastVote() would fail with Unknown identifier.
  • In OnBegin we subscribe handlers. Notice CastVoteEvent is a listenable(agent), so its handler takes (Agent : agent) directly (not ?agent). The TriggeredEvent on the plate hands a ?agent, so that handler unwraps differently.
  • OnStartPressed calls VoteGroup.BeginVote() — this is what actually opens the poll. Until BeginVote runs, CastVote on any option will fail.
  • OnDesertVote / OnSnowVote fire once per cast. We call GetVoteCount() on the option to read the running tally.
  • OnVoteEnded receives the group's EndVoteEvent (a tuple() payload) and compares the two GetVoteCount() totals to declare a winner.
  • OnVoteTied re-opens the poll by calling BeginVote() 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, and GetOptionDescription live on the OPTION. BeginVote, EndVoteEvent, VoteTiedEvent, and GetVotingOptions live on the GROUP. Mixing them up is the #1 mistake.
  • CastVote is <decides> — guard it. Calling Option.CastVote(Agent) directly is a compile error; use if (Option.CastVote[Agent]):. It fails (not crashes) when the agent is out of votes or BeginVote hasn't been called yet.
  • You must call BeginVote first. No votes can be cast — and CastVoteEvent never fires — until the group's poll is open.
  • CastVoteEvent hands a plain agent, not ?agent. A listenable(agent) handler takes (Agent : agent) directly. Only events like the trigger's TriggeredEvent give you a ?agent you must unwrap with if (A := Agent?):.
  • GetOptionDescription returns message, not string. Pass it straight to widgets or HUD message fields. If you want a custom literal, build one with a <localizes> helper — there is no StringToMessage.
  • EndVoteEvent / VoteTiedEvent payloads are tuple(). They carry no useful data; read the winner yourself via GetVoteCount() 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.

Build your own lesson with vote_option_device

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →