Files
UnrealEngineUWP/Engine/Source/Runtime/LevelSequence/Private/LevelSequencePlayer.cpp
david bromberg c31d702845 Sequencer- Resubmission of changelist 34065553 that had been backed out for a versioning issue which is now fixed. Original description below:
Sequencer- Dynamic Binding Refactor
Refactor the 'Dynamic Binding' blueprint system to be Custom Bindings rather than tied directly to FMovieScenePossessable and FMovieSceneSpawnable. As part of this:
? Created UMovieSceneReplaceableDirectorBlueprintBinding and UMovieSceneSpawnableDirectorBlueprintBinding to replace the Possessable and Spawnable versions of DynamicBindings
? Deprecated the DynamicBinding references on possessable and spawnable and created Postload fixup code to automatically create custom bindings instead
? Note that because the previous Dynamic Bindings operated as overrides and the new ones don't, there is a no longer supported path which is using Possessables as a preview in editor for Possessable Dynamic Bindings. Now if you don't mark 'Call in Editor' for a Replaceable Director Blueprint binding, you'll need to define a spawnable preview binding for the binding in order to work with it in Sequencer.
? Since UMG also used Dynamic Bindings on possessables, temporarily add DynamicBinding as a member variable on FWidgetAnimationBinding and create postload logic to move DynamicBindings to live on there for WidgetAnimations.
? USD has custom code to create dynamic bindings automatically for its use case. Convert this also to use the new custom binding system.
? Some cleanup on object binding right-click menus was done as part of this.
? Some further MovieSceneSequence API deprecation and refactoring has been done for the creation of bindings to add SharedPlaybackState where possible which was necessary for dynamic binding setup.

FMovieSceneSpawnable deprecation in Level Sequences
Since I was having to write postload fixup logic for FMovieSceneSpawnables with Dynamic Bindings that converted these to FMovieScenePossessables with custom bindings, it felt like a good chance to deprecate all FMovieSceneSpawnable use in Level Sequences.

I chose at this time not to deprecate FMovieSceneSpawnable use for non-Level-Sequence Movie Scene Sequences. This includes Day Sequences, Template Sequences, Actor Sequences, Widget Animations, Niagara Sequences, and some Meta Human sequences. I made this choice because to do so would require also refactoring those sequence types to use MovieSceneBindingReferences in order to enable custom bindings for those sequence types. This will be a future refactor.

Some notes on this deprecation.
? I kept MovieScene specific code that references or creates FMovieSceneSpawnables due to not refactoring all MovieSceneSequences
? Binding creation code in tools such as Sequencer will check for the presence of Binding References and create custom bindings if present, otherwise will still create FMovieSceneSpawnables
? AvaSequences required some extra work to make this change, including some changes and bugfixes to copy/pasting bindings to move away from custom avalanche copy/paste code, as the copy/paste code for custom bindings is a bit intricate and I didn't want to duplicate it.
? Chaos Cache has been moved away from using an object spawner and instead uses a custom spawnable actor binding inheriting from the regular spawnable actor binding.
? Actor and Take Recording has been updated to support custom spawnable bindings
? Meta Human specific code has been updated to support custom spawnable bindings when using Level Sequences
? FortniteMainBranchObjectVersion has been bumped

Crash fix
As part of the USD work, I found a crash bug where the Binding Properties menu would hold a reference to a level sequence. In the case of USD, the outer of the sequences can be the level, and this caused issues when switching levels. To fix this I ended up needing to add some events when menus are being dismissed so I could clean up these references.

#jira UE-209839, UE-209548, UE-213115, UE-214195, UE-214443
#rb Marc.Audy
#lockdown Marc.Audy
[FYI] juan.portillo, Viktar.Mikhalchuk, Darrien.Blanchard, patrick.boutot

[CL 34127005 by david bromberg in ue5-main branch]
2024-06-05 11:54:07 -04:00

