Verse Wiki — the Verse handbook for Blueprint authors
Chapter 9 · Lesson 29
Migration Project: Translating a Whole Blueprint into Verse
For 28 lessons every page has answered the same question: what is this Blueprint thing called in Verse? This lesson strings them all together. Here is a real Blueprint — variables panel, Event Graph, a Delay, an Actor reference, nothing left out — and you will translate it node by node into a Verse file that compiles. You walk away with three things: a reusable translation order, a list of the places translation goes wrong, and one honest conclusion — some things do not translate at all.
1. The Source Material: One Blueprint, Ready for Translation
Translation needs an original. This graph is called BP_PressurePlate — the kind of small contraption you find in every map: step on it, the door opens; a few seconds later it shuts itself. Its parent class is Actor, the asset is a binary .uasset, and double-clicking it shows two things — the Variables panel in the bottom left, and the Event Graph in the middle.
The Variables panel has three rows. That eye icon column is Instance Editable: switch it on and the variable shows up in the Details panel of every instance in the level, so a designer can tune it without ever opening the Blueprint.
Variable
Type
Instance Editable (eye icon)
Default
IsOpen
Boolean
Off
false
OpenDuration
Float
On
5.0
TargetDoor
Actor reference (BP_Door)
On
None
The Event Graph is a single execution line, seven nodes, strung left to right:
#
Node
What this square does
①
Event ActorBeginOverlap
Something steps on the plate; the execution line starts here
②
Branch
A NOT feeds the condition pin: only take True when IsOpen is false; if the door is already open, do nothing
③
Set IsOpen
Record the state as true
④
TargetDoor → Open()
Call the door open function on the Actor reference
⑤
Delay
Duration pin wired to OpenDuration — a Latent node with the little clock in the corner
⑥
TargetDoor → Close()
Close the door
⑦
Set IsOpen
Record the state back to false; the contraption resets
Read it straight through: step on it → check whether the door is closed → record the state → open → wait OpenDuration seconds → close → reset the state. You can picture the graph by now — seven boxes, one white line, a Delay with its little clock hanging off the right. The next six steps move that graph, block by block, into a .verse file.
One rule before you start: the first pass is faithful, not clever. You already know Verse has race, has option, has prettier ways to say this — but redesigning while translating means that when a bug shows up you cannot tell whether you translated it wrong or designed it wrong. Get the translation behaving exactly like the original first, then talk about refactoring. When to refactor, and into what, is the job of this lesson's advanced extra page.
2. Translating Section by Section: Six Steps Through the Whole Graph
Every step follows the same shape: this block of the original → these lines of Verse → why they correspond. The order matters — skeleton first, then data, then references, and only then the execution line. It is exactly the order you would build a new class in Blueprint.
Step 1: The Class Skeleton and Its Parent
This block of the original: the asset BP_PressurePlate itself. Class Settings says Parent Class = Actor.
pressure_plate_door.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
pressure_plate_door := class(creative_device):
Why they correspond: the three Blueprint moves — right-click, New Blueprint Class, pick a parent, name it — collapse into one line of Verse, child_name := class(parent_name):. The three using lines have no Blueprint equivalent: the node palette lets you search for anything, while Verse wants you to declare which modules you are pulling in (Lesson 7). Naming changes too: the asset's BP_ prefix and PascalCase become snake_case pressure_plate_door (Lesson 4).
Why not Actor as the parent? Because the place Verse actually runs today is UEFN, and there a piece of Verse that can be dragged into a level and has an Event BeginPlay entry point is a subclass of creative_device (Lesson 6). Once UE6 merges UE5 and UEFN, Scene Graph's entity + component is a different skeleton — Lessons 26 and 27 covered it, and the Deep Dive extra takes this very device apart along those lines.
Step 2: Variables Panel → Fields
This block of the original: the first two rows of the Variables panel, IsOpen and OpenDuration.
pressure_plate_door.verse (excerpt)
# Original: OpenDuration, Float, Instance Editable on
@editable
OpenDuration:float = 5.0
# Original: IsOpen, Boolean, Instance Editable off
var IsOpen:logic = false
Why they correspond: Blueprint has exactly one kind of variable and never asks whether it will change. Verse flips that — immutable by default, and only the ones that get Set are written var (Lesson 8). The test is mechanical, no judgment required: hit Find References on the variable in the original, and if anything wired into a Set node, write var; if not, do not. IsOpen is Set in squares ③ and ⑦, so it is a var; OpenDuration is only ever read, so it is not.
That eye icon is @editable (Lesson 22). The type mapping is just as direct: Boolean → logic, Float → float, Integer → int, String → string (Lessons 9, 10, 11). Note that Verse fields must be given a default value — which lines up neatly with the Default Value column in the Blueprint Variables panel.
Step 3: Actor Reference → An @editable Device Reference
This block of the original: the third row, TargetDoor, typed as a BP_Door Actor reference, defaulting to None, and once in the level you use the eyedropper in the Details panel to point it at the actual door. Plus one thing that is not in the Variables panel at all: the collision box on the plate itself — node ① Event ActorBeginOverlap hangs off it.
pressure_plate_door.verse (excerpt)
# The original's collision box: the trigger source moves out to a device in the level
@editable
Plate:button_device = button_device{}
# Original variables panel: TargetDoor, a BP_Door Actor reference
@editable
TargetDoor:barrier_device = barrier_device{}
Why they correspond: this is the square that needs the most thought. A Blueprint Actor reference defaults to None, and forgetting to bind it gets you that universally hated runtime Accessed None. A Verse @editable device reference must be given a default value (barrier_device{} is an empty placeholder), so it is never null at compile time — forgetting to bind shows up as "I pressed it and nothing happened" instead of a crash. Slightly harder to spot, impossible to crash on. The type that genuinely expresses "might not be there" in Verse is option (Lesson 18) — that is this language's direct answer to Accessed None.
One thing has to be said plainly: that collision box has no one-to-one translation. Verse gives you nothing to subscribe to for "my Actor was overlapped by someone"; the trigger source has to be a real device in the level that broadcasts an event. So this step moves the collision box out into a device slot. This lesson demonstrates it with the verified button_device.InteractedWithEvent; if you use a trigger or zone style device instead, check the event name against the official Verse API Reference before you write it.
Step 4: Event Binding → Registering a Listener
This block of the original: node ①, Event ActorBeginOverlap.
pressure_plate_door.verse (excerpt)
OnBegin<override>()<suspends>:void =
Print("Pressure plate translation online, waiting for a trigger.")
spawn{ PlateLoop() }
Why they correspond: a Blueprint event node is mounted for you by the engine — you drag Event ActorBeginOverlap into the graph, wire it up, and registration is already done. You never even noticed there was a registration step. Verse has no such auto-mounting: you have to find an entry point and register the listener yourself. That entry point is OnBegin, the override of Event BeginPlay.
There are two ways to register (Lesson 25): Event.Subscribe(Function) maps exactly onto Blueprint's Bind Event — when the event fires, that function runs; Event.Await() instead parks the current execution line right there. The original says "each trigger reruns the string of nodes below", so loop + Await() matches its shape better — one lap of the loop equals one trigger in the original. And since loop hogs whichever line it sits on, you spawn a permanent line to run it (Lesson 24), and OnBegin clocks out once registration is done.
Step 5: Branch → if; Delay → Sleep
This block of the original: node ② Branch and node ⑤ Delay, the two joints in the execution line.
pressure_plate_door.verse (excerpt)
PlateLoop()<suspends>:void =
loop:
Plate.InteractedWithEvent.Await()
if (not IsOpen?):
OpenDoor()
Sleep(OpenDuration)
CloseDoor()
Branch → if: a Blueprint Branch has one Boolean condition pin and two exits. What goes inside Verse's if parentheses is not a Boolean value but a check that either goes through or does not — officially, a failure context (Lesson 12). So a logic variable needs a ? to turn into an interrogation: IsOpen? goes through when it is true, fails when it is false (Lesson 11). The original hangs a NOT on the condition pin, so the negation is not IsOpen?. The string of nodes off the True pin becomes the block indented under the colon; the original leaves the False pin empty, so the translation writes no else.
Delay → Sleep: Blueprint's Delay is a Latent node with the little clock in the corner. In Verse it is called Sleep(seconds), fed OpenDuration directly — the same act as dragging the variable onto the Duration pin.
But here is the rule Blueprint authors most often miss: anything that can wait may only live inside a function wearing <suspends>. The original can drop a Delay into any Event Graph because Blueprint Event Graphs allow Latent nodes by default; Verse writes that permission into the function signature — the <suspends> in PlateLoop()<suspends>:void is the written licence saying "this function graph may contain nodes with the little clock" (Lesson 23). Delete it and Sleep and Await() stop compiling on the spot. Conversely, the two small functions in the next step carry no <suspends>, because they wait for nothing and return in one breath.
Step 6: Wrapping Up, and the Full Listing
This block of the original: squares ③④ and squares ⑥⑦.
Why they correspond: ③④ and ⑥⑦ are the same shape (record the state + toggle that door) appearing twice, so they get packaged into two small functions — in Blueprint you would Collapse to Function for exactly the same reason. set is the Set node, and not one of them may be dropped (Lesson 8).
There is also a polarity trap here: a barrier device's Enable() means "the barrier exists", i.e. the door is closed; Disable() means "the barrier vanishes", i.e. the door is open — the exact opposite of what the original's Open() / Close() read like. Node-by-node translation gets this square backwards remarkably often. Seal it inside a function name and the graph only ever says "open door" and "close door" — no more mental inversion.
Six steps, all the parts. The next section assembles them into the complete .verse file and runs it line by line.
3. Run It Line by Line: Map the Translation Back to the Graph
Below is the complete Verse translation of BP_PressurePlate, 43 lines. Every step in the run panel tells you which node of the original this line came from — click "Run Next Step" and map it all the way back.
pressure_plate_door.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Verse translation of BP_PressurePlate — node map in this lesson's Blueprint Comparison
pressure_plate_door := class(creative_device):
# The original's collision box: trigger source moves out to a device in the level
@editable
Plate:button_device = button_device{}
# Original variables panel: TargetDoor, BP_Door Actor reference, Instance Editable on
@editable
TargetDoor:barrier_device = barrier_device{}
# Original variables panel: OpenDuration, Float, Instance Editable on
@editable
OpenDuration:float = 5.0
# Original variables panel: IsOpen, Boolean, Instance Editable off
var IsOpen:logic = false
OnBegin<override>()<suspends>:void =
Print("Pressure plate translation online, waiting for a trigger.")
spawn{ PlateLoop() }
PlateLoop()<suspends>:void =
loop:
Plate.InteractedWithEvent.Await()
if (not IsOpen?):
OpenDoor()
Sleep(OpenDuration)
CloseDoor()
OpenDoor():void =
set IsOpen = true
TargetDoor.Disable()
Print("Door open, countdown running.")
CloseDoor():void =
set IsOpen = false
TargetDoor.Enable()
Print("Door closed, plate reset.")
door
Output log
Hit "Run Next Step" — every step names the node it came from in the original graph.
One semantic difference is worth writing down. In the original, Event ActorBeginOverlap fires every time, and node ② Branch is the doorman that turns repeat triggers away. In the translation, until loop winds back to line 29 a new trigger is never caught at all — the structure blocks re-entry by itself, which makes IsOpen redundant as a doorman. Copy it anyway; do not delete it yet. Faithful on the first pass, redesign on the second. That is exactly what the advanced extra page is about.
4. The 5 Places Translation Goes Wrong
These five are not a random list — they are the squares a Blueprint author almost always trips on during a first translation. Each one names the lesson to go back to.
① Forgetting set
Every Set node in the original needs a set in the translation. In Blueprint, changing a variable means dragging a node and pulling a wire — a concrete enough act that you never forget it; in Verse it is two lowercase letters at the start of a line, the easiest thing to lose while transcribing. Losing it does not fail silently, it fails to compile — but the error text will not say "you forgot set". Go back to Lesson 8.
② Reading = as assignment
Following on from the first: drop the set and write IsOpen = true, and Verse does not read it as assignment but as a comparison — "is IsOpen equal to true?". There is no == in Verse at all; a single = is the comparison. So the error takes a long detour and complains that the check is not in a place allowed to fail. When you see the words "failure context", go hunt for the missing set first. Go back to Lesson 13.
③ Forgetting the failure context
Translating Branch literally into if (IsOpen): is the single most common error in this lesson. A Blueprint condition pin eats a Boolean value; what goes inside Verse's if is a check that may or may not go through. A logic variable needs a ?: if (IsOpen?):, and the negation is if (not IsOpen?):. Same goes for those functions with square brackets in their names — they only belong in places like if. Go back to Lessons 11 and 12.
④ Delay versus suspends
In Blueprint a Delay goes wherever you like, as long as that graph allows Latent nodes — and Event Graphs allow them by default, so you have never once worried about it. Verse writes that permission into the signature: Sleep and Await() may only appear inside functions wearing <suspends>. Whenever you meet a node with the little clock, go back and pin that badge on the host function. Go back to Lesson 23.
⑤ Null references become option
An unbound Actor reference in Blueprint is None, and touching it is a runtime Accessed None — every Blueprint author has been bitten by it. In Verse "might not be there" is a type of its own, option, and reading it has to pass through a check that simply fails when there is nothing there. The cost is slightly wordier code; the payoff is that this entire class of crash is sealed off at compile time. Go back to Lesson 18.
Bonus: polarity and naming
Not language pitfalls, but equally high crash rates: Enable / Disable mean the opposite of the original's Open / Close, and BP_MyDoor has to change naming conventions on its way to my_door. Both are solved the same way — wrap them in small, clearly named functions before they go on the graph.
5. What Does Not Translate
An honest migration lesson has to include this section. The things below are not "written differently in Verse" — they are outside Verse's job description. Hunting for an equivalent will only cost you an afternoon.
The Blueprint thing
Where it stands in Verse
What to do instead
Timeline nodes (curve editor, Alpha output, Play / Reverse pins)
No equivalent. Verse has no built-in curve asset and no node that emits an Alpha over time
The "changes over time" half you can write yourself: loop + Sleep + your own interpolation. The curve editor half does not come along — that is an editor tool, not a language feature
Construction Script (runs on placement, builds things live in the editor)
No equivalent. Verse code runs when the game runs
@editable only exposes parameters to the panel; it does not preview anything live in the viewport. "Place it and see the effect" is editor-tooling work
Art-side nodes: material parameters, Anim Blueprint state machines, Niagara parameters, Sequencer tracks
Outside Verse's job description. They belong to the engine's asset systems, not the gameplay logic layer
Keep using their own editors. Verse owns "what should happen when", not "what it looks like"
Event Tick and per-frame logic
You can write it (loop + Sleep(0.0)), but it is almost always the wrong answer
Verse's time model is "stop and wait for events", not "ask again every frame". Most Tick logic should become event subscriptions or concurrency structures — the advanced extra page is devoted to this
Listing these is not about dampening enthusiasm; it is about drawing the boundary correctly on a real project: only part of any Blueprint is gameplay logic, and the rest is art, animation and editor tooling. The former is what you migrate.
On the timeline, here are the facts once more, in the official phrasing: Actor and Blueprint are fully supported in UE6 Early Access (targeted for late 2027) and the early releases after it; deprecation waits until Scene Graph is mature enough, with no date given. Epic has committed to shipping conversion tools before deprecation, but they have not been released. So the hand translation taught here is, short term, training in reading both sides, and long term, the standing to review what a conversion tool produces — a tool can move structure, but it cannot move design intent, and you have to be able to see which parts need rewriting.
Blueprint Cross-Reference
The whole of BP_PressurePlate, node by node and variable by variable. The differences column is the point — it explains why some squares are not a change of spelling but a change of thinking.
How you do it in Blueprint
How you write it in Verse
The difference
The asset BP_PressurePlate, Class Settings Parent Class = Actor
pressure_plate_door := class(creative_device):
A Blueprint class is a binary .uasset; a Verse class is plain-text .verse. Naming moves from BP_ + PascalCase to snake_case
New variable IsOpen (Boolean, eye icon off)
var IsOpen:logic = false
Blueprint variables are mutable by default; Verse is immutable by default, and only what feeds a Set node gets var
New variable OpenDuration (Float, eye icon on)
@editable + OpenDuration:float = 5.0
The eye icon is @editable; Verse fields must have a default value — there is no "leave it blank and fill it later"
New variable TargetDoor (Actor reference, default None)
Blueprint references can be None, so forgetting to bind = runtime Accessed None; a Verse @editable reference must have a default, so forgetting to bind = pointing at an empty shell and "nothing happens"
The collision box on the plate + Event ActorBeginOverlap
Plate.InteractedWithEvent.Await() (trigger source moved out to a device)
No one-to-one translation: Verse offers nothing to subscribe to for "my Actor was overlapped"; the trigger must be broadcast by a device in the level
Event nodes auto-mounted by the engine — drag it in and it works
Register by hand with Subscribe or Await inside OnBegin<override>()
In Blueprint the "register a listener" step is invisible; in Verse it is a line you have to write
The event fires repeatedly (every overlap reruns the graph)
spawn{ PlateLoop() } + loop:
One lap = one trigger in the original; but while the loop is away from its wait point, new triggers are missed, whereas Blueprint still fires and gets turned away by the Branch
Branch node, condition pin fed by NOT + IsOpen
if (not IsOpen?):
The Blueprint condition pin eats a Boolean; Verse's if eats a does-this-go-through check, so logic needs a ?
The string of nodes off the Branch's True pin
The block indented under the colon
Blueprint expresses subordination with wires, Verse with indentation — indent wrong and it fails to compile; a forgotten wire is not a possible bug
Set IsOpen node
set IsOpen = true
Dropping set does not give you assignment, it gives you comparison; Verse has no ==
TargetDoor → Open() / Close()
TargetDoor.Disable() / TargetDoor.Enable()
Inverted polarity: a barrier's Disable = the barrier vanishes = the door opens. Wrap it in OpenDoor() / CloseDoor() to kill the ambiguity
Delay node (Latent, little clock in the corner)
Sleep(OpenDuration)
Blueprint Event Graphs allow Latent nodes by default; Verse requires the host function to wear <suspends> explicitly or nothing compiles
Collapse to Function (folding a repeated string of nodes)
OpenDoor():void = / CloseDoor():void =
Identical thinking; Verse just adds one more decision — does this function need <suspends>?
Timeline / Construction Script / material and animation nodes
—
Does not translate, and should not: outside Verse's job description (see the previous section)
Read the table end to end and you will find the differences cluster in three places: mutability must be declared (var), checks must be able to fail (? and failure contexts), and waiting needs a licence (<suspends>). These three are not Verse being difficult — they are Verse pulling three classes of "blows up at runtime" problems (changed the wrong state, dereferenced nothing, waited where waiting was not allowed) forward into compile time. The cost is a few extra keywords; the payoff is that those three crashes get caught the moment you hit Build.
There is one more difference that is not syntax at all: a Blueprint is a binary asset, so two people editing the same Event Graph usually have to pick a winner; a .verse file is text — diffable, reviewable, mergeable line by line, and readable and writable by AI. Which happens to be the next lesson's subject.
6. Level Challenge
First put the three key pieces of the translation back, then answer three questions. Correct answers earn a star ★; wrong answers can be retried forever.
pressure_plate_door.verse (excerpt)
# Fill in three blanks: the badge that legalizes Delay, the Set node keyword, and Delay itself
PlateLoop()<____>:void =
loop:
Plate.InteractedWithEvent.Await()
if (not IsOpen?):
____ IsOpen = true
TargetDoor.Disable()
____(OpenDuration)
set IsOpen = false
TargetDoor.Enable()
Once node ⑤ Delay becomes Sleep(OpenDuration), why must the host function PlateLoop be written with <suspends>?
Node ② Branch tests "IsOpen is false". Which of these is the correct translation?
If the original also had a Timeline node driving the door animation, how should the migration handle it?
What You Can Do Now
Look back at what happened in this lesson: you were handed an unfamiliar Blueprint with no starter code, and using one fixed order — skeleton, fields, references, events, execution line, wrap-up — you turned it into a .verse file that compiles and behaves the same. That order holds for any Blueprint; a bigger graph just means more repetitions, with not one step changed.
You also picked up two more valuable things. One is judgment: which squares translate literally, which need a change of thinking, and which should not be translated at all. The other is vocabulary: you can now describe Blueprint logic in Verse terms and explain Verse code in Blueprint terms — in both directions.
The next lesson speeds this process up again. A .verse file is plain text — diffable, reviewable, and pasteable into an AI in full; and the UE6 editor already integrates generative AI assistants such as Claude and Gemini. Once you can state "the original looks like this, please translate it into Verse" clearly — which is precisely what this lesson taught you — the manual labor of translation can be handed off, and you keep the judgment half. See you in Lesson 30.
Further Reading
Technique · EXTRA
The Migration Checklist: 8 Questions Before, 10 Checks During, 5 Verifications After
One printable checklist. Ask yourself 8 questions before opening the Blueprint, tick off 10 items while translating, and run 5 verification steps at the end — swap "from memory" for "from the list".
The same logic, taken apart along Scene Graph's composition model: one device split into three components, mapped side by side, echoing Lessons 26 and 27.
Translation Is Not Porting: When to Redesign Instead
Node-by-node translation moves structure, not design intent. Polling, Tick-driven state machines, and piles of Boolean flags all have distinctly better shapes in Verse.