개요


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;
        
        UPROPERTY(EditDefaultsOnly, Category = "UBSCharacterDefinition")
        TArray<FString> GameFeaturesToEnable;
    
        UPROPERTY(EditDefaultsOnly, Instanced, Category="UBSCharacterDefinition")
        TArray<TObjectPtr<UGameFeatureAction>> DefaultGameFeatureActions;
        
        UPROPERTY(EditDefaultsOnly, Category="UBSCharacterDefinition")
        TObjectPtr<const UBSPawnData> DefaultPawnData;
    
        UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UBSCharacterDefinition")
        TArray<TObjectPtr<UBSAbilitySet>> AbilitySets;
    
        UPROPERTY(EditdefaultsOnly, BlueprintReadOnly, Category = "UBSCharacterDefinition")
        TSoftClassPtr<USkeletalMesh> TestSkeletalMeshClass;
    };

  • 프로젝트 세팅의 AssetManager 탭에서 스캔할 프라이머리 에셋 타입을 추가한다.
  • 스캔이라는 건 프로그래머가 로드할 에셋을 찾기 위해, 프라이머리 에셋의 타입과 에셋의 위치와 용도를 가지고 목록을 만드는 것이다.
  • 주의할 점은, 규칙의 쿠킹 관련된 옵션을 선택해주어야, 프로덕션과 개발 단계 둘 다 로드가 가능하다.

  • 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;
        }
    }
    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<FSoftObjectPath> AssetsToLoad;
        AssetsToLoad.Add(AssetPath);
        
        // 비동기 로딩 시작
        LoadAssetList(AssetsToLoad, InLoadCompleteDelegate, FStreamableManager::AsyncLoadHighPriority);
    }

프라이머리 에셋 로드 시


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

  • 세컨더리 에셋 안의 자식들도 자동으로 로드가 된다.
  • auto bLoadComplete = StreamableManager.IsAsyncLoadComplete(NewCharacterDef->TestSkeletalMeshClass.ToSoftObjectPath());
    UE_LOG(LogBS, Log, TEXT("UBSCharacterDefManagerComponent::TestSkeletalMeshClass bLoadComplete = %d"),  bLoadComplete);
    
    bLoadComplete = StreamableManager.IsAsyncLoadComplete(NewCharacterDef->DefaultPawnData->TestPawnMeshClass.ToSoftObjectPath());
    UE_LOG(LogBS, Log, TEXT("UBSCharacterDefManagerComponent::TestPawnMeshClass bLoadComplete = %d"),  bLoadComplete);

  • 결론 : 프라이머리 에셋을 로드하면, 세컨더리 에셋들은 모두 자동으로 로드된다.

결과


  • 프라이머리 에셋인 BSCharacterDefinition 을 AssetManager로 로드하는데 성공했으며, CharacterDefinition 의 CharacterTag 를 출력해서 확인하고 있다.