Professor AweShucks adjusts his spectacles and unrolls the master blueprint. Every lesson you have taken on this island — the Shell Hunt's phase enum, the Codewood helpers, the badge profiles, the gatekeeper, the live portals — was a plank in ONE bridge. Today you nail them together. AweShucks Town stops being a collection of lessons and becomes the hub island: a published, persistent town square where players wash ashore, play a plaza CTF round while they wait, tour the booths, and step through badge-gated portals into every other zone. This is the final integration and publish walkthrough.
Overview — the hub architecture
The town runs on ONE brain device plus four small modules, each imported from an earlier lesson:
awe_shucks_town_hub (creative_device — the brain)
├── ShellHuntKit (South Shores L9/L14) — game_phase enum + SpawnProp helper
├── CodewoodKit (North Jungle L21/L28) — shared helpers + hub_bus event bus
├── IslandProgress (Center Village L12) — persistable badge schema, island-wide
└── portal_gatekeeper (Center Village L13) — ONE access decision for every booth
Data flows in one direction: devices signal the brain, the brain consults the modules, the modules read/write the persistent BadgeMap. No booth ever decides its own access — every open-or-refuse decision funnels through portal_gatekeeper.CheckAccess. When something goes wrong with gating, you know exactly where to look.
What you will build
The whole capstone — the piece that wires the island together:
- Lobby loop (L7): a
Waiting -> Ready -> Launch -> Resetstate machine driven by the South Shoresgame_phaseenum, announced by the town-crier HUD device. - Plaza CTF event (L8/L9/L11): two
capture_item_spawner_deviceflags, team scores tallied in Verse, captures paid out as persistent town coins. - Tour the Town quest (L10): a
tracker_deviceassigned to every arriving player, with a coin payout on completion. - Badge persistence (L12): the island-wide
IslandProgressschema — oneweak_map(player, island_badges)profile that survives sessions and gates the paid zones. - Gated portal booths (L13/L16): interact buttons in front of
matchmaking_portal_devices; free booths always open, paid booths require the previous zone's badge. - Publish (L17): the settings pass and Creator Portal steps that put the town live.
Imports from other zones
This capstone EXPLICITLY reuses three earlier deliverables. In your real project these live in their own .verse files; the walkthrough block below inlines them so the whole hub compiles as one file you can read top to bottom.
| Module | Taught in | What the hub uses |
|---|---|---|
ShellHuntKit |
South Shores L9 (module) + L14 (enum) + L10 (SpawnProp) | game_phase enum, PhaseLabel, SpawnCelebrationProp |
CodewoodKit |
North Jungle L21 (custom modules) + L28 (custom events) | ScoreLine helper, the hub_bus event bus |
IslandProgress |
Center Village L12 (persistence) | island_badges persistable class, BadgeMap, HasBadge, AddCoins |
portal_gatekeeper |
Center Village L13 (gating module) | CheckAccess — the ONE gate function |
Walkthrough
The full hub, one file
using { /Fortnite.com/Devices }
using { /Fortnite.com/Game }
using { /Fortnite.com/Playspaces }
using { /Fortnite.com/Teams }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Turns a plain string into a localizable message for HUD devices.
HubMsg<localizes>(Text : string) : message = "{Text}"
# ══ Imported from SOUTH SHORES (L9 module + L14 enum + L10 SpawnProp) ══
ShellHuntKit<public> := module:
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/SpatialMath }
# The lobby phases — the same enum pattern the Shell Hunt shipped.
game_phase<public> := enum:
Waiting
Ready
Launch
Reset
# One readable town-crier 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 team pads!"
game_phase.Launch => "Plaza CTF is LIVE - capture the flag!"
game_phase.Reset => "Resetting the plaza..."
# The SpawnProp helper from the Shell Hunt celebration.
SpawnCelebrationProp<public>(Asset : creative_prop_asset, Where : vector3) : ?creative_prop =
Result := SpawnProp(Asset, Where, IdentityRotation())
Result(0)
# ══ Imported from NORTH JUNGLE (L21 shared helpers + L28 custom events) ══
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 booths and HUD from the hub brain.
hub_bus<public> := class:
# Fired when a zone portal opens for a specific islander.
PortalOpenedEvent<public> : event(agent) = event(agent){}
# Fired every time the lobby state machine changes phase.
PhaseChangedEvent<public> : event(ShellHuntKit.game_phase) = event(ShellHuntKit.game_phase){}
# ══ Shared ISLAND-WIDE: the IslandProgress persistence schema (L12) ══
IslandProgress<public> := module:
using { /Verse.org/Simulation }
# Which capstone badge a portal can require.
zone_badge<public> := enum:
ShellHunt
ModRally
VolcanoSurvival
SunkenDive
# One persistable profile per player. Fields may be ADDED later
# (with defaults) but NEVER removed or retyped once published.
island_badges<public> := class<final><persistable>:
Version<public> : int = 1
ShellHunt<public> : logic = false
ModRally<public> : logic = false
VolcanoSurvival<public> : logic = false
SunkenDive<public> : logic = false
TownCoins<public> : int = 0
# Module-scoped weak_map: loads on join, saves on every write.
var BadgeMap<public> : weak_map(player, island_badges) = map{}
# Succeeds-as-true when `Player` has earned `Badge`.
HasBadge<public>(Player : player, Badge : zone_badge)<transacts> : logic =
if (Badges := BadgeMap[Player]):
case (Badge):
zone_badge.ShellHunt => Badges.ShellHunt
zone_badge.ModRally => Badges.ModRally
zone_badge.VolcanoSurvival => Badges.VolcanoSurvival
zone_badge.SunkenDive => Badges.SunkenDive
else:
false
# Adds town coins to a player's persistent profile.
# Persistable values are snapshots: rebuild, then write back.
AddCoins<public>(Player : player, Amount : int)<transacts> : void =
if (Badges := BadgeMap[Player]):
if (set BadgeMap[Player] = island_badges{Version := Badges.Version, ShellHunt := Badges.ShellHunt, ModRally := Badges.ModRally, VolcanoSurvival := Badges.VolcanoSurvival, SunkenDive := Badges.SunkenDive, TownCoins := Badges.TownCoins + Amount}) {}
# ══ CENTER VILLAGE L13: the portal gatekeeper module ══
# THE module to inspect when a paid portal opens for a badge-less player:
# every booth decision on the island flows through this ONE function.
portal_gatekeeper<public> := module:
using { /Verse.org/Simulation }
# Free booths pass `false` (no requirement); paid booths name a badge.
CheckAccess<public>(Player : player, MaybeRequired : ?IslandProgress.zone_badge)<transacts> : logic =
if (Required := MaybeRequired?):
IslandProgress.HasBadge(Player, Required)
else:
true
# ══ THE HUB BRAIN: lobby, CTF, quest, badges, and portals — wired together ══
awe_shucks_town_hub := class(creative_device):
# ── Plaza lobby ──
@editable
LobbyTimer : timer_device = timer_device{}
@editable
TownCrier : hud_message_device = hud_message_device{}
@editable
MinPlayers : int = 2
# ── While-you-wait plaza CTF (L11) ──
@editable
RedBaseFlag : capture_item_spawner_device = capture_item_spawner_device{}
@editable
BlueBaseFlag : capture_item_spawner_device = capture_item_spawner_device{}
@editable
RoundSeconds : float = 180.0
@editable
CoinsPerCapture : int = 5
# ── Tour the Town onboarding quest (L10) ──
@editable
TourTracker : tracker_device = tracker_device{}
@editable
CoinsForTour : int = 10
# ── Portal booths: interact button + live matchmaking portal (L13/L16) ──
@editable
SouthBoothButton : button_device = button_device{}
@editable
SouthPortal : matchmaking_portal_device = matchmaking_portal_device{}
@editable
JungleBoothButton : button_device = button_device{}
@editable
JunglePortal : matchmaking_portal_device = matchmaking_portal_device{}
@editable
VolcanoBoothButton : button_device = button_device{}
@editable
VolcanoPortal : matchmaking_portal_device = matchmaking_portal_device{}
@editable
CovesBoothButton : button_device = button_device{}
@editable
CovesPortal : matchmaking_portal_device = matchmaking_portal_device{}
# ── Celebration prop, spawned with the South Shores helper ──
@editable
ConfettiCannonAsset : creative_prop_asset = DefaultCreativePropAsset
# Live state
var CurrentPhase : ShellHuntKit.game_phase = ShellHuntKit.game_phase.Waiting
var Scores : [team]int = map{}
Bus : CodewoodKit.hub_bus = CodewoodKit.hub_bus{}
OnBegin<override>()<suspends> : void =
# EVERY portal starts CLOSED; only the gatekeeper opens them.
SouthPortal.Disable()
JunglePortal.Disable()
VolcanoPortal.Disable()
CovesPortal.Disable()
# Booth buttons -> one shared, gated handler each.
SouthBoothButton.InteractedWithEvent.Subscribe(OnSouthBooth)
JungleBoothButton.InteractedWithEvent.Subscribe(OnJungleBooth)
VolcanoBoothButton.InteractedWithEvent.Subscribe(OnVolcanoBooth)
CovesBoothButton.InteractedWithEvent.Subscribe(OnCovesBooth)
# CTF flags -> the shared capture handler.
RedBaseFlag.ItemCapturedEvent.Subscribe(OnFlagCaptured)
BlueBaseFlag.ItemCapturedEvent.Subscribe(OnFlagCaptured)
# Quest completion pays out coins.
TourTracker.CompleteEvent.Subscribe(OnTourComplete)
# Greet everyone already here, then every later arrival.
for (Islander : GetPlayspace().GetPlayers()):
WelcomePlayer(Islander)
GetPlayspace().PlayerAddedEvent().Subscribe(WelcomePlayer)
# The hub heartbeat: run lobby rounds forever.
loop:
RunLobbyRound()
# ── Lobby state machine (L7): Waiting -> Ready -> Launch -> Reset ──
RunLobbyRound()<suspends> : void =
SetPhase(ShellHuntKit.game_phase.Waiting)
loop:
if (GetPlayspace().GetPlayers().Length >= MinPlayers):
break
Sleep(5.0)
SetPhase(ShellHuntKit.game_phase.Ready)
LobbyTimer.Reset()
LobbyTimer.Start()
LobbyTimer.SuccessEvent.Await()
SetPhase(ShellHuntKit.game_phase.Launch)
RunCtfRound()
SetPhase(ShellHuntKit.game_phase.Reset)
Sleep(8.0)
SetPhase(NewPhase : ShellHuntKit.game_phase) : void =
set CurrentPhase = NewPhase
Bus.PhaseChangedEvent.Signal(NewPhase)
TownCrier.Show(HubMsg(ShellHuntKit.PhaseLabel(NewPhase)), ?DisplayTime := 4.0)
# ── Plaza CTF round (L8/L11): scores accumulate via subscription ──
RunCtfRound()<suspends> : void =
set Scores = map{}
Sleep(RoundSeconds)
AnnounceWinner()
OnFlagCaptured(Scorer : agent) : void =
TeamCollection := GetPlayspace().GetTeamCollection()
if (ScoringTeam := TeamCollection.GetTeam[Scorer]):
Current := Scores[ScoringTeam] or 0
if (set Scores[ScoringTeam] = Current + 1) {}
Print(CodewoodKit.ScoreLine("Captures", Current + 1))
# Captures pay town coins into the persistent profile (L12).
if (Player := player[Scorer]):
IslandProgress.AddCoins(Player, CoinsPerCapture)
AnnounceWinner() : void =
var Best : int = 0
for (Captures : Scores):
if (Captures > Best):
set Best = Captures
TownCrier.Show(HubMsg("Round over! Winning team took {Best} flag(s)."), ?DisplayTime := 6.0)
# Confetti via the South Shores SpawnProp helper.
Above := GetTransform().Translation + vector3{X := 0.0, Y := 0.0, Z := 300.0}
MaybeCannon := ShellHuntKit.SpawnCelebrationProp(ConfettiCannonAsset, Above)
if (MaybeCannon?):
Print("Confetti cannon spawned over the plaza!")
# ── Welcome + quest (L3/L10/L12) ──
WelcomePlayer(Islander : player) : void =
TownCrier.Show(Islander, HubMsg("Welcome to AweShucks Town! Tour the booths to find your next adventure."), ?DisplayTime := 6.0)
TourTracker.Assign(Islander)
# First visit? Mint a fresh persistent badge profile.
if (not IslandProgress.BadgeMap[Islander]):
if (set IslandProgress.BadgeMap[Islander] = IslandProgress.island_badges{}) {}
OnTourComplete(Agent : agent) : void =
if (Player := player[Agent]):
IslandProgress.AddCoins(Player, CoinsForTour)
TownCrier.Show(Agent, HubMsg("Tour complete! Coins added to your island purse."), ?DisplayTime := 5.0)
# ── Portal booths -> the gatekeeper (L13/L16) ──
OnSouthBooth(Agent : agent) : void =
# South Shores is FREE: no badge required, pass the empty option.
TryOpenPortal(Agent, SouthPortal, false, "South Shores")
OnJungleBooth(Agent : agent) : void =
TryOpenPortal(Agent, JunglePortal, option{IslandProgress.zone_badge.ShellHunt}, "Codewood Jungle")
OnVolcanoBooth(Agent : agent) : void =
TryOpenPortal(Agent, VolcanoPortal, option{IslandProgress.zone_badge.ModRally}, "Cinder Volcano")
OnCovesBooth(Agent : agent) : void =
TryOpenPortal(Agent, CovesPortal, option{IslandProgress.zone_badge.VolcanoSurvival}, "Whisper Coves")
# ONE shared gate for every booth on the island.
TryOpenPortal(Agent : agent, Portal : matchmaking_portal_device, Required : ?IslandProgress.zone_badge, ZoneName : string) : void =
if (Player := player[Agent]):
if (portal_gatekeeper.CheckAccess(Player, Required)?):
Portal.Enable()
Bus.PortalOpenedEvent.Signal(Agent)
TownCrier.Show(Agent, HubMsg("The {ZoneName} portal hums to life. Step through!"), ?DisplayTime := 4.0)
else:
TownCrier.Show(Agent, HubMsg("This portal is sealed - earn the previous zone's badge first!"), ?DisplayTime := 4.0)
Reading the brain, top to bottom
| Piece | What it does |
|---|---|
HubMsg<localizes> |
The one string-to-message bridge every HUD call uses. |
ShellHuntKit.game_phase |
The South Shores enum, reused verbatim as the lobby state machine's states. |
CodewoodKit.hub_bus |
Custom event(...) fields (L28). The brain Signals; any future device — a lore screen, a music controller — just Subscribes. No new wiring in the brain. |
IslandProgress.BadgeMap |
The island-wide persistence schema. weak_map(player, island_badges) at module scope loads on join and saves on every successful set. |
portal_gatekeeper.CheckAccess |
The single yes/no for every booth. Free booth = empty option (false), paid booth = option{zone_badge...}. |
OnBegin |
Disables all portals, subscribes every device event, greets current + future players, then loops RunLobbyRound forever. |
RunLobbyRound |
Waits for MinPlayers, runs the ready countdown on the timer_device (SuccessEvent.Await()), launches the CTF round, resets. |
OnFlagCaptured |
The proven CTF pattern: resolve the scorer's team via GetTeamCollection().GetTeam[...], tally in a [team]int map, pay persistent coins. |
TryOpenPortal |
The gatekeeper in action: check access, Enable() the matchmaking_portal_device on success, explain the refusal on failure. |
Device wiring checklist
In the UEFN editor, place and assign — the Details panel of awe_shucks_town_hub must show NO unassigned references:
- Timer Device →
LobbyTimer— duration = your ready-up countdown (30s works), Success on Timer End enabled. - HUD Message Device →
TownCrier— default display time is overridden per call from Verse. - 2x Capture Item Spawner →
RedBaseFlag/BlueBaseFlag— one per base, flag item, Capture Area linked per base. - Tracker Device →
TourTracker— target value = number of booths (5), Show on HUD enabled; wire each booth's button to Increment via the tracker's receiver, or callIncrementfrom Verse if you prefer one brain. - 4x Button Device → the four
*BoothButtonfields — one in front of each portal arch. - 4x Matchmaking Portal Device → the four
*Portalfields — each configured with its target island code (the zone capstones you published in earlier lessons). They start disabled from Verse, so their editor Enabled at Game Start setting can stay default. - Team Settings & Inventory devices (L9) — two teams, spawn pads flanking the plaza.
ConfettiCannonAsset— pick any celebratory prop from your project's Content Browser.
Publish walkthrough
- Island Settings pass: set max players (e.g. 16), enable Use Persistent Storage (required for
weak_mappersistence), set spawn behavior to your team pads, write the island description in Professor AweShucks' voice. - Private playtest: Launch Session → verify the full loop: welcome message → tour quest on HUD → CTF captures print scores → paid booth refuses you → free booth opens.
- Badge test: publish (or session-join) a zone capstone, earn its badge, return to the hub, confirm the next portal now opens — the persistence survives the trip because both islands share the
IslandProgressschema. - Release: Project → Publish → fill the Creator Portal listing → submit for review. When the island code comes back, paste it into each ZONE island's return-portal so the loop closes both ways.
Common patterns
Pattern 1 — A zone island grants its badge
Drop this on each ZONE capstone island. When the victory trigger fires, it flips that zone's badge — and the hub's gatekeeper will honor it next visit, because both islands use the same persistable schema.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Same shape as the hub's IslandProgress schema — persistence follows
# the schema, so matching class + fields means shared progress.
rally_badges := class<final><persistable>:
Version : int = 1
ModRally : logic = false
var RallyBadgeMap : weak_map(player, rally_badges) = map{}
rally_badge_granter := class(creative_device):
@editable
VictoryTrigger : trigger_device = trigger_device{}
OnBegin<override>()<suspends> : void =
VictoryTrigger.TriggeredEvent.Subscribe(OnVictory)
OnVictory(MaybeAgent : ?agent) : void =
if (Agent := MaybeAgent?, Player := player[Agent]):
if (set RallyBadgeMap[Player] = rally_badges{Version := 1, ModRally := true}) {}
Print("Mod Rally badge earned - the volcano portal will open at the hub.")
Pattern 2 — Walk-up booths instead of buttons
Prefer arches players walk through? Swap the button_device for a trigger_device volume — the handler shape changes from agent to ?agent, everything else stays.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
walkup_booth := class(creative_device):
@editable
BoothZone : trigger_device = trigger_device{}
@editable
Portal : matchmaking_portal_device = matchmaking_portal_device{}
OnBegin<override>()<suspends> : void =
Portal.Disable()
BoothZone.TriggeredEvent.Subscribe(OnEnterZone)
OnEnterZone(MaybeAgent : ?agent) : void =
if (Agent := MaybeAgent?):
Portal.Enable()
Print("Portal armed by walk-up.")
Where this goes next
This IS the capstone — AweShucks Town is the island the whole curriculum has been building toward. Everything imports INTO it: the South Shores Shell Hunt grants the ShellHunt badge that opens the jungle booth; the North Jungle Mod Rally grants ModRally for the volcano; the volcano survival run grants VolcanoSurvival for the coves; the sunken dive grants SunkenDive for The Deeps. The scene-graph lessons (L14/L15) reskin every booth with the portal_beacon_component prefab, and the published island code from this walkthrough is what every zone island's return portal points home to. Publish it, share the code, and watch players wash ashore. Professor AweShucks will take it from here — class dismissed, island open.