Reference Devices compiles

Game Phases: Enums as a Simple State Machine

Every round-based game is secretly a state machine, and the friendliest state machine in Verse is an enum in a var. On the South Shores, Coral the flamingo runs her Shell Hunt in three phases - Lobby, Hunting, Celebration - and in this lesson you build the phase controller that the whole capstone minigame hangs off: define `shell_hunt_phase := enum{Lobby, Hunting, Celebration}`, store it in a `var`, gate every button press on the current phase (illegal presses are ignored, by design), and attach per-phase data like banner text and timing with tiny `case` helpers. One variable, one doorway for changes, zero magic numbers.

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

Every good beach game has a rhythm: everyone gathers, the whistle blows, chaos happens, and then somebody gets crowned. In code, that rhythm is a state machine — and the simplest, sturdiest way to build one in Verse is an enum stored in a var. In this lesson you will define the Shell Hunt's three phases, switch behavior per phase, and attach data (labels, timings) to each phase without a single magic number.

What you will build

The LOBBY -> HUNTING -> CELEBRATION phase machine that the entire South Shores Shell Hunt capstone hangs off. Every handler in the finished minigame — shell pickups, the round timer, the scoreboard — will start by asking one question: "what phase are we in?" This device is that answer:

  • A shell_hunt_phase enum with exactly three legal states.
  • A var CurrentPhase that is the single source of truth for the round.
  • Two buttons that request transitions — and get ignored when the request is illegal (pressing "Turn In Shells" in the Lobby does nothing, on purpose).
  • Per-phase data (banner text, banner duration) returned by tiny case helpers — the enum-with-data pattern.

Coral the flamingo's rule of claw: the phase var changes in exactly one place. Everything else just reads it.

Walkthrough

Step 1 — Name the phases. An enum is a fixed menu of named values. You can write it inline as shell_hunt_phase := enum{Lobby, Hunting, Celebration} or in block form (used below — friendlier for comments). Either way the compiler now guarantees CurrentPhase can never be anything except one of these three.

Step 2 — Store the phase in a var. var CurrentPhase : shell_hunt_phase = shell_hunt_phase.Lobby starts every round on the sand. Because it is a var, we reassign it with set.

Step 3 — Gate every transition. Each button handler checks the current phase first. A transition that is not drawn on the diagram (Lobby -> Celebration? no such arrow) simply does not exist in code, so illegal presses fall through to a friendly log line and nothing changes.

Step 4 — Route all changes through one doorway. EnterPhase is the only place set CurrentPhase appears. Announcing, logging — and later, starting timers or unlocking spawners — all hang off that one function.

Step 5 — Attach data with case. Verse enums do not carry fields, so the idiom is a helper function that case-switches on the value and returns the data: PhaseLabel returns each phase's banner text, PhaseBannerTime returns how long to show it. Add a phase later and the compiler walks you to every helper that needs a new arm.

Drop this device on your island, place two Button devices and a HUD Message device, and wire them up in the Details panel:

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

# The three phases of the Shell Hunt round, in play order.
shell_hunt_phase := enum:
    Lobby
    Hunting
    Celebration

