[UE5] 생성자와 BeginPlay에서 절대 하지 말아야 할 것들!

ChangJin·2024년 7월 14일
1

Unreal Engine5

목록 보기
82/114
post-thumbnail

Unreal Engine에서 객체 초기화 시점과 BeginPlay 시점의 차이를 이해하는 것은 매우 중요합니다. 이번 글에서는 왜 생성자에서 레벨 이름을 가져올 수 없는지, 그리고 BeginPlay에서 FObjectFinder를 사용할 수 없는 이유를 상세히 살펴보겠습니다. 또한, 이를 해결할 수 있는 방법까지 제공합니다. 이 글을 통해 UE5 개발 과정에서 자주 발생하는 실수를 방지할 수 있으면 좋겠네요.

아이템을 스폰하려고 하는데 각 월드마다 다른 아이템 조합을 스폰하고 싶었습니다. 예를들어 level001 에서는 열쇠, 손전등을 스폰하고 level002 에서는 거울, 연필을 스폰하려고 했습니다. 스폰할 아이템은 DataTable로 만들어 두었기 때문에 각 레벨마다 DataTable을 만들어주고 여기에 접근하려고 했습니다.

자주자주 보게되는 언리얼의 액터 라이프사이클

왜 생성자에서 레벨 이름을 가져올 수 없을까?

Unreal Engine에서 생성자는 객체가 메모리에 할당되는 동안 호출됩니다. 이 단계에서는 아직 레벨 정보가 완전히 초기화되지 않았기 때문에, GetWorld()와 같은 함수가 유효하지 않습니다. 따라서, 레벨 이름이나 월드와 관련된 다른 정보에 접근할 수 없습니다.

예시 코드: 생성자에서 레벨 이름을 가져오려는 시도

AMyActor::AMyActor()
{
    // 이 시점에서 GetWorld()는 아직 유효하지 않음
    if (GetWorld())
    {
        FString LevelName = GetWorld()->GetMapName();
        UE_LOG(LogTemp, Warning, TEXT("Level Name: %s"), *LevelName);
    }
    else
    {
        UE_LOG(LogTemp, Warning, TEXT("GetWorld() is not valid in constructor."));
    }
}

이 코드에서 GetWorld()는 생성자 내에서는 항상 nullptr를 반환하므로, 레벨 이름을 가져올 수 없습니다.


왜 FObjectFinder를 BeginPlay에서 사용할 수 없을까?

FObjectFinder는 일반적으로 정적 애셋 로드에 사용됩니다. 이는 생성자나 클래스 초기화 시점에서 실행되어야 합니다. BeginPlay는 게임이 시작될 때 호출되지만, FObjectFinder는 이 시점에서는 이미 적절하게 작동하지 않습니다. 이 때문에, BeginPlay에서 FObjectFinder를 사용하는 것은 권장되지 않습니다.

예시 코드: BeginPlay에서 FObjectFinder를 사용하려는 시도

void AMyActor::BeginPlay()
{
    Super::BeginPlay();
    
    // BeginPlay에서 FObjectFinder 사용
    static ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("/Game/Path/To/Your/Mesh.Mesh"));
    if (MeshAsset.Succeeded())
    {
        UE_LOG(LogTemp, Warning, TEXT("Mesh loaded successfully."));
    }
    else
    {
        UE_LOG(LogTemp, Warning, TEXT("Failed to load mesh in BeginPlay."));
    }
}

이 코드에서 FObjectFinder는 정적 초기화 구문에 속해야 하는데, BeginPlay에서 사용하면 의도한 대로 작동하지 않을 수 있습니다.


생성자와 BeginPlay에서의 대안 방법

생성자에서 레벨 이름을 가져오는 대안

생성자에서 레벨 정보를 가져올 수 없기 때문에, 이를 BeginPlay나 다른 초기화 함수에서 처리해야 합니다.

AMyActor::AMyActor()
{
    PrimaryActorTick.bCanEverTick = true;
}

void AMyActor::BeginPlay()
{
    Super::BeginPlay();
    
    // 이 시점에서는 GetWorld()가 유효함
    if (GetWorld())
    {
        FString LevelName = GetWorld()->GetMapName();
        UE_LOG(LogTemp, Warning, TEXT("Level Name: %s"), *LevelName);
    }
}

FObjectFinder를 정적 초기화 구문에서 사용하는 예

AMyActor::AMyActor()
{
    // 정적 자산 로드는 생성자나 클래스 초기화 시점에서 수행
    static ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("/Game/Path/To/Your/Mesh.Mesh"));
    if (MeshAsset.Succeeded())
    {
        MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyMesh"));
        MeshComponent->SetStaticMesh(MeshAsset.Object);
    }
}

void AMyActor::BeginPlay()
{
    Super::BeginPlay();
    
    // BeginPlay에서는 추가적인 초기화 작업을 수행
    if (MeshComponent)
    {
        UE_LOG(LogTemp, Warning, TEXT("MeshComponent initialized in BeginPlay."));
    }
}

결론

  • 생성자에서는 레벨이나 월드 정보를 가져올 수 없으며, GetWorld()가 유효하지 않습니다.

  • FObjectFinder는 정적 자산 로드에 사용되며, 생성자나 클래스 초기화 시점에서 실행되어야 합니다.

  • 레벨 정보는 BeginPlay에서 가져와야 하며, 정적 자산 로드는 생성자에서 처리해야 합니다.

  • 아이템마다 어떤 레벨에서 쓰이는지에 대한 정보를 넣어야하지 않을까합니다.

이러한 차이를 이해하고 각각의 시점에서 적절한 작업을 수행하는 것이 중요합니다.


참고 자료

이 글을 통해 UE5 개발에서 흔히 발생하는 문제들을 예방하고 더 나은 코드를 작성할 수 있기를 바랍니다!!

profile
게임 프로그래머

0개의 댓글