UE4 的一些功能实现
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.
UE4 的一些功能实现
本文对 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
需要在 UBlueprint
的 RootComponent
下的 USkeletalMeshComponent
上添加。
但是我没有找到获取 UBlueprint
的 RootComponent
的方式,只找到 AActor
的。
所以这里的做法是,先创建一个空的 AActor
,在上面修改 Component
,再把 AActor
的 Component
添加到 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);
通过 ContainerPtrToValuePtr
从 FProperty
里读取属性值:
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();
}
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK