Chapter 14

Modules & Subsystems

Where code lives versus when a system runs, the four subsystem lifetimes, the lifecycle hooks, and two gotchas that bite.

In this chapter

  1. The Distinction
  2. Modules — The DLL Boundary
  3. Subsystems — The Managed Singleton
  4. Lifecycle Hooks
  5. Access & Ticking
  6. Two Gotchas
1

The Distinction

Module = where code lives and who can compile against it.
Subsystem = when a system runs and how you access it.

They're complementary, not alternatives — a subsystem lives inside a module.


2

Modules — The DLL Boundary

Make one when a system deserves its own compile boundary, when you're shipping a plugin, or when you need controlled load order.

The cost is real setup overhead. A module is infrastructure, not gameplay — don't reach for one when a subsystem would do.

3

Subsystems — The Managed Singleton

The engine auto-creates, ticks and destroys them on a defined lifetime. You access one anywhere with GetSubsystem<YourSubsystem>() and implement Initialize / Deinitialize.

TypeLifetimeGood for
UEngineSubsystemWhole engine sessionGlobal tools and utilities
UGameInstanceSubsystemOne play sessionMatch state, online services
UWorldSubsystemOne World or levelPer-level managers, spawn systems
ULocalPlayerSubsystemPer playerPer-player UI, input, progression

Use one when you need a manager with a defined lifetime and global access, without hand-maintaining a singleton. Define the class and the engine handles instantiation, lifetime and a typed getter — no spawning, no stored pointer, no wiring into GameMode or GameState.


4

Lifecycle Hooks

UCLASS()
class MYGAME_API UActivityManagerSubsystem : public UWorldSubsystem
{
    GENERATED_BODY()
public:
    virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
    virtual void Initialize(FSubsystemCollectionBase& Collection) override;
    virtual void Deinitialize() override;
    virtual void OnWorldBeginPlay(UWorld& InWorld) override; // WorldSubsystem only
};
HookWhenUse it for
ShouldCreateSubsystemBefore creationGate whether it's created at all. return World->IsGameWorld(); avoids spinning up in editor preview and thumbnail worlds
InitializeEarly, often before actors' BeginPlaySetup that needs the engine up. Collection.InitializeDependency<UOther>() forces another subsystem to init first
OnWorldBeginPlayAfter the world is liveAnything touching GameState or spawned actors — not Initialize, they may not exist yet
DeinitializeTeardownClear timers, unbind delegates

5

Access & Ticking

Access through the matching outer:

From Blueprints you get a Get [Your Subsystem] node auto-generated for free, along with any UFUNCTION(BlueprintCallable) methods on it. That's a big part of why designers can reach a systems programmer's manager without anyone hand-building a singleton accessor.

Ticking

Off by default. Inherit UTickableWorldSubsystem, or add FTickableGameObject and override Tick, IsTickable and GetStatId.

For most managers, timers, delegates and events beat a per-frame poll. Reach for tickable only when you actually need per-frame work.

6

Two Gotchas

Subsystems are not replicated

A subsystem is a UObject, not an Actor — so no replication, no RPCs, no OnRep. A UWorldSubsystem is instantiated independently on the server and on each client, and those instances don't talk to each other.

Run authoritative logic on the server, gated with an authority check, and publish the results to a replicated Actor — the GameState, or a component on it.

The subsystem is the logic; the replicated actor is the shared truth. This is the mechanism behind the architecture in Chapter 5.

Subclassing creates both

UDerived : UBase instantiates both by default, and they fight. If the derived class is meant to replace the base, gate it so only the leaf survives:

bool UBaseManager::ShouldCreateSubsystem(UObject* Outer) const
{
    if (!Super::ShouldCreateSubsystem(Outer)) return false;

    TArray<UClass*> Derived;
    GetDerivedClasses(GetClass(), Derived, false);
    return Derived.Num() == 0; // only the most-derived class is created
}
← Chapter 13 ↑ Index Chapter 15 →