2

UE4 的一些功能实现

 2 years ago
source link: http://www.lymanli.com/2022/03/26/UE4-function-sample/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

UE4 的一些功能实现

发表于 2022-03-26

|

更新于: 2022-03-27

| 分类于 虚幻引擎

| 阅读次数: 9

UE4-function-sample.jpg

本文对 UE4 的一些功能代码做了汇总,可能具备参考价值。

最近开始做 UE4 相关的需求,实现了一个模型自动化导入插件。要求在导入模型后,自动生成角色蓝图和动画蓝图,并把 Actor 放置到场景中自动播放动画。

整个过程要求一键导入,因此,需要使用代码来实现编辑器的一些操作。

这里不得不吐槽下 UE4 的文档,API 不全还难搜,以至于某些功能还得去翻看源码才知道调用了哪个接口。

所以,打算整理下本次涉及到的一些功能实现。由于 UE4 只是初学,本文只做记录不做讲解,若存在纰漏请见谅。

FBX 文件导入

创建 UFbxFactory ,设置 UAssetImportTask ,调用 ImportObject 方法:

UFbxFactory* fbxFactory = NewObject<UFbxFactory>();
fbxFactory->ImportUI->MeshTypeToImport = FBXIT_StaticMesh;
fbxFactory->ImportUI->OriginalImportType = FBXIT_StaticMesh;

fbxFactory->ImportUI->StaticMeshImportData->bCombineMeshes = true;
fbxFactory->ImportUI->StaticMeshImportData->ImportUniformScale = 1.0f;
fbxFactory->ImportUI->bImportMaterials = true;
fbxFactory->ImportUI->bImportTextures = true;
fbxFactory->ImportUI->bAutoComputeLodDistances = true;
fbxFactory->ImportUI->StaticMeshImportData->bAutoGenerateCollision = true;

UAssetImportTask* importTask = NewObject<UAssetImportTask>();
importTask->bAutomated = true;
importTask->bSave = true;

fbxFactory->SetAssetImportTask(importTask);

bool&& canceled = false;
fbxFactory->ImportObject(
UStaticMesh::StaticClass(),
folderPackage,
*FBXName,
EObjectFlags::RF_Transactional | EObjectFlags::RF_Public | EObjectFlags::RF_Standalone,
path,
nullptr,
canceled
);

设置导入完成回调,用于执行下一步操作:

FEditorDelegates::OnAssetPostImport.AddUFunction(this, STATIC_FUNCTION_FNAME(TEXT("UFBXLoader::LoadCallback")));

注意 this 必须继承自 UObject

FString pluginsFolder = FPaths::ConvertRelativePathToFull(FPaths::ProjectPluginsDir());
FString contentFolder = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir());
const FString bpPath = pluginsFolder + "BlendShape/Resources/BP.uasset";
const FString copyPath = contentFolder + "BP.uasset";
IFileManager::Get().Copy(*copyPath, *bpPath);

注意 Copy 里需要传入绝对路径。

通过 LoadObject 加载 UObject 对象:

UBlueprint* sourceBlueprint = LoadObject<UBlueprint>(NULL, *sourceBlueprintPath);
USkeletalMesh* mesh = LoadObject<USkeletalMesh>(NULL, *skeletalPath, NULL, LOAD_None, NULL);
USkeleton* newSkeleton = LoadObject<USkeleton>(NULL, *newSkeletonPath, NULL, LOAD_None, NULL);
UPoseAsset* poseAsset = LoadObject<UPoseAsset>(NULL, *poseAssetPath, NULL, LOAD_None, NULL);
UAnimBlueprint* animBP = LoadObject<UAnimBlueprint>(NULL, *animBPPath, NULL, LOAD_None, NULL);

这里的路径基于 Content Browser 的路径。

通过 LoadClass 加载 UClass 对象:

UClass* animClass = LoadClass<UAnimInstance>(NULL, *animClassPath);

这里的路径同样是基于 Content Browser,但是资源的名称需要写成 name.name_C

动画蓝图重定向

自动生成动画蓝图的前提是有一份原始的动画蓝图,并且事先连接好事件图表,设置好 PoseAsset,然后调用 RetargetAnimations 方法重定向生成一份新的。

USkeleton* oldSkeleton = LoadObject<USkeleton>(NULL, *oldSkeletonPath, NULL, LOAD_None, NULL);
USkeleton* newSkeleton = LoadObject<USkeleton>(NULL, *newSkeletonPath, NULL, LOAD_None, NULL);

TArray<FAssetData> assetsToRetarget;
assetsToRetarget.Add(FAssetData((UObject*)poseAsset));
assetsToRetarget.Add(FAssetData((UObject*)animBP));

bool bRetargetReferredAssets = true;
bool bConvertSpace = true;

