Deep Dive · EXTRA
The Question Mark's Two Other Identities: ?type and ?Name
Why does SpawnProp hand back a result typed ?creative_prop after spawning a prop? See all three faces of the question mark in one sitting.
Open the extra →Every button on a controller does one job, and Verse operators are no different: + - * / handle arithmetic, = and <> play referee in Branch checks (they don't ask "true or false?" — they ask "does this wire go through?"), and / or / not combine conditions, ? does the querying, and :=, set, = each mind their own business. This lesson fills in the entire button map, with a precedence cheat sheet thrown in.
+ - * / hold no surprises — they work on both int (whole numbers) and float (decimals). But Verse has one iron rule: both sides must be the same type. Writing 3 + 1.5, mixing a whole number with a decimal, is a straight-up compile error — in Blueprint, when you drag a Float pin into an Integer pin, UE quietly inserts a conversion node to smooth things over; Verse refuses to cover for you — when a conversion is needed, you make it explicit yourself (check the official Verse API Reference for the exact conversion functions). Behind this pickiness is the same creed: types quietly morphing in the dark is exactly the hardest kind of bug to catch.
The one with real attitude is int (whole-number) division. Coins / 2 hides two surprises: first, dividing by 0 means the wire doesn't go through — so integer division has to hang off a Branch check, and when it doesn't go through, execution slips out the else pin (remember last lesson's dice roll?); second, dividing two whole numbers yields not a whole number but a rational, and to get a whole number back you finish the job with Floor (round down) or Ceil (round up).
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
wallet_device := class(creative_device):
var Coins:int = 17
var Speed:float = 3.5
OnBegin<override>()<suspends>:void =
set Coins += 3
set Speed *= 2.0
Print("Coins: {Coins}")
# int division can fail (dividing by 0 fails), so it must live in a failure context;
# the result is a rational — use Floor to get back to int
if (Half := Floor(Coins / 2)): # Verify against the Verse API Reference
Print("Split into two piles of {Half} coins each")
Graph translation: OnBegin is Event BeginPlay — it fires automatically the moment the game starts. The first two lines are two Set nodes — += 3 adds 3 to Coins and *= 2.0 doubles Speed; then a Print String puts the coin count in the top-left corner of the screen. The last part is a Branch: it first floors Coins / 2 to get Half, and only if that step goes through (no division by zero) does execution enter via the success pin and Print how many coins per pile.
One more for the notebook: + isn't just for math. String concatenation (Blueprint's Append node) and array merging (Add on an Array) are its jobs too — set Names += array{"Bubbles"} is one Set node that enqueues Bubbles at the end of the array. One button, multiple combos.
The comparison family has six members: equals =, not-equals <>, and the ordering four < <= > >=. Let's nail the most mind-bending part first: they do not spit out a Boolean. As last lesson explained, a Verse Branch doesn't check True/False — it checks "does this wire go through?" — and comparison operators are the main workforce of that kind of check: if the comparison holds, the wire goes through and execution moves on; if it doesn't, the wire is dead and you turn down the else path. The referee holds up no scorecard — they either wave you through or block you.
From that follows a hard rule: comparisons may only sit where "not going through" is allowed (a Branch condition, a ForEach filter clause, inside not…). Write Score = 100 on its own line in a normal spot and it lights up red the moment you hit Compile — an expression that "might not go through" has nowhere left to put its "not going through". This is the number-one pitfall of this lesson; we'll bang the chalkboard about it again in the pitfalls section.
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
referee_device := class(creative_device):
var Score:int = 72
var PlayerName:string = "Bubbles"
OnBegin<override>()<suspends>:void =
if (Score >= 60):
Print("Passed, the referee waves you through")
else:
Print("Below the line, run it back")
# equality is a single =, inequality is <> (Verse has no == or !=)
if (PlayerName = "Bubbles"):
Print("Welcome back, Bubbles")
if (Score <> 100):
Print("Not a perfect score yet, keep practicing")
Graph translation: this whole snippet is Branch nodes. The first Branch asks "is Score ≥ 60" — if it goes through, the success pin Prints "Passed"; if not, the else pin Prints "Below the line". The next two Branches work the same way: one asks "does the name equal Bubbles" (equality takes just one =), the other asks "is the score not equal to 100" (the not-equals sign is a pair of angle brackets <>).
One more easily missed rule: = is only open to "comparable" types — whole numbers, decimals, logic (that's Boolean), strings, characters, enums (Enumeration assets), plus option / array / map / tuple whose contents are themselves comparable. Ordinary Blueprint instances are not directly comparable with = by default — force it and it goes red the moment you hit Compile; either compare one of its inner fields, or mark the class <unique> so it compares by identity, "is this the same instance?" (a bit like checking whether two object references in Blueprint point at the same Actor).
The logical operators are three English words: and, or, not — mapping straight onto Blueprint's AND, OR, NOT boolean nodes. Since comparisons give you "goes through / doesn't go through", these three combine the same currency: A and B only goes through if both wires do; A or B tries A first, and if A doesn't go through, it wipes away whatever changes trying A made, then tries B; not A goes through precisely when A doesn't. Last lesson's "commas in a Branch condition mean and" clicks into place here: if (A, B): and if (A and B): are the same thing.
not comes with a fun fact: whatever sits inside it is always rolled back — whether not as a whole goes through or not, every Set done inside gets undone, as if it never happened. So don't sneak variable changes in inside a not; they won't stick. Also, when A and B goes through, the whole expression actually carries a value (B's value), and A or B carries the value of whichever side went through first — but in practice you almost always use only their "goes through / doesn't go through" to decide where the Branch goes; don't evaluate them like math formulas.
Last up is the query operator, postfix ?: after a logic (Boolean) it asks "are you true?", after an option (that kind of box that might hold something or might be empty) it asks "got anything in there?" — if it's true / there's cargo, it goes through (and hands over the cargo); otherwise it doesn't. It's another one of those "might not go through" expressions, so it likewise has to sit in a Branch-style check position.
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
combo_gate_device := class(creative_device):
var HasKey:logic = false
var Score:int = 80
var MaybeBonus:?int = option{50}
OnBegin<override>()<suspends>:void =
# ? asks a logic: "are you true?"
if (HasKey?):
Print("Key acquired")
else:
Print("No key yet")
# combo move: comparison + and + not
if (Score > 60 and not HasKey?):
Print("Score is there, just missing a key")
# ? asks an option: "got anything in there?" If yes, unwrap into Bonus
if (Bonus := MaybeBonus?):
Print("Easter egg unboxed: {Bonus} points")
Graph translation: three Branches. The first uses HasKey? as its condition — asking whether this Boolean is true; if yes, Print "Key acquired", otherwise take the else path. The second wires "Score > 60" and "not HasKey?" together with and — only when both hold does it Print. The third is box-opening: MaybeBonus? takes the value out of the box and names it Bonus; only if that works (box not empty) does execution step in and Print the bonus points.
In many text-based languages a single = can both assign and compare — one slip of the finger and you have a classic bug (Blueprint never had this headache: Set variable and the Equal comparison are two different nodes to begin with). Verse simply splits the three jobs across three spellings, and none of them can impersonate another:
| Spelling | Role | When to use it |
|---|---|---|
Reward:int = 100 |
Definition with explicit type | Introducing the name for the first time, spelling out the type yourself |
Reward := 100 |
Type-inferred definition | Introducing the name for the first time, letting Verse figure out the type |
set Combo = 3 |
Assignment | Giving an existing var a new value |
set Combo += 2 |
Compound assignment | Adding, subtracting, multiplying, dividing on top of the current value (-=, *=, /= work the same) |
Combo = 5 (in expression position) |
Equality comparison, might not go through | May only live in Branch-style check positions |
Key takeaways: := only does "first introductions" — hit the same name with := again and it's a duplicate definition, going red the moment you press Compile; changing an existing variable answers only to set (the Set node); an = sitting in a normal check position is always treated as a comparison. Also keep the Branch's two faces apart: if (X := Items[0]): is a value binding (grab element 0 of the array — if that works, name it X; if not, take the else path), while if (X = 100): is a comparison (asking whether X equals 100) — one colon apart, completely different meanings. The combo machine below brings all three brothers together; hit "Run next step" and see with your own eyes what every = is doing.
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
equals_family_device := class(creative_device):
var Combo:int = 0
OnBegin<override>()<suspends>:void =
Reward := 100
set Combo = 3
set Combo += 2
if (Combo = 5):
Print("{Combo}-hit combo, claim your {Reward} coins")
Hit "Run next step" to watch the code execute line by line.
When several buttons get pressed in a row, which fires first? Just like in math class, Verse operators have precedence. Here's the table from highest to lowest (within a tier, they associate left to right):
| Tier (high → low) | Operators | How to remember |
|---|---|---|
| 1 | ( ) grouping |
Parentheses cut in line — always computed first |
| 2 | Postfix ?, member access . |
Little tails stuck right onto a name — they cling the tightest |
| 3 | not, unary - |
Prefixes taking a single operand |
| 4 | * / |
Multiply and divide first |
| 5 | + - |
Then add and subtract |
| 6 | = <> < <= > >= |
The whole comparison family shares one tier |
| 7 | and |
Moves before or |
| 8 | or |
The closing act — computed last |
Two corollaries you can use right away: not A and B parses as (not A) and B — not only grabs the single thing right after it; and comparisons rank above and / or, so X > 0 and Y > 0 works on intuition, no parentheses needed. For the exact ordering of adjacent tiers (say, ? versus not), defer to the full precedence table in the official Verse Language Quick Reference — and when in doubt, add parentheses. Parentheses are never wrong.
Next up is this lesson's minesweeping manual — every entry is a real compile-error crime scene:
Score = 100 on its own line and it gets blocked the moment you hit Compile — a comparison is an expression that "might not go through", and it may only live where that's allowed: Branch conditions, ForEach filters, inside not. Want to change the value? Write set Score = 100 (one Set node).== and inequality != — Verse recognizes neither. Here equality is a single =, and inequality is a pair of angle brackets <>.Result := A or B hands you "the value of whichever side went through first", not true / false. Combine conditions all you like — but let them stay inside a Branch and decide where execution goes.set (Set node) in there leaves no trace whatsoever.X / Y might not go through (dividing by 0 doesn't), and the result is still a rational, not a whole number — finish with Floor / Ceil, and the whole expression has to hang off a Branch-style check.3 + 1.5 goes red the moment you hit Compile — Verse doesn't slip in a conversion node the way Blueprint does; both sides must match.<unique> to compare by "same instance" identity.++ (add one) and condition ? A : B (pick one of two) — Verse has neither. Add one with set X += 1; pick one of two with if (C) then A else B (like Blueprint's Select node). Want to "overload operators" for your own types? Not open for business either — the extra page has the full set of workarounds.Recognition without reps doesn't stick. The energy gate below has had three operators carved out: the magic word for changing a variable (Set), the single-equals equality comparison, and that "angle-bracket not-equals". Fill them back in and hit "Check answers".
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
energy_gate_device := class(creative_device):
var Energy:int = 10
OnBegin<override>()<suspends>:void =
____ Energy += 5
# the equality comparison takes exactly one equals sign
if (Energy ____ 15):
Print("Energy is exactly 15")
# the not-equals sign is a pair of angle brackets
if (Energy ____ 0):
Print("Energy is not 0")
Nearly every operator in this lesson is a node you have wired countless times. The differences column is the point — especially the = row, which is where this lesson most often runs off the road.
| What you do in Blueprint | How you write it in Verse | Difference |
|---|---|---|
| Add / Subtract / Multiply / Divide math nodes | + - * / |
When you drag pins together, UE quietly inserts a conversion node for you; Verse insists both sides share a type, so 3 + 1.5 lights Compile up red |
| An Equal (==) node that outputs a Boolean | A = B |
Two changes: equality takes exactly one equals sign — Verse has no == at all — and it doesn’t output a boolean; it either goes through or it doesn’t, so it can only live inside a failure context such as a Branch condition |
| A Not Equal (!=) node | A <> B |
A pair of angle brackets instead; != does not exist in Verse |
| A Set variable node | set Combo = 3 |
The word set can’t be dropped; drop it and you’ve written a comparison — and a comparison in that position lights Compile up red |
| “Add 2 to the current value” takes three nodes: Get → Add → Set | set Combo += 2 |
Blueprint has no compound assignment; Verse has += -= *= /= — three nodes collapse into one line |
| Creating a variable in the Variables panel and filling in a default | Reward := 100 or Reward:int = 100 |
In Blueprint, “make a variable” and “change a variable” happen in two entirely different places; Verse separates them with := versus set, and a given name may be :=’d only once |
| AND / OR / NOT boolean nodes | and / or / not |
Blueprint’s boolean nodes just compute true/false; Verse’s three combine “goes through / doesn’t”, and or undoes whatever the left side changed when it fails, while not undoes its changes either way |
| A Select node (pick one of two values by condition) | if (A > B) then A else B |
Verse has no condition ? A : B ternary shorthand and no ++; “add one” is written set X += 1 |
The biggest difference fits in one sentence: Blueprint’s Equal node hands you a Boolean you can store and pass around; Verse’s = hands you the pass-or-fail outcome itself. That’s why in Blueprint you can wire an Equal output into a variable and keep it for later, while in Verse a lone line reading Score = 100 is an error — it has no value to give you, only a verdict on whether the wire goes through, and something like a Branch has to catch it on the spot.
The other difference hides in the division of labor between symbols. Blueprint keeps definition, assignment, and comparison physically apart by giving each its own node, so you couldn’t mix them if you tried; Verse is plain text and has to keep them apart with three spellings: :=, set …=, and =. Early on this is your most frequent compile error — but once it sticks, you’ll find it far more direct to read than hunting through a node graph for where that Set node was wired.
Button map complete — into the training arena for three live rounds. Zero penalty for wrong answers; retry as many times as you like.
Given var Coins:int = 5, what is the correct way to change it to 6?
Which operator means "not equal" in Verse?
How does Verse parse not A and B?
Deep Dive · EXTRA
Why does SpawnProp hand back a result typed ?creative_prop after spawning a prop? See all three faces of the question mark in one sitting.
Open the extra →Technique · EXTRA
Starting from a transacts no_rollback compile-red, learn the right way to write comparators for your own types.
Open the extra →Deep Dive · EXTRA
How does renowned UE educator Marcos Romero teach the very same operators? Cross-check your understanding.
Open the extra →