kevinpbuckley/unreal-engine-skills

ue-subsystems

Implement engine-managed singletons scoped to a defined lifetime using Unreal's Subsystem framework — UEngineSubsystem, UGameInstanceSubsystem, UWorldSubsystem, UTickableWorldSubsystem, and ULocalPlayerSubsystem.

View source

How to use

  1. Copy the installation command and review the source package before installing it in your own agent environment.
  2. Check the source document for required tools, credentials and supported hosts.
  3. Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/kevinpbuckley/unreal-engine-skills --skill ue-subsystems

Before you start

  • Installation does not include a model subscription, API allowance or permission to access your files.
  • Repository stars and installation counts describe activity, not an independent quality or security test.
Original skill document

Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.

Subsystems

A Subsystem is a UObject-derived singleton whose lifetime matches a specific engine construct (engine process, game instance, world, or local player). The engine creates it automatically when the owning object is created and destroys it when the owner is torn down — no manual instancing, no GC juggling, and Blueprint/Python exposure comes for free.

Subsystems replace hand-rolled static singletons and "manager actors". They are the canonical pattern for cross-cutting services in modern Unreal C++.

When to use this skill

  • You need a service or manager class (inventory, save system, match lifecycle, analytics,

ability registry, audio coordinator) and want it created and cleaned up automatically.

  • You need to choose between a UGameInstanceSubsystem, UWorldSubsystem, or a manager actor.
  • You are building a plugin and want the plugin's systems initialized without burdening the

game project with manual setup code.

  • You want Blueprint-callable game services without casting to game-specific classes.
  • You want per-local-player state that is split-screen-safe.

The five subsystem types (choose by lifetime)

Base classLifetimeCreated/owned byAccess
UEngineSubsystemEngine process; survives level loads and PIE sessionsUEngine / GEngineGEngine->GetEngineSubsystem<T>()
UGameInstanceSubsystemGame session; persists across level loadsUGameInstanceGameInstance->GetSubsystem<T>()
UWorldSubsystemOne UWorld; reset on every level change or seamless travelUWorldWorld->GetSubsystem<T>()
UTickableWorldSubsystemSame as UWorldSubsystem plus automatic per-frame TickUWorldWorld->GetSubsystem<T>()
ULocalPlayerSubsystemOne local player; one instance per split-screen slotULocalPlayerLocalPlayer->GetSubsystem<T>()

UEditorSubsystem (editor-only module) follows the same pattern for editor tooling; it is not covered here. See references/subsystem-types.md for full per-type detail.

Authoring a subsystem

A minimal UGameInstanceSubsystem:

cpp
// MySaveSubsystem.h
#pragma once
#include "Subsystems/GameInstanceSubsystem.h"
#include "MySaveSubsystem.generated.h"

UCLASS()
class MYGAME_API UMySaveSubsystem : public UGameInstanceSubsystem
{
    GENERATED_BODY()
public:
    // Called after UGameInstance is initialized. Collection is used to declare dependencies.
    virtual void Initialize(FSubsystemCollectionBase& Collection) override;
    virtual void Deinitialize() override;

    // Returns false to skip creation entirely (e.g. on dedicated server).
    virtual bool ShouldCreateSubsystem(UObject* Outer) const override;

    UFUNCTION(BlueprintCallable, Category = "Save")
    void SaveProfile();

private:
    UPROPERTY()
    TObjectPtr<USaveGame> CurrentSave;
};
cpp
// MySaveSubsystem.cpp
#include "MySaveSubsystem.h"
#include "AnotherSubsystem.h"   // dependency example

void UMySaveSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
    // Ensure UAnotherSubsystem is initialized before this one.
    Collection.InitializeDependency<UAnotherSubsystem>();
    Super::Initialize(Collection);
    // Perform initialization — world is not guaranteed here for game-instance scope.
}

void UMySaveSubsystem::Deinitialize()
{
    // Clear timers, delegates, file handles, etc.
    Super::Deinitialize();
}

bool UMySaveSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
    // Skip on dedicated server builds:
    if (!Super::ShouldCreateSubsystem(Outer)) { return false; }
    return !IsRunningDedicatedServer();
}

Key rules:

  • Initialize/Deinitialize are the lifecycle hooks — not BeginPlay/EndPlay.
  • The subsystem is a UObject; use UPROPERTY() on all UObject* members.
  • ShouldCreateSubsystem is called on the CDO before any instance is created; keep it cheap.
  • Call Super::Initialize(Collection) first, Super::Deinitialize() last.

Accessing a subsystem