EditorAnimUtils::FNameDuplicationRule nameRule;
nameRule.Prefix = meshName + "_";
nameRule.FolderPath = FString(GameFolder) + "/" + newSkeletonFolder;

EditorAnimUtils::RetargetAnimations(oldSkeleton, newSkeleton, assetsToRetarget, bRetargetReferredAssets, &nameRule, bConvertSpace);

角色蓝图生成

复制一个蓝图类不能简单地拷贝文件,正确的操作在编辑器里叫 Duplicate ,这样可以指定新蓝图类的类名。

UEditorAssetLibrary::DuplicateLoadedAsset(sourceBlueprint, blueprintPath);

路径同样是基于 Content Browser ,最终新类名由 blueprintPath 的文件名指定。

角色蓝图设置 SkeletalMesh

SkeletalMesh 需要在 UBlueprintRootComponent 下的 USkeletalMeshComponent 上添加。

但是我没有找到获取 UBlueprintRootComponent 的方式,只找到 AActor 的。

所以这里的做法是,先创建一个空的 AActor,在上面修改 Component,再把 AActorComponent 添加到 UBlueprint 上。

FPreviewScene* scene = new FPreviewScene();

FActorSpawnParameters spawnparam;
spawnparam.bAllowDuringConstructionScript = true;
AActor* tempActor = scene->GetWorld()->SpawnActor<AActor>(AActor::StaticClass(), FTransform::Identity);

USceneComponent* rootComponent = NewObject<USceneComponent>(tempActor, FName(ActorRootComponentName));
tempActor->AddOwnedComponent(rootComponent);
tempActor->AddInstanceComponent(rootComponent);
rootComponent->RegisterComponent();
tempActor->SetRootComponent(rootComponent);

FString skeletalPath = FString(GameFolder) + "/" + toFolder + "/" + meshName;
USkeletalMesh* mesh = LoadObject<USkeletalMesh>(NULL, *skeletalPath, NULL, LOAD_None, NULL);
USkeletalMeshComponent* meshComponent = NewObject<USkeletalMeshComponent>(tempActor, FName(*UKismetSystemLibrary::GetObjectName(mesh)));
tempActor->AddOwnedComponent(meshComponent);
tempActor->AddInstanceComponent(meshComponent);
meshComponent->RegisterComponent();
meshComponent->SetSkeletalMesh(mesh);

meshComponent->AttachToComponent(tempActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);

FKismetEditorUtilities::AddComponentsToBlueprint(blueprint, tempActor->GetComponents().Array());
delete(scene);

角色蓝图关联动画蓝图

读取 UClass 后,通过 SetAnimClass 指定动画蓝图类:

FString animClassName = meshName + "_" + SourceAnimBPName;
FString animClassPath = "AnimBlueprint'" + FString(GameFolder) + "/" + toFolder + "/" + animClassName + "." + animClassName + "_C'";
UClass* animClass = LoadClass<UAnimInstance>(NULL, *animClassPath);
meshComponent->SetAnimClass(animClass);

Actor 放置到场景

通过 SpawnActor ,可以在场景中添加一个 Actor 对象:

UClass* bpClass = blueprint->StaticClass();
UWorld* world = GEditor->GetEditorWorldContext().World();
AActor *actor = world->SpawnActor<AActor>(blueprint->GeneratedClass, FVector(-250, 0, 100), FRotator(0, 90, 0));

为了后续方便获取到这个 Actor,给 Actor 加上 Tag :

actor->Tags.Add(ActorTag);

修改 Actor 属性

先通过 Tag 获取到场景中的 Actor 对象:

TArray<AActor*> actors;
UWorld* world = GWorld->GetWorld();
UGameplayStatics::GetAllActorsWithTag(world, ActorTag, actors);
AActor* actor = actors[0];

通过 FindFieldChecked 获取 FProperty

FArrayProperty* arrayProperty = FindFieldChecked<FArrayProperty>(actor->GetClass(), *name);

通过 ContainerPtrToValuePtrFProperty 里读取属性值:

TArray<FString> arrayOfStrings = *arrayProperty->ContainerPtrToValuePtr<TArray<FString>>(actor);

通过 SetPropertyValue_InContainer 修改 Actor 的属性值:

strProperty->SetPropertyValue_InContainer(actor, path);

Runtime 切换摄像机

我们希望在运行的时候,切换到特定的视角。这里的做法是动态添加 ACameraActor,调整位置,并在 Runtime 时把视角切换到新添加的 ACameraActor 上。

由于是在 Runtime 执行切换,所以要先继承 ACameraActor,在子类的 BeginPlay 方法里实现切换逻辑:

void AFaceCameraActor::BeginPlay()
{
GetWorld()->RegisterAutoActivateCamera(this, 0);
APlayerController* playerController = UGameplayStatics::GetPlayerController(this, 0);
playerController->SetViewTarget(this);

Super::BeginPlay();
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK