Importing assets to Unreal Engine

How to import various kind of assets to Unreal Engine with C++?

Importing Assets with C++ to Unreal Engine

Importing assets using UBlueprintFunctionLibrary class works in the editor mode only. While we use it in the editor, we need to extend project's build settings through .Build.cs file for 2 additional modules, see below.

if (Target.bBuildEditor == true)
{
  PrivateDependencyModuleNames.AddRange(
    new string[]
		{
      "AssetTools",
      "UnrealEd",
    }
  );
};

Usually we will use 2 following custom help functions for importing - CreateImportTask and ProcessImportTask. They may be defined within the same UExampleClass or in a separated class we will ink to.

  • CreateImportTask custom help function

    /**
     * Editor Only - Will not work in packaged build.
     * 
     * Create an import task that you can then process to import any kind of assets
     *
     * @param SourcePath		The path of the source file: "C:/Temp/MyTexture.png"
     * @param DestinationPath	The path of the imported asset: "/Game/Folder/MyTexture"
     * @param ExtraFactory		Optional. Some asset types require a special factory to import them
     * @param ExtraOptions		Optional. Some asset types have some extra settings you can set
     * @param bOutSuccess		If the action was a success or not
     * @param OutInfoMsg	More information about the action's result
     *
     * @return The import task
     */
    UFUNCTION(BlueprintCallable, Category = "Import Basic Assets")
    static class UAssetImportTask* CreateImportTask(FString SourcePath, FString DestinationPath, UFactory* ExtraFactory, UObject* ExtraOptions, bool& bOutSuccess, FString& OutInfoMsg);
    #include "Editor/UnrealEd/Public/AssetImportTask.h" // UnrealEd (Editor Only)
    #include "AssetToolsModule.h" // AssetTools (Editor Only)
       
    UAssetImportTask* UExampleClass::CreateImportTask(FString SourcePath, FString DestinationPath, UFactory* ExtraFactory, UObject* ExtraOptions, bool& bOutSuccess, FString& OutInfoMsg)
    {
       // Create task object
       UAssetImportTask* Task = NewObject<UAssetImportTask>();
       if (Task == nullptr)
       {
          bOutSuccess = false;
          OutInfoMsg = FString::Printf(TEXT("Create Import Task Failed - Was not able to create the import task object. That shouldn't really happen. - '%s'"), *SourcePath);
          return nullptr;
       }
    
       // Set path informations - fill with function parameters
       Task->Filename = SourcePath;
       Task->DestinationPath = FPaths::GetPath(DestinationPath);
       Task->DestinationName = FPaths::GetCleanFilename(DestinationPath);
    
       // Set basic options
       Task->bSave = false; // save file once task completed?
       Task->bAutomated = true; // automated = no popups for the user
       Task->bAsync = false;
       Task->bReplaceExisting = true;
       Task->bReplaceExistingSettings = false;
    
       // Optional extra factory and options
       Task->Factory = ExtraFactory;
       Task->Options = ExtraOptions;
    
       // Return the task
       bOutSuccess = true;
       OutInfoMsg = FString::Printf(TEXT("Create Import Task Succeeded - '%s'"), *SourcePath);
       return Task;
    }
    PrivateDependencyModuleNames.AddRange
    (
      new string[] 
      { 
        // Default Modules
        "Core", 
        "CoreUObject", 
        "Engine", 
    
        // New Modules - Editor Only
        "AssetTools",,
        "UnrealEd",
      }
    );

    In the configuration above, imported assets are not saved automatically due to Task->bSave = false setup.

  • ProcessImportTask custom help function

    /**
     * Editor Only - Will not work in packaged build.
     *
     * Process the import task to import the assets
     *
     * @param ImportTask		The task you want to process
     * @param bOutSuccess		If the action was a success or not
     * @param OutInfoMsg	More information about the action's result
     *
     * @return The imported asset
     */
    UFUNCTION(BlueprintCallable, Category = "Import Basic Assets")
    static UObject* ProcessImportTask(UAssetImportTask* ImportTask, bool& bOutSuccess, FString& OutInfoMsg);
    UObject* UExampleClass::ProcessImportTask(UAssetImportTask* ImportTask, bool& bOutSuccess, FString& OutInfoMsg)
    {
       if (ImportTask == nullptr)
       {
          bOutSuccess = false;
          OutInfoMsg = FString::Printf(TEXT("Process Import Task Failed - The task was invalid. Cannot process."));
          return nullptr;
       }
    
       // Get the AssetTools module
       FAssetToolsModule* AssetTools = FModuleManager::LoadModulePtr<FAssetToolsModule>("AssetTools");
       if (AssetTools == nullptr)
       {
          bOutSuccess = false;
          OutInfoMsg = FString::Printf(TEXT("Process Import Task Failed - The AssetTools module is invalid. - '%s'"), *ImportTask->Filename);
          return nullptr;
       }
    
       // Import the asset
       AssetTools->Get().ImportAssetTasks({ Task });
       // Check if anything was imported during the process
       if (ImportTask->GetObjects().Num() == 0)
       {
          bOutSuccess = false;
          OutInfoMsg = FString::Printf(TEXT("Process Import Task Failed - Nothing was imported. Is your file valid? Is the asset type supported? - '%s'"), *ImportTask->Filename);
          return nullptr;
       }
       // Because some import tasks actually create multiple assets (e.g. SkeletalMesh), we manually get the right asset
       UObject* ImportedAsset = StaticLoadObject(UObject::StaticClass(), nullptr, *FPaths::Combine(ImportTask->DestinationPath, ImportTask->DestinationName));
    
       // Return the asset
       bOutSuccess = true;
       OutInfoMsg = FString::Printf(TEXT("Process Import Task Succeeded - '%s'"), *ImportTask->Filename);
       return ImportedAsset;
    }
    PrivateDependencyModuleNames.AddRange
    (
      new string[] 
      { 
        // Default Modules
        "Core", 
        "CoreUObject", 
        "Engine", 
    
        // New Modules - Editor Only
        "AssetTools",,
        "UnrealEd",
      }
    );

