Overview
An abstract class in Verse is a class marked with the <abstract> specifier. It cannot be instantiated directly — you can never write my_abstract_class{}. Instead, it exists purely as a blueprint that concrete subclasses extend and complete.
Why reach for it?
- Shared structure, varied behaviour. All your enemy types share
TakeDamage()andGetDisplayName(), but each implements them differently. - Write-once game logic. A wave manager that calls
enemy.Spawn()works for every enemy type without knowing which one it is. - Safe polymorphism. Verse's type system guarantees every concrete subclass has implemented every abstract method before the code compiles.
Abstract classes sit between a plain class (fully concrete) and an interface (no data, no implementation at all). Use them when your types share both data fields and some default behaviour, but still need per-subclass overrides for key actions.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
Scenario: A Multi-Hazard Arena
You are building a survival arena. The island has three hazard types — a turret, a VFX trap, and a channel broadcaster — but your wave manager only knows about a generic arena_hazard. Each hazard activates differently, but the wave manager calls the same Activate() method on all of them.
Below is the complete, compilable example. Read the line-by-line explanation that follows.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# ─── Abstract base: every hazard must implement Activate and Deactivate ───
arena_hazard := class<abstract>:
# Subclasses MUST override this.
Activate<public>()<suspends>:void = {}
Deactivate<public>():void = {}
# ─── Concrete hazard: wraps a vfx_spawner_device ───
vfx_hazard := class<concrete>(arena_hazard):
@editable
VFX : vfx_spawner_device = vfx_spawner_device{}
Activate<override>()<suspends>:void =
VFX.Enable()
Sleep(5.0) # VFX runs for 5 seconds
VFX.Disable()
Deactivate<override>():void =
VFX.Disable()
# ─── Concrete hazard: wraps a channel_device to broadcast a danger signal ───
channel_hazard := class<concrete>(arena_hazard):
@editable
Channel : channel_device = channel_device{}
Activate<override>()<suspends>:void =
Channel.Transmit(false) # broadcast with no specific agent
Sleep(1.0)
Deactivate<override>():void =
Channel.Transmit(false)
# ─── The wave manager device — placed on the island ───
wave_manager_device := class(creative_device):
# Wire up your two hazard devices in the editor via these fields.
@editable
VFXHazard : vfx_hazard = vfx_hazard{}
@editable
ChannelHazard : channel_hazard = channel_hazard{}
OnBegin<override>()<suspends>:void =
# Build a heterogeneous array of the abstract base type.
Hazards : []arena_hazard = array{VFXHazard, ChannelHazard}
# Activate every hazard in sequence — the wave manager
# never needs to know which concrete type it is calling.
for (H : Hazards):
spawn { H.Activate() }
# Wait for the wave to finish, then deactivate everything.
Sleep(10.0)
for (H : Hazards):
H.Deactivate()```
**Line-by-line explanation**
| Lines | What's happening |
|---|---|
| `arena_hazard<abstract>` | Declares the abstract base. No `@editable` fields here — it can never be placed directly. |
| `Activate<public>()<suspends>:void = {}` | Provides an empty default body. Subclasses override it. The `<suspends>` effect means callers can `Sleep` inside. |
| `vfx_hazard<concrete>` | Marks this as a fully instantiable subclass. |
| `@editable VFX : vfx_spawner_device` | The real device reference, wired in the UEFN editor. |
| `VFX.Enable()` / `VFX.Disable()` | Real `vfx_spawner_device` API calls — Enable starts the effect, Disable stops it. |
| `Channel.Transmit(false)` | Real `channel_device` API — broadcasts on the channel with no specific agent. |
| `Hazards : []arena_hazard = array{VFXHazard, ChannelHazard}` | Polymorphic array: both concrete types fit because they extend `arena_hazard`. |
| `spawn { H.Activate() }` | Launches each hazard concurrently. Because `Activate` is `<suspends>`, it must run in its own task. |
| `H.Deactivate()` | Calls the abstract method — Verse dispatches to the correct concrete implementation at runtime. |
## Common patterns
### Pattern 1 — Abstract base with a shared data field
Abstract classes *can* hold data. Here a `score_value` field is shared by all subclasses, while `Collect()` is overridden per type. The `vfx_spawner_device` plays a burst effect when any collectible is picked up.
```verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Abstract collectible — holds shared data, demands a Collect override.
collectible<abstract> := class:
ScoreValue<public> : int = 10
Collect<public>(Picker : agent)<suspends>:void = {}
# Gold coin: plays a VFX burst and prints the score.
gold_coin<concrete> := class(collectible):
@editable
BurstVFX : vfx_spawner_device = vfx_spawner_device{}
Collect<override>(Picker : agent)<suspends>:void =
BurstVFX.Restart() # Triggers burst-mode VFX
Print("Collected gold! +{ScoreValue}")
Sleep(0.5)
BurstVFX.Disable()
# Bonus star: higher value, different VFX.
bonus_star<concrete> := class(collectible):
ScoreValue<override> : int = 50
@editable
StarVFX : vfx_spawner_device = vfx_spawner_device{}
Collect<override>(Picker : agent)<suspends>:void =
StarVFX.Enable()
Print("BONUS STAR! +{ScoreValue}")
Sleep(1.0)
StarVFX.Disable()
# Device that drives the demo.
collectible_demo_device := class(creative_device):
@editable
Coin : gold_coin = gold_coin{}
@editable
Star : bonus_star = bonus_star{}
OnBegin<override>()<suspends>:void =
# Polymorphic array — both fit as collectible.
Items : []collectible = array{Coin, Star}
for (Item : Items):
Print("Item worth: {Item.ScoreValue} points")
Pattern 2 — Abstract class with a channel_device event subscription
This pattern shows an abstract game_objective whose concrete subclasses react to a channel_device broadcast differently — one enables a VFX effect, the other disables it.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Abstract objective: subclasses decide what OnSignal does.
game_objective<abstract> := class:
OnSignal<public>(Sender : ?agent):void = {}
# Lights up a VFX spawner when the channel fires.
activate_objective<concrete> := class(game_objective):
@editable
Effect : vfx_spawner_device = vfx_spawner_device{}
OnSignal<override>(Sender : ?agent):void =
Effect.Enable()
Print("Objective activated!")
# Shuts down a VFX spawner when the channel fires.
deactivate_objective<concrete> := class(game_objective):
@editable
Effect : vfx_spawner_device = vfx_spawner_device{}
OnSignal<override>(Sender : ?agent):void =
Effect.Disable()
Print("Objective deactivated!")
# Device wires a channel to two objectives polymorphically.
objective_manager_device := class(creative_device):
@editable
TriggerChannel : channel_device = channel_device{}
@editable
ObjA : activate_objective = activate_objective{}
@editable
ObjB : deactivate_objective = deactivate_objective{}
# Handler must match listenable(?agent) signature.
OnChannelReceived(Sender : ?agent):void =
Objectives : []game_objective = array{ObjA, ObjB}
for (Obj : Objectives):
Obj.OnSignal(Sender)
OnBegin<override>()<suspends>:void =
TriggerChannel.ReceivedTransmitEvent.Subscribe(OnChannelReceived)
loop:
Sleep(999.0)
Pattern 3 — Abstract class driving a VFX enable/disable toggle
A minimal pattern: an abstract vfx_controller with TurnOn and TurnOff methods. Two concrete subclasses invert the logic (one enables on TurnOn, one disables). Demonstrates that abstract classes work perfectly as lightweight strategy objects.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
vfx_controller<abstract> := class:
TurnOn<public>():void = {}
TurnOff<public>():void = {}
normal_vfx_controller<concrete> := class(vfx_controller):
@editable
VFX : vfx_spawner_device = vfx_spawner_device{}
TurnOn<override>():void = VFX.Enable()
TurnOff<override>():void = VFX.Disable()
inverted_vfx_controller<concrete> := class(vfx_controller):
@editable
VFX : vfx_spawner_device = vfx_spawner_device{}
# Inverted: TurnOn actually disables, TurnOff enables.
TurnOn<override>():void = VFX.Disable()
TurnOff<override>():void = VFX.Enable()
vfx_toggle_demo := class(creative_device):
@editable
Normal : normal_vfx_controller = normal_vfx_controller{}
@editable
Inverted : inverted_vfx_controller = inverted_vfx_controller{}
OnBegin<override>()<suspends>:void =
Controllers : []vfx_controller = array{Normal, Inverted}
for (C : Controllers):
C.TurnOn()
Sleep(3.0)
for (C : Controllers):
C.TurnOff()
Gotchas
1. You cannot instantiate an abstract class directly
arena_hazard{} will fail to compile. Abstract classes exist only as base types. Always instantiate the concrete subclass (vfx_hazard{}) and store it in a variable typed as the abstract base if you need polymorphism.
2. <override> is mandatory, not optional
If your concrete subclass does not mark its method with <override>, Verse treats it as a new method that shadows the base — the polymorphic dispatch won't work as expected. Always write MyMethod<override>():void =.
3. Effect specifiers must match exactly
If the abstract method is declared <suspends>, every override must also be <suspends>. Mismatched effect specifiers are a compile error. Plan your effect signatures carefully before writing subclasses.
4. @editable fields belong on concrete classes, not the abstract base
The UEFN editor only wires @editable fields on classes that are actually placed (i.e., creative_device subclasses or concrete helper classes). Putting @editable on an abstract class compiles but the editor cannot surface it — keep device references on the concrete leaf classes.
5. Abstract classes are not interfaces
An abstract class can have fields and method bodies. An interface cannot. If you need multiple inheritance (a type that is both a hazard and a collectible), use interfaces for the secondary role and an abstract class for the primary hierarchy.
6. Polymorphic arrays require the base type annotation
Verse infers types strictly. Write Items : []arena_hazard = array{VFXHazard, ChannelHazard} — if you omit the explicit type annotation, the compiler may infer a narrower type and reject the mixed array.
7. spawn is required for <suspends> calls inside loops
When iterating a polymorphic array and calling a <suspends> method, you must spawn { H.Activate() } rather than calling it directly, because a for loop body is not itself a suspending context.