346 lines
13 KiB
Markdown
346 lines
13 KiB
Markdown
---
|
||
title: Sequence Runtime Binding
|
||
date: 2024-05-16 17:55:58
|
||
excerpt:
|
||
tags:
|
||
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()
|
||
- 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<UDefaultLevelSequenceInstanceData>(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<TObjectPtr<UMovieSceneSection>> 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<AActor*>& Actors, const FMovieSceneBindingProxy& ObjectBinding)
|
||
|
||
|
||
### ActorToSequencer
|
||
菜单出现逻辑位于:FLevelSequenceEditorActorBinding::BuildSequencerAddMenu(FMenuBuilder& MenuBuilder)。
|
||
|
||
`FLevelSequenceEditorActorBinding::AddPossessActorMenuExtensions(FMenuBuilder& MenuBuilder)`
|
||
=>
|
||
`FLevelSequenceEditorActorBinding::AddActorsToSequencer(AActor*const* InActors, int32 NumActors)`
|
||
=>
|
||
`TArray<FGuid> FSequencer::AddActors(const TArray<TWeakObjectPtr<AActor> >& InActors, bool bSelectActors);`
|
||
=>
|
||
`TArray<FGuid> FSequencerUtilities::AddActors(TSharedRef<ISequencer> Sequencer, const TArray<TWeakObjectPtr<AActor> >& InActors)`
|
||
|
||
```c++
|
||
TArray<FGuid> FSequencerUtilities::AddActors(TSharedRef<ISequencer> Sequencer, const TArray<TWeakObjectPtr<AActor> >& InActors)
|
||
{
|
||
TArray<FGuid> 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<AActor> 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<ACameraActor>(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>(
|
||
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<FMovieSceneEntitySystemRunner> 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。 |