Import of basic data types (textures, sounds...) (Editor based)

As mentioned, we work in an inherited class from UBlueprintFunctionLibrary, thus our header file should look like below:

UCLASS()
class UExampleClass : public UBlueprintFunctionLibrary
{
 GENERATED_BODY()
public:
 /**
 * Editor Only - Will not work in packaged build.
 *
 * Create and process a basic import task.
 * Will work for the basic asset types like textures and sounds.
 * Can also be used for meshes, but you might not have all the control you want.
 *
 * @param SourcePath    The path of the source file: "C:/Temp/MyTexture.png"
 * @param DestinationPath   The path of the imported asset: "/Game/Folder/MyTexture"
 * @param bOutSuccess   If the action was a success or not
 * @param OutInfoMsg    More information about the action's result
 *
 * @return The imported asset
 */
 UFUNCTION(BlueprintCallable, Category = "Import Basic Assets")
 static UObject* ImportAsset(FString SourcePath, FString DestinationPath, bool& bOutSuccess, FString& OutInfoMsg);

 // CreateImportTask help function
 // ProcessImportTask help function
};
#include "Editor/UnrealEd/Public/AssetImportTask.h" // UnrealEd (Editor Only)
#include "AssetToolsModule.h" // AssetTools (Editor Only)

UObject* UExampleClass::ImportAsset(FString SourcePath, FString DestinationPath, bool& bOutSuccess, FString& OutInfoMsg)
{
   // Create the import task
   UAssetImportTask* Task = CreateImportTask(SourcePath, DestinationPath, nullptr, nullptr, bOutSuccess, OutInfoMsg);
   if (!bOutSuccess) return nullptr;

   // Import the asset
   UObject* RetAsset = ProcessImportTask(Task, bOutSuccess, OutInfoMsg);
   if (!bOutSuccess) return nullptr;
   
   // Return the imported asset
   bOutSuccess = true;
   OutInfoMsg = FString::Printf(TEXT("Import Asset Succeeded - '%s'"), *DestinationPath);
   return RetAsset;
}

