Working with Skeletal Mesh in Unreal Engine

What is it Skeletal Mesh, what properties does it have and how to work with it in the Unreal Engine?

Skeletal meshes

Skeletal Meshes are made up of two parts: A set of polygons composed to make up the surface of the Skeletal Mesh, and a hierarchical set of interconnected bones which can be used to animate the vertices of the polygons.

They are often used to represent characters or other animating objects. The 3D models, rigging and animations are created in an external modeling and animation application (3DSMax, Maya, Softimage, etc), and are then imported into Unreal Engine and saved into packages by using Unreal Editor's Content Browser.

Import Skeletal Mesh

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

#include "Kismet/BlueprintFunctionLibrary.h" // Engine
UCLASS()
class UExampleClass : public UBlueprintFunctionLibrary
{
 GENERATED_BODY()
public:
 /**
  * Editor Only - Will not work in packaged build.
  *
  * Create and process a SkeletalMesh import task.
  *
  * @param SourcePath   The path of the source file: "C:/Temp/MySkeletalMesh.fbx"
  * @param DestinationPath The path of the imported asset: "/Game/Folder/MySkeletalMesh"
  * @param bOutSuccess  If the action was a success or not
  * @param OutInfoMsg   More information about the action's result
  *
  * @return The imported SkeletalMesh
  */
 UFUNCTION(BlueprintCallable, Category = " ... ")
 static class USkeletalMesh* ImportSkeletalMesh(FString SourcePath, FString DestinationPath, bool& bOutSuccess, FString& OutInfoMsg);
};
#include "Factories/FbxImportUI.h" // UnrealEd (Editor Only)
#include "Factories/FbxSkeletalMeshImportData.h" // UnrealEd (Editor Only)

USkeletalMesh* UExampleCLass::ImportSkeletalMesh(FString SourcePath, FString DestinationPath, bool& bOutSuccess, FString& OutInfoMsg)
{
 UFbxImportUI* Options = NewObject<UFbxImportUI>();

 // Required options to specify the skeletal mesh import
 Options->bAutomatedImportShouldDetectType = false;
 Options->MeshTypeToImport = EFBXImportType::FBXIT_SkeletalMesh;
 Options->bImportMesh = true;
 Options->bImportAsSkeletal = true;
 Options->bCreatePhysicsAsset = true;

 // More options
 Options->bImportAnimations = false;
 Options->bImportTextures = true;
 Options->bImportMaterials = true;
 Options->bResetToFbxOnMaterialConflict = true;
 Options->LodNumber = 0;

 // UFbxAssetImportData
 Options->SkeletalMeshImportData->ImportTranslation = FVector(0.0f);
 Options->SkeletalMeshImportData->ImportRotation = FRotator(0.0f);
 Options->SkeletalMeshImportData->ImportUniformScale = 1.0f;
 Options->SkeletalMeshImportData->bConvertScene = true;
 Options->SkeletalMeshImportData->bForceFrontXAxis = true;
 Options->SkeletalMeshImportData->bConvertSceneUnit = true;

 // UFbxMeshImportData
 Options->SkeletalMeshImportData->bTransformVertexToAbsolute = false;
 Options->SkeletalMeshImportData->bBakePivotInVertex = false;
 Options->SkeletalMeshImportData->bImportMeshLODs = true;
 Options->SkeletalMeshImportData->NormalImportMethod = EFBXNormalImportMethod::FBXNIM_ComputeNormals;
 Options->SkeletalMeshImportData->NormalGenerationMethod = EFBXNormalGenerationMethod::BuiltIn;
 Options->SkeletalMeshImportData->bComputeWeightedNormals = true;
 Options->SkeletalMeshImportData->bReorderMaterialToFbxOrder = false;

 // UFbxSkeletalMeshImportData
 Options->SkeletalMeshImportData->ImportContentType = EFBXImportContentType::FBXICT_All;
 Options->SkeletalMeshImportData->VertexColorImportOption = EVertexColorImportOption::Replace;
 Options->SkeletalMeshImportData->bUpdateSkeletonReferencePose = true;
 Options->SkeletalMeshImportData->bUseT0AsRefPose = true;
 Options->SkeletalMeshImportData->bPreserveSmoothingGroups = true;
 Options->SkeletalMeshImportData->bImportMeshesInBoneHierarchy = true;
 Options->SkeletalMeshImportData->bImportMorphTargets = true;
 Options->SkeletalMeshImportData->ThresholdPosition = 0.0f;
 Options->SkeletalMeshImportData->ThresholdTangentNormal = 0.0f;
 Options->SkeletalMeshImportData->ThresholdUV = 0.0f;
 Options->SkeletalMeshImportData->MorphThresholdPosition = 0.0f;

 // Create the import task
 UAssetImportTask* Task = UExampleClass::CreateImportTask(SourcePath, DestinationPath, nullptr, Options, bOutSuccess, OutInfoMsg);
 if (!bOutSuccess) return nullptr;
	
 // Import the asset
 USkeletalMesh* RetAsset = Cast<USkeletalMesh>(UExampleClass::ProcessImportTask(Task, bOutSuccess, OutInfoMsg));
 if (!bOutSuccess) return nullptr;
	
 // Return the imported asset
 bOutSuccess = true;
 OutInfoMsg = FString::Printf(TEXT("Import Skeletal Mesh Succeeded - '%s'"), *DestinationPath);
 return RetAsset;
}

