UE5 C++ USubsystem 가이드
개요 — Subsystem이란
Section titled “개요 — Subsystem이란”USubsystem은 UE5에서 제공하는 자동 생성·자동 소멸 싱글턴 서비스 패턴입니다. GameInstance 등 특정 Owner 객체와 수명이 연동되어, 별도의 GetInstance() 패턴 없이도 어디서든 GetSubsystem<T>()로 접근할 수 있습니다.
기존의 GameInstance에 모든 서비스를 몰아 넣던 안티패턴 대신, 기능별 Subsystem으로 분리하면 코드 유지보수성이 크게 향상됩니다.
1. Subsystem 네 가지 유형
Section titled “1. Subsystem 네 가지 유형”| 유형 | 기반 클래스 | 수명 | 인스턴스 수 | 주요 사용처 |
|---|---|---|---|---|
| GameInstance | UGameInstanceSubsystem | 게임 시작 ~ 종료 | 1개 | 세이브, 스코어, 글로벌 서비스 |
| World | UWorldSubsystem | 레벨 로드 ~ 언로드 | World당 1개 | 레벨별 게임 상태, AI 관리자 |
| Engine | UEngineSubsystem | 에디터/런타임 전체 | 1개 | 에디터 툴, 글로벌 에디터 서비스 |
| LocalPlayer | ULocalPlayerSubsystem | 플레이어 생성 ~ 소멸 | 플레이어당 1개 | 입력 설정, UI 상태, 분할 화면 |
2. GameInstance Subsystem
Section titled “2. GameInstance Subsystem”2.1 선언
Section titled “2.1 선언”#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;};2.2 구현
Section titled “2.2 구현”#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;}2.3 접근 방법
Section titled “2.3 접근 방법”// 어디서든 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")); }}3. World Subsystem
Section titled “3. World Subsystem”레벨이 로드될 때마다 새로 생성되며, 해당 World에 종속됩니다. 레벨 전환 시 자동 소멸·재생성됩니다.
#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);};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(); }}4. LocalPlayer Subsystem
Section titled “4. LocalPlayer Subsystem”플레이어당 하나씩 존재하며, 분할 화면(Split Screen) 환경에서 각 플레이어별 상태를 독립적으로 관리합니다.
#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); } }}5. Subsystem 간 의존성 주입
Section titled “5. Subsystem 간 의존성 주입”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(); // 예시 }}6. 접근 방법 요약
Section titled “6. 접근 방법 요약”// GameInstance SubsystemUGameInstance* GI = GetWorld()->GetGameInstance();USaveGameSubsystem* SaveSys = GI->GetSubsystem<USaveGameSubsystem>();
// World SubsystemUWaveManagerSubsystem* WaveMgr = GetWorld()->GetSubsystem<UWaveManagerSubsystem>();
// LocalPlayer SubsystemULocalPlayer* 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()을 재정의해 특정 조건(게임 월드만)에서만 생성되도록 제어합니다.