426 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LevelSequencePlayer.h"
#include "Engine/GameInstance.h"
#include "GameFramework/Actor.h"
#include "MovieScene.h"
#include "Misc/CoreDelegates.h"
#include "EngineGlobals.h"
#include "Engine/Level.h"
#include "Camera/PlayerCameraManager.h"
#include "UObject/Package.h"
#include "GameFramework/PlayerController.h"
#include "Camera/CameraComponent.h"
#include "Tickable.h"
#include "Engine/LevelScriptActor.h"
#include "MovieSceneCommonHelpers.h"
#include "Sections/MovieSceneSubSection.h"
#include "LevelSequenceSpawnRegister.h"
#include "Engine/Engine.h"
#include "Engine/LevelStreaming.h"
#include "Engine/LocalPlayer.h"
#include "Tracks/MovieSceneCinematicShotTrack.h"
#include "Sections/MovieSceneCinematicShotSection.h"
#include "Systems/MovieSceneMotionVectorSimulationSystem.h"
#include "EntitySystem/MovieSceneEntitySystemLinker.h"
#include "EntitySystem/MovieSceneSharedPlaybackState.h"
#include "LevelSequenceActor.h"
#include "Modules/ModuleManager.h"
#include "LevelUtils.h"
#include "ProfilingDebugging/CsvProfiler.h"
#include "LevelSequenceModule.h"
#include "Generators/MovieSceneEasingCurves.h"
#include "UniversalObjectLocatorResolveParams.h"
#include "UniversalObjectLocators/ActorLocatorFragment.h"
#include "UniversalObjectLocatorResolveParameterBuffer.inl"
#include "Evaluation/CameraCutPlaybackCapability.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(LevelSequencePlayer)
/* ULevelSequencePlayer structors
*****************************************************************************/
ULevelSequencePlayer::ULevelSequencePlayer(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{}
/* ULevelSequencePlayer interface
*****************************************************************************/
ULevelSequencePlayer* ULevelSequencePlayer::CreateLevelSequencePlayer(UObject* WorldContextObject, ULevelSequence* InLevelSequence, FMovieSceneSequencePlaybackSettings Settings, ALevelSequenceActor*& OutActor)
{
if (InLevelSequence == nullptr)
{
return nullptr;
}
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
if (World == nullptr || World->bIsTearingDown)
{
return nullptr;
}
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
SpawnParams.ObjectFlags |= RF_Transient;
SpawnParams.bAllowDuringConstructionScript = true;
// Defer construction for autoplay so that BeginPlay() is called
SpawnParams.bDeferConstruction = true;
ALevelSequenceActor* Actor = World->SpawnActor<ALevelSequenceActor>(SpawnParams);
Actor->PlaybackSettings = Settings;
Actor->GetSequencePlayer()->SetPlaybackSettings(Settings);
Actor->SetSequence(InLevelSequence);
Actor->InitializePlayer();
OutActor = Actor;
FTransform DefaultTransform;
Actor->FinishSpawning(DefaultTransform);
return Actor->GetSequencePlayer();
}
/* ULevelSequencePlayer implementation
*****************************************************************************/
void ULevelSequencePlayer::Initialize(ULevelSequence* InLevelSequence, ULevel* InLevel, const FLevelSequenceCameraSettings& InCameraSettings)
{
using namespace UE::MovieScene;
World = InLevel->OwningWorld;
Level = InLevel;
CameraSettings = InCameraSettings;
SpawnRegister = MakeShareable(new FLevelSequenceSpawnRegister);
UMovieSceneSequencePlayer::Initialize(InLevelSequence);
// The parent player class' root evaluation template may or may not have re-initialized itself.
// For instance, if we are given the same sequence asset we already had before, and nothing else
// (such as playback context) has changed, no actual re-initialization occurs and we keep the
// same shared playback state as before.
// That state would already have the spawn register and camera cut capabilies... however, our
// spawn register was just re-created (see a few lines above) so we need to overwrite the
// capability pointer to the new object.
InitializeLevelSequenceRootInstance(RootTemplateInstance.GetSharedPlaybackState().ToSharedRef());
}
void ULevelSequencePlayer::SetSourceActorContext(UWorld* InStreamingWorld, FActorContainerID InContainerID, FTopLevelAssetPath InSourceAssetPath)
{
WeakStreamingWorld = InStreamingWorld;
ContainerID = InContainerID;
SourceAssetPath = InSourceAssetPath;
}
void ULevelSequencePlayer::ResolveBoundObjects(UE::UniversalObjectLocator::FResolveParams& LocatorResolveParams, const FGuid& InBindingId, FMovieSceneSequenceID SequenceID, UMovieSceneSequence& InSequence, TArray<UObject*, TInlineAllocator<1>>& OutObjects) const
{
using namespace UE::UniversalObjectLocator;
using namespace UE::MovieScene;
bool bAllowDefault = PlaybackClient ? PlaybackClient->RetrieveBindingOverrides(InBindingId, SequenceID, OutObjects) : true;
if (bAllowDefault)
{
if (const FMovieSceneBindingReferences* BindingReferences = InSequence.GetBindingReferences())
{
FMovieSceneBindingResolveParams BindingResolveParams{ &InSequence, InBindingId, SequenceID, LocatorResolveParams.Context };
if (LocatorResolveParams.ParameterBuffer == nullptr)
{
// Allocate temporary local buffer for this
TInlineResolveParameterBuffer<128> Buffer;
LocatorResolveParams.ParameterBuffer = &Buffer;
LocatorResolveParams.ParameterBuffer->AddParameter(FActorLocatorFragmentResolveParameter::ParameterType, WeakStreamingWorld.Get(), ContainerID, SourceAssetPath);
BindingReferences->ResolveBinding(BindingResolveParams, LocatorResolveParams, ConstCastSharedPtr<const UE::MovieScene::FSharedPlaybackState>(const_cast<ULevelSequencePlayer*>(this)->FindSharedPlaybackState()), OutObjects);
LocatorResolveParams.ParameterBuffer = nullptr;
}
else
{
LocatorResolveParams.ParameterBuffer->AddParameter(FActorLocatorFragmentResolveParameter::ParameterType, WeakStreamingWorld.Get(), ContainerID, SourceAssetPath);
BindingReferences->ResolveBinding(BindingResolveParams, LocatorResolveParams, ConstCastSharedPtr<const UE::MovieScene::FSharedPlaybackState>(const_cast<ULevelSequencePlayer*>(this)->FindSharedPlaybackState()), OutObjects);
}
}
else
{
if (LocatorResolveParams.ParameterBuffer == nullptr)
{
// Allocate temporary local buffer for this
TInlineResolveParameterBuffer<128> Buffer;
LocatorResolveParams.ParameterBuffer = &Buffer;
LocatorResolveParams.ParameterBuffer->AddParameter(FActorLocatorFragmentResolveParameter::ParameterType, WeakStreamingWorld.Get(), ContainerID, SourceAssetPath);
InSequence.LocateBoundObjects(InBindingId, LocatorResolveParams, GetSharedPlaybackState(), OutObjects);
LocatorResolveParams.ParameterBuffer = nullptr;
}
else
{
LocatorResolveParams.ParameterBuffer->AddParameter(FActorLocatorFragmentResolveParameter::ParameterType, WeakStreamingWorld.Get(), ContainerID, SourceAssetPath);
InSequence.LocateBoundObjects(InBindingId, LocatorResolveParams, GetSharedPlaybackState(), OutObjects);
}
}
}
}
bool ULevelSequencePlayer::CanPlay() const
{
return World.IsValid();
}
void ULevelSequencePlayer::OnStartedPlaying()
{
EnableCinematicMode(true);
}
void ULevelSequencePlayer::OnStopped()
{
EnableCinematicMode(false);
if (World != nullptr && World->GetGameInstance() != nullptr)
{
APlayerController* PC = World->GetGameInstance()->GetFirstLocalPlayerController();
if (PC != nullptr)
{
if (PC->PlayerCameraManager)
{
PC->PlayerCameraManager->bClientSimulatingViewTarget = false;
}
}
}
}
void ULevelSequencePlayer::UpdateMovieSceneInstance(FMovieSceneEvaluationRange InRange, EMovieScenePlayerStatus::Type PlayerStatus, const FMovieSceneUpdateArgs& Args)
{
UMovieSceneSequencePlayer::UpdateMovieSceneInstance(InRange, PlayerStatus, Args);
// TODO-ludovic: we should move this to a post-evaluation callback when the evaluation is asynchronous.
FLevelSequencePlayerSnapshot NewSnapshot;
TakeFrameSnapshot(NewSnapshot);
if (!PreviousSnapshot.IsSet() || PreviousSnapshot.GetValue().CurrentShotName != NewSnapshot.CurrentShotName)
{
CSV_EVENT_GLOBAL(TEXT("%s"), *NewSnapshot.CurrentShotName);
//UE_LOG(LogMovieScene, Log, TEXT("Shot evaluated: '%s'"), *NewSnapshot.CurrentShotName);
}
PreviousSnapshot = NewSnapshot;
}
/* FCameraCutPlaybackCapability interface
*****************************************************************************/
bool ULevelSequencePlayer::ShouldUpdateCameraCut()
{
return !PlaybackSettings.bDisableCameraCuts;
}
float ULevelSequencePlayer::GetCameraBlendPlayRate()
{
return PlaybackSettings.PlayRate;
}
TOptional<EAspectRatioAxisConstraint> ULevelSequencePlayer::GetAspectRatioAxisConstraintOverride()
{
return CameraSettings.bOverrideAspectRatioAxisConstraint ?
TOptional<EAspectRatioAxisConstraint>(CameraSettings.AspectRatioAxisConstraint) :
TOptional<EAspectRatioAxisConstraint>();
}
void ULevelSequencePlayer::OnCameraCutUpdated(const UE::MovieScene::FOnCameraCutUpdatedParams& Params)
{
CachedCameraComponent = Params.ViewTargetCamera;
if (OnCameraCut.IsBound())
{
OnCameraCut.Broadcast(Params.ViewTargetCamera);
}
}
/* IMovieScenePlayer interface
*****************************************************************************/
UObject* ULevelSequencePlayer::GetPlaybackContext() const
{
if (ALevelSequenceActor* LevelSequenceActor = GetTypedOuter<ALevelSequenceActor>())
{
return LevelSequenceActor;
}
return World.Get();
}
TArray<UObject*> ULevelSequencePlayer::GetEventContexts() const
{
TArray<UObject*> EventContexts;
if (World.IsValid())
{
GetEventContexts(*World, EventContexts);
}
return EventContexts;
}
void ULevelSequencePlayer::GetEventContexts(UWorld& InWorld, TArray<UObject*>& OutContexts)
{
if (InWorld.GetLevelScriptActor())
{
OutContexts.Add(InWorld.GetLevelScriptActor());
}
for (ULevelStreaming* StreamingLevel : InWorld.GetStreamingLevels())
{
if (StreamingLevel && StreamingLevel->GetLevelScriptActor())
{
OutContexts.Add(StreamingLevel->GetLevelScriptActor());
}
}
}
void ULevelSequencePlayer::InitializeRootInstance(TSharedRef<UE::MovieScene::FSharedPlaybackState> NewSharedPlaybackState)
{
using namespace UE::MovieScene;
Super::InitializeRootInstance(NewSharedPlaybackState);
InitializeLevelSequenceRootInstance(NewSharedPlaybackState);
}
void ULevelSequencePlayer::InitializeLevelSequenceRootInstance(TSharedRef<UE::MovieScene::FSharedPlaybackState> NewSharedPlaybackState)
{
NewSharedPlaybackState->SetOrAddCapabilityRaw<FMovieSceneSpawnRegister>(SpawnRegister.Get());
NewSharedPlaybackState->SetOrAddCapabilityRaw<FCameraCutPlaybackCapability>((FCameraCutPlaybackCapability*)this);
}
void ULevelSequencePlayer::TakeFrameSnapshot(FLevelSequencePlayerSnapshot& OutSnapshot) const
{
if (!ensure(Sequence))
{
return;
}
// In Play Rate Resolution
const FFrameTime StartTimeWithoutWarmupFrames = SnapshotOffsetTime.IsSet() ? StartTime + SnapshotOffsetTime.GetValue() : StartTime;
const FFrameTime CurrentPlayTime = PlayPosition.GetCurrentPosition();
// In Playback Resolution
const FFrameTime CurrentSequenceTime = ConvertFrameTime(CurrentPlayTime, PlayPosition.GetInputRate(), PlayPosition.GetOutputRate());
OutSnapshot.RootTime = FQualifiedFrameTime(CurrentPlayTime, PlayPosition.GetInputRate());
OutSnapshot.RootName = Sequence->GetName();
OutSnapshot.CurrentShotName = OutSnapshot.RootName;
OutSnapshot.CurrentShotLocalTime = FQualifiedFrameTime(CurrentPlayTime, PlayPosition.GetInputRate());
OutSnapshot.CameraComponent = CachedCameraComponent.IsValid() ? CachedCameraComponent.Get() : nullptr;
OutSnapshot.ShotID = MovieSceneSequenceID::Invalid;
OutSnapshot.ActiveShot = Cast<ULevelSequence>(Sequence);
UMovieScene* MovieScene = Sequence->GetMovieScene();
#if WITH_EDITORONLY_DATA
OutSnapshot.SourceTimecode = MovieScene->GetEarliestTimecodeSource().Timecode.ToString();
#endif
UMovieSceneCinematicShotTrack* ShotTrack = MovieScene->FindTrack<UMovieSceneCinematicShotTrack>();
if (ShotTrack)
{
UMovieSceneCinematicShotSection* ActiveShot = nullptr;
for (UMovieSceneSection* Section : ShotTrack->GetAllSections())
{
if (!ensure(Section))
{
continue;
}
// It's unfortunate that we have to copy the logic of UMovieSceneCinematicShotTrack::GetRowCompilerRules() to some degree here, but there's no better way atm
bool bThisShotIsActive = Section->IsActive();
TRange<FFrameNumber> SectionRange = Section->GetRange();
bThisShotIsActive = bThisShotIsActive && SectionRange.Contains(CurrentSequenceTime.FrameNumber);
if (bThisShotIsActive && ActiveShot)
{
if (Section->GetRowIndex() < ActiveShot->GetRowIndex())
{
bThisShotIsActive = true;
}
else if (Section->GetRowIndex() == ActiveShot->GetRowIndex())
{
// On the same row - latest start wins
bThisShotIsActive = TRangeBound<FFrameNumber>::MaxLower(SectionRange.GetLowerBound(), ActiveShot->GetRange().GetLowerBound()) == SectionRange.GetLowerBound();
}
else
{
bThisShotIsActive = false;
}
}
if (bThisShotIsActive)
{
ActiveShot = Cast<UMovieSceneCinematicShotSection>(Section);
}
}
if (ActiveShot)
{
// Assume that shots with no sequence start at 0.
FMovieSceneSequenceTransform OuterToInnerTransform = ActiveShot->OuterToInnerTransform();
UMovieSceneSequence* InnerSequence = ActiveShot->GetSequence();
FFrameRate InnerTickResoloution = InnerSequence ? InnerSequence->GetMovieScene()->GetTickResolution() : PlayPosition.GetOutputRate();
FFrameRate InnerFrameRate = InnerSequence ? InnerSequence->GetMovieScene()->GetDisplayRate() : PlayPosition.GetInputRate();
FFrameTime InnerDisplayTime = ConvertFrameTime(CurrentSequenceTime * OuterToInnerTransform, InnerTickResoloution, InnerFrameRate);
OutSnapshot.CurrentShotName = ActiveShot->GetShotDisplayName();
OutSnapshot.CurrentShotLocalTime = FQualifiedFrameTime(InnerDisplayTime, InnerFrameRate);
OutSnapshot.ShotID = ActiveShot->GetSequenceID();
OutSnapshot.ActiveShot = Cast<ULevelSequence>(ActiveShot->GetSequence());
#if WITH_EDITORONLY_DATA
FFrameNumber InnerFrameNumber = InnerFrameRate.AsFrameNumber(InnerFrameRate.AsSeconds(InnerDisplayTime));
FFrameNumber InnerStartFrameNumber = ActiveShot->TimecodeSource.Timecode.ToFrameNumber(InnerFrameRate);
FFrameNumber InnerCurrentFrameNumber = InnerStartFrameNumber + InnerFrameNumber;
FTimecode InnerCurrentTimecode = ActiveShot->TimecodeSource.Timecode.FromFrameNumber(InnerCurrentFrameNumber, InnerFrameRate, false);
OutSnapshot.SourceTimecode = InnerCurrentTimecode.ToString();
#else
OutSnapshot.SourceTimecode = FTimecode().ToString();
#endif
}
}
}
void ULevelSequencePlayer::EnableCinematicMode(bool bEnable)
{
// iterate through the controller list and set cinematic mode if necessary
bool bNeedsCinematicMode = PlaybackSettings.bDisableMovementInput || PlaybackSettings.bDisableLookAtInput || PlaybackSettings.bHidePlayer || PlaybackSettings.bHideHud;
if (bNeedsCinematicMode)
{
if (World.IsValid())
{
for (FConstPlayerControllerIterator Iterator = World->GetPlayerControllerIterator(); Iterator; ++Iterator)
{
APlayerController* PC = Iterator->Get();
if (PC && PC->IsLocalController())
{
PC->SetCinematicMode(bEnable, PlaybackSettings.bHidePlayer, PlaybackSettings.bHideHud, PlaybackSettings.bDisableMovementInput, PlaybackSettings.bDisableLookAtInput);
}
}
}
}
}
void ULevelSequencePlayer::RewindForReplay()
{
// Stop the sequence when starting to seek through a replay. This restores our state to be unmodified
// in case the replay is seeking to before playback. If we're in the middle of playback after rewinding,
// the replay will feed the correct packets to synchronize our playback time and state.
Stop();
NetSyncProps.LastKnownPosition = FFrameTime(0);
NetSyncProps.LastKnownStatus = EMovieScenePlayerStatus::Stopped;
NetSyncProps.LastKnownNumLoops = 0;
NetSyncProps.LastKnownSerialNumber = 0;
}