Skip to content

UE5 C++ USubsystem 가이드

USubsystem은 UE5에서 제공하는 자동 생성·자동 소멸 싱글턴 서비스 패턴입니다. GameInstance 등 특정 Owner 객체와 수명이 연동되어, 별도의 GetInstance() 패턴 없이도 어디서든 GetSubsystem<T>()로 접근할 수 있습니다.

기존의 GameInstance에 모든 서비스를 몰아 넣던 안티패턴 대신, 기능별 Subsystem으로 분리하면 코드 유지보수성이 크게 향상됩니다.


유형기반 클래스수명인스턴스 수주요 사용처
GameInstanceUGameInstanceSubsystem게임 시작 ~ 종료1개세이브, 스코어, 글로벌 서비스
WorldUWorldSubsystem레벨 로드 ~ 언로드World당 1개레벨별 게임 상태, AI 관리자
EngineUEngineSubsystem에디터/런타임 전체1개에디터 툴, 글로벌 에디터 서비스
LocalPlayerULocalPlayerSubsystem플레이어 생성 ~ 소멸플레이어당 1개입력 설정, UI 상태, 분할 화면

SaveGameSubsystem.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "SaveGameSubsystem.generated.h"
UCLASS()
class MYGAME_API USaveGameSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// USubsystem 인터페이스
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// 게임 저장/로드 API
UFUNCTION(BlueprintCallable, Category = "SaveGame")
void SaveGame(const FString& SlotName);
UFUNCTION(BlueprintCallable, Category = "SaveGame")
bool LoadGame(const FString& SlotName);
UFUNCTION(BlueprintPure, Category = "SaveGame")
int32 GetHighScore() const { return HighScore; }
private:
int32 HighScore = 0;
FString CurrentSlotName;
};
SaveGameSubsystem.cpp
#include "SaveGameSubsystem.h"
#include "Kismet/GameplayStatics.h"
#include "MySaveGame.h"
void USaveGameSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UE_LOG(LogTemp, Log, TEXT("SaveGameSubsystem initialized"));
// 자동으로 마지막 슬롯 로드
LoadGame(TEXT("DefaultSave"));
}
void USaveGameSubsystem::Deinitialize()
{
// 종료 시 자동 저장
SaveGame(CurrentSlotName);
Super::Deinitialize();
}
void USaveGameSubsystem::SaveGame(const FString& SlotName)
{
UMySaveGame* SaveGameObj = Cast<UMySaveGame>(
UGameplayStatics::CreateSaveGameObject(UMySaveGame::StaticClass()));
if (SaveGameObj)
{
SaveGameObj->HighScore = HighScore;
UGameplayStatics::SaveGameToSlot(SaveGameObj, SlotName, 0);
CurrentSlotName = SlotName;
}
}
bool USaveGameSubsystem::LoadGame(const FString& SlotName)
{
if (!UGameplayStatics::DoesSaveGameExist(SlotName, 0))
{
return false;
}
UMySaveGame* SaveGameObj = Cast<UMySaveGame>(
UGameplayStatics::LoadGameFromSlot(SlotName, 0));
if (SaveGameObj)
{
HighScore = SaveGameObj->HighScore;
CurrentSlotName = SlotName;
return true;
}
return false;
}
// 어디서든 GameInstance Subsystem에 접근
void AMyActor::SomeFunction()
{
UGameInstance* GI = GetWorld()->GetGameInstance();
if (USaveGameSubsystem* SaveSys = GI->GetSubsystem<USaveGameSubsystem>())
{
SaveSys->SaveGame(TEXT("Slot1"));
int32 HighScore = SaveSys->GetHighScore();
}
}
// PlayerController / Character에서 축약
void AMyPlayerController::SaveProgress()
{
if (USaveGameSubsystem* SaveSys = GetGameInstance()->GetSubsystem<USaveGameSubsystem>())
{
SaveSys->SaveGame(TEXT("Auto"));
}
}

레벨이 로드될 때마다 새로 생성되며, 해당 World에 종속됩니다. 레벨 전환 시 자동 소멸·재생성됩니다.

