Reference Verse compiles

Golden Shells: Subclass + override for Polymorphic Pickups

Every shell on South Shores is worth a point — but one rare golden shell is worth three, and it sparkles when you grab it. Instead of littering your collection code with special cases, you'll teach the golden shell to carry its own rules: a base `shell` class with an `OnCollected():int` method, and a `golden_shell` subclass that uses Verse's `<override>` specifier to change the payout and fire a bonus effect. Same collection path, richer reward — that's polymorphism, and it's the exact piece the Shell-Hunt capstone needs.

Updated Examples verified on the live UEFN compiler
Watch the Knotoverride_method in ~90 seconds.

Overview

In Verse, every class can declare methods that subclasses are allowed to replace. When you mark a method <override> in a subclass, Verse calls your version whenever that method is invoked on an instance of that subclass — even if the caller only knows about the parent type. This is called runtime polymorphism.

The rules are strict but logical:

  • The overriding method's parameter types must be supertypes of (or identical to) the overridden method's parameters.
  • The overriding method's return type must be a subtype of (or identical to) the overridden method's return type.
  • The overriding method must have no more effects than the overridden method (e.g., you can't add <suspends> if the base doesn't have it).
  • Use (super:)Method() to call the parent implementation from inside the override.

When do you reach for this? Any time you have multiple things that share a lifecycle or interface but behave differently — enemy types, collectible pickups, round managers, trap variants, etc.

API Reference

(API surface could not be resolved for this device.)

Walkthrough

Scenario: A Family of Trap Devices

Imagine a dungeon island with three trap types — a spike trap, a freeze trap, and a launch trap. Each trap needs to Activate() when a player steps on a pressure plate, but each does something different. We model this with a base trap_base class and three subclasses, then wire them all up from a single trap_manager_device.

The manager holds an array of trap_base references and calls Activate() on each — it never needs to know which specific trap it's talking to.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/SpatialMath }

# ─── Base trap class ───────────────────────────────────────────────
trap_base := class:
    # Default activation — subclasses override this
    Activate<public>() : void =
        # No-op by default; subclasses provide the real behavior
        {}

    # Shared helper: log which trap fired (uses a tag string)
    Describe<public>() : string = "unknown trap"

# ─── Spike Trap ────────────────────────────────────────────────────
spike_trap := class(trap_base):
    # Reference to a prop that visually represents the spikes
    SpikeProp<public> : creative_prop = creative_prop{}

    Activate<override>() : void =
        # Show the spike prop (it was hidden at game start)
        SpikeProp.Show()

    Describe<override>() : string = "spike trap"

# ─── Freeze Trap ───────────────────────────────────────────────────
freeze_trap := class(trap_base):
    FreezeProp<public> : creative_prop = creative_prop{}

    Activate<override>() : void =
        # Hide the freeze prop (e.g., ice block disappears on trigger)
        FreezeProp.Hide()

    Describe<override>() : string = "freeze trap"

# ─── Launch Trap ───────────────────────────────────────────────────
launch_trap := class(trap_base):
    LaunchProp<public> : creative_prop = creative_prop{}

    Activate<override>() : void =
        # Apply an upward impulse to the launch pad prop
        LaunchProp.ApplyLinearImpulse(vector3{Forward := 0.0, Left := 0.0, Up := 500.0})

    Describe<override>() : string = "launch trap"

# ─── Manager Device ────────────────────────────────────────────────
trap_manager_device := class(creative_device):

    # Editable props wired in the UEFN Details panel
    @editable
    SpikePropRef : creative_prop = creative_prop{}

    @editable
    FreezePropRef : creative_prop = creative_prop{}

    @editable
    LaunchPropRef : creative_prop = creative_prop{}

    # Pressure plate that triggers all traps
    @editable
    Plate : trigger_device = trigger_device{}

    # Internal array of base-typed trap references
    var Traps : []trap_base = array{}

    OnBegin<override>()<suspends> : void =
        # Build concrete instances, injecting the editable props
        SpikeTrap  := spike_trap  { SpikeProp  := SpikePropRef  }
        FreezeTrap := freeze_trap { FreezeProp := FreezePropRef }
        LaunchTrap := launch_trap { LaunchProp := LaunchPropRef }

        # Store as base type — the manager only knows trap_base
        set Traps = array{ SpikeTrap, FreezeTrap, LaunchTrap }

        # Hide all props at game start so traps look "unset"
        SpikePropRef.Hide()
        FreezePropRef.Hide()

        # Subscribe to the pressure plate
        Plate.TriggeredEvent.Subscribe(OnPlateTriggered)

    # Handler fires when a player steps on the plate
    OnPlateTriggered(Agent : ?agent) : void =
        # Activate every trap — polymorphism picks the right Activate()
        for (Trap : Traps):
            Trap.Activate()```

**Line-by-line explanation:**

| Lines | What's happening |
|---|---|
| `trap_base` | Defines the shared interface. `Activate()` is a no-op; subclasses replace it. |
| `spike_trap / freeze_trap / launch_trap` | Each subclass overrides `Activate()` with its own logic using real `creative_prop` API calls (`Show`, `Hide`, `ApplyLinearImpulse`). |
| `var Traps : []trap_base` | The manager stores everything as the **base type** — this is the power of polymorphism. |
| `set Traps = array{...}` | Concrete instances are assigned to base-typed slots. Verse allows this because subclasses are subtypes. |
| `Plate.TriggeredEvent.Subscribe(OnPlateTriggered)` | Wires the pressure plate event to our handler. |
| `for (Trap : Traps): Trap.Activate()` | One loop activates all three traps; each calls its own overridden version. |

---

## Common patterns

### Pattern 1 — Calling `(super:)` to extend, not replace

Sometimes you want the parent's behavior **plus** something extra. Use `(super:)` to run the base implementation first.

```verse
using { /Fortnite.com/Devices }

