[UE5] Unreal Object Pointers

2026. 5. 26. 11:26·게임개발/Unreal Engine

언리얼 개발에서 무조건 알아야 하는 개념 중 하나인 포인터.

 

언리얼 스크립트는 C++로 작성하기에 기본 원시 포인터 *는 당연히 존재한다.

그러나 언리얼 UObject에 맞춰 새롭게 만든 포인터가 있으니,  대표적으로 TObjectPtr<T>가 있다.

또한 C++의 스마트 포인터 (WeakPtr, SharedPtr)와 같이 순환참조, 댕글링 포인터 등을 방지하기 위한 포인터들도 존재한다.

 

이러한 언리얼의 오브젝트 포인터들에 대해 자세히 알아보도록 하자.


 

언리얼 공식 사이트에 정리된 표를 보면 아래와 같이 되어있다.

Unreal Object Pointers

*: 언리얼 헤더 툴 (UHT)에서는 UPROPERTY()로 표시된 원시 포인터(*)가 GC에 영향을 주거나, Serialization / Networking을 지원하는 경우 

†: TObjectPtr는 UPROPERTY()로 마크된 경우에만 GC safe하다.

‡: TLazyObjectPtr 더 이상 사용되지 않을 예정이며, 대신 TSoftObjectPtr 사용한다.

§: TStrongObjectPtr 설명 참고

❡: TStrongObjectPtr는 UPROPERTY로 마크할 수 없기 때문에, 어디서든 GC에 영향을 준다(스택 메모리, 람다 캡쳐, 등등).

 

이런 포인터들을 각각 알맞은 상황에 써야하는데, 이를 위한 표도 간단하게 정리되어있다.

  • T* (원시 포인터): UPROPERTY로 표시되지 않은 지역 변수, 매개변수, 또는 단기 참조에 사용.
  • TObjectPtr: GC 추적, 직렬화, 또는 리플리케이션이 필요한 UCLASS 또는 USTRUCT 위의 영구적인 UObject 참조에 사용
  • TSoftObjectPtr: 요청 전까지 강제 로드되지 않아야 하거나 hard dependency를 만들지 않아야 하는 에셋 참조에 사용
  • TWeakObjectPtr: 언제든지 소멸될 수 있는 UObject에 대한 비소유 참조 또는 캐시
  • TStrongObjectPtr: UObject가 아닌 클래스나 구조체에서 UObject를 강하게 참조할 때

 

하지만 이 내용만 가지고는 실제로 쓰기 막막하다. 각 포인터에 대한 더 자세한 설명을 알아보자.


 

1. TObjectPtr

Raw 포인터(*)의 대체재로 설계된 포인터다.

UObject 파생 타입에만 사용 가능하고, UPROPERTY()로 마킹하면
GC가 대상 오브젝트를 수거하지 않도록 막는 강한 참조(Strong Reference)가 된다.

가능하면 Raw 포인터 대신 TObjectPtr를 쓰는 게 권장되는데,
쿡(Cook) 타임 의존성 추적을 지원하고 점진적 GC 마킹도 활성화해주기 때문이다.
리플리케이션도 지원한다.

단, 워커 스레드에서 TObjectPtr를 통해 UObject에 직접 접근하는 건 위험하다.
GC로부터 안전하게 루트 등록이 된 오브젝트가 아닌 이상 절대 하면 안 된다.
워커 스레드에서 UObject를 다뤄야 한다면 TWeakObjectPtr + Pin()으로
TStrongObjectPtr를 얻어서 사용하자.

 

// 다른 UObject 내부에서 UPROPERTY 마킹
UPROPERTY()
TObjectPtr<UMyOtherObject> MyOtherObject;

// 액터 컴포넌트 하드 참조
UPROPERTY()
TObjectPtr<UStaticMeshComponent> Mesh;

 

2. TLazyObjectPtr

이후 버전에서 더 이상 사용되지 않을 예정인 포인터다. 새로운 코드에서는 TSoftObjectPtr를 쓰자.

 

3. TSoftObjectPtr

디스크 상의 오브젝트 경로를 추적하는 약한 참조(Weak Reference)다.

 

가리키는 오브젝트의 GC 여부에는 영향을 주지 않는다.

대상이 메모리에 로드/언로드됨에 따라 Valid와 Pending 상태를 오간다.

대상이 아직 유효하지 않다면 동기 또는 비동기로 직접 로드해줘야 한다.

 

에셋을 온디맨드(On-demand) 방식으로 비동기 로드하거나, 하드 디펜던시(Hard Dependency)를 피하고 싶을 때 유용하다.

 

// 에디터에서 에셋 지정 후, 필요할 때 로드
UPROPERTY(EditAnywhere)
TSoftObjectPtr<UNiagaraSystem> NiagaraVFX;

void OnLoadComplete()
{
    UNiagaraSystem* LoadedVFX = NiagaraVFX.Get();
}

 


언제 쓰는지 감이 안 잡힐 수 있는데, 예시를 보면 바로 이해된다.