WaveManagerSubsystem.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/WorldSubsystem.h"
#include "WaveManagerSubsystem.generated.h"
DECLARE_MULTICAST_DELEGATE_OneParam(FOnWaveStarted, int32 /*WaveNumber*/);
UCLASS()
class MYGAME_API UWaveManagerSubsystem : public UWorldSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// ShouldCreateSubsystem: 특정 조건에서만 생성
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
void StartNextWave();
int32 GetCurrentWave() const { return CurrentWave; }
FOnWaveStarted OnWaveStarted;
private:
int32 CurrentWave = 0;
FTimerHandle WaveTimerHandle;
void SpawnEnemiesForWave(int32 WaveNumber);
};
WaveManagerSubsystem.cpp
bool UWaveManagerSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
// 게임 월드에서만 생성 (에디터 월드 제외)
if (!Super::ShouldCreateSubsystem(Outer)) return false;
UWorld* World = Cast<UWorld>(Outer);
return World && World->IsGameWorld();
}
void UWaveManagerSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UE_LOG(LogTemp, Log, TEXT("WaveManagerSubsystem initialized for world: %s"), *GetWorld()->GetName());
}
void UWaveManagerSubsystem::StartNextWave()
{
++CurrentWave;
OnWaveStarted.Broadcast(CurrentWave);
SpawnEnemiesForWave(CurrentWave);
}
void UWaveManagerSubsystem::SpawnEnemiesForWave(int32 WaveNumber)
{
int32 EnemyCount = WaveNumber * 5;
UE_LOG(LogTemp, Log, TEXT("Wave %d: spawning %d enemies"), WaveNumber, EnemyCount);
// 스폰 로직...
}
// 접근
void AMyGameMode::BeginPlay()
{
Super::BeginPlay();
if (UWaveManagerSubsystem* WaveMgr = GetWorld()->GetSubsystem<UWaveManagerSubsystem>())
{
WaveMgr->OnWaveStarted.AddUObject(this, &AMyGameMode::HandleWaveStarted);
WaveMgr->StartNextWave();
}
}

플레이어당 하나씩 존재하며, 분할 화면(Split Screen) 환경에서 각 플레이어별 상태를 독립적으로 관리합니다.

PlayerSettingsSubsystem.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/LocalPlayerSubsystem.h"
#include "PlayerSettingsSubsystem.generated.h"
UCLASS()
class MYGAME_API UPlayerSettingsSubsystem : public ULocalPlayerSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
UFUNCTION(BlueprintCallable, Category = "Settings")
void SetMouseSensitivity(float Sensitivity);
UFUNCTION(BlueprintPure, Category = "Settings")
float GetMouseSensitivity() const { return MouseSensitivity; }
UFUNCTION(BlueprintCallable, Category = "Settings")
void SetVolumeLevel(float Volume);
private:
float MouseSensitivity = 1.0f;
float VolumeLevel = 1.0f;
void LoadSettings();
void SaveSettings();
};
// LocalPlayer Subsystem 접근
void AMyPlayerController::ApplySettings()
{
if (ULocalPlayer* LP = GetLocalPlayer())
{
if (UPlayerSettingsSubsystem* Settings = LP->GetSubsystem<UPlayerSettingsSubsystem>())
{
Settings->SetMouseSensitivity(1.5f);
}
}
}

Initialize(FSubsystemCollectionBase&)Collection 파라미터로 다른 Subsystem이 먼저 초기화되도록 보장합니다.

void UWaveManagerSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
// SaveGameSubsystem이 반드시 먼저 초기화되도록 보장
Collection.InitializeDependency<USaveGameSubsystem>();
Super::Initialize(Collection);
// 이제 안전하게 SaveGameSubsystem 사용 가능
UGameInstance* GI = GetWorld()->GetGameInstance();
if (USaveGameSubsystem* SaveSys = GI->GetSubsystem<USaveGameSubsystem>())
{
int32 SavedWave = SaveSys->GetHighScore(); // 예시
}
}

// GameInstance Subsystem
UGameInstance* GI = GetWorld()->GetGameInstance();
USaveGameSubsystem* SaveSys = GI->GetSubsystem<USaveGameSubsystem>();
// World Subsystem
UWaveManagerSubsystem* WaveMgr = GetWorld()->GetSubsystem<UWaveManagerSubsystem>();
// LocalPlayer Subsystem
ULocalPlayer* LP = GetWorld()->GetFirstLocalPlayerFromController();
UPlayerSettingsSubsystem* Settings = LP->GetSubsystem<UPlayerSettingsSubsystem>();
// Engine Subsystem (에디터 툴 등)
UMyEditorSubsystem* EditorSys = GEngine->GetEngineSubsystem<UMyEditorSubsystem>();

  • Subsystem은 자동 생성·소멸로 싱글턴 수명 관리를 엔진이 책임집니다.
  • GameInstance Subsystem: 세션 전체 유지 → 세이브·글로벌 서비스
  • World Subsystem: 레벨 단위 → 웨이브·AI 관리자
  • LocalPlayer Subsystem: 플레이어 단위 → 입력·UI·설정
  • Initialize(Collection)에서 InitializeDependency<T>()로 의존 순서를 명시합니다.
  • ShouldCreateSubsystem()을 재정의해 특정 조건(게임 월드만)에서만 생성되도록 제어합니다.