# Base game-mode manager
game_mode_base := class:
    OnRoundStart<public>() : void =
        # Base logic: reset scores, show HUD
        {}

# Elimination mode adds its own setup on top
elimination_mode := class(game_mode_base):
    @editable
    SpawnPad : creature_spawner_device = creature_spawner_device{}

    OnRoundStart<override>() : void =
        # Run base setup first
        (super:)OnRoundStart()
        # Then do elimination-specific setup
        SpawnPad.Enable()

# Device that drives the mode
elimination_mode_device := class(creative_device):
    @editable
    TriggerPlate : trigger_device = trigger_device{}

    var Mode : game_mode_base = game_mode_base{}

    OnBegin<override>()<suspends> : void =
        EliminationMode := elimination_mode{ SpawnPad := SpawnPadRef }
        set Mode = EliminationMode
        TriggerPlate.TriggeredEvent.Subscribe(OnRoundBegin)

    @editable
    SpawnPadRef : creature_spawner_device = creature_spawner_device{}

    OnRoundBegin(Agent : ?agent) : void =
        Mode.OnRoundStart()

Pattern 2 — Overriding OnBegin and OnEnd in creative_device

creative_device itself exposes OnBegin and OnEnd as overridable methods. This is the most common override in all of UEFN — you use it every time you write a Verse device. Here's an explicit example that also uses OnEnd to clean up a prop.

using { /Fortnite.com/Devices }

cleanup_device := class(creative_device):

    @editable
    MarkerProp : creative_prop = creative_prop{}

    # Called when the game experience begins
    OnBegin<override>()<suspends> : void =
        MarkerProp.Show()
        MarkerProp.SetLinearVelocity(vector3{X := 0.0, Y := 0.0, Z := 0.0})

    # Called when the game experience ends — clean up the prop
    OnEnd<override>() : void =
        MarkerProp.Hide()

Pattern 3 — Polymorphic Describe() for UI / debug text

Overriding a method that returns a value (here a string) lets you drive UI or debug output from a base-typed reference without any type checks.

using { /Fortnite.com/Devices }

# Base collectible
collectible_base := class:
    GetLabel<public>() : string = "item"
    GetPointValue<public>() : int = 0

# Coin subclass
coin_collectible := class(collectible_base):
    GetLabel<override>() : string = "Gold Coin"
    GetPointValue<override>() : int = 10

# Gem subclass
gem_collectible := class(collectible_base):
    GetLabel<override>() : string = "Ruby Gem"
    GetPointValue<override>() : int = 50

# Device that manages a collection of items
collectible_manager_device := class(creative_device):

    @editable
    CoinProp : creative_prop = creative_prop{}

    @editable
    GemProp : creative_prop = creative_prop{}

    var Collectibles : []collectible_base = array{}
    var TotalPoints : int = 0

    OnBegin<override>()<suspends> : void =
        Coin := coin_collectible{}
        Gem  := gem_collectible{}
        set Collectibles = array{ Coin, Gem }

        # Show all collectible props
        CoinProp.Show()
        GemProp.Show()

        # Tally up starting point values using polymorphic call
        for (Item : Collectibles):
            set TotalPoints = TotalPoints + Item.GetPointValue()

Gotchas

1. You can't add effects the base doesn't have

If the base method has no effect specifiers, you cannot add <suspends> or <transacts> in the override. This is a compile error. If you need async behavior, make the base method <suspends> from the start.

# ✗ WRONG — base has no <suspends>, override can't add it
base_thing := class:
    DoWork() : void = {}

bad_sub := class(base_thing):
    DoWork<override>()<suspends> : void =  # COMPILE ERROR
        Sleep(1.0)

2. <override> is required — omitting it creates a new method, not an override

If you forget <override>, Verse treats it as a new, separate method on the subclass. The base-typed reference will still call the parent version. Always double-check the specifier.

3. creative_prop fields must be @editable or constructed — never left as bare defaults in production

The default creative_prop{} is a null-like placeholder. If you forget to wire the @editable field in the Details panel, calls like .Show() or .ApplyLinearImpulse() will silently do nothing or crash. Always verify wiring in UEFN before testing.

4. (super:) requires the parent to have a concrete implementation

You can only call (super:)Method() if the parent class actually provides a body for that method. If the parent body is external {} (a native method), (super:) is not available from Verse code.

5. Effect subtyping for <transacts>

A method marked <transacts> in the base can be overridden with <transacts> or with no effect (which is a subtype). But you cannot override a <transacts> method with one that has more effects like <suspends>.

6. Arrays of base types hold subtype instances — no casting needed

Once you store a spike_trap in a []trap_base, you call methods on it as trap_base. You cannot call spike_trap-specific methods without a type check pattern. Design your base interface to expose everything callers need.

Guides & scripts that use override_method

Step-by-step tutorials that put this object to work.

Build your own lesson with override_method

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →