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.