In the similar way, there's possible:

Reimport Asset (Editor based)

The code below is a C++ way of following manual operation: "Selecting an asset in Content window" → Right mouse click → Selecting Reimport option or alternatively Reimport With New File option.

/**
 * Editor Only - Will not work in packaged build.
 *
 * Reimport an already imported asset
 *
 * @param AssetPath           The path of the asset to reimport: "/Game/Folder/MyTexture"
 * @param SourcePathOverride  (Optional) The path of the source file (if you want to use a new file): "C:/Temp/MyTexture.png"			
 * @param OutInfoMsg          More information about the action's result
 * @param return              If the action was a success or not
 */
UFUNCTION(BlueprintCallable, Category = "Reimport Asset")
static bool ReimportAsset(FString AssetPath, FString SourcePathOverride, FString& OutInfoMsg);
#include "EditorReimportHandler.h" // UnrealEd
bool UExampleClass::ReimportAsset(FString AssetPath, FString SourcePathOverride, FString& OutInfoMsg)
{
	// Load existing asset
	UObject* ExistingAsset = StaticLoadObject(UObject::StaticClass(), nullptr, *AssetPath);
	if (ExistingAsset == nullptr)
	{
		OutInfoMsg = FString::Printf(TEXT("Reimport Asset Failed - Asset isn't valid. '%s'"), *AssetPath);
		return false;
	}
   
	// Update path
	if (SourcePathOverride != "" && !UExampleClass::SetAssetImportPath(AssetPath, SourcePathOverride, OutInfoMsg)) return false;

	// Reimport
	if (FReimportManager::Instance()->Reimport(ExistingAsset))
	{
		OutInfoMsg = FString::Printf(TEXT("Reimport Asset Succeeded - '%s'"), *AssetPath);
		return true;
	}

   bool bOutSuccess;
	FString Path = UExampleClass::GetAssetImportPath(AssetPath, bOutSuccess, OutInfoMsg);
	OutInfoMsg = FString::Printf(TEXT("Reimport Asset Failed - Is the source path valid? \n'%s'"), *Path);
   return False;
}
PrivateDependencyModuleNames.AddRange
(
  new string[] 
  { 
    // Default Modules
    "Core", 
    "CoreUObject", 
    "Engine", 

    // New Modules - Editor Only
    "UnrealEd",
  }
);

Get / Set Asset Import Source Path (Editor based)

The import path (File Path) is the path of the file that was used to import the asset to Unreal Engine. The code below allows to get File Path of any asset as well as change it to link at another file.

Get Asset Import Path

/**
 * Editor Only - Will not work in packaged build.
 *
 * Get the path used to import his asset
 *
 * @param AssetPath  The path of the asset: "/Game/Folder/MyTexture"
 * @param bOutSuccess   If the action was a success or not
 * @param OutInfoMsg   More information about the action's result
 *
 * @return The path used to import this asset
 */
UFUNCTION(BlueprintCallable, Category = "Modify Asset Import Path")
static FString GetAssetImportPath(FString AssetPath, bool& bOutSuccess, FString& OutInfoMsg);
#include "EditorReimportHandler.h" // UnrealEd
#include "EditorFramework/AssetImportData.h" // Engine
FString UExampleClass::GetAssetImportPath(FString AssetPath, bool& bOutSuccess, FString& OutInfoMsg)
{
	// Load asset
	UObject* Asset = StaticLoadObject(UObject::StaticClass(), nullptr, *AssetPath);

	if (Asset == nullptr)
	{
		bOutSuccess = false;
		OutInfoMsg = FString::Printf(TEXT("Get Asset Import Path Failed - Asset isn't valid. '%s'"), *AssetPath);
		return "";
	}

	// Can we use the ReimportManager to get the import path?
	TArray<FString> Paths = TArray<FString>();
	if (FReimportManager::Instance()->CanReimport(Asset, &Paths) && Paths.Num() > 0)
	{
		FString Path = Paths[0];

		bOutSuccess = true;
		OutInfoMsg = FString::Printf(TEXT("Get Asset Import Path Succeeded [ReimportManager] - '%s'\n'%s'"), *AssetPath, *Path);
		return Path;
	}
	else
	{
		// Get the path manually
		UAssetImportData* AssetImportData = GetAssetImportData(Asset);
		if (AssetImportData != nullptr)
		{
			FString Path = AssetImportData->GetFirstFilename();

			bOutSuccess = true;
			OutInfoMsg = FString::Printf(TEXT("Get Asset Import Path Succeeded - '%s'\n'%s'"), *AssetPath, *Path);
			return Path;
		}
	}

	bOutSuccess = false;
	OutInfoMsg = FString::Printf(TEXT("Get Asset Import Path Failed - Asset class not in GetAssetImportData: '%s'"), *AssetPath);
	return "";
}
PrivateDependencyModuleNames.AddRange
(
  new string[] 
  { 
    // Default Modules
    "Core", 
    "CoreUObject", 
    "Engine", 

    // New Modules - Editor Only
    "UnrealEd",
  }
);

