← Back to path Download .md

🦩 South Shores (Free)

Teen 24 lessons Β· ~192 min Β· Generated 2026-07-04

Real UEFN Shell-Hunt island needs: (TERRAIN) a beach/shore biome with a beached pirate galleon hero landmark (start line + celebration stage), dunes, tide pools, a lagoon, and a small wreck interior for the echo ambience; player spawn pads on the sand. (DEVICES) 1x button_device

Learning Outcomes

Lesson 1: Wash Ashore: UEFN Editor & Creative Basics

Objectives

Student can open a UEFN project, navigate the viewport, place a device from Content Browser, and Launch Session to playtest.

🧩 Your capstone piece: The Shell-Hunt beach level itself: terrain, spawn pad, galleon landmark placement

Quiz

  1. In the article's Verse script, which event fires when a player presses the Button device?
    • A. OnTouched
    • B. PressedEvent
    • C. InteractedWithEvent
    • D. ClickEvent
  2. What does `MagicSwitch.InteractedWithEvent.Subscribe(OnSwitchPressed)` do?
    • A. It presses the button once, automatically
    • B. It tells the event: when you fire, call the OnSwitchPressed function
    • C. It deletes the button device from the level
    • D. It renames the button to OnSwitchPressed
  3. What does the `@editable` attribute on MagicSwitch and SecretDoor do?
    • A. It lets players edit the devices during a match
    • B. It makes the code compile faster
    • C. It makes a slot appear in the Details panel so you can point the script at the real devices you placed
    • D. It automatically spawns the devices when the game starts
  4. Predict the result: you Launch Session, walk to the button, and press it. According to the script, what happens?
    • A. Nothing β€” the script only defines devices
    • B. The door opens, because SecretDoor.Open(Player) runs when InteractedWithEvent fires
    • C. The door opens and closes repeatedly forever
    • D. The button disappears and a new door spawns
  5. You Launch Session and press the button, but nothing happens. What is the most likely mistake from the walkthrough?
    • A. You forgot to set MagicSwitch and SecretDoor in the Details panel, so the script points at nothing you placed
    • B. Buttons never work in a Launch Session
    • C. The door needs to be made of wood
    • D. You must restart your computer after building Verse code
Answer key
  1. C β€” The script subscribes to `MagicSwitch.InteractedWithEvent`. An event is something that happens β€” like a bell ringing β€” and InteractedWithEvent rings when a player presses the button.
  2. B β€” `Subscribe` means "when this bell rings, call my function." Every time a player presses the button, OnSwitchPressed runs.
  3. C β€” `@editable` exposes the property in the Details panel. In Step 4 you select your Verse device and set MagicSwitch to your Button and SecretDoor to your Lock.
  4. B β€” Pressing the button fires InteractedWithEvent, and the subscribed function calls `SecretDoor.Open(Player)` β€” the door opens for the player who pressed it.
  5. A β€” Step 4.5 matters: the @editable slots must be wired to the real Button and Lock devices in the Details panel, or the script has nothing to listen to and nothing to open.

Recap

One step closer to Shell-Hunt β€” The Shell-Hunt beach level itself: terrain, spawn pad, galleon landmark placement

Sources

Lesson 2: First Contact: the Button Device + Your First Verse Script

Objectives

Student can add a creative_device Verse file, use @editable to reference a button_device, and Subscribe to InteractedWithEvent with an OnBegin handler that Prints.

🧩 Your capstone piece: The 'Start Hunt' button on the galleon deck that kicks off a round

Quiz

  1. What type does `SetInteractionText` require for its `Text` parameter?
    • A. string
    • B. message
    • C. text
    • D. localizable_string
  2. You call `SetMaxTriggerCount(0)` on a button. What does `GetTriggerCountRemaining()` return?
    • A. 10000
    • B. A very large number representing infinity
    • C. 0, because 0 means unlimited and remaining is also reported as 0
    • D. An error β€” you cannot call GetTriggerCountRemaining on an unlimited button
  3. When exactly does `InteractedWithEvent` fire?
    • A. The moment the player presses the interact key
    • B. After the player holds the button for the full interaction time duration
    • C. When the player releases the interact key
    • D. Every frame the player is within range of the button
  4. Which of the following correctly declares a button_device reference that is connected to a placed device in the level?
    • A. MyButton := button_device{} inside OnBegin
    • B. @editable MyButton : button_device = button_device{} as a class field
    • C. var MyButton : button_device = button_device{} inside OnBegin
    • D. MyButton : button_device = button_device{} inside OnBegin
  5. You want a button that can be pressed at most 5 times before it stops working. Which call configures this?
    • A. VaultButton.SetInteractionTime(5.0)
    • B. VaultButton.SetMaxTriggerCount(5)
    • C. VaultButton.Disable() called 5 times
    • D. VaultButton.SetMaxTriggerCount(0)