Within the UExampleClass::ImportAsset, we link at 2 help custom functions - CreateImportTask and ProcessImportTask that can be defined within the same UExampleClass.

CreateImportTask function has numerous parameters while the 4th is Options. We can use this parameter to set .fbx file import pipeline, see the example below.

Importing assets using UBlueprintFunctionLibrary class that works in editor mode only. While we use it in the editor, we need to place an additional module into project's .Build.cs file. However, as it's editor only, those modules must be removed from the .Build.cs file before packaging the project (e.g. building the Windows app).

PrivateDependencyModuleNames.AddRange
(
    new string[]
   { 
        // Default Modules
        "Core", 
        "CoreUObject", 
        "Engine", 
	// New Modules - Editor Only
	"UnrealEd",
   }
);

Import animation for Skeletal Mesh

/**
 * Editor Only - Will not work in packaged build.
 *
 * Create and process a Animation import task.
 *
 * @param SourcePath The path of the source file: "C:/Temp/MyAnimation.fbx"
 * @param DestinationPath  The path of the imported asset: "/Game/Folder/MyAnimation"
 * @param SkeletonPath  The path of the the skeleton to use: "/Game/Folder/MySkeleton"
 * @param bOutSuccess   If the action was a success or not
 * @param OutInfoMsg More information about the action's result
 *
 * @return The imported Animation
 */
UFUNCTION(BlueprintCallable, Category = " ... ")
static class UAnimSequence* ImportAnimation(FString SourcePath, FString DestinationPath, FString SkeletonPath, bool& bOutSuccess, FString& OutInfoMsg);
#include "Factories/FbxImportUI.h" // UnrealEd (Editor Only)
#include "Factories/FbxAnimSequenceImportData.h" // UnrealEd (Editor Only)

UAnimSequence* UExampleClass::ImportAnimation(FString SourcePath, FString DestinationPath, FString SkeletonPath, bool& bOutSuccess, FString& OutInfoMsg)
{
 UFbxImportUI* Options = NewObject<UFbxImportUI>();

 // Required options to specify that we're importing an animation
 Options->bAutomatedImportShouldDetectType = false;
 Options->MeshTypeToImport =  EFBXImportType::FBXIT_Animation;
 Options->bImportAnimations = true;
 Options->Skeleton = Cast<USkeleton>(StaticLoadObject(UObject::StaticClass(), nullptr, *SkeletonPath));

 if (Options->Skeleton == nullptr)
 {
  bOutSuccess = false;
  OutInfoMsg = FString::Printf(TEXT("Import Animation Failed - Skeleton is invalid: '%s'"), *SkeletonPath);
  return nullptr;
 }

 // Animation only, don't import anything else
 Options->bImportMesh = false;
 Options->bImportAsSkeletal = false;
 Options->bCreatePhysicsAsset = false;
 Options->bImportTextures = false;
 Options->bImportMaterials = false;
 Options->bResetToFbxOnMaterialConflict = true;
 Options->LodNumber = 0;
 Options->OverrideAnimationName = "";

 // UFbxAssetImportData
 Options->AnimSequenceImportData->ImportTranslation = FVector(0.0f);
 Options->AnimSequenceImportData->ImportRotation = FRotator(0.0f);
 Options->AnimSequenceImportData->ImportUniformScale = 1.0f;
 Options->AnimSequenceImportData->bConvertScene = true;
 Options->AnimSequenceImportData->bForceFrontXAxis = true;
 Options->AnimSequenceImportData->bConvertSceneUnit = true;

 // UFbxAnimSequenceImportData
 Options->AnimSequenceImportData->bImportMeshesInBoneHierarchy = false;
 Options->AnimSequenceImportData->AnimationLength = EFBXAnimationLengthImportType::FBXALIT_ExportedTime;
 Options->AnimSequenceImportData->FrameImportRange = FInt32Interval();
 Options->AnimSequenceImportData->bUseDefaultSampleRate = false;
 Options->AnimSequenceImportData->CustomSampleRate = 0;
 Options->AnimSequenceImportData->bSnapToClosestFrameBoundary = false;
 Options->AnimSequenceImportData->bImportCustomAttribute = false;
 Options->AnimSequenceImportData->bDeleteExistingCustomAttributeCurves = false;
 Options->AnimSequenceImportData->bDeleteExistingNonCurveCustomAttributes = false;
 Options->AnimSequenceImportData->bSetMaterialDriveParameterOnCustomAttribute = false;
 Options->AnimSequenceImportData->bRemoveRedundantKeys = false;
 Options->AnimSequenceImportData->bDeleteExistingMorphTargetCurves = false;
 Options->AnimSequenceImportData->bDoNotImportCurveWithZero = false;
 Options->AnimSequenceImportData->bPreserveLocalTransform = false;

 // Create the import task
 UAssetImportTask* Task = CreateImportTask(SourcePath, DestinationPath, nullptr, Options, bOutSuccess, OutInfoMessage);
 if (!bOutSuccess) return nullptr;
	
 // Import the asset
 UAnimSequence* RetAsset = Cast<UAnimSequence>(ProcessImportTask(Task, bOutSuccess, OutInfoMessage));
 if (!bOutSuccess) return nullptr;
	
 // Return the imported asset
 bOutSuccess = true;
 OutInfoMsg = FString::Printf(TEXT("Import Animation Succeeded - '%s'"), *DestinationPath);
 return RetAsset;
}

Assign material for Skeletal Mesh