Set Asset Import Path

	/**
 * Editor Only - Will not work in packaged build.
 *
 * Set the path used to import his asset
 *
 * @param AssetPath  The path of the asset: "/Game/Folder/MyTexture"
 * @param NewImportPath The path of the new source files: "C:/Temp/MyTexture.png"
 * @param OutInfoMsg More information about the action's result
 * @param return If the action was a success or not
 */
UFUNCTION(BlueprintCallable, Category = "Modify Asset Import Path")
static bool SetAssetImportPath(FString AssetPath, FString NewImportPath, FString& OutInfoMsg);
#include "EditorReimportHandler.h" // UnrealEd
#include "EditorFramework/AssetImportData.h" // Engine
bool UExampleClass::SetAssetImportPath(FString AssetPath, FString NewImportPath, FString& OutInfoMsg)
{
	// Load asset
	UObject* Asset = StaticLoadObject(UObject::StaticClass(), nullptr, *AssetPath);

	if (Asset == nullptr)
	{
		OutInfoMsg = FString::Printf(TEXT("Set Asset Import Path Failed - Asset isn't valid. '%s'"), *AssetPath);
		return false;
	}

	// Can we use the ReimportManager to set the import path?
	if (FReimportManager::Instance()->CanReimport(Asset, nullptr)) 
   {
		FReimportManager::Instance()->UpdateReimportPath(Asset, NewImportPath, 0);

		OutInfoMsg = FString::Printf(TEXT("Set Asset Import Path Succeeded [ReimportManager] - '%s'\n'%s'"), *AssetPath, *NewImportPath);
		return true;
	} else {
		// Set the path manually
		UAssetImportData* AssetImportData = GetAssetImportData(Asset);
		if (AssetImportData != nullptr)
		{
			AssetImportData->SetSourceFiles({ NewImportPath });

			OutInfoMsg = FString::Printf(TEXT("Set Asset Import Path Succeeded - '%s'\n'%s'"), *AssetPath, *NewImportPath);
			return true;
		}
	}

	OutInfoMsg = FString::Printf(TEXT("Set Asset Import Path Failed - Asset class not in GetAssetImportData: '%s'"), *AssetPath);
   return false;
}
PrivateDependencyModuleNames.AddRange
(
  new string[] 
  { 
    // Default Modules
    "Core", 
    "CoreUObject", 
    "Engine", 

    // New Modules - Editor Only
    "UnrealEd",
  }
);
  • Setting Source File path for Meshes works only when setting a path with unreal-supported asset type endings (.fbx, and so on).
  • Setting Source File for Texture does not care about extension of the source path
  • Setting Source File for Sound does not care about extension of the source path

Get the Asset Import Data based on the asset class

	/**
 * Editor Only - Will not work in packaged build.
 *
 * Get the Asset Import Data based on the asset class
 *
 * @param Asset   The asset
 * 
 * @return Asset import data
 */