Answer key
  1. B β€” `SetInteractionText` takes a `message` β€” a localized type in Verse. You must wrap your string in a `<localizes>` helper function. Passing a raw `string` literal causes a compile error.
  2. C β€” When `GetMaxTriggerCount()` returns `0` (unlimited), `GetTriggerCountRemaining()` also returns `0`. You must check `GetMaxTriggerCount()` first to know whether the button is unlimited before interpreting the remaining count.
  3. B β€” `InteractedWithEvent` fires only after the player holds the interact key for the full duration set by `SetInteractionTime` (or the device's configured hold time). It does not fire on key-down.
  4. B β€” You MUST declare the device as an `@editable` field at class scope. This allows UEFN to wire the field to the actual placed device in the level. Any declaration inside `OnBegin` creates a disconnected default object whose events never fire.
  5. B β€” `SetMaxTriggerCount(5)` caps the button at 5 successful interactions, after which the device automatically disables itself. `SetMaxTriggerCount(0)` means unlimited. `SetInteractionTime` controls hold duration, not use count.

Recap

One step closer to Shell-Hunt β€” The 'Start Hunt' button on the galleon deck that kicks off a round

Sources

Lesson 3: Talk to Players: HUD Message Device

Objectives

Student can show, update, and hide on-screen messages from Verse via hud_message_device (the UI arc's first rung).

🧩 Your capstone piece: All hunt feedback text: welcome banner, shell-count updates, winner announcement

Quiz

  1. You want to show a HUD message to every player at once. Which method do you call?
    • A. Show()
    • B. Show(Agent)
    • C. SetText()
    • D. ClearAllMessages()
  2. What does passing `DisplayTime := 0.0` to `Show(Agent, Message, ?DisplayTime := 0.0)` do?
    • A. Hides the message immediately
    • B. Uses the device's default display time
    • C. Displays the message persistently until manually hidden
    • D. Causes a compile error β€” 0.0 is invalid
  3. A `trigger_device.TriggeredEvent` handler receives `(Agent : ?agent)`. You want to call `MyHUD.Show(Agent)`. What must you do first?
    • A. Cast Agent to agent using `agent(Agent)`
    • B. Unwrap the option with `if (A := Agent?):` and pass `A`
    • C. Nothing β€” ?agent and agent are interchangeable
    • D. Call `Show()` instead since the agent is optional
  4. Which of the following correctly passes text to `SetText`?
    • A. MyHUD.SetText("Vault Opened!")
    • B. MyHUD.SetText(StringToMessage("Vault Opened!"))
    • C. MyHUD.SetText(VaultMsg("Vault Opened!")) where VaultMsg<localizes>(S:string):message = "{S}"
    • D. MyHUD.SetText(message{"Vault Opened!"})
  5. You call `Show()` while a message is already visible. What happens?
    • A. The new message is queued and plays after the current one finishes
    • B. The new message replaces the currently active message immediately
    • C. A compile error is thrown
    • D. The call is silently ignored
Answer key
  1. A β€” `Show()` (no arguments) broadcasts the currently set message to all players affected by the device. `Show(Agent)` targets a single player, `SetText` only sets the text without displaying it, and `ClearAllMessages` removes messages.
  2. C β€” A `DisplayTime` of `0.0` means the message stays on screen indefinitely. You must call `Hide()`, `Hide(Agent)`, or `ClearAllMessages()` to remove it.
  3. B β€” `Show(Agent:agent)` requires a non-optional `agent`. You must unwrap the `?agent` with `if (A := Agent?):` before passing it. Passing `?agent` directly is a compile error.
  4. C β€” `SetText` expects a `message` type. You must declare a `<localizes>` function to convert a string into a `message`. There is no `StringToMessage` function, and raw string literals are not `message` values.
  5. B β€” According to the API, `Show()` 'will replace any previously active message.' If you need sequential messages, sleep between calls or track timing with `GetDisplayTime()`.

Recap

One step closer to Shell-Hunt β€” All hunt feedback text: welcome banner, shell-count updates, winner announcement

Sources

Lesson 4: Invisible Tripwires: the Trigger Device

Objectives

Student can place trigger_device volumes and handle TriggeredEvent in Verse, reading the optional agent payload.

🧩 Your capstone piece: The shell pickup zones β€” one trigger per shell spot detects the collecting player

Quiz

  1. What type does the trigger_device's TriggeredEvent hand to your subscribed handler?
    • A. agent
    • B. ?agent (an optional agent)
    • C. player
    • D. fort_character
  2. Why must you declare the trigger as an @editable field inside your creative_device class?
    • A. To make it run faster
    • B. So you can bind it to the placed device and call its methods; a bare Device.Method() fails
    • C. Because TriggeredEvent only works on editable fields
    • D. It is optional and only for readability
  3. What value passed to SetMaxTriggerCount means 'no limit'?
    • A. 20
    • B. -1
    • C. 0
    • D. 100
  4. What does GetTriggerCountRemaining() return when the device's max trigger count is unlimited?
    • A. The number of players in the game
    • B. 20
    • C. 0
    • D. An error
  5. Which call fires the device's TriggeredEvent from code while attaching a specific player?
    • A. Trigger()
    • B. Trigger(Agent)
    • C. Enable()
    • D. SetTransmitDelay(Agent)
Answer key
  1. B β€” TriggeredEvent is listenable(?agent) because the device can also be fired from code with no agent, so you must unwrap the option before using it.
  2. B β€” Calling a method on a placed device requires an @editable field you point at the device in the Details panel; otherwise you get 'Unknown identifier'.
  3. C β€” 0 indicates unlimited triggers. The argument is clamped between 0 and 20.
  4. C β€” Per the API, it returns 0 when GetMaxTriggerCount is unlimited, so don't treat 0 as 'empty' in that configuration.
  5. B β€” Trigger(Agent:agent) fires the event and passes that agent along; the agentless Trigger() fires with no agent.

Recap

One step closer to Shell-Hunt β€” The shell pickup zones β€” one trigger per shell spot detects the collecting player

Sources

Lesson 5: Keeping Score: var, set, and Mutable State

Objectives

Student can declare var Count:int = 0, mutate it with set, and explain why plain values are immutable in Verse.

🧩 Your capstone piece: The per-round shell counter variable the whole game reads and writes

Quiz

  1. Which keyword is required to change the value of a `var` field in Verse?
    • A. set
    • B. mut
    • C. let
    • D. update
  2. After subscribing to a built-in device event (e.g. `button.InteractedWithEvent.Subscribe(...)`) in `OnBegin`, do you need `Sleep(Inf)` to keep the subscription alive?
    • How do you correctly test whether a `logic` variable named `DoorIsOpen` is true inside an `if` expression?
      • A. if (DoorIsOpen = true):
      • B. if (DoorIsOpen == true):
      • C. if (DoorIsOpen?):
      • D. if (DoorIsOpen != false):
    • An event handler method (subscribed via `Subscribe`) needs to run `Sleep(3.0)` before hiding a prop. What is the correct approach?
      • A. Call Sleep(3.0) directly inside the handler β€” it automatically suspends
      • B. Use `spawn { MyAsyncMethod() }` inside the handler to launch a separate concurrent task
      • C. Declare the handler with `<suspends>` and call Sleep directly
      • D. Use a loop with no Sleep to busy-wait for 3 seconds
    • If you place two copies of a device that has `var PressCount : int = 0`, what happens to their counters?
      • A. Both copies share the same counter because `var` fields are class-level globals
      • B. Each copy has its own independent counter
      • C. Only the first placed copy tracks the counter; the second is ignored
      • D. The compiler rejects multiple instances of a device with mutable fields
    Answer key
    1. A β€” Verse uses the `set` keyword to mutate a `var` field. Writing `MyField = NewValue` without `set` creates a new immutable local binding instead of updating the field.
    2. C β€” The `?` postfix operator is the idiomatic Verse way to test a `logic` value in a failure context. It succeeds (continues the if branch) when the value is `true`.
    3. B β€” Subscribed event handlers are synchronous and cannot call `<suspends>` functions directly. You must use `spawn { }` to launch an async task from within the handler.
    4. B β€” `var` fields are per-instance. Each placed device object has its own copy of the field, so two placed devices count independently.

    Recap

    One step closer to Shell-Hunt β€” The per-round shell counter variable the whole game reads and writes

    Sources

    Lesson 6: Say It With Braces: String Interpolation

    Objectives

    Student can build dynamic strings like "Shells: {Count}/{Total}" and push them to the HUD every time state changes.

    🧩 Your capstone piece: The live 'Shells: {n}/{Total}' counter text on the hunt HUD

    Quiz

    1. You want to show an interpolated string on a player's HUD. Which type does `hud_message_device.Show(Agent, ...)` require for the message argument?
      • A. message
      • B. string
      • C. text
      • D. localized_string
    2. What is the correct way to embed a `float` variable called `Speed` into a Verse string?
      • A. "Speed: " + Speed
      • B. "Speed: {Speed}"
      • C. "Speed: " + ToString(Speed)
      • D. Both B and C are valid
    3. You call `WelcomeHUD.SetDisplayTime(0.0)`. What happens?
      • A. The message is hidden immediately
      • B. The message displays for 0 seconds then disappears
      • C. The message is displayed persistently until hidden or cleared
      • D. The call is ignored; 0.0 is an invalid value
    4. A `trigger_device.TriggeredEvent` handler receives `(Agent : ?agent)`. Which code correctly unwraps the agent before calling `HUD.Show`?
      • A. HUD.Show(Agent, MyMsg())
      • B. if (A := Agent?): HUD.Show(A, MyMsg())
      • C. if (Agent <> false): HUD.Show(Agent, MyMsg())
      • D. HUD.Show(Agent!, MyMsg())
    5. Which `hud_message_device` method removes queued messages for ALL players at once?
      • A. Hide()
      • B. Hide(Agent)
      • C. ClearAllMessages()
      • D. SetText(EmptyMessage)
    Answer key
    1. A β€” All HUD device methods that accept text require the `message` type, not `string`. You must convert your interpolated string through a `<localizes>` function.
    2. D β€” Verse supports both `{Speed}` interpolation syntax and explicit `ToString(Speed)` concatenation. The `+` operator on its own won't work because Verse doesn't auto-convert float to string.
    3. C β€” Per the API docs, passing `0.0` to `SetDisplayTime` makes the message display persistently. You must call `Hide` or `ClearAllMessages` to remove it.
    4. B β€” `?agent` is an option type. The correct Verse idiom is `if (A := Agent?):` which unwraps the option into a concrete `agent` value `A` that the method accepts.
    5. C β€” `ClearAllMessages()` clears every queued message from all players affected by the device. `Hide()` only hides the currently active message without clearing the queue for all players.

    Recap

    One step closer to Shell-Hunt β€” The live 'Shells: {n}/{Total}' counter text on the hunt HUD

    Sources

    Lesson 7: Clean Functions: One Job Each

    Objectives

    Student can extract CollectShell(), UpdateHud(), and CheckWin() as small single-purpose functions with parameters and return values (first SOLID habit).

    🧩 Your capstone piece: The refactor that turns one giant OnBegin into the capstone's clean function set

    Quiz

    1. You are sorting the messy OnShellTouched blob into the three clean functions. Which bucket assignment is correct for these three lines: `set ShellsFound += 1`, `HuntHUD.Show()`, and `EndGame.Activate(Winner)`?
      • A. CollectShell() / UpdateHud() / CheckWin()
      • B. UpdateHud() / CheckWin() / CollectShell()
      • C. CheckWin() / CollectShell() / UpdateHud()
      • D. All three belong in CollectShell(), since a pickup causes all of them
    2. What is the honest 'smell test' this lesson gives for spotting a function with too many responsibilities?
      • A. If describing what the function does honestly requires the word 'and', it has more than one job
      • B. If the function is longer than 5 lines, it must be split
      • C. If the function has any parameters, it is doing too much
      • D. If the function is called from more than one place, it has too many jobs
    3. IsHuntComplete(Found:int, Total:int):logic is called a 'pure' function in this lesson. What makes it pure, and how do you branch on its result?
      • A. It reads no device state and has no side effects β€” same inputs always give the same answer; you branch with the query operator: if (IsHuntComplete(ShellsFound, TotalShells)?)
      • B. It is marked <pure> in its signature, and you branch with if (IsHuntComplete(...) = true)
      • C. It only uses int parameters, and logic values can be used directly: if (IsHuntComplete(...))
      • D. Pure means it cannot be called from CheckWin β€” only from OnBegin
    4. After subscribing to a built-in device event (e.g. `button.InteractedWithEvent.Subscribe(...)`) in `OnBegin`, do you need `Sleep(Inf)` to keep the subscription alive?
      • Predict the output: in the bonus_score_device example, OnBegin runs `for (N := 1..3): set TotalScore += ScoreForShell(N)` where ScoreForShell returns 25 when ShellNumber >= 3 and 10 otherwise. What does `Print("Final score: {TotalScore}")` show?
        • A. Final score: 30
        • B. Final score: 75
        • C. Final score: 45
        • D. Final score: 35
      Answer key
      1. A β€” Recording the pickup (`set ShellsFound += 1`) is CollectShell's one job, painting the screen (`HuntHUD.Show()`) is UpdateHud's one job, and ending the round (`EndGame.Activate(Winner)`) is CheckWin's one job. A pickup *causes* all three, but causing is delegation β€” CollectShell calls the other two rather than doing their work itself.
      2. A β€” Length and call counts are rough hints at best β€” the reliable test is the description. 'It records the pickup AND updates the HUD AND checks the win' names three jobs hiding in one function. Parameters are the opposite of a smell: they make a function's inputs explicit and reusable.
      3. A β€” Pure means no hidden inputs and no side effects: give it two ints, get back a logic, every time. Because the return type is `logic`, you use the query operator `?` to branch on it inside an `if` β€” `if (IsHuntComplete(ShellsFound, TotalShells)?):`. That determinism is what makes pure helpers trivially easy to reason about and reuse.
      4. C β€” The loop calls ScoreForShell(1), ScoreForShell(2), and ScoreForShell(3). Shells 1 and 2 are below 3, so they score 10 each; shell 3 hits the `ShellNumber >= 3` branch and scores 25. 10 + 10 + 25 = 45. Because ScoreForShell is a pure function, you can compute this in your head with total confidence β€” same inputs, same answer.

      Recap

      One step closer to Shell-Hunt β€” The refactor that turns one giant OnBegin into the capstone's clean function set

      Sources

      Lesson 8: One Handler, Many Devices: Event Wiring Patterns

      Objectives

      Student can wire an array of @editable triggers to a shared handler and identify which device/agent fired (fan-in event pattern).

      🧩 Your capstone piece: Wiring all ~10 shell triggers to a single CollectShell handler instead of 10 copies

      Quiz

      1. A `trigger_device.TriggeredEvent` is typed `listenable(?agent)`. What does the `?` mean for your handler?
        • A. The handler is optional and may not be called
        • B. The agent parameter is optional β€” the trigger may have been fired from code with no player, so you must unwrap it
        • C. The handler receives a list of agents
        • D. The trigger only fires when no player is present
      2. You want a button on your island to show the text "Lower Anchor" when a player looks at it. Which call is correct?
        • A. BridgeButton.SetInteractionText("Lower Anchor")
        • B. BridgeButton.SetInteractionText(MyLabel("Lower Anchor")) where MyLabel<localizes>(S:string):message = "{S}"
        • C. BridgeButton.Label = "Lower Anchor"
        • D. BridgeButton.SetText(message{"Lower Anchor"})
      3. You place a `trigger_device` in the editor but forget to mark your field `@editable`. What happens?
        • A. Verse automatically finds the nearest trigger in the level
        • B. The code compiles but the device reference points to a disconnected in-memory object, not your placed device
        • C. The compiler throws an 'Unknown identifier' error
        • D. The subscription works but fires twice
      4. You call `CannonTrigger.SetMaxTriggerCount(25)` on a `trigger_device`. What is the actual max trigger count stored?
        • A. 25
        • B. 20, because trigger_device clamps the value to [0, 20]
        • C. 0, because values above 20 are treated as unlimited
        • D. 10000, because that is the global maximum
      5. Where must you call `Subscribe` to correctly register a handler for a device event?
        • A. At the class field declaration level, next to the @editable field
        • B. In a separate `init` function called before OnBegin
        • C. Inside OnBegin (or a function called from OnBegin), after the devices have been bound to their editor instances
        • D. In a static initializer outside any class
      Answer key
      1. B β€” The `?agent` type is an optional agent. When a trigger is fired via code (e.g. `Trigger()` with no argument), there is no player, so the value is `false`. You must unwrap with `if (A := MaybeAgent?):` before using the agent.
      2. B β€” SetInteractionText takes a `message` type, not a raw string. You must declare a `<localizes>` helper method to convert a string literal into a `message`, then pass the result.
      3. B β€” Without `@editable`, the field is initialised with a default-constructed object that has no connection to any placed device. The code compiles, but your handler will never fire because it's subscribed to a phantom device, not the one in your level.
      4. B β€” trigger_device.SetMaxTriggerCount clamps its input to the range [0, 20]. Passing 25 results in a stored value of 20. button_device has a different range of [0, 10000].
      5. C β€” Subscriptions must happen at runtime. OnBegin is the entry point where devices are fully bound to their placed editor instances. Subscribing at field-declaration level or before OnBegin runs means the device references aren't live yet.

      Recap

      One step closer to Shell-Hunt β€” Wiring all ~10 shell triggers to a single CollectShell handler instead of 10 copies

      Sources

      Lesson 9: Your First Module: the ShellHuntKit Toolbox

      Objectives

      Student can define a module with public helper functions (HUD helpers, counter), import it with using, and understand why modules make code reusable across projects/zones.

      🧩 Your capstone piece: ShellHuntKit module (HudHelper + ShellCounter) β€” the zone's EXPORT that later zones import

      Quiz

      1. You write `TreasureTimer.Start(Pirate)` and get 'Unknown identifier: timer_device'. What's the most likely fix?
        • A. Add `using { /Fortnite.com/Devices }` at the top of the file
        • B. Rename the class to timer_device
        • C. Call Print instead of Start
        • D. Convert Pirate to a float
      2. A `trigger_device`'s TriggeredEvent hands your handler `(Agent : ?agent)`. How do you safely use the player?
        • A. Use Agent directly as a player
        • B. Cast Agent to int first
        • C. Unwrap it with `if (Pirate := Agent?):`
        • D. Call Agent.Enable()
      3. In your own `cove_helpers := module:`, why is `BeginRunFor<public>` marked `<public>`?
        • A. So it can be called from code outside the module after `using`
        • B. To make it run faster
        • C. Because timers require it
        • D. It converts the return type to void
      4. Which call starts the countdown for the entire lobby rather than one pirate?
        • A. TreasureTimer.Start(Pirate)
        • B. TreasureTimer.StartForAll()
        • C. TreasureTimer.Enable()
        • D. TreasureTimer.SetResetDelay(2.0)
      5. Why does `StartPlate.SetResetDelay(2.0)` use `2.0` instead of `2`?
        • A. Style preference only
        • B. Because SetResetDelay takes a float and Verse doesn't auto-convert int to float
        • C. To trigger twice as fast
        • D. Because triggers count in milliseconds
      Answer key
      1. A β€” `timer_device` is defined in the /Fortnite.com/Devices module. Missing that `using` import is what produces 'Unknown identifier'.
      2. C β€” A listenable(?agent) delivers an OPTIONAL agent; you must unwrap it with `Agent?` inside an `if` before using it.
      3. A β€” Without `<public>`, module members are hidden. `<public>` makes `BeginRunFor` usable once you `using { cove_helpers }`.
      4. B β€” `StartForAll()` starts the timer for all agents; `Start(Agent)` only affects the one pirate passed in.
      5. B β€” SetResetDelay's parameter is a float. Verse never auto-converts int↔float, so you must write 2.0.

      Recap

      One step closer to Shell-Hunt β€” ShellHuntKit module (HudHelper + ShellCounter) β€” the zone's EXPORT that later zones import

      Sources

      Lesson 10: Conjuring Shells: SpawnProp at Runtime

      Objectives

      Student can call SpawnProp with a creative_prop_asset + transform to spawn (and Dispose) props at runtime at chosen positions.

      🧩 Your capstone piece: Runtime-spawning the N collectible shell props at shuffled spawn points each round

      Quiz

      1. Predict the result: what does SpawnProp return, and how do you check that the spawn succeeded?
        • A. It returns tuple(?creative_prop, spawn_prop_result); unwrap element 0 with if (Shell := Result(0)?) - success means the option held a real creative_prop.
        • B. It returns a creative_prop directly, or crashes the device if the spawn fails.
        • C. It returns a logic value: true for success, false for failure.
        • D. It returns void; you must query the island afterwards to find the new prop.
      2. Your spawns start failing with spawn_prop_result.TooManyProps. What limit did you hit?
        • A. The island rules currently allow 100 spawned props per script device and 200 total per island.
        • B. SpawnProp can only be called 10 times per second.
        • C. Each creative_prop_asset can only be spawned once.
        • D. The SpawnMarkers array is limited to 16 entries.
      3. How do you remove a shell you spawned earlier, and how do you avoid touching one that is already gone?
        • A. Call Shell.Dispose() to destroy it; guard with Shell.IsValid[] first, which fails if the prop was already disposed.
        • B. Call SpawnProp again with the same transform to overwrite it.
        • C. Set the variable holding it to false - the prop is garbage-collected off the island.
        • D. Call Shell.Hide() - props cannot actually be removed at runtime.
      4. Which module provides Shuffle and GetRandomFloat for randomizing your spawn points?
        • A. /Verse.org/Random
        • B. /Fortnite.com/Devices
        • C. /UnrealEngine.com/Temporary/SpatialMath
        • D. /Verse.org/Simulation
      5. Fill in the blank: @editable ShellAsset : creative_prop_asset = ______. What is the safe placeholder default before you drag a real asset into the Details panel?
        • A. EmptyPropAsset
        • B. false
        • C. DefaultCreativePropAsset
        • D. creative_prop_asset{}
      Answer key
      1. A β€” SpawnProp returns a tuple: element 0 is ?creative_prop (the spawned prop, or false on failure) and element 1 is a spawn_prop_result enum describing the outcome. The idiomatic success check is unwrapping element 0: if (Shell := Result(0)?).
      2. A β€” TooManyProps means you exceeded the runtime prop budget: 100 props per script device and 200 per island. Disposing props between rounds (like CleanupRound does) keeps you under the cap.
      3. A β€” creative_prop.Dispose() destroys the prop and removes it from the island. IsValid[] is a failable check that succeeds only while the prop still exists, so 'for (Shell : SpawnedShells, Shell.IsValid[])' safely skips already-collected shells.
      4. A β€” Shuffle(Input:[]t) and GetRandomFloat(Low, High) both live in /Verse.org/Random. Adding 'using { /Verse.org/Random }' at the top of the file brings them into scope.
      5. C β€” The walkthrough declares the field as ShellAsset : creative_prop_asset = DefaultCreativePropAsset. DefaultCreativePropAsset is the safe placeholder default until you wire a real shell asset into the @editable field in UEFN.

      Recap

      One step closer to Shell-Hunt β€” Runtime-spawning the N collectible shell props at shuffled spawn points each round

      Sources

      Lesson 11: Make It Bob: MoveTo Prop Animation

      Objectives

      Student can animate a creative_prop with MoveTo over time (the animation arc's first rung, before ease curves and prop_mover in later zones).

      🧩 Your capstone piece: The gentle bobbing loop on every spawned shell that makes them visible from afar

      Quiz

      1. Why must you call `MoveTo` inside a `spawn`ed coroutine rather than directly in an event handler?
        • A. Because `MoveTo` has the `<suspends>` effect and will block execution until complete, but event handlers are not `<suspends>` functions
        • B. Because `MoveTo` requires a `player` argument that is only available inside coroutines
        • C. Because event handlers run on a separate thread that cannot access props
        • D. Because `spawn` automatically retries the move if it fails
      2. What happens if you leave an `@editable creative_prop` field unassigned in the UEFN Details panel and then call `MoveTo` on it?
        • A. The game silently skips the move
        • B. A Verse compile error is reported before the game starts
        • C. A Verse runtime error occurs at `MoveToInternal`, crashing the coroutine
        • D. The prop moves to world origin (0,0,0)
      3. Which syntax is correct for calling `TeleportTo` on a `creative_prop`?
        • A. `MyProp.TeleportTo(NewPos, NewRot)`
        • B. `if (MyProp.TeleportTo[NewPos, NewRot]):`
        • C. `spawn { MyProp.TeleportTo(NewPos, NewRot) }`
        • D. `MyProp.TeleportTo<decides>(NewPos, NewRot)`
      4. You want a platform to visit waypoint A, then waypoint B, then return home, repeating forever. Which Verse construct achieves this most cleanly?
        • A. Three separate `spawn` calls in `OnBegin`
        • B. A `loop:` block inside a `<suspends>` coroutine with three sequential `MoveTo` calls
        • C. Three `Subscribe` calls on a timer event
        • D. A `race:` block with three `MoveTo` calls
      5. What is the correct way to write a 400 cm X-axis offset when building a `vector3` in Verse?
        • A. `vector3{ X := ClosedPos.X + 400, Y := ClosedPos.Y, Z := ClosedPos.Z }`
        • B. `vector3{ X := ClosedPos.X + 400.0, Y := ClosedPos.Y, Z := ClosedPos.Z }`
        • C. `vector3{ X := float(ClosedPos.X + 400), Y := ClosedPos.Y, Z := ClosedPos.Z }`
        • D. `vector3{ X := ClosedPos.X + int(400.0), Y := ClosedPos.Y, Z := ClosedPos.Z }`
      Answer key
      1. A β€” `MoveTo` is declared with `<suspends>`, meaning it pauses the current coroutine until the motion finishes. Event handler methods are not `<suspends>`, so they cannot directly call suspending functions. You must `spawn { MyCoroutine() }` from the handler and put `MoveTo` inside the coroutine.
      2. C β€” An unassigned `@editable creative_prop` field holds the default empty `creative_prop{}` value. Calling `MoveTo` on it triggers an internal runtime error at `MoveToInternal` because there is no real prop object to move. Always assign every prop reference in the Details panel before playing.
      3. B β€” `TeleportTo` is declared `<transacts><decides>`, meaning it can fail. Failable (`<decides>`) functions must be called inside a failure context β€” an `if` expression using `[]` bracket syntax. Option A would cause a compile error because the failure is unhandled.
      4. B β€” Because `MoveTo` is `<suspends>`, sequential calls inside a `loop:` block in a coroutine naturally chain β€” each call waits for the previous move to finish before the next begins. This is the simplest and most readable pattern for looping waypoint motion.
      5. B β€” Verse does not automatically convert `int` literals to `float`. The `vector3` fields (`X`, `Y`, `Z`) are `float`, so you must write `400.0` (a float literal) rather than `400` (an int literal). Using `400` causes a type-mismatch compile error.

      Recap

      One step closer to Shell-Hunt β€” The gentle bobbing loop on every spawned shell that makes them visible from afar

      Sources

      Lesson 12: Snap Back: TeleportTo for Players and Props

      Objectives

      Student can teleport an agent/prop to a position or device location and use it for round-start staging and out-of-bounds resets.

      🧩 Your capstone piece: Teleporting all hunters back to the galleon start line at round start and after the win

      Quiz

      1. Why must you call `TeleportTo` inside an `if` expression in Verse?
        • A. Because it returns a `logic` value that must be stored
        • B. Because it carries `<decides>`, meaning it can fail and must be handled as a failable expression
        • C. Because `fort_character` methods always require an `if` wrapper
        • D. Because `TeleportTo` is asynchronous and needs `<suspends>`
      2. What type does `TriggeredEvent` send to its subscriber, and how do you safely use it?
        • A. It sends `agent` directly β€” no unwrapping needed
        • B. It sends `?agent` (optional agent) β€” unwrap with `if (A := MaybeAgent?)`
        • C. It sends `fort_character` β€” cast with `GetFortCharacter[]`
        • D. It sends `player` β€” use `.Agent` to get the underlying agent
      3. You call `DockTrigger.SetMaxTriggerCount(25)`. How many times will the trigger actually fire?
        • A. 25 times β€” the value is stored exactly
        • B. 0 times β€” values above 20 are rejected and reset to 0 (unlimited)
        • C. 20 times β€” the value is clamped to the maximum of 20
        • D. Infinite times β€” any value above 10 is treated as unlimited
      4. What is the difference between `SetResetDelay` and `SetTransmitDelay` on a `trigger_device`?
        • A. `SetResetDelay` controls how long until linked editor devices are notified; `SetTransmitDelay` controls the player-facing cooldown
        • B. `SetResetDelay` controls the player-facing cooldown before the trigger can fire again; `SetTransmitDelay` controls how long before linked editor devices are notified
        • C. They are identical β€” both delay the next trigger activation
        • D. `SetResetDelay` only applies to code-triggered calls; `SetTransmitDelay` only applies to player-triggered calls
      5. You want to teleport a player only if they are currently swimming. Which character state check should you use, and how?
        • A. `if (Character.IsInWater[]):` β€” `IsInWater` is a `<decides>` expression that succeeds when the character is in a water volume
        • B. `Character.IsInWater() = true` β€” compare the boolean return value
        • C. `if (Character.IsInWater?):` β€” unwrap the optional logic value
        • D. `Character.IsInWater` β€” read the property directly, no call needed
      Answer key
      1. B β€” `TeleportTo` is marked `<decides>`, which means it can fail (e.g. invalid destination). In Verse, failable expressions must appear inside an `if` (or another failure-handling context) or the code won't compile.
      2. B β€” `TriggeredEvent` is typed `listenable(?agent)`. The payload is an optional agent because the trigger can also fire without any agent (e.g. triggered by code). You must unwrap it with `if (Agent := MaybeAgent?)` before passing it to APIs that require a plain `agent`.
      3. C β€” `SetMaxTriggerCount` clamps its input to the range [0, 20]. Passing 25 silently becomes 20. Use 0 if you want unlimited triggers.
      4. B β€” `SetResetDelay` is the cooldown before the trigger itself can fire again (the player-facing delay). `SetTransmitDelay` is the delay before the trigger informs other devices wired to it in the editor. They are independent settings.
      5. A β€” `IsInWater` is a `<decides>` method β€” it succeeds (rather than returning a boolean) when the character is inside a water volume. The correct pattern is `if (Character.IsInWater[]):` which executes the body only when the condition holds.

      Recap

      One step closer to Shell-Hunt β€” Teleporting all hunters back to the galleon start line at round start and after the win

      Sources

      Lesson 13: Collector's Loadout: Team Settings & Inventory Device

      Objectives

      Student can control what players carry via team_settings_and_inventory_device (grant a pickaxe-only collector loadout, strip drops) β€” the custom-inventory arc's first rung.

      🧩 Your capstone piece: The hunt loadout: everyone spawns bare-handed with a collection net, no weapons

      Quiz

      1. What is the correct way to use IsOnTeam in Verse?
        • A. if (Device.IsOnTeam[Agent]):
        • B. if (Device.IsOnTeam(Agent) = true):
        • C. Device.IsOnTeam(Agent).Print()
        • D. set X = Device.IsOnTeam(Agent)
      2. TeamOutOfRespawnsEvent is listenable(tuple()). What does its handler signature look like?
        • A. OnWiped(Team : team):void
        • B. OnWiped(Agent : agent):void
        • C. OnWiped():void
        • D. OnWiped(Players : []agent):void
      3. To make Team 2 win when Team 1 is wiped out, which call do you make?
        • A. Team1Settings.EndRound()
        • B. Team2Settings.EndRound()
        • C. Team1Settings.RemoveTemporaryMembersFromTeam()
        • D. Team2Settings.GetTeamMembers()
      4. TeamMemberEliminatedEvent hands you an agent. Why can't you pass it straight to RespawnAtPlayerSpawner?
        • A. RespawnAtPlayerSpawner needs a player, so you must narrow the agent first
        • B. The agent is already despawned and unusable
        • C. RespawnAtPlayerSpawner only accepts a []agent
        • D. You must convert it with StringToPlayer
      5. Which method returns the team's current roster as []agent?
        • A. GetTeam()
        • B. GetTeamMembers()
        • C. IsOnTeam()
        • D. GetValue()
      Answer key
      1. A β€” IsOnTeam is a <decides> method, so you call it with square brackets inside an if/conditional context β€” it succeeds or fails rather than returning a bool.
      2. C β€” A listenable(tuple()) carries no payload, so the handler takes no parameters.
      3. B β€” EndRound() makes the device's OWN team the winner, so you call it on Team 2's device.
      4. A β€” RespawnAtPlayerSpawner(Player:player) requires a player; narrow with if (P := player[Agent]): before calling.
      5. B β€” GetTeamMembers()<reads>:[]agent returns the agents currently on the device's configured team.

      Recap

      One step closer to Shell-Hunt β€” The hunt loadout: everyone spawns bare-handed with a collection net, no weapons

      Sources

      Lesson 14: Game Phases: Enums as a Simple State Machine

      Objectives

      Student can define shell_hunt_phase := enum{Lobby, Hunting, Celebration}, store it in a var, switch behavior per phase, and attach data to phases (the state-machine arc's first rung).

      🧩 Your capstone piece: The LOBBY -> HUNTING -> CELEBRATION phase machine that gates every handler

      Quiz

      1. CurrentPhase starts at Lobby. A player presses: (1) Turn In Shells, (2) Start Hunt, (3) Start Hunt. Which presses actually change the phase, and where does it end up?
        • A. Only press 2 fires (Lobby -> Hunting); presses 1 and 3 are ignored; final phase is Hunting
        • B. All three fire; final phase is Celebration
        • C. Presses 1 and 2 fire; final phase is Celebration
        • D. Press 2 and press 3 both fire; final phase is Celebration
      2. Which declaration creates the Shell Hunt phase enum?
        • A. shell_hunt_phase := enum{Lobby, Hunting, Celebration}
        • B. shell_hunt_phase := class{Lobby, Hunting, Celebration}
        • C. enum shell_hunt_phase = [Lobby, Hunting, Celebration]
        • D. var shell_hunt_phase : enum = {Lobby, Hunting, Celebration}
      3. Why does the controller funnel every phase change through the single EnterPhase function instead of calling `set CurrentPhase` wherever it is convenient?
        • A. So announcing, logging, and future side effects happen consistently on every transition, and there is exactly one place to look when the phase changes
        • B. Because Verse only allows `set` to appear once per class
        • C. Because enums can only be assigned inside functions that take them as parameters
        • D. It is purely stylistic - scattering `set CurrentPhase` behaves identically as the game grows
      4. Verse enum values cannot carry fields. How does this lesson attach data (like banner text) to each phase?
        • A. A helper function that uses `case` on the enum value and returns the data for that phase
        • B. By declaring the enum members with parameters, like Lobby(4.0)
        • C. By storing the data in the enum with `@editable` attributes on each member
        • D. By converting the enum to a string and parsing the data out of its name
      5. The phase is Hunting and a player presses Turn In Shells. What happens on screen?
        • A. The Hunting banner replays for another 6 seconds
        • B. The Celebration banner "Hunt over - crown the shell champion!" shows for 10 seconds
        • C. Nothing - the press is ignored and only a log line prints
        • D. The Lobby banner shows, because Turn In Shells resets the round
      Answer key
      1. A β€” Press 1 asks for Hunting -> Celebration while in Lobby, so the guard ignores it. Press 2 matches the Lobby guard and fires Lobby -> Hunting. Press 3 asks to start again while already Hunting - ignored. The machine ends in Hunting.
      2. A β€” Verse enums use `name := enum{Value1, Value2, ...}` (or the equivalent indented block form). The compiler then guarantees a shell_hunt_phase value is always exactly one of the named members.
      3. A β€” Verse happily lets you `set` a var anywhere in the class - but then every new side effect (banners, timers, spawner unlocks) must be copy-pasted to every call site. One doorway means one place to react, one place to log, one place to extend.
      4. A β€” The enum-with-data pattern: keep the enum as a plain set of names, then write functions like PhaseLabel and PhaseBannerTime that `case` on the value and return the associated data. Adding a phase later means the compiler points you at every helper needing a new arm.
      5. B β€” OnFinishPressed's guard passes (CurrentPhase = Hunting), so EnterPhase(Celebration) runs. AnnouncePhase then shows PhaseLabel(Celebration) - "Hunt over - crown the shell champion!" - with ?DisplayTime := PhaseBannerTime(Celebration), which is 10.0 seconds.

      Recap

      One step closer to Shell-Hunt β€” The LOBBY -> HUNTING -> CELEBRATION phase machine that gates every handler

      Sources

      Lesson 15: Golden Shells: Subclass + override for Polymorphic Pickups

      Objectives

      Student can write a base shell class with OnCollected():int and a golden_shell subclass using <override> to change points/effects β€” first OO polymorphism.

      🧩 Your capstone piece: The rare golden shell worth 3 points with a bonus effect, same collection code path

      Quiz

      1. A `golden_shell` instance sits inside a `[]shell` array. Your code calls `OnCollected()` on that element through the base `shell` type. Which method body runs?
        • A. The `golden_shell` override β€” Verse dispatches on the object's actual class at runtime, not the array's declared type.
        • B. The base `shell` version β€” the array type decides which method runs.
        • C. Both bodies run, base first, then the override.
        • D. It fails to compile β€” you cannot store a subclass in a base-typed array.
      2. You want `golden_shell` to replace the base class's `OnCollected()` behaviour. What must the subclass write?
        • A. OnCollected<override>() : int = ...
        • B. OnCollected<virtual>() : int = ...
        • C. override OnCollected() : int { ... }
        • D. OnCollected<new>() : int = ...
      3. The base class declares `OnCollected() : int`. Which override is valid in the subclass?
        • A. OnCollected<override>() : int = 3
        • B. OnCollected<override>() : string = "golden"
        • C. OnCollected<override>(Bonus : int) : int = Bonus
        • D. OnCollected<override>()<suspends> : int = 3
      4. Your subclass only needs to change the point VALUE, not the pickup logic. What is the leanest correct approach?
        • A. Override just the field β€” `Points<override> : int = 3` β€” and inherit the base method that reads it.
        • B. Copy the entire base method into the subclass and change the number.
        • C. Add an `if (IsGolden?)` branch inside the base class method.
        • D. Fields cannot be overridden in Verse; you must always override a method.
      5. In the walkthrough, `BeachShells` is set to `array{shell{}, shell{}, GoldenShell, shell{}}`. You press the button four times. What is `Score` after the fourth press?
        • A. 6 β€” 1 + 1 + 3 (golden) + 1.
        • B. 4 β€” every shell is worth 1 through the base-typed array.
        • C. 12 β€” every shell is worth 3 once a golden shell is in the array.
        • D. 3 β€” only the golden shell banks points.
      Answer key
      1. A β€” This is dynamic dispatch, the heart of polymorphism: the variable (or array slot) may be typed as the base class, but the method that executes belongs to the object's real class. A golden_shell always behaves like a golden_shell, even when it is hiding in a []shell array.
      2. A β€” Verse uses the `<override>` specifier on the method name in the subclass. The signature (parameters, return type, and any effect specifiers) must match the base method exactly.
      3. A β€” An override must keep the exact same signature as the base method β€” same parameters, same return type, same effect specifiers. Changing the return type, adding parameters, or adding <suspends> all mismatch the base declaration and fail to compile.
      4. A β€” Verse allows field overrides with the same `<override>` specifier. The inherited method now reads the subclass's value β€” the smallest possible subclass. An if-branch in the base class is the exact special-casing polymorphism exists to remove.
      5. A β€” The first two presses collect plain shells (+1 each), the third collects the golden shell (+3 with a sparkle burst), and the fourth is plain again (+1). Dynamic dispatch means each element pays out according to its actual class: 1 + 1 + 3 + 1 = 6.

      Recap

      One step closer to Shell-Hunt β€” The rare golden shell worth 3 points with a bonus effect, same collection code path

      Sources

      Lesson 16: Beach Buffs: Powerup Devices

      Objectives

      Student can place/configure concrete powerup devices (movement-speed etc.) and Pickup/grant them from Verse when the golden shell is collected.

      🧩 Your capstone piece: The speed-boost reward the golden shell grants its finder

      Quiz

      1. Which method grants a powerup's buff directly to a specific player from Verse?
        • A. Spawn(Agent)
        • B. Pickup(Agent)
        • C. GrantEffect(Agent)
        • D. ApplyBuff(Agent)
      2. How do you correctly call GetRemainingTime, whose signature is (Agent:agent)<transacts>:float?
        • A. RewardPowerup.GetRemainingTime[Agent]
        • B. RewardPowerup.GetRemainingTime(Agent)
        • C. if (RewardPowerup.GetRemainingTime[Agent]):
        • D. RewardPowerup.GetRemainingTime<decides>(Agent)
      3. IsSpawned() is declared <transacts><decides>:void. How must it be used?
        • A. As a bare statement: RewardPowerup.IsSpawned()
        • B. In a failure context with []: if (RewardPowerup.IsSpawned[]):
        • C. Assigned to a logic: Ok := RewardPowerup.IsSpawned()
        • D. With == true comparison
      4. What does GetRemainingTime return when the agent does NOT have the effect applied?
        • A. -1.0
        • B. 0.0
        • C. false
        • D. It fails/rolls back
      5. What does GetRemainingTime return for an infinite-duration effect?
        • A. 0.0
        • B. -1.0
        • C. A very large positive float
        • D. It fails
      Answer key
      1. B β€” Pickup(Agent:agent):void grants the powerup straight to that agent without them touching the physical orb.
      2. B β€” It returns a float and is not <decides>, so call it with () and read the value; [] would be a compile error.
      3. B β€” A <decides> function is fallible and must be called with [] inside a failure context like an if condition.
      4. B β€” It returns 0.0 when the agent has no effect, and -1.0 for an infinite-duration effect.
      5. B β€” The API returns -1.0 to signal an infinite duration.

      Recap

      One step closer to Shell-Hunt β€” The speed-boost reward the golden shell grants its finder

      Sources

      Lesson 17: CAPSTONE: Shell-Hunt β€” Ship the Minigame

      Objectives

      Student assembles every prior lesson into one playable, compile-verified Shell-Hunt round loop and playtests it end to end.

      πŸ” Builds on: Imports the student's own ShellHuntKit module from lesson 9 (first taste of cross-file imports)

      🧩 Your capstone piece: The whole game: this IS the capstone assembly lesson

      Quiz

      1. Ship-checklist item 3 fails: touching a shell makes it disappear, but the HUD stays at 'Shells: 0/10'. Which bug most likely broke the counter?
        • A. The shell triggers were wired to a handler that mutates ShellsFound but never calls the kit's UpdateShellHud afterwards.
        • B. hud_message_device cannot display numbers, only fixed strings.
        • C. The timer must be running for SetText to take effect.
        • D. SpawnProp failed, so the counter is frozen by the engine.
      2. This capstone reuses the ShellHuntKit module you wrote in lesson 9. Which line makes its helpers (UpdateShellHud, ShellCountMsg) available inside the game device's file?
        • A. using { shell_hunt_kit }
        • B. import shell_hunt_kit
        • C. include "ShellHuntKit.verse"
        • D. shell_hunt_kit.Open()
      3. OnTimeUp writes `spawn { EndRound() }` instead of calling EndRound() directly. Why?
        • A. EndRound is <suspends> (it Sleeps during the celebration), and an event handler cannot suspend β€” spawn launches it as its own task.
        • B. spawn makes the call run faster on the server.
        • C. Timer events can only trigger spawned functions.
        • D. Direct calls are forbidden inside classes.
      4. In EndRound, TeleportTo is wrapped as `if (Character.TeleportTo[HomePosition, HomeRot]) {}`. What does this guard against?
        • A. TeleportTo is a failable <decides> function β€” it can fail (e.g. an invalid destination), so Verse requires calling it in a failure context like if.
        • B. It prevents two players from teleporting at the same time.
        • C. It checks whether the player owns a teleporter device.
        • D. Nothing β€” the if is optional style.
      5. SpawnShellProps calls SpawnProp and then writes `if (Shell := SpawnResult(0)?)`. What is it unwrapping?
        • A. SpawnProp returns tuple(?creative_prop, spawn_prop_result) β€” element (0) is an optional prop, so it must be unwrapped with ? before you can keep it.
        • B. SpawnResult is an array of every shell spawned so far, and (0) picks the first one.
        • C. The ? converts the prop into an @editable reference the Details panel can see.
        • D. SpawnProp returns a logic value, and (0)? checks whether it was true.
      Answer key
      1. A β€” The HUD only changes when something calls UpdateShellHud (SetText + Show) after the state changes. If CollectShell bumps the counter but skips the HUD helper, shells still vanish (the trigger fired) while the on-screen count never moves. That separation of jobs β€” and knowing which function owns which checklist item β€” is lesson 7 paying off.
      2. A β€” Verse imports modules with a using clause naming the module β€” the same syntax used for Epic's own modules like /Fortnite.com/Devices. It works identically whether the module sits in the same file or in its own ShellHuntKit.verse file, which is exactly why the kit is reusable by later zones.
      3. A β€” TriggeredEvent/SuccessEvent handlers are plain :void functions with no <suspends> effect, so they cannot call a suspending function directly. spawn starts EndRound as an independent concurrent task β€” and EndRound's phase guard makes it safe even when the timer and the final shell both spawn it.
      4. A β€” TeleportTo carries the <decides> effect: it either succeeds or fails, and failable calls use square brackets and must run inside a failure context such as if. Lesson 12 introduced this; the capstone applies it to every hunter on the way home so one failed teleport never crashes the round loop.
      5. A β€” Lesson 10's pattern: SpawnProp hands back a tuple whose first element is ?creative_prop β€” the spawn can fail, so the prop is optional. Unwrapping it with ? inside an if is what lets the capstone store the real prop in SpawnedShells for Dispose() at round end.

      Recap

      One step closer to Shell-Hunt β€” The whole game: this IS the capstone assembly lesson

      Sources

      Lesson 18: Victory Beat: Patchwork Drum Player

      Objectives

      Student can drive the Patchwork drum_player_device from Verse to play a celebration jingle on the win event.

      🧩 Your capstone piece: The CELEBRATION-phase victory jingle

      Quiz

      1. What must you do before you can call `DrumPlayer.Enable()` in Verse?
        • A. Declare DrumPlayer as an @editable field inside your creative_device class and link it to a placed drum_player_device in the UEFN Details panel.
        • B. Import the drum_player_device class at the top of your file with a using statement.
        • C. Call DrumPlayer.Disable() first to reset its state.
        • D. Add a drum_sequencer_device to the same class.
      2. What does calling `DrumPlayer.Enable()` actually do?
        • A. It immediately plays a drum sound effect.
        • B. It makes the device responsive to incoming Patchwork note signals so it can produce audio.
        • C. It starts a built-in drum loop stored inside the device.
        • D. It connects the device to the nearest drum_sequencer_device automatically.
      3. A `trigger_device`'s `TriggeredEvent` handler receives which parameter type?
        • A. agent
        • B. player
        • C. ?agent
        • D. tuple()
      4. Which of the following is TRUE about `drum_player_device` events in Verse?
        • A. It fires a BeatEvent every time a drum note plays.
        • B. It fires an EnabledEvent when Enable() is called.
        • C. It exposes no events β€” you can only call Enable() and Disable() on it.
        • D. It inherits TriggeredEvent from patchwork_device.
      5. Why is it best practice to call `DrumPlayer.Disable()` explicitly in `OnBegin` rather than relying on the editor's default state?
        • A. Verse ignores editor settings entirely at runtime.
        • B. Calling Disable() in OnBegin ensures your code is the single source of truth for initial state, making behaviour predictable regardless of editor configuration.
        • C. The device always starts in an enabled state at runtime no matter what the editor says.
        • D. OnBegin resets all device states automatically, so Disable() is needed to override that reset.
      Answer key
      1. A β€” Verse requires every placed device to be referenced through an @editable field. Without it, the identifier is unknown and the code won't compile, let alone run.
      2. B β€” Enable() activates the device's ability to respond to Patchwork note inputs. Audio only plays when note signals arrive from upstream Patchwork devices like a drum_sequencer_device.
      3. C β€” TriggeredEvent is a listenable(?agent), so the handler receives an optional agent. You must unwrap it with `if (A := Agent?):` before using the agent value.
      4. C β€” The drum_player_device API surface only exposes Enable and Disable methods. There are no listenable events on this device.
      5. B β€” Editor defaults can be changed accidentally or differ between team members. Setting state explicitly in OnBegin makes your Verse logic self-contained and reliable.

      Recap

      One step closer to Shell-Hunt β€” The CELEBRATION-phase victory jingle

      Sources

      Lesson 19: Cave Echoes: Patchwork Echo Effect

      Objectives

      Student can chain the echo_effect_device into the Patchwork signal path (correct /Fortnite.com/Devices/Patchwork using-clause) for beach-cave ambience.

      🧩 Your capstone piece: Ambient audio polish inside the galleon wreck

      Quiz

      1. Which two methods toggle the echo effect on and off?
        • A. StartEcho() / StopEcho()
        • B. Enable() / Disable()
        • C. TurnOn() / TurnOff()
        • D. Activate() / Deactivate()
      2. Why must GetPlayerUI[Player] be called with square brackets inside an if?
        • A. Because it returns an array
        • B. Because it is fallible and only valid in a failure context
        • C. Because [] concatenates the player
        • D. Because it is a suspends function
      3. How do you display the HUD to every player currently in the session?
        • A. Call GetPlayers() directly
        • B. for (Player : GetPlayspace().GetPlayers()):
        • C. for (Player : AllPlayers):
        • D. Player.ShowHUD()
      4. What method updates the text_block's displayed text after creation?
        • A. SetText
        • B. SetLabel
        • C. UpdateText
        • D. SetContent
      5. Why is there no () before .Subscribe on TriggeredEvent?
        • A. It is a device event, which is a field accessed directly
        • B. Because it suspends
        • C. Because it returns void
        • D. It is a bug that should be fixed
      Answer key
      1. B β€” echo_effect_device inherits Enable() and Disable() from the creative device base to turn the effect on and off.
      2. B β€” GetPlayerUI is a fallible (<decides>) function, so it must be called with [] inside a failure context such as an if condition.
      3. B β€” You iterate the playspace's players with for (Player : GetPlayspace().GetPlayers()): β€” there is no bare GetPlayers() symbol.
      4. A β€” Calling StatusText.SetText(EchoOnMsg) updates the text_block's text live.
      5. A β€” Device events like TriggeredEvent are fields, so you write TriggeredEvent.Subscribe(...). Only character events need () before .Subscribe.

      Recap

      One step closer to Shell-Hunt β€” Ambient audio polish inside the galleon wreck

      Sources

      Lesson 20: Lagoon Synthwave: Omega Synthesizer

      Objectives

      Student can sequence the omega_synthesizer_device for a looping lobby soundtrack toggled by game phase.

      🧩 Your capstone piece: Lobby-phase background music that stops when the hunt starts

      Quiz

      1. What is the correct way to call Enable() on an omega_synthesizer_device placed in your level?
        • A. Declare it as an @editable field and call MySynth.Enable() inside a creative_device method.
        • B. Create a new omega_synthesizer_device{} inline and call .Enable() on it directly.
        • C. Use Print("Enable") to signal the device.
        • D. Call omega_synthesizer_device.Enable() as a static method.
      2. Which of the following Verse-accessible methods does omega_synthesizer_device expose?
        • A. Play() and Stop()
        • B. Enable() and Disable()
        • C. Mute() and Unmute()
        • D. Start() and Pause()
      3. You want the synth to play for exactly 3 seconds then stop. Which Sleep call is correct?
        • A. Sleep(3)
        • B. Sleep(3.0)
        • C. Sleep("3")
        • D. Wait(3.0)
      4. The omega_synthesizer_device has no Verse events. How do you trigger Enable/Disable in response to a player action?
        • A. Subscribe to the synthesizer's own OnNoteEvent.
        • B. Subscribe to another device's event (e.g., button_device.InteractedWithEvent) and call Enable/Disable inside the handler.
        • C. Override the synthesizer's OnBegin method.
        • D. Poll the synthesizer's state in a loop using its IsEnabled() method.
      5. After calling Disable() on an omega_synthesizer_device, what happens to the Patchwork cable connections made in the editor?
        • A. All Patchwork cables are permanently disconnected and must be rewired.
        • B. The cable graph is preserved; calling Enable() resumes audio from where the note chain left off.
        • C. The device is destroyed and must be respawned.
        • D. The cables are paused and resume only after a level reload.
      Answer key
      1. A β€” Placed devices must be referenced through @editable fields wired in the UEFN Details panel. A bare omega_synthesizer_device{} literal is an unplaced default object and has no effect on the actual device in the level.
      2. B β€” omega_synthesizer_device inherits from patchwork_device, which provides exactly two methods: Enable() and Disable(). There are no Play, Stop, Mute, or Pause methods on this device.
      3. B β€” Sleep takes a float argument. Verse does not auto-convert int to float, so Sleep(3) is a compile error. Sleep(3.0) is the correct form. There is no built-in Wait function.
      4. B β€” omega_synthesizer_device exposes no events. You must subscribe to events from other devices β€” buttons, triggers, zone devices β€” and call Enable() or Disable() on the synthesizer inside those handlers.
      5. B β€” Disable() gates the synthesizer's audio output but does not alter the Patchwork wiring graph. Calling Enable() again resumes normal operation with all connections intact.

      Recap

      One step closer to Shell-Hunt β€” Lobby-phase background music that stops when the hunt starts

      Sources

      Lesson 21: Victory Lap: Surfboard Spawner

      Objectives

      Student can spawn and grant a surfboard via vehicle_spawner_surfboard_device as a scripted reward.

      🧩 Your capstone piece: The winner's surfboard victory lap during CELEBRATION

      Quiz

      1. Which event should you subscribe to in order to receive the `fort_vehicle` reference when a surfboard spawns?
        • A. SpawnedEvent
        • B. VehicleSpawnedEvent
        • C. DestroyedEvent
        • D. AgentEntersVehicleEvent
      2. What happens when you call `RespawnVehicle()` while a surfboard is already active in the level?
        • A. Nothing β€” RespawnVehicle only works when no vehicle exists.
        • B. The existing board is destroyed first, then a new one spawns at the spawner's location.
        • C. A second board spawns alongside the existing one.
        • D. The existing board teleports back to the spawn point without being destroyed.
      3. You want to auto-mount a player onto the surfboard when they walk into a trigger zone. The trigger's `TriggeredEvent` handler receives `(Agent : ?agent)`. Which of the following correctly calls `AssignDriver`?
        • A. SurfSpawner.AssignDriver(Agent)
        • B. if (A := Agent?): SurfSpawner.AssignDriver(A)
        • C. SurfSpawner.AssignDriver(Agent?)
        • D. if (Agent?): SurfSpawner.AssignDriver(Agent)
      4. You place a `vehicle_spawner_surfboard_device` in your level but forget to wire it to the `@editable` field in the Details panel. What happens when your Verse code calls `SurfSpawner.Enable()`?
        • A. A compile error is thrown immediately.
        • B. The call targets the default empty device instance, not the placed actor β€” the board in your level is unaffected.
        • C. Verse automatically finds the nearest surfboard spawner in the level.
        • D. The game crashes at runtime with a null reference exception.
      5. Which pair of events are marked deprecated and should be replaced in new code?
        • A. AgentEntersVehicleEvent and AgentExitsVehicleEvent
        • B. SpawnedEvent and DestroyedEvent
        • C. VehicleSpawnedEvent and VehicleDestroyedEvent
        • D. SpawnedEvent and VehicleDestroyedEvent
      Answer key
      1. A β€” `SpawnedEvent` is the current (non-deprecated) event and it sends the `fort_vehicle` that was spawned. `VehicleSpawnedEvent` is deprecated and sends only an empty `tuple()` with no vehicle reference.
      2. B β€” The API documentation states: 'Spawns a new vehicle. The previous vehicle will be destroyed before a new vehicle spawns.' This means `DestroyedEvent` will fire before the new board appears.
      3. B β€” `TriggeredEvent` sends `?agent` (optional). You must unwrap it with `if (A := Agent?):` to get a concrete `agent` value before passing it to `AssignDriver`, which expects a non-optional `agent`.
      4. B β€” An `@editable` field that is not wired in the Details panel points to the default-constructed instance, which has no connection to any placed actor. Your methods run against a no-op object and the board in the level is never affected. Always wire `@editable` fields in the Details panel.
      5. C β€” As of UEFN 25.00, `VehicleSpawnedEvent` and `VehicleDestroyedEvent` are deprecated. The replacements are `SpawnedEvent` (which also sends the `fort_vehicle`) and `DestroyedEvent`.

      Recap

      One step closer to Shell-Hunt β€” The winner's surfboard victory lap during CELEBRATION

      Sources

      Lesson 22: Dune Runner: Dirtbike Spawner

      Objectives

      Student can configure the vehicle_spawner_dirtbike_device and script respawn/assignment β€” reinforcing the vehicle-spawner pattern on a second device.

      🧩 Your capstone piece: Optional free-roam dirtbike run along the tide pools between rounds

      Quiz

      1. Which event should you subscribe to in order to receive the actual `fort_vehicle` object when a dirtbike spawns?
        • A. SpawnedEvent
        • B. VehicleSpawnedEvent
        • C. DestroyedEvent
        • D. AgentEntersVehicleEvent
      2. What does `RespawnVehicle()` do if a dirtbike is already present at the spawner?
        • A. It does nothing β€” you must call `DestroyVehicle()` first.
        • B. It destroys the existing bike and then spawns a fresh one.
        • C. It spawns a second bike alongside the existing one.
        • D. It teleports the existing bike back to the spawn point without destroying it.
      3. You want to force a specific player onto the dirtbike the moment it spawns. Which method do you call?
        • A. Enable()
        • B. RespawnVehicle()
        • C. AssignDriver(Agent)
        • D. DestroyVehicle()
      4. The handler signature for `AgentEntersVehicleEvent` is `OnAgentEnters(Agent : agent) : void`. Why is there NO optional unwrap (`if (A := Agent?):`) needed here?
        • A. Because `agent` is always `none` for vehicle events.
        • B. Because `AgentEntersVehicleEvent` is typed `listenable(agent)`, not `listenable(?agent)`, so the value is never optional.
        • C. Because Verse automatically unwraps optionals in event handlers.
        • D. Because `AssignDriver` accepts `?agent` and handles `none` internally.
      5. You subscribe to `DestroyedEvent` to auto-respawn the bike. What gotcha must you guard against?
        • A. The event fires on a background thread and cannot call device methods.
        • B. Calling `RespawnVehicle()` inside the handler destroys the new bike immediately.
        • C. `RespawnVehicle()` triggers `DestroyedEvent` for the old bike, which can cause infinite respawn loops if unguarded.
        • D. `DestroyedEvent` only fires when the player destroys the bike, not when `DestroyVehicle()` is called from Verse.
      Answer key
      1. A β€” `SpawnedEvent` is the modern, non-deprecated event and its handler receives a `fort_vehicle` parameter. `VehicleSpawnedEvent` is deprecated and only sends `tuple()` with no vehicle reference.
      2. B β€” The API documentation states: 'Spawns a new vehicle. The previous vehicle will be destroyed before a new vehicle spawns.' This means `DestroyedEvent` will fire before the new `SpawnedEvent`.
      3. C β€” `AssignDriver(Agent:agent):void` sets the given agent as the vehicle's driver, placing them in the seat immediately.
      4. B β€” `AgentEntersVehicleEvent` is declared as `listenable(agent)` β€” it sends a concrete `agent`, not an optional. Only events typed `listenable(?agent)` (like many trigger events) require the `if (A := Agent?):` unwrap pattern.
      5. C β€” Because `RespawnVehicle()` destroys the current bike before spawning a new one, it fires `DestroyedEvent`. If your handler calls `RespawnVehicle()` unconditionally, the new bike's destruction event will trigger another respawn β€” an infinite loop. Use a boolean guard or a delay to break the cycle.

      Recap

      One step closer to Shell-Hunt β€” Optional free-roam dirtbike run along the tide pools between rounds

      Sources

      Lesson 23: Graceful Goodbyes: Detecting Player Leave

      Objectives

      Student can handle a player leaving mid-round (PlayersLeftEvent / participant tracking) and clean up their shells/score so the round stays fair.

      🧩 Your capstone piece: Mid-round dropout handling so Shell-Hunt never soft-locks waiting on a gone player

      Quiz

      1. A player leaves Shell-Hunt while holding the golden shell. In the dropout handler, which cleanup steps run, and in what order?
        • A. Respawn the golden shell via the item spawner, remove the leaver's score-map entry, then check whether the round should end.
        • B. Check whether the round should end, remove the leaver's score-map entry, then respawn the golden shell.
        • C. Remove the leaver's score-map entry only β€” the golden shell respawns automatically when a player leaves.
        • D. End the round immediately β€” a dropout while holding the objective always forfeits the round.
      2. Which real Verse API fires when a human player leaves your island?
        • A. GetPlayspace().PlayerRemovedEvent() β€” a listenable(player) on fort_playspace
        • B. GetPlayspace().PlayersLeftEvent β€” a listenable(agent) on fort_playspace
        • C. creative_device.OnPlayerLeft() β€” an overridable lifecycle method
        • D. player.LeftGameEvent β€” a listenable(void) on the player class
      3. Your round mixes human players with AI beachcombers, and you want to react when EITHER kind leaves. What do you use?
        • A. GetPlayspace().ParticipantRemovedEvent(), then narrow with player[Agent] to tell humans from AI
        • B. GetPlayspace().PlayerRemovedEvent() twice β€” once for humans, once for AI
        • C. GetPlayspace().GetPlayers() in a polling loop, since AI never appears in events
        • D. agent.RemovedEvent() on each AI agent individually
      4. How do you remove a leaver's entry from a `var ShellCounts : [player]int` map in Verse?
        • A. Rebuild it: loop `for (Key -> Value : ShellCounts, Key <> Leaver)` into a fresh map, then `set ShellCounts = Rebuilt`
        • B. Call ShellCounts.RemoveKey(Leaver)
        • C. Write `set ShellCounts[Leaver] = false` to clear the slot
        • D. You cannot β€” map entries are permanent for the session
      5. This line fails to compile: `GetPlayspace().PlayerRemovedEvent.Subscribe(OnPlayerLeft)`. What is the bug?
        • A. PlayerRemovedEvent is a playspace event β€” a function returning a listenable β€” so it needs () before .Subscribe: `PlayerRemovedEvent().Subscribe(...)`
        • B. Subscribe requires a lambda, not a named function like OnPlayerLeft
        • C. You must call GetPlayspace() inside OnBegin's for loop, not at the top level
        • D. PlayerRemovedEvent only exists on creative_device, not on the playspace
      Answer key
      1. A β€” Order matters: the objective item returns to play first (a stranded objective is the soft-lock), the score map is cleaned second so no later math counts the ghost, and the round-end check runs last against the already-cleaned state.
      2. A β€” The concept creators call a 'players left event' is PlayerRemovedEvent() on fort_playspace: `PlayerRemovedEvent<public>():listenable(player)`. Note the () β€” playspace events are functions returning a listenable, unlike device events which are fields.
      3. A β€” ParticipantRemovedEvent() is a listenable(agent) that fires for any participant leaving β€” human players and AI-controlled agents alike. Inside the handler, the failable cast player[Agent] succeeds only for humans.
      4. A β€” Verse maps have no remove-key operation. The proven pattern is a filtered rebuild: iterate Key -> Value pairs with a `Key <> Leaver` filter, insert each survivor into a new map with `if (set Rebuilt[Key] = Value) {}`, then assign the rebuilt map back.
      5. A β€” Playspace events like PlayerRemovedEvent() and PlayerAddedEvent() are functions that return a listenable, so they take () before .Subscribe. The lesson calls mixing this up 'the number-one compile error on this lesson.'

      Recap

      One step closer to Shell-Hunt β€” Mid-round dropout handling so Shell-Hunt never soft-locks waiting on a gone player

      Sources

      Lesson 24: Beyond Devices: First Look at Scene Graph Entities

      Objectives

      Student can create an entity with components in Scene Graph and understands the entity/component model they'll master in later zones (arc teaser, free-tier hook).

      🧩 Your capstone piece: Not in the shipped capstone β€” the forward-looking hook that sells the paid zones' prefab/component arc

      Quiz

      1. In Scene Graph, what is an Entity?
        • A. An object in your game world, like a tree, a sword, or a door
        • B. A Verse function that runs when the game starts
        • C. A setting that controls your island's frame rate
        • D. The editor window where you write Verse code
      2. What role do Components play in Scene Graph?
        • A. They give entities behavior, like skills or actions
        • B. They store the island's save data
        • C. They replace Verse code entirely
        • D. They control the game's lighting and weather
      3. In the door tutorial, which device actually slides the door prop open and closed?
        • A. prop_mover_device
        • B. trigger_device
        • C. creative_device
        • D. shooting_range_target_track_device
      4. Why does the tutorial use a prop_mover_device instead of moving the door mesh directly from Verse?
        • A. Verse has no free-form MoveObject API, so prop_mover_device is the real UEFN device for animating props at runtime
        • B. prop_mover_device compiles faster than plain Verse code
        • C. Verse can move props, but only on dedicated servers
        • D. prop_mover_device is the only device that can be marked @editable
      5. Fill in the blank: DoorTrigger.TriggeredEvent.______(OnDoorTriggered)
        • A. Subscribe
        • B. Listen
        • C. Connect
        • D. Await
      Answer key
      1. A β€” The tutorial explains that in Scene Graph every object in your game is an Entity β€” a character or prop such as a tree, a sword, or a door. Behavior is added separately through Components.
      2. A β€” Components are described as skills you attach to entities: a door entity might have a Move component, a player entity a Health component. Putting components on an entity gives it behavior.
      3. A β€” The prop_mover_device holds the door mesh and handles the sliding movement between its start (closed) and end (open) waypoints. The trigger_device only detects the player; it never moves anything.
      4. A β€” The code comment says it directly: Verse has no free-form MoveObject API; the prop_mover_device is the real UEFN device for animating props at runtime, lerping the prop between editor-set positions.
      5. A β€” In OnBegin the script calls DoorTrigger.TriggeredEvent.Subscribe(OnDoorTriggered), which registers OnDoorTriggered to run every time the trigger fires.

      Recap

      One step closer to Shell-Hunt β€” Not in the shipped capstone β€” the forward-looking hook that sells the paid zones' prefab/component arc

      Sources

      Generated by Verse Island on 2026-07-04.