ReferenceVerse

override-method: Polymorphic Behavior in Verse Devices

Method overriding is the backbone of polymorphism in Verse — it lets you define a base behavior in a parent class and replace it with something specialized in a subclass, all while the rest of your code talks to the same interface. Whether you're building a family of enemy AI types, a set of interactable props, or a suite of game-mode managers, overriding lets each variant do its own thing without duplicating the wiring code. Master this pattern and your UEFN islands become dramatically easier t

Updated Compile verifiedExamples verified on the live UEFN compiler
Watch the Knot — override_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.

Build your own lesson with override_method

Generate a personalized, compile-checked lesson grounded in this exact reference.

Build a lesson →