--- title: Sequence Runtime Binding date: 2024-05-16 17:55:58 excerpt: tags: rating: ⭐ --- # 前言 ***MovieSceneTextTrack ***引擎插件可以作为参考。 参考: 1. [sequencer mute tracks at runtime from c](https://forums.unrealengine.com/t/sequencer-mute-tracks-at-runtime-from-c/476278/3) 可行思路: 1. 给导播创建一个快捷键(Hotkey),之后在触发根据Tag切换对应角色动画蓝图中的逻辑。 2. 使用Sequence的EventTrack设置若干事件。 3. 模改UMovieSceneSkeletalAnimationTrack,实现一个可以自动根据Tag捕获对应角色并且播发动画的功能。 # 方案一 - UMovieSceneTrack::SetEvalDisabled(bool) - ULevelSequence::MarkAsChanged() - MovieSceneCompiledDataManager::GetPrecompiledData()->Compile(ULevelSequence*) ```c++ // You first have to set the unwanted track as disabled via void YourDisableTrackFunction(UMovieSceneTrack* MovieSceneTrack, bool Value) { if (MovieSceneTrack) { MovieSceneTrack->SetEvalDisabled(Value); } } // Then notify the sequencer that the tracks have been modified using MarkAsChanged and UMovieSceneCompiledDataManager::GetPrecompiledData()->Compile. void YourMarkAsChangedFunction(ULevelSequence* Sequence) { if (Sequence) { Sequence->MarkAsChanged(); if (UMovieSceneCompiledDataManager::GetPrecompiledData()) { UMovieSceneCompiledDataManager::GetPrecompiledData()->Compile(Sequence); } } } ``` # 方案二 UMovieSceneSequenceExtensions methods are not exported. They don’t have the PLUGIN_API macro in the class, so you can’t link to them. Someone on discord said you can copy the plugin into your own project and add your own API macro and rebuild, but seems like a bit of work. ```c++ #include "LevelSequencePlayer.h" #include "MovieSceneSequencePlaybackSettings.h" #include "DefaultLevelSequenceInstanceData.h" #include "LevelSequenceActor.h" void UMyAnimInstance::StartSequence_Implementation() { FMovieSceneSequencePlaybackSettings Settings; ALevelSequenceActor *SequenceActor = nullptr; ULevelSequencePlayer* player = ULevelSequencePlayer::CreateLevelSequencePlayer(this, this->MySequence, Settings, SequenceActor); check(player); check(SequenceActor); SequenceActor->bOverrideInstanceData = 1; UDefaultLevelSequenceInstanceData* InstanceData = Cast(SequenceActor->DefaultInstanceData.Get()); InstanceData->TransformOrigin = this->GetOwningActor()->GetActorTransform(); UMovieScene* MovieScene = this->MySequence->GetMovieScene(); check(MovieScene); const FMovieSceneBinding* Binding = Algo::FindBy(MovieScene->GetBindings(), FString("SequencerActor"), &FMovieSceneBinding::GetName); check(Binding); EMovieSceneObjectBindingSpace Space = EMovieSceneObjectBindingSpace::Root; FMovieSceneObjectBindingID BindingID = UE::MovieScene::FRelativeObjectBindingID(Binding->GetObjectGuid()); // Not sure about this if (Space == EMovieSceneObjectBindingSpace::Root) { BindingID.ReinterpretAsFixed(); } SequenceActor->AddBinding(BindingID, this->GetOwningActor()); player->Play(); // Save actor and player in properties this->SequencerActor = SequenceActor; this->SequencerPlayer = player; } ``` # 自定义Track - Runtime修改绑定 - UE4:扩展Sequencer的BakeTransform有关Camera的参数:https://zhuanlan.zhihu.com/p/554484287 - **LevelSequence对象绑定分析**:https://zhuanlan.zhihu.com/p/548155341 - 自定义Track - https://zhuanlan.zhihu.com/p/396353973 - https://zhuanlan.zhihu.com/p/414179358 - https://zhuanlan.zhihu.com/p/413151867 - LevelSequence分析 - https://zhuanlan.zhihu.com/p/157892605 ## 继承关系 UMovieSceneSignedObject -> UObject。一个UMovieScene由若干个MasterTrack(UMovieSceneTrack)组成,UMovieScene相当于是UMovieSceneTracks的容器,那么UMovieSceneTrack又由UMovieSceneSections组成,UMovieSceneSection就是一个Track中间的某一段。 ### UMovieSceneSignedObject 子类有UMovieScene、UMovieSceneSequence、UMovieSceneTrack、UMovieSceneSection。 定义: - FGuid Signature:用于确定Object身份,以便于绑定。 - FOnSignatureChanged OnSignatureChangedEvent:ChangedEvent。 ### UMovieSceneSection #### UMovieSceneSkeletalAnimationSection ### UMovieSceneTrack #### UMovieSceneSkeletalAnimationTrack - `TArray> AnimationSections`:存储所有动画Section。 - FMovieSceneSkeletalAnimRootMotionTrackParams RootMotionParams:RootMotion控制。 编辑器代码:**FSkeletalAnimationTrackEditor** ## Sequence绑定机制笔记 1. _SpawnableObject_(自动Spawn的Object) 表示该Object在Sequence被Evaluate的时候自动Spawn,并且由Sequence来管理生命周期 2. _PossessableObject_(可以被赋予的Object) 表示该Object可以被外部赋予,比如我手动赋予一个Actor给某个Track ### UMovieSceneSequence UMovieSceneSequence::CreatePossessable() UMovieSceneSequence::BindPossessableObject() 绑定过程可以参考: ULevelSequenceEditorSubsystem::AddActorsToBinding(const TArray& Actors, const FMovieSceneBindingProxy& ObjectBinding) ### ActorToSequencer 菜单出现逻辑位于:FLevelSequenceEditorActorBinding::BuildSequencerAddMenu(FMenuBuilder& MenuBuilder)。 `FLevelSequenceEditorActorBinding::AddPossessActorMenuExtensions(FMenuBuilder& MenuBuilder)` => `FLevelSequenceEditorActorBinding::AddActorsToSequencer(AActor*const* InActors, int32 NumActors)` => `TArray FSequencer::AddActors(const TArray >& InActors, bool bSelectActors);` => `TArray FSequencerUtilities::AddActors(TSharedRef Sequencer, const TArray >& InActors)` ```c++ TArray FSequencerUtilities::AddActors(TSharedRef Sequencer, const TArray >& InActors) { TArray PossessableGuids; UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (!Sequence) { return PossessableGuids; } UMovieScene* MovieScene = Sequence->GetMovieScene(); if (!MovieScene) { return PossessableGuids; } if (MovieScene->IsReadOnly()) { ShowReadOnlyError(); return PossessableGuids; } const FScopedTransaction Transaction(LOCTEXT("AddActors", "Add Actors")); Sequence->Modify(); for (TWeakObjectPtr WeakActor : InActors) { if (AActor* Actor = WeakActor.Get()) { FGuid ExistingGuid = Sequencer->FindObjectId(*Actor, Sequencer->GetFocusedTemplateID()); if (!ExistingGuid.IsValid()) { FGuid PossessableGuid = CreateBinding(Sequencer, *Actor, Actor->GetActorLabel()); PossessableGuids.Add(PossessableGuid); if (ACameraActor* CameraActor = Cast(Actor)) { NewCameraAdded(Sequencer, CameraActor, PossessableGuid); } Sequencer->OnActorAddedToSequencer().Broadcast(Actor, PossessableGuid); } } } return PossessableGuids; } ``` 其他参考 ```c++ const FGuid PossessableGuid = OwnerMovieScene->AddPossessable(InName, InObject.GetClass()); if (!OwnerMovieScene->FindPossessable(PossessableGuid)->BindSpawnableObject(Sequencer->GetFocusedTemplateID(), &InObject, &Sequencer.Get())) { OwnerSequence->BindPossessableObject(PossessableGuid, InObject, BindingContext); } ``` ```c++ FGuid FSequencerUtilities::CreateBinding(TSharedRef Sequencer, UObject& InObject, const FString& InName) { const FScopedTransaction Transaction(LOCTEXT("CreateBinding", "Create New Binding")); UMovieSceneSequence* OwnerSequence = Sequencer->GetFocusedMovieSceneSequence(); UMovieScene* OwnerMovieScene = OwnerSequence->GetMovieScene(); OwnerSequence->Modify(); OwnerMovieScene->Modify(); const FGuid PossessableGuid = OwnerMovieScene->AddPossessable(InName, InObject.GetClass()); // Attempt to use the parent as a context if necessary UObject* ParentObject = OwnerSequence->GetParentObject(&InObject); UObject* BindingContext = Sequencer->GetPlaybackContext(); AActor* ParentActorAdded = nullptr; FGuid ParentGuid; if (ParentObject) { // Ensure we have possessed the outer object, if necessary ParentGuid = Sequencer->GetHandleToObject(ParentObject, false); if (!ParentGuid.IsValid()) { ParentGuid = Sequencer->GetHandleToObject(ParentObject); ParentActorAdded = Cast(ParentObject); } if (OwnerSequence->AreParentContextsSignificant()) { BindingContext = ParentObject; } // Set up parent/child guids for possessables within spawnables if (ParentGuid.IsValid()) { FMovieScenePossessable* ChildPossessable = OwnerMovieScene->FindPossessable(PossessableGuid); if (ensure(ChildPossessable)) { ChildPossessable->SetParent(ParentGuid, OwnerMovieScene); } FMovieSceneSpawnable* ParentSpawnable = OwnerMovieScene->FindSpawnable(ParentGuid); if (ParentSpawnable) { ParentSpawnable->AddChildPossessable(PossessableGuid); } } } if (!OwnerMovieScene->FindPossessable(PossessableGuid)->BindSpawnableObject(Sequencer->GetFocusedTemplateID(), &InObject, &Sequencer.Get())) { OwnerSequence->BindPossessableObject(PossessableGuid, InObject, BindingContext); } // Broadcast if a parent actor was added as a result of adding this object if (ParentActorAdded && ParentGuid.IsValid()) { Sequencer->OnActorAddedToSequencer().Broadcast(ParentActorAdded, ParentGuid); } return PossessableGuid; } ``` ### FAudioTrackEditor FAudioTrackEditor::HandleAssetAdded(UObject* Asset, const FGuid& TargetObjectGuid) => FAudioTrackEditor::AddNewSound() => UMovieSceneAudioTrack::AddNewSoundOnRow(USoundBase* Sound, FFrameNumber Time, int32 RowIndex) ## MovieSceneSkeletalAnimationTrack - UMovieSceneSkeletalAnimationTrack - UMovieSceneSkeletalAnimationSection - FSkeletalAnimationTrackEditor - UMovieSceneSkeletalAnimationSystem - 里面有介绍System相关的逻辑 https://zhuanlan.zhihu.com/p/413151867 - FSkeletalAnimationTrackEditMode Track的编辑器注册位于MovieSceneToolsModule.cpp: ```c++ AnimationTrackCreateEditorHandle = SequencerModule.RegisterTrackEditor( FOnCreateTrackEditor::CreateStatic( &FSkeletalAnimationTrackEditor::CreateTrackEditor ) ); ``` 只对UMovieSceneSkeletalAnimationTrack生效: ```c++ bool FSkeletalAnimationTrackEditor::SupportsSequence(UMovieSceneSequence* InSequence) const { ETrackSupport TrackSupported = InSequence ? InSequence->IsTrackSupported(UMovieSceneSkeletalAnimationTrack::StaticClass()) : ETrackSupport::NotSupported; return TrackSupported == ETrackSupport::Supported; } FEditorModeRegistry::Get().RegisterMode( FSkeletalAnimationTrackEditMode::ModeName, NSLOCTEXT("SkeletalAnimationTrackEditorMode", "SkelAnimTrackEditMode", "Skeletal Anim Track Mode"), FSlateIcon(), false); ``` ### FSkeletalAnimationTrackEditor FSkeletalAnimationTrackEditor::BuildTrackContextMenu():在MovieSceneSkeletalAnimationTrack的Animation上右键出现的菜单。 FSkeletalAnimationTrackEditor::BuildOutlinerEditWidget:MovieSceneSkeletalAnimationTrack下面生成Animtion那一列。 ## 播放逻辑 ```c++ ALevelSequenceActor::InitializePlayer() ALevelSequenceActor->SequencePlayer->Play(); ALevelSequenceActor->SequencePlayer->Update(DeltaSeconds); ``` 具体的逻辑位于UMovieSceneSequencePlayer::PlayInternal(): ```c++ void UMovieSceneSequencePlayer::PlayInternal() { if (NeedsQueueLatentAction()) { QueueLatentAction(FMovieSceneSequenceLatentActionDelegate::CreateUObject(this, &UMovieSceneSequencePlayer::PlayInternal)); return; } if (!IsPlaying() && Sequence && CanPlay()) { const FString SequenceName = GetSequenceName(true); UE_LOG(LogMovieScene, Verbose, TEXT("PlayInternal - %s (current status: %s)"), *SequenceName, *UEnum::GetValueAsString(Status)); // Set playback status to playing before any calls to update the position Status = EMovieScenePlayerStatus::Playing; float PlayRate = bReversePlayback ? -PlaybackSettings.PlayRate : PlaybackSettings.PlayRate; // If at the end and playing forwards, rewind to beginning if (GetCurrentTime().Time == GetLastValidTime()) { if (PlayRate > 0.f) { SetPlaybackPosition(FMovieSceneSequencePlaybackParams(FFrameTime(StartTime), EUpdatePositionMethod::Jump)); } } else if (GetCurrentTime().Time == FFrameTime(StartTime)) { if (PlayRate < 0.f) { SetPlaybackPosition(FMovieSceneSequencePlaybackParams(GetLastValidTime(), EUpdatePositionMethod::Jump)); } } // Update now if (PlaybackSettings.bRestoreState) { RootTemplateInstance.EnableGlobalPreAnimatedStateCapture(); } bPendingOnStartedPlaying = true; Status = EMovieScenePlayerStatus::Playing; TimeController->StartPlaying(GetCurrentTime()); if (PlayPosition.GetEvaluationType() == EMovieSceneEvaluationType::FrameLocked) { if (!OldMaxTickRate.IsSet()) { OldMaxTickRate = GEngine->GetMaxFPS(); } GEngine->SetMaxFPS(1.f / PlayPosition.GetInputRate().AsInterval()); } //播放的核心逻辑估计在这里。 if (!PlayPosition.GetLastPlayEvalPostition().IsSet() || PlayPosition.GetLastPlayEvalPostition() != PlayPosition.GetCurrentPosition()) { UpdateMovieSceneInstance(PlayPosition.PlayTo(PlayPosition.GetCurrentPosition()), EMovieScenePlayerStatus::Playing); } RunLatentActions(); UpdateNetworkSyncProperties(); if (bReversePlayback) { if (OnPlayReverse.IsBound()) { OnPlayReverse.Broadcast(); } } else { if (OnPlay.IsBound()) { OnPlay.Broadcast(); } } } } void UMovieSceneSequencePlayer::UpdateMovieSceneInstance(FMovieSceneEvaluationRange InRange, EMovieScenePlayerStatus::Type PlayerStatus, bool bHasJumped) { FMovieSceneUpdateArgs Args; Args.bHasJumped = bHasJumped; UpdateMovieSceneInstance(InRange, PlayerStatus, Args); } void UMovieSceneSequencePlayer::UpdateMovieSceneInstance(FMovieSceneEvaluationRange InRange, EMovieScenePlayerStatus::Type PlayerStatus, const FMovieSceneUpdateArgs& Args) { if (Observer && !Observer->CanObserveSequence()) { return; } UMovieSceneSequence* MovieSceneSequence = RootTemplateInstance.GetSequence(MovieSceneSequenceID::Root); if (!MovieSceneSequence) { return; } #if !NO_LOGGING if (UE_LOG_ACTIVE(LogMovieScene, VeryVerbose)) { const FQualifiedFrameTime CurrentTime = GetCurrentTime(); const FString SequenceName = GetSequenceName(true); UE_LOG(LogMovieScene, VeryVerbose, TEXT("Evaluating sequence %s at frame %d, subframe %f (%f fps)."), *SequenceName, CurrentTime.Time.FrameNumber.Value, CurrentTime.Time.GetSubFrame(), CurrentTime.Rate.AsDecimal()); } #endif if (PlaybackClient) { PlaybackClient->WarpEvaluationRange(InRange); } // Once we have updated we must no longer skip updates bSkipNextUpdate = false; // We shouldn't be asked to run an async update if we have a blocking sequence. check(!Args.bIsAsync || !EnumHasAnyFlags(MovieSceneSequence->GetFlags(), EMovieSceneSequenceFlags::BlockingEvaluation)); // We shouldn't be asked to run an async update if we don't have a tick manager. check(!Args.bIsAsync || TickManager != nullptr); FMovieSceneContext Context(InRange, PlayerStatus); Context.SetHasJumped(Args.bHasJumped); TSharedPtr Runner = RootTemplateInstance.GetRunner(); if (Runner) { Runner->QueueUpdate(Context, RootTemplateInstance.GetRootInstanceHandle()); if (Runner == SynchronousRunner || !Args.bIsAsync) { Runner->Flush(); } } } ``` 暂停的核心逻辑: ```c++ FMovieSceneEvaluationRange CurrentTimeRange = PlayPosition.GetCurrentPositionAsRange(); const FMovieSceneContext Context(CurrentTimeRange, EMovieScenePlayerStatus::Stopped); Runner->QueueUpdate(Context, RootTemplateInstance.GetRootInstanceHandle(), FSimpleDelegate::CreateWeakLambda(this, FinishPause)); ``` ## SequenceRecorder SequenceRecorder模块的基类是ISequenceRecorder。