UE4:使用Python导出LevelSequence轨道的Actor名字获取问题

前言

UE4 提供了不少Python接口来帮助TA快速执行一些程序化的脚本,提高一些工作的效率

这天我们TA大佬跟我反映,他发现用Python脚本导出bake过的LevelSequence的tracks到fbx文件后,有些Actor的名字多了后缀,但是在编辑器下使用UI菜单的导出按钮导出到fbx文件的时候这几个Actor名字并没有多后缀,和Sequence编辑器里的一模一样,让我帮忙看看怎么回事

具体问题

这次没有那么费解了,因为老早就知道了Level的Outline里面显示的Name不一定和GetName一致,而且WorldOutline里面的标题明明白白的写了,这个是Label

而GetName获取的这个FName在这个Actor被创建的时候会检查唯一性,不唯一则加后缀

在使用SpawnActor的时候,如果传进去的Setting的Name不为空,且多次Spawn的时候Name相同,那一定会直接fetal error

所以问题其实很明显,在没有看具体代码之前,我合理怀疑点击UI的地方拿的是Label,脚本的逻辑估计因为各种原因不方便直接调用原有的地方的逻辑,重载的方法调用的是GetName。当然这是初步猜测,还是要具体看一下代码

相关代码

首先去看了一下UI这边是怎么弄的,一层层往里看最后找到这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
FbxNode* FFbxExporter::ExportActor(AActor* Actor, bool bExportComponents, INodeNameAdapter& NodeNameAdapter, bool bSaveAnimSeq )
{
//省略大量代码
FbxNode* ActorNode = FindActor(Actor, &NodeNameAdapter);
if (ActorNode == NULL)
{
// 拿名字的在这里
FString FbxNodeName = NodeNameAdapter.GetActorNodeName(Actor);

//省略大量代码
}
//省略大量代码
}

按一下F12去看看这个GetActorNodeName,发现是个虚函数,类是INodeNameAdapter

我接着去看了一下脚本入口,发现最后依然是到了ExportActor,同样是INodeNameAdapter的GetActorNodeName方法,那么基本可以确定是子类重写了父类函数

倒回去看从脚本调进来的函数传递的参数的数据类型(好几层,烦= =):

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// SequencerTools.cpp
bool ExportFBXInternal(UWorld* World, UMovieSceneSequence* Sequence, const TArray<FSequencerBindingProxy>& InBindings, UFbxExportOption* OverrideOptions, const FString& InFBXFileName, UMovieSceneSequencePlayer* Player)
{
UnFbx::FFbxExporter* Exporter = UnFbx::FFbxExporter::GetInstance();
//Show the fbx export dialog options
Exporter->SetExportOptionsOverride(OverrideOptions);

UMovieScene* MovieScene = Sequence->GetMovieScene();
TArray<FGuid> Bindings;
for (const FSequencerBindingProxy& Proxy : InBindings)
{
if (Proxy.Sequence == Sequence)
{
Bindings.Add(Proxy.BindingID);
}
}

// 重点在这里
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
INodeNameAdapter NodeNameAdapter;
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////

Player->State.AssignSequence(MovieSceneSequenceID::Root, *Sequence, *Player);
FMovieSceneSequenceIDRef Template = MovieSceneSequenceID::Root;
bool bDidExport = false;
FMovieSceneSequenceTransform RootToLocalTransform;

{
FSpawnableRestoreState SpawnableRestoreState(MovieScene);

if (SpawnableRestoreState.bWasChanged)
{
// Evaluate at the beginning of the subscene time to ensure that spawnables are created before export
Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(UE::MovieScene::DiscreteInclusiveLower(MovieScene->GetPlaybackRange()).Value, EUpdatePositionMethod::Play));
}

bDidExport = MovieSceneToolHelpers::ExportFBX(World, MovieScene, Player, Bindings, NodeNameAdapter, Template, InFBXFileName, RootToLocalTransform);
}

Player->Stop();
Exporter->SetExportOptionsOverride(nullptr);

return bDidExport;
}

好吧这里直接创建的是基类,拿的是GetName

另一个地方也看一下

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Sequencer.cpp
void FSequencer::ExportFBXInternal(const FString& ExportFilename, TArray<FGuid>& Bindings)
{
{
UnFbx::FFbxExporter* Exporter = UnFbx::FFbxExporter::GetInstance();
//Show the fbx export dialog options
bool ExportCancel = false;
bool ExportAll = false;
Exporter->FillExportOptions(false, true, ExportFilename, ExportCancel, ExportAll);
if (!ExportCancel)
{
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
UWorld* World = Cast<UWorld>(GetPlaybackContext());
FMovieSceneSequenceIDRef Template = GetFocusedTemplateID();

// 重点在这里
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
UnFbx::FFbxExporter::FLevelSequenceNodeNameAdapter NodeNameAdapter(MovieScene, this, Template);
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////

{
FSpawnableRestoreState SpawnableRestoreState(MovieScene);
if (SpawnableRestoreState.bWasChanged)
{
// Evaluate at the beginning of the subscene time to ensure that spawnables are created before export
SetLocalTimeDirectly(UE::MovieScene::DiscreteInclusiveLower(GetTimeBounds()));
}

if (MovieSceneToolHelpers::ExportFBX(World, MovieScene, this, Bindings, NodeNameAdapter, Template, ExportFilename, RootToLocalTransform))
{
FNotificationInfo Info(NSLOCTEXT("Sequencer", "ExportFBXSucceeded", "FBX Export Succeeded."));
Info.Hyperlink = FSimpleDelegate::CreateStatic([](FString InFilename) { FPlatformProcess::ExploreFolder(*InFilename); }, ExportFilename);
Info.HyperlinkText = FText::FromString(ExportFilename);
Info.ExpireDuration = 5.0f;
FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Success);
}
else
{
FNotificationInfo Info(NSLOCTEXT("Sequencer", "ExportFBXFailed", "FBX Export Failed."));
Info.ExpireDuration = 5.0f;
FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Fail);
}
}

ForceEvaluate();
}
}
}

看一下这个子类的方法的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
FString FFbxExporter::FLevelSequenceNodeNameAdapter::GetActorNodeName(const AActor* Actor)
{
FString NodeName = Actor->GetName();

for ( const FMovieSceneBinding& MovieSceneBinding : MovieScene->GetBindings() )
{
for ( TWeakObjectPtr<UObject> RuntimeObject : MovieScenePlayer->FindBoundObjects(MovieSceneBinding.GetObjectGuid(), SequenceID) )
{
if (RuntimeObject.Get() == Actor)
{
NodeName = MovieScene->GetObjectDisplayName(MovieSceneBinding.GetObjectGuid()).ToString();
}
}
}

// Maya does not support dashes. Change all dashes to underscores
NodeName = NodeName.Replace(TEXT("-"), TEXT("_") );

// Maya does not support spaces. Change all spaces to underscores
NodeName = NodeName.Replace(TEXT(" "), TEXT("_") );

return NodeName;
}

好啊好啊,DisplayName……

解决方案

这个看具体需求,我们TA大佬希望是和LevelSequence的编辑器的名字一致,这个是Label

那么就是要把脚本的调用里面的NodeNameAdapter改成FLevelSequenceNodeNameAdapter类型

这个类型的话需要传递参数来完成构造

看一下构造函数

1
FLevelSequenceNodeNameAdapter( UMovieScene* InMovieScene, IMovieScenePlayer* InMovieScenePlayer, FMovieSceneSequenceIDRef InSequenceID);

非常不错的是,在这个函数里我们都能拿到这些

改完之后是这样:

1
2
- INodeNameAdapter NodeNameAdapter;
+ UnFbx::FFbxExporter::FLevelSequenceNodeNameAdapter NodeNameAdapter( Sequence->GetMovieScene(),Player,MovieSceneSequenceID::Root);

总结

  1. 世界大纲显示的是Actor的Label不是类设计的Name这个属性
  2. Sequence的脚本导出工具看上去是基于原本的Sequence的方法重写的
  3. 重命名一个不相同的名字可以解决Label和Name不一致的问题

UE4:使用Python导出LevelSequence轨道的Actor名字获取问题
http://muchenhen.com/posts/52092/
作者
木尘痕
发布于
2022年7月15日
许可协议