개요

GameFeature 를 사용하는 중에 AssetManager 와 스캔할 프라이머리 에셋 타입이라는 게 있어서, 공부해보았다. 에셋 매니저를 사용하지 않아도, 게임은 만들 수 있다. 그럼 왜 필요한지 알아보자.


에셋 매니저를 사용하는 이유

1. 에셋 로드를 위해 경로를 직접 작성, 에디터에서 클래스 레퍼런스 연결하는 걸 막을 수 있다.

  • 에셋 경로를 직접 작성하고 있는 경우
  • LoadObject("/Game/Characters/Hero/BP_Hero.BP_Hero");
    
  • 에디터에서 레퍼런스를 연결하는 경우
  • UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ABSGameState")
    TMap<FGameplayTag, TSoftObjectPtr<const UBSCharacterDefinition>> AvailableCharacterDefinitionMap;
    

2. 원하는 에셋을 원하는 때에 로드, 언로드할 수 있다.

위로 얻는 효과는 프로그래머의 실수를 방지하고, 메모리를 효율적으로 사용할 수 있다.

생각보다 사용 방법은 쉬웠다. 왜 진작 사용하지 않았을까?

에셋 매니저 사용 방법

  • 에셋 매니저가 로드할 수 있는 에셋은 Primary Asset 뿐이다.
    • 기본적으로 UWorld(레벨) 에셋이 유일한 PrimaryAsset 이며, 상속 후에 GetPrimaryAssetId 함수를 오버라이드 하면 PrimaryAsset 이 된다.
    • PrimaryDataAsset 을 상속받은 BP 또는 DA 가 해당 된다.

  • PrimaryAsset 은 이름 그대로 주요한 에셋으로, 직접 관리하고 싶은 최상위 에셋이다.
    • PrimaryAsset 안에는 Secondary Asset 으로 구분된다.
    • Primary 이 SecondaryAsset 참조하거나 사용하려는 경우 언리얼 엔진이 자동으로 세컨더리 에셋을 로드한다.

  • 예제를 보면 UBSCharacterDefinition 은 PrimaryAsset, 멤버 변수는 SecondaryAsset 이다.
  • 이제 PrimaryAssetID 로 경로나, 레퍼런스 필요 없이 검색하여 에셋을 가져올 수 있다.
  • class BISHOUJO_DOOM_API UBSCharacterDefinition : public UPrimaryDataAsset
    {
        GENERATED_BODY()
    
    public:
        UBSCharacterDefinition();
        
    public:
        virtual FPrimaryAssetId GetPrimaryAssetId() const override
        {
           return FPrimaryAssetId("Character", CharacterTag.GetTagLeafName());
        }
        
        UPROPERTY(EditDefaultsOnly, Category = "UBSCharacterDefinition")
        FGameplayTag CharacterTag;
        
        // ... (이하 Secondary Asset들)
        UPROPERTY(EditDefaultsOnly, Instanced, Category="UBSCharacterDefinition")
        TArray> DefaultGameFeatureActions;
        
        UPROPERTY(EditDefaultsOnly, Category="UBSCharacterDefinition")
        TObjectPtrconst UBSPawnData> DefaultPawnData;
    
        UPROPERTY(EditdefaultsOnly, BlueprintReadOnly, Category = "UBSCharacterDefinition")
        TSoftClassPtr TestSkeletalMeshClass;
    };

프로젝트 세팅 (스캔 규칙 설정)

프로젝트 세팅의 AssetManager 탭에서 스캔할 프라이머리 에셋 타입 추가

  • 스캔은 프로그래머가 로드할 에셋을 찾기 위해, 프라이머리 에셋의 타입과 위치를 가지고 목록을 만드는 과정이다.
  • 주의할 점: 규칙의 쿠킹(Cooking) 관련된 옵션을 선택해주어야, 프로덕션(Production)과 개발 단계 둘 다 로드가 가능하다.

AssetManager를 사용한 로드 (비동기)

PrimaryAssetID를 이용한 로드 호출

const FPrimaryAssetId CharacterDefID("Character", InTag.GetTagLeafName());

// 1. CharacterDefinition 비동기 로드
UBSAssetManager::Get().LoadCharacterDefinition(CharacterDefID, FStreamableDelegate::CreateLambda([this, CharacterDefID, InPlayerState, InTag]()
{
    // 2. CharacterDefinition 비동기 로드 완료
    const auto LoadedCharacterDef = UBSAssetManager::Get().GetPrimaryAssetObject(CharacterDefID);
    if (!LoadedCharacterDef)
    {
       UE_LOG(LogBS, Warning, TEXT("Invalid LoadedCharacterDef"));
       return;
    }
}

실제 Asset 로드 함수 구현 (커스텀 AssetManager 내)

void UBSAssetManager::LoadCharacterDefinition(const FPrimaryAssetId& InCharacterDefinitionId,
                                              const FStreamableDelegate& InLoadCompleteDelegate)
{
    // 기본 에셋 경로 가져오기
    const FSoftObjectPath AssetPath = GetPrimaryAssetPath(InCharacterDefinitionId);
    if (!AssetPath.IsValid())
    {
       UE_LOG(LogTemp, Error, TEXT("Invalid asset path for: %s"), *InCharacterDefinitionId.ToString());
       InLoadCompleteDelegate.ExecuteIfBound();
       return;
    }
    
    // 로드할 에셋 목록 구성
    TArray AssetsToLoad;
    AssetsToLoad.Add(AssetPath);
    
    // 비동기 로딩 시작
    LoadAssetList(AssetsToLoad, InLoadCompleteDelegate, FStreamableManager::AsyncLoadHighPriority);
}

프라이머리 에셋 로드 시 (Secondary Asset 동작)

  • 결론 : 프라이머리 에셋을 로드하면, 세컨더리 에셋들은 모두 자동으로 로드된다.
  • TSoftClassPtr 자동 로드
    Secondary Asset인 TSoftClassPtr 타입의 TestSkeletalMeshClass 는 자동으로 로드가 된다.
    (독립형 게임 모드에서도 확인 완료)
    • 자동으로 로드되지 않게 하려면, StreamableMananger로 직접 로드해야 한다.
    • 확인 코드 예시:
      FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager();
      auto bLoadComplete = StreamableManager.IsAsyncLoadComplete(NewCharacterDef->TestSkeletalMeshClass.ToSoftObjectPath());
      UE_LOG(LogBS, Log, TEXT("UBSCharacterDefManagerComponent::TestSkeletalMeshClass bLoadComplete = %d"),  bLoadComplete);

  • 세컨더리 에셋 안의 자식들도 자동 로드
    세컨더리 에셋이 참조하는 자식 에셋들(DefaultPawnData->TestPawnMeshClass)도 자동으로 로드가 된다.
    • 확인 코드 예시:
      bLoadComplete = StreamableManager.IsAsyncLoadComplete(NewCharacterDef->DefaultPawnData->TestPawnMeshClass.ToSoftObjectPath());
      UE_LOG(LogBS, Log, TEXT("UBSCharacterDefManagerComponent::TestPawnMeshClass bLoadComplete = %d"),  bLoadComplete);