UFUNCTION(BlueprintCallable, Category = "Modify Asset Import Path")
static class UAssetImportData* GetAssetImportData(UObject* Asset);
#include "Engine/StaticMesh.h" // Engine
#include "Engine/SkeletalMesh.h" // Engine
#include "Engine/Texture.h" // Engine
#include "Animation/AnimSequence.h" // Engine
#include "Engine/DataTable.h" // Engine
#include "Sound/SoundWave.h" // Engine
UAssetImportData* UExampleClass::GetAssetImportData(UObject* Asset)
{
  if (Cast<UStaticMesh>(Asset) != nullptr) // UStaticMesh
  {
    return Cast<UStaticMesh>(Asset)->AssetImportData;
  }
  else if (Cast<USkeletalMesh>(Asset) != nullptr) // USkeletalMesh
  {
    return Cast<USkeletalMesh>(Asset)->GetAssetImportData();
  }
  else if (Cast<UTexture>(Asset) != nullptr) // UTexture
  {
    return Cast<UTexture>(Asset)->AssetImportData;
  }
  else if (Cast<UAnimSequence>(Asset) != nullptr) // UAnimSequence
  {
    return Cast<UAnimSequence>(Asset)->AssetImportData;
  }
  else if (Cast<UDataTable>(Asset) != nullptr) // UDataTable
  {
    return Cast<UDataTable>(Asset)->AssetImportData;
  }
  else if (Cast<USoundWave>(Asset) != nullptr) // USoundWave
  {
    return Cast<USoundWave>(Asset)->AssetImportData;
  }
  // Add all other asset types you want to support.
  return nullptr;
}

Import Alembic Geometry Cache

#include "Kismet/BlueprintFunctionLibrary.h" // Engine
/**
 * Editor Only - Will not work in packaged build.
 *
 * Create and process a GeometryCache import task.
 *
 * @param SourcePath		The path of the source file: "C:/Temp/MyGeometryCache.abc"
 * @param DestinationPath	The path of the imported asset: "/Game/Folder/MyGeometryCache"
 * @param bOutSuccess		If the action was a success or not
 * @param OutInfoMsg	More information about the action's result
 *
 * @return The imported GeometryCache
 */
UFUNCTION(BlueprintCallable, Category = "Alex Quevillon - Import Alembic Geometry Cache")
static UGeometryCache* ImportGeometryCache(FString SourcePath, FString DestinationPath, bool& bOutSuccess, FString& OutInfoMsg);
#include "GeometryCache.h" // GeometryCache
#include "AlembicImportFactory.h" // AlembicImporter (Editor Only)
#include "AbcImportSettings.h" // AlembicLibrary

