本文对 UE4 的一些功能代码做了汇总,可能具备参考价值。
最近开始做 UE4 相关的需求,实现了一个模型自动化导入插件。要求在导入模型后,自动生成角色蓝图和动画蓝图,并把 Actor 放置到场景中自动播放动画。
整个过程要求一键导入,因此,需要使用代码来实现编辑器的一些操作。
这里不得不吐槽下 UE4 的文档,API 不全还难搜,以至于某些功能还得去翻看源码才知道调用了哪个接口。
所以,打算整理下本次涉及到的一些功能实现。由于 UE4 只是初学,本文只做记录不做讲解,若存在纰漏请见谅。
FBX 文件导入
创建 UFbxFactory
,设置 UAssetImportTask
,调用 ImportObject
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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 );
|
设置导入完成回调,用于执行下一步操作:
1
| FEditorDelegates::OnAssetPostImport.AddUFunction(this, STATIC_FUNCTION_FNAME(TEXT("UFBXLoader::LoadCallback")));
|
注意 this
必须继承自 UObject
。
文件拷贝
1 2 3 4 5
| 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
对象:
1 2 3 4 5
| 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
对象:
1
| UClass* animClass = LoadClass<UAnimInstance>(NULL, *animClassPath);
|
这里的路径同样是基于 Content Browser,但是资源的名称需要写成 name.name_C
。
动画蓝图重定向
自动生成动画蓝图的前提是有一份原始的动画蓝图,并且事先连接好事件图表,设置好 PoseAsset,然后调用 RetargetAnimations
方法重定向生成一份新的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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
,这样可以指定新蓝图类的类名。
1
| UEditorAssetLibrary::DuplicateLoadedAsset(sourceBlueprint, blueprintPath);
|
路径同样是基于 Content Browser ,最终新类名由 blueprintPath
的文件名指定。
角色蓝图设置 SkeletalMesh
SkeletalMesh
需要在 UBlueprint
的 RootComponent
下的 USkeletalMeshComponent
上添加。
但是我没有找到获取 UBlueprint
的 RootComponent
的方式,只找到 AActor
的。
所以这里的做法是,先创建一个空的 AActor
,在上面修改 Component
,再把 AActor
的 Component
添加到 UBlueprint
上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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
指定动画蓝图类:
1 2 3 4
| 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 对象:
1 2 3
| 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 :
1
| actor->Tags.Add(ActorTag);
|
修改 Actor 属性
先通过 Tag 获取到场景中的 Actor 对象:
1 2 3 4
| TArray<AActor*> actors; UWorld* world = GWorld->GetWorld(); UGameplayStatics::GetAllActorsWithTag(world, ActorTag, actors); AActor* actor = actors[0];
|
通过 FindFieldChecked
获取 FProperty
:
1
| FArrayProperty* arrayProperty = FindFieldChecked<FArrayProperty>(actor->GetClass(), *name);
|
通过 ContainerPtrToValuePtr
从 FProperty
里读取属性值:
1
| TArray<FString> arrayOfStrings = *arrayProperty->ContainerPtrToValuePtr<TArray<FString>>(actor);
|
通过 SetPropertyValue_InContainer
修改 Actor 的属性值:
1
| strProperty->SetPropertyValue_InContainer(actor, path);
|
Runtime 切换摄像机
我们希望在运行的时候,切换到特定的视角。这里的做法是动态添加 ACameraActor
,调整位置,并在 Runtime 时把视角切换到新添加的 ACameraActor
上。
由于是在 Runtime 执行切换,所以要先继承 ACameraActor
,在子类的 BeginPlay
方法里实现切换逻辑:
1 2 3 4 5 6 7 8
| void AFaceCameraActor::BeginPlay() { GetWorld()->RegisterAutoActivateCamera(this, 0); APlayerController* playerController = UGameplayStatics::GetPlayerController(this, 0); playerController->SetViewTarget(this);
Super::BeginPlay(); }
|
参考