跳转到主要内容

汇总 | UE4 的一些功能实现

demi 提交于

<a href="http://www.lymanli.com/2022/03/26/UE4-function-sample/"><font color="#9a9a9a">来源:Lyman's Blog</font></a>
<hr>

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

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

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

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

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

<font style="line-height: 40px;" color="#c200ff"><strong>FBX 文件导入</strong></font>

创建 UFbxFactory ,设置 UAssetImportTask ,调用 ImportObject 方法:
<pre>UFbxFactory* fbxFactory = NewObject&lt;UFbxFactory&gt;();
fbxFactory-&gt;ImportUI-&gt;MeshTypeToImport = FBXIT_StaticMesh;
fbxFactory-&gt;ImportUI-&gt;OriginalImportType = FBXIT_StaticMesh;

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

UAssetImportTask* importTask = NewObject&lt;UAssetImportTask&gt;();
importTask-&gt;bAutomated = true;
importTask-&gt;bSave = true;

fbxFactory-&gt;SetAssetImportTask(importTask);

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

</pre>

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

<pre>FEditorDelegates::OnAssetPostImport.AddUFunction(this, STATIC_FUNCTION_FNAME(TEXT("UFBXLoader::LoadCallback")));</pre>

注意 this 必须继承自 UObject 。

<font style="line-height: 40px;" color="#c200ff"><strong>文件拷贝</strong></font>

<pre>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);</pre>

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

<font style="line-height: 40px;" color="#c200ff"><strong>资源加载</strong></font>

通过 LoadObject 加载 UObject 对象:

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

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

通过 LoadClass 加载 UClass 对象:
<pre>UClass* animClass = LoadClass&lt;UAnimInstance&gt;(NULL, *animClassPath);</pre>

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

<font style="line-height: 40px;" color="#c200ff"><strong>动画蓝图重定向</strong></font>

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

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

TArray&lt;FAssetData&gt; 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);
</pre>

<font style="line-height: 40px;" color="#c200ff"><strong>角色蓝图生成</strong></font>

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

<pre>UEditorAssetLibrary::DuplicateLoadedAsset(sourceBlueprint, blueprintPath);</pre>

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

<font style="line-height: 40px;" color="#c200ff"><strong>角色蓝图设置 SkeletalMesh</strong></font>

SkeletalMesh 需要在 UBlueprint 的 RootComponent 下的 USkeletalMeshComponent 上添加。

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

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

<pre>FPreviewScene* scene = new FPreviewScene();

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

USceneComponent* rootComponent = NewObject&lt;USceneComponent&gt;(tempActor, FName(ActorRootComponentName));
tempActor-&gt;AddOwnedComponent(rootComponent);
tempActor-&gt;AddInstanceComponent(rootComponent);
rootComponent-&gt;RegisterComponent();
tempActor-&gt;SetRootComponent(rootComponent);

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

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

FKismetEditorUtilities::AddComponentsToBlueprint(blueprint, tempActor-&gt;GetComponents().Array());
delete(scene);
</pre>

<font style="line-height: 40px;" color="#c200ff"><strong>角色蓝图关联动画蓝图</strong></font>

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

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

<font style="line-height: 40px;" color="#c200ff"><strong>Actor 放置到场景</strong></font>

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

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

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

<pre>actor-&gt;Tags.Add(ActorTag);</pre>

<font style="line-height: 40px;" color="#c200ff"><strong>修改 Actor 属性</strong></font>

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

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

通过 FindFieldChecked 获取 FProperty:

<pre>FArrayProperty* arrayProperty = FindFieldChecked&lt;FArrayProperty&gt;(actor-&gt;GetClass(), *name);</pre>

通过 ContainerPtrToValuePtr 从 FProperty 里读取属性值:

<pre>TArray&lt;FString&gt; arrayOfStrings = *arrayProperty-&gt;ContainerPtrToValuePtr&lt;TArray&lt;FString&gt;&gt;(actor);
</pre>

通过 SetPropertyValue_InContainer 修改 Actor 的属性值:

<pre>strProperty-&gt;SetPropertyValue_InContainer(actor, path);</pre>

<font style="line-height: 40px;" color="#c200ff"><strong>Runtime 切换摄像机</strong></font>

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

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

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

Super::BeginPlay();
}</pre>

<strong>参考</strong>
<a href="https://blog.csdn.net/KKsuser/article/details/112343030"><font color="#9a9a9a">UE4 编辑器资源操作篇</font></a>
<a href="https://neil3d.github.io/unreal/bp_in_depth.html"><font color="#9a9a9a">深入 Unreal 蓝图开发:理解蓝图技术架构</font></a>
<a href="https://www.jianshu.com/p/d67cb85e762b"><font color="#9a9a9a">Unreal 动画实时重定向的源码分析</font></a>
<a href="https://zhuanlan.zhihu.com/p/368360247"><font color="#9a9a9a">UE4 C++ 访问蓝图里的变量</font></a>
<a href="http://supervj.top/2021/11/08/%E9%87%8D%E5%AE%9A%E5%90%91%E6%BA%90%E7%A… color="#9a9a9a">离线重定向源码分析</font></a>
<a href="https://www.cnblogs.com/kekec/p/14274356.html"><font color="#9a9a9a">UE4 类型系统、语言修饰符和元数据</font></a>
<a href="https://www.cnblogs.com/lyj0704/p/11199843.html"><font color="#9a9a9a">UE4 使用蓝图或 C++ 切换摄像机视角</font></a>
<hr>
<font color="#9a9a9a">声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有,如涉及侵权,请联系小编邮箱 demi@eetrend.com 进行处理。</font>