cpp
// From any actor — GameInstance subsystem:
if (UGameInstance* GI = GetGameInstance())
{
    if (UMySaveSubsystem* Save = GI->GetSubsystem<UMySaveSubsystem>())
        Save->SaveProfile();
}

// Static helper — safe even if GI is null:
UMySaveSubsystem* Save = UGameInstance::GetSubsystem<UMySaveSubsystem>(GetGameInstance());

// World subsystem (returns null if ShouldCreateSubsystem returned false):
UMyWorldSub* WS = GetWorld()->GetSubsystem<UMyWorldSub>();

// Checked accessor (asserts on null; prefer when null is a bug):
UMyWorldSub& WS = *GetWorld()->GetSubsystemChecked<UMyWorldSub>();

// Local player subsystem from a PlayerController:
UMyLPSub* S = ULocalPlayer::GetSubsystemFromController<UMyLPSub>(this);

In Blueprints, subsystems appear as typed auto-context getter nodes — no casting required. The node is available for any subsystem that is a UCLASS() (even without BlueprintType), as long as the function is marked BlueprintCallable. USubsystemBlueprintLibrary provides the static getters that Blueprint graph searches surface.

Dependency ordering

cpp
void UMySubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
    // Template form — preferred, type-safe:
    Collection.InitializeDependency<UOtherSubsystem>();
    Super::Initialize(Collection);
}

InitializeDependency only works within the same collection (same owner). You cannot declare a UWorldSubsystem dependency on a UGameInstanceSubsystem — they live in separate collections. See references/lifecycle-and-access.md for the full initialization sequence.

Ticking — UTickableWorldSubsystem

When per-frame work is needed in a world-scoped subsystem, inherit from UTickableWorldSubsystem instead of UWorldSubsystem:

cpp
UCLASS()
class MYGAME_API UMyTickingSystem : public UTickableWorldSubsystem
{
    GENERATED_BODY()
public:
    virtual void Initialize(FSubsystemCollectionBase& Collection) override;
    virtual void Deinitialize() override;
    virtual void Tick(float DeltaTime) override;

    // Required pure virtual — used by the stats system:
    virtual TStatId GetStatId() const override
    {
        RETURN_QUICK_DECLARE_CYCLE_STAT(UMyTickingSystem, STATGROUP_Tickables);
    }
};

Rules:

  • Forward Initialize/Deinitialize to Super to enable/disable ticking correctly.
  • GetStatId is a pure virtual — failing to implement it prevents compilation.
  • Prefer timers (ue-timers-and-async) over ticking when the work is infrequent.

Choosing subsystem vs alternatives

NeedRecommendation
Stateless utility functionsFree functions or a BlueprintFunctionLibrary
Replicated state (HP, score)AGameState, APlayerState (subsystems do not replicate)
Single placed world object (obstacle spawner)Manager actor placed in the level
Per-actor behavior/dataComponent on the actor
Cross-level session stateUGameInstanceSubsystem
Per-level state, reset on travelUWorldSubsystem
Per-player UI/input stateULocalPlayerSubsystem
Plugin initializationUEngineSubsystem or UGameInstanceSubsystem

Subsystems do not replicate. Keep authoritative networked state on GameState, PlayerState, or replicated actors. Use subsystems on the local side for coordination, caching, and UI logic. See ue-networking-and-replication and ue-gameplay-framework.

Gotchas

  • No world access in GameInstance/Engine Initialize — GetWorld() on a

UGameInstanceSubsystem returns null during Initialize; use a UWorldSubsystem or defer world-dependent work to OnWorldBeginPlay (world subsystems) or a timer.

  • Wrong scope — session-persistent data in a UWorldSubsystem is wiped on level change;

per-level data in a UGameInstanceSubsystem accumulates across travels.

  • `ShouldCreateSubsystem` returns false, but caller doesn't null-check — GetSubsystem<T>

returns nullptr when the subsystem was skipped; always null-check in the caller.

  • Dependency across collections — InitializeDependency only works within the same

collection; cross-scope coordination needs an accessor call in Initialize.

  • Forgetting `Super` in Initialize/Deinitialize — for UTickableWorldSubsystem,

Super::Initialize starts ticking and Super::Deinitialize stops it; skipping them breaks the tick lifecycle.

  • Heavy work in `Initialize` blocking startup — defer expensive loading via

FStreamableManager or AsyncTask (see ue-timers-and-async).

  • Circular dependencies — InitializeDependency detects simple cycles at runtime and

asserts; avoid mutual dependencies by splitting shared state into a third subsystem.

References & source material

Engine source (UE 5.8, Engine/Source/Runtime/Engine/Public/Subsystems/):

  • Subsystem.h — USubsystem:44 — ShouldCreateSubsystem:56, Initialize:59,

