해당 포스팅은 Stuart Butler , Tom Oliver의 Game Development Patterns with Unreal Engine 5를 바탕으로 작성한 내용입니다.
싱글턴(Singleton) 패턴이란?
싱글턴 패턴은 클래스에서 단 하나의 인스턴스만 존재하도록 보장하는 생성 패턴이다.
“이 클래스의 객체는 하나만 있으면 충분하다”는 상황에서 사용된다.
싱글턴 예제
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SingletonActor.generated.h"
UCLASS()
class YOURPROJECT_API ASingletonActor : public AActor
{
GENERATED_BODY()
public:
ASingletonActor();
/** 싱글턴 초기화 (BeginPlay에서 호출) */
void Init();
static ASingletonActor* GetInstance()
{
return Instance.Get();
}
protected:
virtual void BeginPlay() override;
private:
/** 싱글턴 인스턴스 */
static TObjectPtr<ASingletonActor> Instance;
};
#include "SingletonActor.h"
TObjectPtr<ASingletonActor> ASingletonActor::Instance = nullptr;
ASingletonActor::ASingletonActor()
{
PrimaryActorTick.bCanEverTick = false;
}
void ASingletonActor::BeginPlay()
{
Super::BeginPlay();
Init();
}
void ASingletonActor::Init()
{
if (Instance == nullptr)
{
// 최초 인스턴스 등록
Instance = this;
UE_LOG(LogTemp, Log, TEXT("SingletonActor initialized"));
}
else if (Instance != this)
{
// 이미 존재 → 중복 제거
UE_LOG(LogTemp, Warning, TEXT("Duplicate SingletonActor destroyed"));
Destroy();
}
}싱글턴 패턴의 장점
1. 인스턴스 유일성 보장
이를 통해 시스템 전반에서 동일한 상태와 데이터를 공유할 수 있다.
- 중복 객체 생성 방지
- 전역 상태의 일관성 유지
2. 전역 접근 가능
- 매번 객체를 전달할 필요 없음
- 접근 비용이 낮고 사용이 간편
이로 인해 빠른 프로토타이핑이나 소규모 시스템에서는 생산성이 높다.
3. 공용 자원 관리에 유리
설정값, 상태값, 리소스처럼 공용으로 관리되어야 하는 자원에 적합하다.
싱글턴 패턴의 단점
싱글턴은 편리하지만 그만큼 구조적인 위험성을 내포한 패턴이다.
1. 전역 변수와 유사한 문제
- 어디서든 접근 가능 → 사용 지점 추적 어려움
- 의존성이 코드 전반에 암묵적으로 퍼짐
2. 결합도 증가
싱글턴을 직접 참조하는 구조는 객체 간 결합도를 높인다.
- 구현체에 직접 의존
- 인터페이스 기반 설계가 어려워짐
- 변경에 취약한 구조 형성
3. 라이프사이클 관리 어려움
싱글턴은 생성과 파괴 시점이 명확하지 않은 경우가 많다.
- 언제 생성되는지 명시적이지 않음
- 레벨 전환, 멀티 월드 환경에서 문제 발생 가능
- 잘못 관리되면 Dangling Pointer 위험
특히 게임 엔진처럼 명확한 라이프사이클을 가진 프레임워크에서는 문제가 두드러진다.
언리얼엔진에서의 싱글턴 해결
언리얼 엔진은 전통적인 싱글턴 패턴을 직접 구현하는 방식보다는, 엔진이 제공하는 라이프사이클과 의존성 관리 구조를 활용하는 것을 권장한다.
Subsystem
언리얼의 Subsystem은 컨텍스트당 하나만 생성되는 시스템 객체로, 싱글턴이 가지는 목적을 안전하게 달성할 수 있는 구조이다.특징
- 엔진이 생성 및 파괴 시점 관리
- 명확한 라이프사이클
- 테스트 및 유지보수 용이
- PIE / 멀티 월드 환경에서도 안정적
UMySubsystem* Subsystem = GetGameInstance()->GetSubsystem<UMySubsystem>();의존성 주입 (Dependency Injection)
언리얼에서는 객체가 싱글턴을 직접 조회하는 대신, 필요한 의존성을 외부에서 주입받는 구조가 더 바람직하다.
void AMyActor::Init(UMySubsystem* InSubsystem)
{
MySubsystem = InSubsystem;
}💡 언리얼 엔진에서 전통적인 싱글턴은 지양하자.
대부분의 경우 Subsystem을 이용하면 좋다. 객체 간 관계는 의존성 주입으로 연결하자. 싱글턴은 학습용 또는 매우 제한적인 상황에서만 사용하는 것이 좋다.