Professor AweShucks taps the master blueprint one last time. You built the Shell Hunt kit on the South Shores, the Codewood helpers in the North Jungle, the save module on the East Volcano — and in the last lesson the hub island imported every one of them. Here is the part Epic's documentation never quite says out loud: those modules are not welded to this island. A Verse module is just a file, and a file can board a boat. This lesson shows you how to package a zone kit so it drops into a DIFFERENT island project and compiles on the first build — which is how experienced creators ship island number two in a fraction of the time island number one took.
What you will build
The portable zone-kit trio — three modules packaged for travel, plus the proof device that runs them on a fresh island:
ShellHuntKit(from South Shores L9) — thegame_phaseenum andPhaseLabelannouncer, unchanged from the beach.CodewoodKit(from North Jungle L21/L28) — theScoreLinehelper and thekit_buscustom-event bus.VolcanoSaveKit(from East Volcano) — the<persistable>save record and itsRecordScore/BestScoreForAPI.kit_reuse_demo— a device on the NEW island that imports all three with threeusinglines and wires them to devices that exist only there.
This is the capstone's closing argument: the hub island (previous lesson) imports every zone kit, and now you can make ANY island do the same. The curriculum compounds — every module you finish is a part you never build again.
Walkthrough
Step 1 — the export checklist (what makes a module portable)
A module travels cleanly between island projects when four things are true. Pin this list above your desk:
<public>on the module AND on every member the other island calls. Access specifiers are the customs office. A module member without<public>isinternal— visible inside its own module, invisible to the importing file. The import line will work, but the first call to a hidden member is a compile error on the new island.- No
@editablefields inside the module. Modules are not devices; they have no Details panel and nothing to wire. Device references enter as function parameters — the importing island passes in ITS button, ITS HUD device (see Common patterns below). - No project-local asset references. A
creative_prop_assetdragged from THIS project's Content Browser does not exist in the next project. Keep asset references in the device layer; keep the kit pure logic. - The kit carries its own
using { }lines. If the module body callsPrintor touchesplayer, the module block imports/Verse.org/Simulation(and friends) itself. Never assume the host file imported them for you.
Step 2 — moving the file between projects
Epic gives you no package manager, but the mechanics are honest file plumbing:
- In the SOURCE project, open Verse Explorer, right-click
ShellHuntKit.verse→ Show in Explorer to find it on disk. - Copy the file into the NEW project's Verse folder (open the new project's Verse Explorer and use its folder on disk — same trick).
- Back in UEFN, the file appears in Verse Explorer. Run Build Verse Code. If the checklist above holds, it compiles with zero edits.
- In any script on the new island, write
using { ShellHuntKit }and start calling.
One habit worth stealing: keep a Kits/ folder in every project and a master copy of each kit in source control. When a kit improves, the fix travels to every island that carries it.
Step 3 — the complete file
For this lesson the three kits and the demo device sit in ONE file so you can read the whole reuse story top to bottom — in a real project each := module: block lives in its own .verse file, and only the using lines and the device remain here.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# One localizable text helper for HUD devices.
KitMsg<localizes>(Text : string) : message = "{Text}"
# ============================================================
# ShellHuntKit -- packaged from SOUTH SHORES (L9).
# In your real project this whole module lives in its own file:
# ShellHuntKit.verse. That file is the thing you carry between islands.
# ============================================================
ShellHuntKit<public> := module:
# The phase enum the Shell Hunt shipped with.
game_phase<public> := enum:
Waiting
Ready
Launch
Reset
# One readable announcer line per phase.
PhaseLabel<public>(Phase : game_phase) : string =
case (Phase):
game_phase.Waiting => "Waiting for islanders to wash ashore..."
game_phase.Ready => "Round starting - take your pads!"
game_phase.Launch => "GO!"
game_phase.Reset => "Resetting the beach..."
# ============================================================
# CodewoodKit -- packaged from NORTH JUNGLE (L21 modules + L28 events).
# File on disk: CodewoodKit.verse.
# ============================================================
CodewoodKit<public> := module:
using { /Verse.org/Simulation }
# HUD text helper the Mod Rally used for its scoreboard lines.
ScoreLine<public>(Label : string, Value : int) : string =
"{Label}: {Value}"
# The custom event bus -- decouples devices from the brain
# on ANY island that imports this kit.
kit_bus<public> := class:
PhaseChangedEvent<public> : event(ShellHuntKit.game_phase) = event(ShellHuntKit.game_phase){}
# ============================================================
# VolcanoSaveKit -- packaged from EAST VOLCANO (persistence module).
# File on disk: VolcanoSaveKit.verse.
# ============================================================
VolcanoSaveKit<public> := module:
using { /Verse.org/Simulation }
# One persistable record per player. Fields may be ADDED later
# (with defaults) but NEVER removed or retyped once published.
zone_save<public> := class<final><persistable>:
Version<public> : int = 1
BestScore<public> : int = 0
# Module-scoped weak_map: loads on join, saves on every write.
var SaveMap<public> : weak_map(player, zone_save) = map{}
# Persistable records are snapshots: rebuild, then write back.
RecordScore<public>(Player : player, Score : int)<transacts> : void =
if (Existing := SaveMap[Player]):
if (Score > Existing.BestScore):
if (set SaveMap[Player] = zone_save{Version := Existing.Version, BestScore := Score}) {}
else:
if (set SaveMap[Player] = zone_save{BestScore := Score}) {}
# Reads a player's best score, or 0 for a fresh castaway.
BestScoreFor<public>(Player : player)<transacts> : int =
if (Existing := SaveMap[Player]):
Existing.BestScore
else:
0
# Import the kits -- the SAME three lines you write on any island
# that carries these files. This is the whole reuse story.
using { ShellHuntKit }
using { CodewoodKit }
using { VolcanoSaveKit }
# The proof device: a brand-new island running three zones' proven parts.
kit_reuse_demo := class(creative_device):
# Wire both in the Details panel of the NEW island.
@editable
StartButton : button_device = button_device{}
@editable
TownCrier : hud_message_device = hud_message_device{}
# The North Jungle event bus, instantiated fresh on this island.
Bus : kit_bus = kit_bus{}
var Phase : game_phase = game_phase.Waiting
var Launches : int = 0
OnBegin<override>()<suspends>:void =
TownCrier.SetText(KitMsg(PhaseLabel(Phase)))
TownCrier.Show()
StartButton.InteractedWithEvent.Subscribe(OnStart)
spawn { WatchPhase() }
# Awaits kit_bus signals -- the CodewoodKit pattern, unchanged.
WatchPhase()<suspends>:void =
loop:
NewPhase := Bus.PhaseChangedEvent.Await()
TownCrier.SetText(KitMsg(PhaseLabel(NewPhase)))
Print("Phase now: {PhaseLabel(NewPhase)}")
OnStart(Agent : agent):void =
set Phase = game_phase.Launch
Bus.PhaseChangedEvent.Signal(Phase)
set Launches += 1
Print(ScoreLine("Launches", Launches))
if (Player := player[Agent]):
VolcanoSaveKit.RecordScore(Player, Launches * 100)
Print(ScoreLine("Best score", VolcanoSaveKit.BestScoreFor(Player)))
Line-by-line: where each rule shows up
| Lines | What's happening |
|---|---|
ShellHuntKit<public> := module: |
The module itself is <public> — rule 1, half one. |
game_phase<public> := enum: |
Every exported member is <public> — rule 1, half two. Drop the specifier and the new island's var Phase : game_phase line fails to compile. |
CodewoodKit ... using { /Verse.org/Simulation } |
The kit imports its OWN dependencies (rule 4) — it never leans on the host file's imports. |
event(ShellHuntKit.game_phase) |
Kits may depend on each other — by full module name, so the dependency survives the boat ride. Ship them together. |
zone_save<public> := class<final><persistable>: |
The volcano save record. Persistable classes must be <final>, with defaults on every field. |
var SaveMap<public> : weak_map(player, zone_save) = map{} |
Module-scoped persistence: the engine loads a player's record on join and saves on every write — on whichever island the module lands. |
using { ShellHuntKit } (×3) |
The entire import ceremony on the destination island. Three lines, three zones of proven parts. |
@editable StartButton / TownCrier |
Device references live on the DEVICE, never in the kit (rule 2). The new island wires its own hardware. |
Bus.PhaseChangedEvent.Signal(Phase) / .Await() |
The North Jungle event bus running unmodified in a new postcode. |
VolcanoSaveKit.RecordScore(Player, ...) |
Calling a kit by qualified name also works — handy when two kits export the same function name. |
Common patterns
Pattern 1 — devices enter as parameters, never as fields
The single most common porting failure is an @editable buried in a module. The fix is always the same shape — the kit declares WHAT it needs, the island provides WHICH one:
# PORTABLE: the kit takes the device as a parameter...
AnnouncerKit<public> := module:
using { /Fortnite.com/Devices }
KitText<localizes><public>(Text : string) : message = "{Text}"
# The importing island hands us ITS hud_message_device.
Announce<public>(Crier : hud_message_device, Line : string) : void =
Crier.SetText(KitText(Line))
Crier.Show()
using { AnnouncerKit }
# ...and only the device that LIVES on this island holds the @editable.
announcer_demo := class(creative_device):
@editable
Crier : hud_message_device = hud_message_device{}
OnBegin<override>()<suspends>:void =
Announce(Crier, "This line came from a kit that owns no devices.")
Pattern 2 — stamp every kit with a version
Two islands carrying different vintages of the same kit is a debugging swamp. One public constant drains it:
# Every kit carries a version constant -- one Print tells you
# which vintage of the kit a published island is actually running.
StampedKit<public> := module:
KitVersion<public> : string = "1.2.0"
Greet<public>() : string =
"StampedKit v{KitVersion} reporting for duty"
using { StampedKit }
kit_version_probe := class(creative_device):
OnBegin<override>()<suspends>:void =
Print(Greet())
Where this goes next
This is the final Center Village lesson, and the proof was one lesson back: AweShucks Town (awe-shucks-town-hub) is itself an island that imports ShellHuntKit, CodewoodKit, IslandProgress, and the portal_gatekeeper — every zone's capstone piece, nailed into one bridge. From here, every new island you start begins with a Kits/ folder full of parts you already trust: the West Coves' RaceHelpers (shared-helper-module) slots in the same way, and the badge schema from cross-zone-progress-persistence gives any sequel island a shared player profile on day one. Build the part once, sail it everywhere. Professor AweShucks would call that graduating.