본 포스팅의 구조는 'AssetManager ⇄ Bundle System 구조 수정'에서 수정 됨.


문제 발생

Level 진입 전 필요한 Asset을 미리 Preload를 위한 Bundle을 지정하는 과정에서
모든 에셋에 수동적으로 작업해야하는 번거로움이 발생했다.


초기 아이디어

Level을 Migration하면 필요한 Asset들이 함께 복사되는 것처럼,
AssetRegistry를 이용하면 현재 Level이 사용하는 Asset들을 자동으로 수집할 수 있을 것이라 생각하였다.

image-1783614682971


AssetRegistry 기반 Dependency Scan

Editor에서는 AssetRegistry를 이용하여

  • Material
  • Texture
  • StaticMesh
  • SkeletalMesh
  • Blueprint
  • Widget Blueprint
  • Niagara
  • Animation
  • Sound

등의 Dependency를 Recursive하게 수집할 수 있다.
이를 이용하여 현재 Level이 사용하는 Asset 목록을 자동 생성하도록 설계하였다.

void CollectDependenciesRecursive(
    const FName PackageName,
    IAssetRegistry& AssetRegistry,
    TSet<FName>& VisitedPackages,
    TArray<FName>& OutDependencyPackages)
{
    if (VisitedPackages.Contains(PackageName))
    {
        return;
    }

    VisitedPackages.Add(PackageName);

    TArray<FName> Dependencies;
    const UE::AssetRegistry::FDependencyQuery DependencyQuery =
        UE::AssetRegistry::FDependencyQuery(
            UE::AssetRegistry::EDependencyQuery::Hard |
            UE::AssetRegistry::EDependencyQuery::Soft);

    AssetRegistry.GetDependencies(
        PackageName,
        Dependencies,
        UE::AssetRegistry::EDependencyCategory::Package,
        DependencyQuery);

    for (const FName DependencyPackage : Dependencies)
    {
        OutDependencyPackages.Add(DependencyPackage);
        CollectDependenciesRecursive(DependencyPackage, AssetRegistry, VisitedPackages, OutDependencyPackages);
    }
}

Asset Bundle 분류

image-1783617474394

각 Asset은 AssetClassPath를 통해 타입을 구별해 Bundle을 나누게했다.

AddRule(TEXT("/Script/Engine.Texture"), TEXT("Texture"));
AddRule(TEXT("/Script/Engine.MaterialInterface"), TEXT("Material"));
AddRule(TEXT("/Script/UMG.WidgetBlueprint"), TEXT("UI"));
AddRule(TEXT("/Script/Niagara.NiagaraSystem"), TEXT("VFX"));
AddRule(TEXT("/Script/Engine.AnimationAsset"), TEXT("Animation"));
AddRule(TEXT("/Script/Engine.Blueprint"), TEXT("Blueprint"));
AddRule(TEXT("/Script/Engine.StaticMesh"), TEXT("Preload"));
AddRule(TEXT("/Script/Engine.SkeletalMesh"), TEXT("Preload"));
bool IsClassMatched(const FAssetData& AssetData, const FAssetBundleGeneratorClassificationRule& Rule)
{
    const UClass* RuleClass = Rule.AssetClass.LoadSynchronous();
    if (!RuleClass)
    {
        return false;
    }

    if (AssetData.AssetClassPath == RuleClass->GetClassPathName())
    {
        return true;
    }

    if (!Rule.bIncludeDerivedClasses)
    {
        return false;
    }

    const UClass* AssetClass = AssetData.GetClass();
    return AssetClass && AssetClass->IsChildOf(RuleClass);
}

Base DataAsset class

Plugin이 사용할 Base Class를 제공한다.
TArray<TSoftObjectPtr<UObject>>TMap<FName, FAssetBundleGeneratorEntry>으로 저장해서
여러 Asset타입들을 Bundle로 가져올 수 있는 구조다.

USTRUCT(BlueprintType)
struct ASSETBUNDLEGENERATOR_API FAssetBundleGeneratorEntry
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Asset Bundle")
    TArray<TSoftObjectPtr<UObject>> Assets;
};

UCLASS(Abstract, BlueprintType)
class ASSETBUNDLEGENERATOR_API UAssetBundleDataAssetBase : public UPrimaryDataAsset
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Asset Bundle")
    TMap<FName, FAssetBundleGeneratorEntry> Bundles;

    virtual void SetBundleAssets(FName BundleName, const TArray<FSoftObjectPath>& AssetPaths);
    virtual void ClearBundle(FName BundleName);
    virtual void ClearAllBundles();
    virtual const TMap<FName, FAssetBundleGeneratorEntry>& GetBundles() const;
    virtual void RebuildAssetBundleData();

#if WITH_EDITORONLY_DATA
    virtual void UpdateAssetBundleData() override;
#endif
};

프로젝트는 이를 상속하여 사용한다.

UCLASS(BlueprintType)
class PROJECTFT_API UFTLevelAssetBundleDataAsset : public UAssetBundleDataAssetBase
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Level")
    FName LevelId;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Level")
    TSoftObjectPtr<UWorld> Level;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Level")
    FText Description;
};

image-1783615396341


Editor Tool

DataAsset을 자동 생성, 자동 갱신 하는 Editor Tool을 만들어서
현재 레벨에서 AssetRegistry Scan을 통해 Dependency 수집해서 Bundle을 분류하게 했다.
만들어진 Bundle을 UAssetBundleDataAssetBase을 상속받는 DataAsset에 저장한다.

image-1783615105250


남아있는 문제

AssetRegistry는 Level이 직접 참조하는 Dependency는 대부분 수집할 수 있다.
하지만 코드에서 동적으로 사용하는 Asset은 자동으로 수집할 수 없다.


향후 개선 방향

에셋 사용을 런타임에 기록하는 기능을 추가해서 실제로 Load되는 Asset 기록하고 List를 생성해
DataAsset에 추가하는 기능을 만들 생각이다.

다만 이 방법도 실제로 플레이한 경로 에서 사용된 Asset만 기록되므로
런타임에 기록되지 않고 AssetRegistry Scan에 탐색되지 않은 에셋은 직접 추가해주어야 한다.

반응형

'Unreal Engine' 카테고리의 다른 글

MyaCat 정리 1  (0) 2026.07.27
AssetManager ⇄ Bundle System 구조 수정  (0) 2026.07.13
Unreal Engine GC  (0) 2026.07.08
Unreal Engine Singleton  (0) 2026.07.07
Unreal Engine Tick 최적화  (0) 2026.07.06