RPG 게임에서 플레이어가 스킬을 배울 때마다 해당 스킬의 이펙트 에셋을 로드한다고 하자.

스킬이 100개라면 게임 시작 시 100개의 이펙트 에셋이 전부 메모리에 올라오게 된다.
대부분은 당장 쓰지도 않는 에셋인데 메모리를 잡아먹는 것이다.

이럴 때 TSoftObjectPtr를 쓰면, 에셋의 경로만 들고 있다가
실제로 스킬을 사용하는 순간에만 로드할 수 있다.

// UMySkillData.h
UCLASS()
class UMySkillData : public UDataAsset
{
    GENERATED_BODY()

public:
    UPROPERTY(EditDefaultsOnly)
    FString SkillName;

    // 경로만 들고 있음. 게임 시작 시 로드 안 함.
    UPROPERTY(EditDefaultsOnly)
    TSoftObjectPtr<UNiagaraSystem> SkillVFX;

    UPROPERTY(EditDefaultsOnly)
    TSoftObjectPtr<USoundBase> SkillSound;
};


// AMyCharacter.cpp
void AMyCharacter::UseSkill(UMySkillData* SkillData)
{
    // 이미 로드된 경우 바로 사용
    if (SkillData->SkillVFX.IsValid())
    {
        SpawnSkillVFX(SkillData->SkillVFX.Get());
        return;
    }

    // 아직 로드 안 됐으면 비동기 로드 후 사용
    TArray<FSoftObjectPath> AssetsToLoad;
    AssetsToLoad.Add(SkillData->SkillVFX.ToSoftObjectPath());
    AssetsToLoad.Add(SkillData->SkillSound.ToSoftObjectPath());

    FStreamableManager& Streamable = UAssetManager::GetStreamableManager();
    Streamable.RequestAsyncLoad(AssetsToLoad, [this, SkillData]()
    {
        SpawnSkillVFX(SkillData->SkillVFX.Get());
        PlaySkillSound(SkillData->SkillSound.Get());
    });
}



TObjectPtr였다면 UMySkillData가 생성되는 순간 SkillVFX, SkillSound가
전부 메모리에 올라왔을 것이다.

TSoftObjectPtr는 경로만 들고 있다가 UseSkill()이 호출될 때 처음으로 로드하기 때문에
불필요한 메모리 낭비를 막을 수 있다.

 

4. TWeakObjectPtr

대상 오브젝트의 GC를 막지 않는 약한 포인터(Weak Pointer)다.

 

UPROPERTY 마킹이 필수는 아니지만 지원되며, 직렬화와 네트워킹도 지원한다.

대상 오브젝트가 GC로 수거되거나 소멸되면 포인터는 자동으로 null이 된다.

사용 전에는 반드시 IsValid()로 유효성을 확인하거나, Get()으로 가져온 뒤 null 여부를 체크해야 한다.

 

주의할 점은 TMap의 키나 TSet의 원소로는 사용할 수 없다는 것이다. UObject를 키로 쓰고 싶다면 TObjectKey를 사용하자.

// 오브젝트 캐싱
TMap<TSubclassOf<UObject>, TWeakObjectPtr<UObject>> CachedObjects;

// 람다에서 약한 포인터 캡처
MyDelegate.BindLambda(
    [MyWeakObject = MakeWeakObjectPtr(MyObject)]()
    {
        if (TStrongObjectPtr<UMyObject> MyStrongObject = MyWeakObject.Pin())
        {
            // 오브젝트에 안전하게 접근 가능
        }
    }
);

 

 

처음 접했을 때 매우 쓰기 애매한 포인터인데, 예시 하나만 더 살펴보면 금방 익숙해질 수 있다.

만약 게임에서 Raycast를 통해 상호작용한 물체를 하이라이트하는 기능을 구현했다고 하자.
플레이어는 Raycast로 감지한 물체를 하이라이트 활성/비활성화 해야 하므로, 탐지된 객체를 참조해야한다.

이때 탐지된 객체를 TObjectPtr로 들고 있으면 어떻게 될까?

플레이어가 바라보는 순간 GC가 해당 오브젝트를 수거하지 못하게 된다.
예를 들어 폭발로 인해 해당 오브젝트가 Destroy()됐어도 메모리에서 안 사라지고,
하이라이트가 켜진 채로 남아있는 유령 오브젝트가 생기게 된다.

플레이어가 오브젝트를 소유하는 게 아니라 그냥 바라보는 것이기 때문에,
이런 경우엔 TWeakObjectPtr를 써야 한다.

 

// AMyCharacter.h
TWeakObjectPtr<AMyInteractableActor> CurrentLookTarget;