UGeometryCache* UExampleClass::ImportGeometryCache(FString SourcePath, FString DestinationPath, bool& bOutSuccess, FString& OutInfoMsg)
{
	// Create factory to import a Geometry Cache
	UAlembicImportFactory* AbcFactory = NewObject<UAlembicImportFactory>();

	// Alembic
	AbcFactory->ImportSettings->ImportType = EAlembicImportType::GeometryCache;

	// Geometry Cache - Only useful when ImportType is EAlembicImportType::GeometryCache
	AbcFactory->ImportSettings->GeometryCacheSettings.bFlattenTracks = false;
	AbcFactory->ImportSettings->GeometryCacheSettings.bStoreImportedVertexNumbers = false;
	AbcFactory->ImportSettings->GeometryCacheSettings.bApplyConstantTopologyOptimizations = false;
	AbcFactory->ImportSettings->GeometryCacheSettings.bCalculateMotionVectorsDuringImport_DEPRECATED = false;
	AbcFactory->ImportSettings->GeometryCacheSettings.MotionVectors = EAbcGeometryCacheMotionVectorsImport::NoMotionVectors;
	AbcFactory->ImportSettings->GeometryCacheSettings.bOptimizeIndexBuffers = false;
	AbcFactory->ImportSettings->GeometryCacheSettings.CompressedPositionPrecision = 0.01f;
	AbcFactory->ImportSettings->GeometryCacheSettings.CompressedTextureCoordinatesNumberOfBits = 10;
	// Static Mesh - Only useful when ImportType is EAlembicImportType::StatisMesh
	AbcFactory->ImportSettings->StaticMeshSettings.bMergeMeshes = true;
	AbcFactory->ImportSettings->StaticMeshSettings.bPropagateMatrixTransformations = true;
	AbcFactory->ImportSettings->StaticMeshSettings.bGenerateLightmapUVs = true;
	// Compression - Only useful when ImportType is EAlembicImportType::Skeletal
	AbcFactory->ImportSettings->CompressionSettings.bMergeMeshes = false;
	AbcFactory->ImportSettings->CompressionSettings.bBakeMatrixAnimation = true;
	AbcFactory->ImportSettings->CompressionSettings.BaseCalculationType = EBaseCalculationType::PercentageBased;
	AbcFactory->ImportSettings->CompressionSettings.PercentageOfTotalBases = 100.0f;
	AbcFactory->ImportSettings->CompressionSettings.MaxNumberOfBases = 0;
	AbcFactory->ImportSettings->CompressionSettings.MinimumNumberOfVertexInfluencePercentage = 0.0f;

	// Sampling
	AbcFactory->ImportSettings->SamplingSettings.SamplingType = EAlembicSamplingType::PerFrame;
	AbcFactory->ImportSettings->SamplingSettings.FrameSteps = 1;
	AbcFactory->ImportSettings->SamplingSettings.TimeSteps = 0.0f;
	AbcFactory->ImportSettings->SamplingSettings.FrameStart = 0;
	AbcFactory->ImportSettings->SamplingSettings.FrameEnd = 29;
	AbcFactory->ImportSettings->SamplingSettings.bSkipEmpty = false;

	// Normal Calculation
	AbcFactory->ImportSettings->NormalGenerationSettings.bRecomputeNormals = false;
	AbcFactory->ImportSettings->NormalGenerationSettings.HardEdgeAngleThreshold = 0.9f;
	AbcFactory->ImportSettings->NormalGenerationSettings.bForceOneSmoothingGroupPerObject = false;
	AbcFactory->ImportSettings->NormalGenerationSettings.bIgnoreDegenerateTriangles = true;
	AbcFactory->ImportSettings->NormalGenerationSettings.bSkipComputingTangents = false;

	// Materials
	AbcFactory->ImportSettings->MaterialSettings.bCreateMaterials = false;
	AbcFactory->ImportSettings->MaterialSettings.bFindMaterials = false;

	// Conversion
	AbcFactory->ImportSettings->ConversionSettings.Preset = EAbcConversionPreset::Custom;
	AbcFactory->ImportSettings->ConversionSettings.bFlipU = false;
	AbcFactory->ImportSettings->ConversionSettings.bFlipV = true;
	AbcFactory->ImportSettings->ConversionSettings.Scale = FVector(1000.0f);
	AbcFactory->ImportSettings->ConversionSettings.Rotation = FVector::ZeroVector;

	// Create the import task
	UAssetImportTask* Task = UExampleClass::CreateImportTask(SourcePath, DestinationPath, AbcFactory, nullptr, bOutSuccess, OutInfoMsg);
	if (!bOutSuccess) return nullptr;
	
	// Import the asset
	UGeometryCache* RetAsset = Cast<UGeometryCache>(UExampleClass::ProcessImportTask(Task, bOutSuccess, OutInfoMsg));
	if (!bOutSuccess) return nullptr;
	
	// Return the imported asset
	bOutSuccess = true;
	OutInfoMsg = FString::Printf(TEXT("Import Geometry Cache Succeeded - '%s'"), *DestinationPath);
	return RetAsset;
}
PrivateDependencyModuleNames.AddRange
(
  new string[] 
  { 
    // Default Modules
    "Core", 
    "CoreUObject", 
    "Engine", 

    // Required Modules
    "GeometryCache",
    "AlembicLibrary",
  }

  if (Target.bBuildEditor == true)
	{
		PrivateDependencyModuleNames.AddRange(
			new string[]
			{
				"UnrealEd",
				"AssetTools",
				"AlembicImporter",
			}
		);
	}
);