前言
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 bool ExportFBXInternal (UWorld* World, UMovieSceneSequence* Sequence, const TArray<FSequencerBindingProxy>& InBindings, UFbxExportOption* OverrideOptions, const FString& InFBXFileName, UMovieSceneSequencePlayer* Player) { UnFbx::FFbxExporter* Exporter = UnFbx::FFbxExporter::GetInstance (); 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) { 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 void FSequencer::ExportFBXInternal (const FString& ExportFilename, TArray<FGuid>& Bindings) { { UnFbx::FFbxExporter* Exporter = UnFbx::FFbxExporter::GetInstance (); 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) { 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 (); } } } NodeName = NodeName.Replace (TEXT ("-" ), TEXT ("_" ) ); 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);
总结
世界大纲显示的是Actor的Label不是类设计的Name这个属性
Sequence的脚本导出工具看上去是基于原本的Sequence的方法重写的
重命名一个不相同的名字可以解决Label和Name不一致的问题