# Runs the Shell Hunt round as a tiny state machine.
shell_hunt_phase_controller := class(creative_device):

    # Wire to the driftwood "Start Hunt" button on the beach.
    @editable
    StartButton : button_device = button_device{}

    # Wire to the "Turn In Shells" button at the judging table.
    @editable
    FinishButton : button_device = button_device{}

    # Announces every phase change to all players.
    @editable
    Announcer : hud_message_device = hud_message_device{}

    # The single source of truth for the whole round.
    var CurrentPhase : shell_hunt_phase = shell_hunt_phase.Lobby

    # Wraps a plain string so message-typed parameters accept it.
    Msg<localizes>(S : string) : message = "{S}"

    OnBegin<override>()<suspends> : void =
        StartButton.InteractedWithEvent.Subscribe(OnStartPressed)
        FinishButton.InteractedWithEvent.Subscribe(OnFinishPressed)
        AnnouncePhase()

    OnStartPressed(Interactor : agent) : void =
        # This transition only exists FROM Lobby. Anywhere else: ignored.
        if (CurrentPhase = shell_hunt_phase.Lobby):
            EnterPhase(shell_hunt_phase.Hunting)
        else:
            Print("Start pressed during {PhaseLabel(CurrentPhase)} - ignored")

    OnFinishPressed(Interactor : agent) : void =
        # Only a live hunt can be finished.
        if (CurrentPhase = shell_hunt_phase.Hunting):
            EnterPhase(shell_hunt_phase.Celebration)
        else:
            Print("Finish pressed during {PhaseLabel(CurrentPhase)} - ignored")

    # ONE doorway for every phase change: reassign, then react.
    EnterPhase(NewPhase : shell_hunt_phase) : void =
        set CurrentPhase = NewPhase
        AnnouncePhase()

    AnnouncePhase() : void =
        Announcer.Show(Msg(PhaseLabel(CurrentPhase)), ?DisplayTime := PhaseBannerTime(CurrentPhase))
        Print("Phase is now: {PhaseLabel(CurrentPhase)}")

    # --- Data attached to each phase (the enum-with-data pattern) ---

    # Each phase's on-screen banner text.
    PhaseLabel(Phase : shell_hunt_phase) : string =
        case (Phase):
            shell_hunt_phase.Lobby => "Lobby - hunters gathering on the sand"
            shell_hunt_phase.Hunting => "The shell hunt is ON!"
            shell_hunt_phase.Celebration => "Hunt over - crown the shell champion!"
            _ => "Unknown phase"

    # How long each phase's banner stays on screen, in seconds.
    PhaseBannerTime(Phase : shell_hunt_phase) : float =
        case (Phase):
            shell_hunt_phase.Lobby => 4.0
            shell_hunt_phase.Hunting => 6.0
            shell_hunt_phase.Celebration => 10.0
            _ => 4.0

Playtest it: press Start Hunt on the beach and the banner flips to Hunting for six seconds. Press it again — ignored, with a log line telling you why. Press Turn In Shells and the Celebration banner lingers a full ten seconds, exactly as PhaseBannerTime promised.

Common patterns

Pattern 1 — Phase-gate every future handler

The whole point of the machine is that other systems read it. When the capstone adds shell pickups, the handler opens with the same guard — one if line makes an entire feature phase-aware:

# A future shell-pickup handler: gated by the SAME phase var.
    OnShellCollected(Interactor : agent) : void =
        if (CurrentPhase = shell_hunt_phase.Hunting):
            Print("Shell counted!")   # scorekeeping comes in a later lesson
        else:
            Print("Shell ignored - the hunt is not running")

Pattern 2 — A single cycle button

Prototyping solo and tired of running between two buttons? Wire one button to a handler that walks the loop with an if / else if chain (the same shape our case helpers use, just choosing actions instead of returning data):

# One button that walks the phases in a fixed loop.
    OnCyclePressed(Interactor : agent) : void =
        if (CurrentPhase = shell_hunt_phase.Lobby):
            EnterPhase(shell_hunt_phase.Hunting)
        else if (CurrentPhase = shell_hunt_phase.Hunting):
            EnterPhase(shell_hunt_phase.Celebration)
        else:
            EnterPhase(shell_hunt_phase.Lobby)   # back to the beach for round two

Both patterns keep the sacred rule intact: they call EnterPhase, never set CurrentPhase directly.

Where this goes next

This device is the first rung of the state-machine arc and the spine of the Shell Hunt capstone. Upcoming South Shores lessons import it directly:

  • Shell pickups & scorekeeping subscribe their handlers and gate them on CurrentPhase = shell_hunt_phase.Hunting.
  • The round timer calls EnterPhase(shell_hunt_phase.Celebration) when time runs out — a second caller, still one doorway.
  • The capstone assembly wires every device on the beach through this one var, so the whole minigame starts, runs, and ends in lockstep.

When you meet richer state machines later in the arc (entry/exit actions, async phases with Inkbeard in the west coves), they are this exact pattern wearing a bigger hat.

Build your own lesson with enum_with_data_pattern

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 →