diff --git a/03-UnrealEngine/Sequence/Sequence Runtime Binding.md b/03-UnrealEngine/Sequence/Sequence Runtime Binding.md index 3f19952..260eae6 100644 --- a/03-UnrealEngine/Sequence/Sequence Runtime Binding.md +++ b/03-UnrealEngine/Sequence/Sequence Runtime Binding.md @@ -9,6 +9,11 @@ rating: ⭐ 参考: 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() @@ -52,9 +57,11 @@ UMovieSceneSequenceExtensions methods are not exported. They don’t have the PL - 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 +UMovieSceneSignedObject -> UObject。一个UMovieScene由若干个MasterTrack(UMovieSceneTrack)组成,UMovieScene相当于是UMovieSceneTracks的容器,那么UMovieSceneTrack又由UMovieSceneSections组成,UMovieSceneSection就是一个Track中间的某一段。 ### UMovieSceneSignedObject 子类有UMovieScene、UMovieSceneSequence、UMovieSceneTrack、UMovieSceneSection。 @@ -74,5 +81,266 @@ UMovieSceneSignedObject -> UObject 编辑器代码:**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; +} +``` + +### FAudioTrackEditor +FAudioTrackEditor::HandleAssetAdded(UObject* Asset, const FGuid& TargetObjectGuid) => FAudioTrackEditor::AddNewSound() => UMovieSceneAudioTrack::AddNewSoundOnRow(USoundBase* Sound, FFrameNumber Time, int32 RowIndex) + +## MovieSceneSkeletalAnimationTrack +- UMovieSceneSkeletalAnimationTrack +- UMovieSceneSkeletalAnimationSection +- FSkeletalAnimationTrackEditor +- UMovieSceneSkeletalAnimationSystem +- 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。 \ No newline at end of file