// AMyCharacter.cpp
void AMyCharacter::UpdateLookTarget()
{
    FHitResult HitResult;
    FVector Start = FollowCamera->GetComponentLocation();
    FVector End = Start + FollowCamera->GetForwardVector() * InteractRange;

    bool bHit = GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECC_Visibility);

    AMyInteractableActor* NewTarget = nullptr;
    if (bHit)
    {
        NewTarget = Cast<AMyInteractableActor>(HitResult.GetActor());
    }

    // 이전 타겟과 달라졌을 때만 처리
    if (CurrentLookTarget.Get() != NewTarget)
    {
        // 이전 타겟 하이라이트 끄기
        // IsValid() 체크 중요 - 이전 타겟이 폭발 등으로 이미 사라졌을 수도 있음
        if (CurrentLookTarget.IsValid())
        {
            CurrentLookTarget->SetHighlight(false);
        }

        // 새 타겟 하이라이트 켜기
        if (NewTarget)
        {
            NewTarget->SetHighlight(true);
        }

        CurrentLookTarget = NewTarget;
    }
}

void AMyCharacter::TryInteract()
{
    if (CurrentLookTarget.IsValid())
    {
        CurrentLookTarget->Interact(this);
    }
}



TWeakObjectPtr는 대상이 Destroy()되는 순간 자동으로 null이 되기 때문에
IsValid() 체크 하나로 유령 오브젝트 문제가 깔끔하게 처리된다.

결국 TWeakObjectPtr는 "내가 소유하는 게 아니라 그냥 참조만 할게, 생사는 니가 결정해" 라는 의미다.
소유 관계가 없는 참조라면 TWeakObjectPtr를 쓰는 게 맞다.

 

5. TStrongObjectPtr

대상 오브젝트에 대한 참조 카운트를 관리하며,

스코프 내에서 GC가 오브젝트를 수거하지 못하도록 강제로 유지하는 강한 포인터다.

 

UPROPERTY를 지원하지 않기 때문에 UObject 파생 클래스의 멤버 필드로는 적합하지 않다.

만약 UObject 내부에서 TStrongObjectPtr를 쓰면 수거 불가능한 순환 참조(Uncollectable Cycle)가 생길 위험이 있다.

예를 들어 자기 자신을 TStrongObjectPtr로 참조하면, 다른 참조가 전혀 없어도 그 오브젝트는 절대 삭제되지 않는다.

 

생성·소멸 비용이 크고, GC 추적 참조를 하나씩 추가하기 때문에 오남용하면 성능 저하로 이어진다.

자주 변경되지 않는 장기적인 참조에만 사용하자.

 

UObject 내부의 소유 참조라면 TObjectPtr + UPROPERTY, 비소유 참조라면 TWeakObjectPtr를 쓰는 게 맞다. TStrongObjectPtr는 UObject가 아닌 클래스나 구조체처럼 UPROPERTY를 쓸 수 없는 환경에서 UObject를 강하게 참조해야 할 때 쓰는 것이다.

 

class FMyClass  // UObject가 아닌 클래스
{
    TStrongObjectPtr<UMyObject> MyObject;
}

 


 

참고

더보기

https://dev.epicgames.com/documentation/unreal-engine/object-pointers-in-unreal-engine

 

Object Pointers in Unreal Engine | Unreal Engine 5.7 Documentation | Epic Developer Community

Unreal Engine provides several templated, smart pointers designed for a variety of use-cases.

dev.epicgames.com

 

저작자표시 변경금지 (새창열림)

'게임개발 > Unreal Engine' 카테고리의 다른 글

[UE5] FName, FText, FString  (0) 2026.03.28
[UE5] 언리얼의 Multi-threading  (0) 2026.02.16
[UE5] Replicate Montage Multicast in C++ not working  (0) 2025.03.01
[UE5] C++ AActor::Destroy() not working in BeginPlay()  (0) 2025.02.06
[UE5] Unreal Engine C++ API References  (2) 2024.08.20
'게임개발/Unreal Engine' 카테고리의 다른 글
  • [UE5] FName, FText, FString
  • [UE5] 언리얼의 Multi-threading
  • [UE5] Replicate Montage Multicast in C++ not working
  • [UE5] C++ AActor::Destroy() not working in BeginPlay()
깜냥c
깜냥c
게임 개발/클라이언트/AI/PS/기타 연구
  • 깜냥c
    Choice Program
    깜냥c
  • 전체
    오늘
    어제
    • 분류 전체보기 (64)
      • 언어 (12)
        • C,C++ (10)
        • C# (1)
        • Python (1)
      • PS (20)
        • 백준 문제 (19)
        • 알고리즘 (1)
      • 인공지능 (2)
      • 게임제작 (7)
      • 게임개발 (20)
        • Unity (10)
        • Unreal Engine (8)
        • Godot Engine (1)
      • 기타 (2)
  • 블로그 메뉴

    • 홈
    • 방명록
    • 블로그 소개
  • 링크

    • 김병장의 IT 블로그
    • 식품영양과 데이터사이언스
  • 공지사항

  • 인기 글

  • 태그

    C++
    C언어
    Godot
    Unreal
    UE5
    백준
    입출력
    unity
    BOJ
    배낭 문제
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
깜냥c
[UE5] Unreal Object Pointers
상단으로

티스토리툴바