Where code lives versus when a system runs, the four subsystem lifetimes, the lifecycle hooks, and two gotchas that bite.
They're complementary, not alternatives — a subsystem lives inside a module.
.Build.cs, which controls public versus private include paths and explicit dependency and load order.Make one when a system deserves its own compile boundary, when you're shipping a plugin, or when you need controlled load order.
The engine auto-creates, ticks and destroys them on a defined lifetime. You access one anywhere with GetSubsystem<YourSubsystem>() and implement Initialize / Deinitialize.
| Type | Lifetime | Good for |
|---|---|---|
| UEngineSubsystem | Whole engine session | Global tools and utilities |
| UGameInstanceSubsystem | One play session | Match state, online services |
| UWorldSubsystem | One World or level | Per-level managers, spawn systems |
| ULocalPlayerSubsystem | Per player | Per-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.
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
};
| Hook | When | Use it for |
|---|---|---|
| ShouldCreateSubsystem | Before creation | Gate whether it's created at all. return World->IsGameWorld(); avoids spinning up in editor preview and thumbnail worlds |
| Initialize | Early, often before actors' BeginPlay | Setup that needs the engine up. Collection.InitializeDependency<UOther>() forces another subsystem to init first |
| OnWorldBeginPlay | After the world is live | Anything touching GameState or spawned actors — not Initialize, they may not exist yet |
| Deinitialize | Teardown | Clear timers, unbind delegates |
Access through the matching outer:
GetWorld()->GetSubsystem<T>()GetGameInstance()->GetSubsystem<T>()LocalPlayer->GetSubsystem<T>()GEngine->GetEngineSubsystem<T>()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.
Off by default. Inherit UTickableWorldSubsystem, or add FTickableGameObject and override Tick, IsTickable and GetStatId.
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.
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
}