Tip · EXTRA
Hand-Rolling ToString for enum: The case Mapping Pattern
enum can't be Printed directly? The community's standard fix is a single case function — with string interpolation support thrown in for free.
Read the extra →Blueprint's Branch node consumes a True/False boolean; Verse's if pointedly doesn't — it consumes «can this wire fire». That logic slot in your backpack gets pulled out for a proper look this lesson: whether the box is checked (true/false) is a value, while whether this wire can fire is something else entirely — one of the most fundamental ideas in Verse. We'll also unlock enum and hang proper nameplates on your game states, waving goodbye to magic numbers for good.
Back in the variables lesson, logic made a cameo as one of the four slot types: it's the Boolean you know from Blueprint — the checkbox-style switch whose only possible values are true and false, built for recording black-and-white facts — is the door open, do we have the key, is the player ready. Opening slots, making constants, comparing values: it plays by the same rules as int and string:
using { /Fortnite.com/Devices }
switch_room_device := class(creative_device):
# logic slots: hold only true or false
var IsDoorOpen:logic = false
var HasKey:logic = true
# No var means a constant: this light stays on forever
LightAlwaysOn:logic = true
▸ Blueprint translation: this is like adding two Boolean variables in the variables panel — IsDoorOpen (unchecked by default) and HasKey (checked by default); that last LightAlwaysOn has no var, making it a constant with no Set node, locked to true.
To compare two logics, use = and <>, as in if (A = B):. Careful: these comparisons aren't computing a true/false to hand you — they're asking «can this wire fire?». If it can, execution moves on; if it can't, this step simply produces no result. This can-it-fire kind of check is officially called a «failable expression» — it stars in the next lesson, so jot the name down in your notebook for now.
Now for this lesson's loudest blackboard moment: in Verse, «is the box checked» and «can this wire fire» are two different things. The Branch node you're used to wiring in Blueprint consumes a True/False boolean; Verse's branching pointedly doesn't — it consumes «can this step go through?». false is a value, resting contentedly in that unchecked switch; «can't go through» means the execution wire hit a wall and this step produced no result. The two merely look alike — they're not remotely related. So if you feed the HasKey switch straight into if the way you'd wire a Branch, it lights up red on the spot (the same red you get when a Blueprint Compile fails): the fork wants «did it go through», and you handed it a «checked-or-not value». This is the wall new Verse developers hit hardest, and it's also the language's foundation stone — Lesson 12 builds the whole tower on it; this lesson we just learn to cut a door through the wall.
Want two paths to fork off this switch's state? Append a question mark ? and turn the «checked-or-not value» into «can this wire fire», right on the spot: HasKey? fires when HasKey is checked (true) and doesn't when it's unchecked (false). Now if (HasKey?): behaves like a perfectly normal Branch and wires up fine. You can read ? as an interrogation — «did you really check the box?» — and failing to answer means the wire doesn't fire. Click «Run next step» and watch the same question mark walk two different wires:
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
key_gate_device := class(creative_device):
# logic slot: do we have the key?
var HasKey:logic = false
OnBegin<override>()<suspends>:void =
if (HasKey?):
Print("Click — the door swings open!")
else:
Print("The door won't budge... go find the key first.")
set HasKey = true
if (HasKey?):
Print("Click — the door swings open!")
Click «Run next step» to watch the code execute line by line.
The revolving door spins both ways. The reverse direction is written logic{…}: run the «does this wire fire» check inside the braces once — fired yields true, didn't yields false — sealing the outcome of one wire check into a switch value you can store in a variable. On top of that, if in Verse spits out a value of its own, so if (condition) then true else false can also mint a logic value on the spot (a bit like Blueprint's Select node, picking one value to output based on a condition):
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Random }
coin_device := class(creative_device):
var IsReady:logic = true
var HasKey:logic = true
OnBegin<override>()<suspends>:void =
# logic{}: translate success/failure into a stored true/false
CoinFlip := logic{GetRandomInt(0, 1) = 1}
if (CoinFlip?):
Print("Coin flip: heads!")
# if is an expression too — it can also mint a logic
Score := 150
HasWon := if (Score > 100) then true else false
if (HasWon?):
Print("Score threshold met!")
# Combining logics: give each its own ? first, then chain with and
if (IsReady? and HasKey?):
Print("All set — let's go!")
▸ Blueprint translation: the first part stuffs the check «does the random number equal 1» into logic{}, sealing it into the switch CoinFlip, then unpacks it back into a wire with ? — heads means print. The second part uses the pick-a-value-by-condition form (≈ Blueprint's Select node) to compute the HasWon switch, printing once the score qualifies. The last part gives each switch its own ? to become a wire, then chains them with and into «both wires must fire» — like running two checks through an AND node and then into a Branch.
Finally, look at those closing two lines: to combine several conditions, the words and, or, not are all you need (they're Blueprint's AND / OR / NOT nodes). The catch: what they chain are also «can-this-wire-fire» checks, so every logic switch must first grow its own ? to become a wire before and can join them. Write IsReady and HasKey (no question marks) and Compile lights up red.
New scene. Your map has three phases: waiting in the lobby, battle in progress, podium and curtain call. Track them with an int (0, 1, 2)? Three months from now nobody remembers what 2 meant — «magic numbers» are an ancestral trap. Track them with a string ("Lobby")? One slip of the finger types "Loby", the compiler doesn't bat an eye, and the bug ships. Enter enum — the very Enumeration asset you right-click-create in Blueprint: it builds a dedicated dropdown list whose values can only come from the entries you name, and a misspelled name lights up red at compile time.
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
# enums live at module level, peer to the device class — never inside a function body
game_phase := enum{Lobby, Battle, Podium}
phase_device := class(creative_device):
# open a slot of the enum type, parked in the lobby at first
var CurrentPhase:game_phase = game_phase.Lobby
OnBegin<override>()<suspends>:void =
set CurrentPhase = game_phase.Battle
# enums support only = and <> comparisons (both are failable expressions)
if (CurrentPhase = game_phase.Battle):
Print("Battle phase — everyone rally up!")
if (CurrentPhase <> game_phase.Podium):
Print("Not podium time yet.")
▸ Blueprint translation: the first line is like right-click-creating an Enumeration asset named game_phase with three entries: Lobby, Battle, Podium. The device declares a variable CurrentPhase of that enum type, parked at Lobby by default. On game start (Event BeginPlay), a Set node switches it to Battle, then two Branches ask «is it Battle now?» and «is it not Podium yet?» — note that enums only understand «equals / not-equals» comparisons.
Three points. The definition reads game_phase := enum{Lobby, Battle, Podium}, and it has to sit at the outermost level — peer to your device, the way an Enumeration is a standalone asset you'd never create inside some event graph; write it inside a function or event and Compile bounces it. Naming follows Verse convention: type names in lower_snake_case, entry names in PascalCase. Using a value means the full name game_phase.Lobby (enum-name prefix included), so it can never be confused with a same-named entry in another enum.
enum's powers are deliberately narrow: = and <> can only ask «is it this entry?»; no ordering, no arithmetic; there is no number hiding behind an entry, and it won't auto-convert into an integer or a piece of text; want to Print String it straight to the screen? No dice. To route by the current entry, besides a chain of Branches there's a dedicated case form (like a multi-exit splitter — one lane per entry, exactly one taken at a time) — here's a sneak peek:
game_phase := enum{Lobby, Battle, Podium}
# case expression: check members one by one; whichever matches, that branch runs
PhaseLabel(Phase:game_phase):string =
case (Phase):
game_phase.Lobby => "Lobby"
game_phase.Battle => "Battle"
game_phase.Podium => "Podium"
▸ Blueprint translation: this PhaseLabel is a Blueprint function (takes a game_phase entry, returns a piece of text). The case inside is that multi-exit splitter: an incoming Lobby yields "Lobby", Battle yields "Battle", Podium yields "Podium" — one exit per entry, and only the matching lane runs.
For a default (closed) enum, as long as case spells out every entry, you don't need a «fallback» exit (like Blueprint's Switch on Enum, where the Default pin may stay unwired); better yet, if you later add an entry to the enum but forget to update a case, Compile calls out exactly whom you missed — this safety net is one of enum's most valuable perks. What closed means and whether you can switch to open — the extras page at the end has the full story. First, a small exercise: fill the two key blanks back in:
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Blank 1: the keyword that defines an enumeration
game_phase := ____{Lobby, Battle, Podium}
ready_check_device := class(creative_device):
var CurrentPhase:game_phase = game_phase.Lobby
var IsReady:logic = true
OnBegin<override>()<suspends>:void =
set CurrentPhase = game_phase.Battle
if (CurrentPhase = game_phase.Battle):
Print("Entering battle phase!")
# Blank 2: the symbol you add to hand a logic to if
if (IsReady____):
Print("Player is ready.")
▸ Blueprint translation: this just glues the two halves together — first build the game_phase enum (blank 1 is the keyword that creates an enum), switch to Battle on game start and Branch on the phase; then that IsReady switch needs the symbol that turns it into a wire (blank 2) before it can feed the if.
Two rules of thumb cover ninety percent of everyday cases:
First: the answer is naturally yes/no, and will forever be only those two → use logic. Got the key or not, door open or not, alive or not — all this family. Second: there are three or more states; or only two, but the meaning is «A or B» rather than «yes or no» (say, red team / blue team) → use enum. Team:team = team.Red reads far better than IsRedTeam:logic, and adding a spectator faction later takes just one more member.
There's an advanced reason too: composing state from multiple logics breeds «ghost states». Want a door with three states — open / closed / locked? Model it with two logics, IsOpen plus IsLocked, and you get four combinations — including «open and locked at once», which physically cannot exist yet struts around your code just fine, waiting to ambush you some midnight. Switch to door_state := enum{Open, Closed, Locked} and the illegal state becomes unrepresentable at the type level. «Make illegal states unrepresentable» is the golden rule of type design, and enum is its cheapest implementation.
| Scenario | Use | Why |
|---|---|---|
| Got the key? Ready or not? | logic |
Naturally yes/no, capped at two values |
| Game phase: lobby / battle / podium | enum |
Three or more states; the names document themselves |
| Faction: red team / blue team | enum |
Meaning is «A or B», not «yes or no» — and easy to extend |
| Door: open / closed / locked | enum |
Two logics would breed the «open and locked» ghost state |
Almost every pitfall in this lesson grows out of the same crack: «a checked-or-not value ≠ whether the wire fires». Find your own footprints below and get immunized early:
if (HasKey): — the single most frequent error of this lesson, bar none. Compile lights up red, complaining it wants «can the wire fire», not a ready-made switch value. Fix: add the question mark, if (HasKey?):.IsOpen and IsLocked (chaining two switches straight with and) — combine conditions with and, or, not (Blueprint's AND / OR / NOT nodes); and every logic switch must first grow its own ? to become a wire before combining: IsOpen? and IsLocked?.if (HasKey = true): — compiles, but takes the scenic route; the idiom is if (HasKey?):, and that's how the official docs and style guide write it.Print(CurrentPhase) or stuffing an enum into "{CurrentPhase}" — enums carry no built-in textual form, so pushing one to the screen with Print String lights up red; the standard fix is a handwritten case mapping function that translates each entry into a piece of text — the extras page has the complete recipe.game_phase.Lobby to equal 0 — no number hides behind an enum entry, and it won't auto-convert into an integer; it's a nameplate, not a serial number.B to true / false — three steps collapse into one: B := logic{condition}, sealing «did the wire fire» straight into a switch — shorter, and no extra variable to open.Every concept in this lesson has a Blueprint counterpart — but the pair that lines up worst, Branch versus if, happens to be the lesson's foundation stone.
| In Blueprints | In Verse | Difference |
|---|---|---|
| Create a Boolean in the variables panel (that checkbox) | var IsDoorOpen:logic = false |
Only the name changed: Blueprints call it Boolean, Verse calls it logic, and the values are still just true / false |
| Branch node: eats a True/False, forks two execution wires | if (HasKey?): |
The biggest mismatch on the page. Verse's if eats «can this go through», and a logic value needs a ? before it may enter |
| AND / OR / NOT nodes | and / or / not |
What they chain is also «can this go through», so every logic must grow its own ? before being chained |
| Select node: pick one value to output based on a condition | if (Score > 100) then true else false |
In Verse if is itself an expression — no second node needed for it to hand back a value |
| A Set node on each lane of a Branch, flipping a Boolean to true / false | B := logic{condition} |
Three steps collapse into one: seal «did it go through» straight into a switch value, without even opening a variable first |
| Right-click to create an Enumeration asset | game_phase := enum{Lobby, Battle, Podium} |
Even the placement maps: an enum sits at the outermost level, peer to your device class, like a standalone asset — never inside a function body |
| Switch on Enum node | case (Phase): |
Spell out every entry and the Default pin can stay unwired; add an entry later and forget to update the case, and Compile names the one you missed |
| The integer index behind each enum member, and its automatic name string | Neither exists | A Verse enum converts to no int and prints to no screen; for text you write your own case mapping function |
The differences concentrate in row two, and their root is in the language core. A Blueprint execution wire has exactly one state — running — so a fork has to be commanded by a boolean handed in from outside, which is why Branch needs a True/False input. In Verse every expression already carries two possible outcomes, went-through and didn't, and the fork is a direct consequence of that outcome rather than an order from elsewhere. In this system logic is just an ordinary data type — like int and string, it exists to be stored, not to command. ? and logic{} are the two revolving doors between the worlds.
The enum rows are a different kind of gap. A Blueprint Enumeration is, underneath, a named integer, so it converts, prints and orders. A Verse enum is none of those things — it is a nameplate that answers exactly one question: is it this entry? What you buy with the amputated powers is case's exhaustiveness check: miss an entry and the compiler names it, instead of a player finding the unhandled branch for you some evening.
Coin and nameplates in hand — clear three mini-levels to prove it. Wrong answers cost nothing; retry as often as you like.
HasKey is a logic variable, and you want the door to open when it's true. Which form both compiles and is the officially recommended idiom?
Which statement about enum is true?
Ok := logic{Score > 100} — when Score is 50, what ends up inside Ok?
Tip · EXTRA
enum can't be Printed directly? The community's standard fix is a single case function — with string interpolation support thrown in for free.
Read the extra →Deep Dive · EXTRA
Can an enum grow new members once your project ships? How version compatibility gets written into the type system — required reading for anything you plan to run long-term.
Read the extra →Advanced · EXTRA
The theoretical answer to «if won't eat a logic» hides in Epic's ICFP 2023 paper — a glimpse of Verse's linguistic core.
Read the extra →