Reference Devices compiles

abstract class in Verse: Building Flexible Game Systems with Polymorphism

When your island needs multiple enemy types, collectible variants, or interactable objects that share behaviour but differ in specifics, Verse's `abstract` class modifier is your best tool. An abstract class defines a contract — a set of methods subclasses *must* override — without providing a concrete implementation itself. This lets you write one piece of game logic that works with any subclass, keeping your code clean, reusable, and easy to extend.

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

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() and GetDisplayName(), 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.

Build your own lesson with abstract_class

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 →