Deinitialize:62. UDynamicSubsystem:80 (base for UEngineSubsystem).

  • SubsystemCollection.h — FSubsystemCollectionBase:16, InitializeDependency:35,

ActivateExternalSubsystem:50, DeactivateExternalSubsystem:55.

  • GameInstanceSubsystem.h — UGameInstanceSubsystem:16 (Within=GameInstance),

GetGameInstance():25.

  • WorldSubsystem.h — UWorldSubsystem:16 — PostInitialize:37,

OnWorldBeginPlay:43, DoesSupportWorldType:66. UTickableWorldSubsystem:80 — GetStatId pure virtual:92.

  • LocalPlayerSubsystem.h — ULocalPlayerSubsystem:17 (Within=LocalPlayer),

PlayerControllerChanged:35.

  • EngineSubsystem.h — UEngineSubsystem:20 (derives from UDynamicSubsystem).
  • SubsystemBlueprintLibrary.h — Blueprint-internal getters for all subsystem types:15.

Engine source (UE 5.8, Engine/Source/Runtime/Engine/Classes/Engine/):

  • GameInstance.h — GetSubsystem<T>():440, static GetSubsystem(GameInstance*):450.
  • World.h — GetSubsystem<T>():4312, GetSubsystemChecked<T>():4321,

static GetSubsystem(World*):4331.

  • LocalPlayer.h — GetSubsystem<T>():365, GetSubsystemFromController<T>():389.
  • Engine.h — GetEngineSubsystem<T>():3847, EngineSubsystemCollection:3877.

Official docs (UE 5.8):

  • Programming Subsystems —

<https://dev.epicgames.com/documentation/unreal-engine/programming-subsystems-in-unreal-engine>

Deep-dive references in this skill:

timing, valid GetWorld() availability, DoesSupportWorldType, dynamic subsystems, UEditorSubsystem.

Initialize/Deinitialize sequence, dependency ordering, accessing subsystems from C++ and Blueprints, collection internals.

subsystem vs manager actor vs GameInstance override vs component; networking constraints; plugin patterns.

Related by task

from this repository

More skills

All skills
kevinpbuckley
Community

ue-character-and-movement

Implement player/AI characters in Unreal C++ with ACharacter and UCharacterMovementComponent — capsule/mesh setup, movement modes (walking/falling/flying/swimming/custom), rotation behaviors, jumping, crouching, root motion sources, client-predicted networked movement, and the experimental Mover plugin successor. Use when creating or configuring a Character class, setting movement speeds/gravity/air-control, overriding a custom movement mode (PhysCustom), adding root motion, or debugging network

installs
1
GitHub stars
60
Updated
Oct 1
kevinpbuckley
Community

ue-control-rig-and-ik

Procedural animation and inverse kinematics in Unreal Engine — Control Rig (RigVM-based graph that manipulates a bone/control hierarchy), IK Rig (solver definitions with Full-Body IK, Limb IK, Set Transform), the IK Retargeter (transfers animation between skeletons of different proportions), and lightweight AnimGraph IK nodes (Two Bone IK, FABRIK, CCDIK). Use when implementing foot placement on terrain, hand/weapon IK, look-at, procedural pose fixups, runtime retargeting, or sharing an animation

installs
1
GitHub stars
60
Updated
Oct 1
kevinpbuckley
Community

ue-core-types-and-containers

Use Unreal's core C++ types instead of the standard library — containers (TArray, TMap, TSet, TQueue, TArrayView), string types (FString, FName, FText, TStringBuilder) with conversion patterns and localization rules, math types (FVector, FRotator, FQuat, FTransform) with Large World Coordinates (LWC/double precision), and utility types (TOptional, TVariant, TTuple). Use when writing any UE C++ that stores collections, manipulates strings, does 3D math, or when choosing between FString/FName/ FTe

installs
1
GitHub stars
60
Updated
Oct 1
kevinpbuckley
Community

ue-data-driven-design

Drive Unreal gameplay from externally-editable data instead of hardcoded values — DataTables (UDataTable, FTableRowBase, CSV/JSON import, UCompositeDataTable), DataAssets (UDataAsset, UPrimaryDataAsset with AssetManager), Curves (UCurveFloat, UCurveTable, FRuntimeFloatCurve), config-driven UPROPERTY(config) in .ini files, and DeveloperSettings (UDeveloperSettings) for project-wide tuning. Use when defining item/enemy/level/balance schemas, choosing the right data container, exposing designer-tun

installs
1
GitHub stars
60
Updated
Oct 1