하드 레퍼런스의 문제점


class BISHOUJO_DOOM_API ATestCharacter : public AActor
{
    GENERATED_BODY()
    
public: 
    ATestCharacter();

protected:
    virtual void BeginPlay() override;

public: 
    virtual void Tick(float DeltaTime) override;

public:
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    USkeletalMesh* WeaponMesh;

public:
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
    USkeletalMeshComponent* MeshComponent;
};

ATestCharacterWeaponMesh를 하드 레퍼런스로 가지고 있다.

UCLASS()
class BISHOUJO_DOOM_API ATestCharacterManager : public AActor
{
    GENERATED_BODY()
    
public: 
    ATestCharacterManager();

protected:
    virtual void BeginPlay() override;

public: 
    virtual void Tick(float DeltaTime) override;

private:
    UPROPERTY(EditAnywhere)
    TSubclassOf TestClass;
};

ATestCharacterManagerATestCharacter의 클래스를 참조한다.

TSubClassOf: 클래스의 메타데이터와 타입 정보만 참조.

  • 클래스 타입을 변수로 저장하고 인스턴스 생성 시 사용.
  • CDO(기본 오브젝트) 포함.
  • CDO의 모든 하드 레퍼런스 에셋들이 함께 로드됨.

TestMap의 사이즈맵에 TestCharacterManager 밑의 TestCharacterSkeletalMesh가 포함된다. 맵이 로딩될 때, 같이 로드 된다는 얘기이다.

소프트 레퍼런스로 해결


class BISHOUJO_DOOM_API ATestCharacter : public AActor
{
    GENERATED_BODY()
    
public: 
    ATestCharacter();

protected:
    virtual void BeginPlay() override;

public: 
    virtual void Tick(float DeltaTime) override;

    void OnCharacterAssetsLoaded();

public:
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    TSoftObjectPtr WeaponMesh;
    
    // UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
    // USkeletalMesh* WeaponMesh;
};

WeaponMesh를 소프트 레퍼런스로 변경하였다.

TestMap의 사이즈맵에 TestCharacter의 메시가 포함되지 않는 것을 볼 수 있다.

소프트 레퍼런스 에셋을 비동기로 로딩하는 방법

void ATestCharacter::BeginPlay()
{
    Super::BeginPlay();

    // 로드할 에셋들의 경로를 수집
    TArray AssetsToLoad;
    
    if (!WeaponMesh.IsNull())
    {
       AssetsToLoad.Add(WeaponMesh.ToSoftObjectPath());
    }

    if (AssetsToLoad.Num() > 0)
    {
       // 비동기 로딩 시작
       UE_LOG(LogTemp, Warning, TEXT("[SOFT_REF] 스켈레탈 메시 %d개 로딩중....."), AssetsToLoad.Num());
       UE_LOG(LogTemp, Warning, TEXT("[SOFT_REF] 로딩 시작 시간 : %f"),GetWorld()->GetTimeSeconds());
       
       UAssetManager& AssetManager = UAssetManager::Get();
       FStreamableManager& StreamableManager = AssetManager.GetStreamableManager();
        
       auto AllAssetsLoadHandle = StreamableManager.RequestAsyncLoad(
          AssetsToLoad,
          FStreamableDelegate::CreateUFunction(this, FName("OnCharacterAssetsLoaded"))
       );

       UE_LOG(LogTemp, Warning, TEXT("[SOFT_REF] 로딩중에도 게임 계속 실행중....."));
    }
}

void ATestCharacter::OnCharacterAssetsLoaded()
{
    UE_LOG(LogTemp, Warning, TEXT("[SOFT_REF] 캐릭터의 스켈레탈 메시 로딩 완료"));
    UE_LOG(LogTemp, Warning, TEXT("[SOFT_REF] 로딩 완료 시간 : %f"),GetWorld()->GetTimeSeconds());

    if (WeaponMesh.IsValid())
    {
       MeshComponent->SetSkeletalMesh(WeaponMesh.Get());
    }
    else
    {
       UE_LOG(LogTemp, Warning, TEXT("[SOFT_REF] Weapon Mesh is unvalid."));
    }
}

AssetManager를 사용해서 비동기로 로딩할 수 있다.

동기로 로딩하지 않는 이유는 로딩할 에셋이 큰 경우 게임이 끊기는 현상이 발생할 수 있기 때문이다.