Files
UnrealEngine/Engine/Source/Runtime/LevelSequence/Private/LevelSequence.cpp
2025-05-18 13:04:45 +08:00

1065 lines
36 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LevelSequence.h"
#include "IMovieSceneMetaData.h"
#include "MovieSceneMetaData.h"
#include "Engine/EngineTypes.h"
#include "HAL/IConsoleManager.h"
#include "UniversalObjectLocator.h"
#include "UniversalObjectLocatorFragmentType.h"
#include "UniversalObjectLocatorResolveParameterBuffer.inl"
#include "UniversalObjectLocators/ActorLocatorFragment.h"
#include "WorldPartition/IWorldPartitionObjectResolver.h"
#include "LegacyLazyObjectPtrFragment.h"
#include "SubObjectLocator.h"
#include "Components/ActorComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/Actor.h"
#include "LevelSequenceDirector.h"
#include "Engine/Engine.h"
#include "MovieScene.h"
#include "MovieSceneCommonHelpers.h"
#include "UObject/AssetRegistryTagsContext.h"
#include "UObject/Package.h"
#include "UObject/UObjectHash.h"
#include "Animation/AnimInstance.h"
#include "LevelSequenceModule.h"
#include "MovieSceneSpawnableAnnotation.h"
#include "Tracks/MovieScene3DAttachTrack.h"
#include "Tracks/MovieScene3DPathTrack.h"
#include "Tracks/MovieSceneAudioTrack.h"
#include "Tracks/MovieSceneCameraCutTrack.h"
#include "Tracks/MovieSceneCinematicShotTrack.h"
#include "Tracks/MovieSceneEventTrack.h"
#include "Tracks/MovieSceneFadeTrack.h"
#include "Tracks/MovieSceneLevelVisibilityTrack.h"
#include "Tracks/MovieSceneDataLayerTrack.h"
#include "Tracks/MovieSceneMaterialParameterCollectionTrack.h"
#include "Tracks/MovieSceneSkeletalAnimationTrack.h"
#include "Tracks/MovieSceneSlomoTrack.h"
#include "Tracks/MovieSceneSpawnTrack.h"
#include "Tracks/MovieSceneSubTrack.h"
#include "Tracks/MovieSceneTimeWarpTrack.h"
#include "Tracks/MovieSceneCVarTrack.h"
#include "Tracks/MovieSceneBindingLifetimeTrack.h"
#include "Modules/ModuleManager.h"
#include "LevelSequencePlayer.h"
#include "Compilation/MovieSceneCompiledDataManager.h"
#include "Evaluation/MovieSceneEvaluationTemplateInstance.h"
#include "UniversalObjectLocators/AnimInstanceLocatorFragment.h"
#include "Engine/AssetUserData.h"
#include "Misc/App.h"
#include "Misc/DateTime.h"
#include "UObject/FortniteMainBranchObjectVersion.h"
#include "Bindings/MovieSceneSpawnableDirectorBlueprintBinding.h"
#include "Bindings/MovieSceneReplaceableDirectorBlueprintBinding.h"
#include "Bindings/MovieSceneSpawnableActorBinding.h"
#include "MovieSceneFolder.h"
#include "Sections/MovieSceneBindingLifetimeSection.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(LevelSequence)
#if WITH_EDITOR
#include "UObject/SequencerObjectVersion.h"
#include "UObject/ObjectRedirector.h"
ULevelSequence::FPostDuplicateEvent ULevelSequence::PostDuplicateEvent;
ULevelSequence::FFixupDynamicBindingsEvent ULevelSequence::FixupDynamicBindingsEvent;
#endif
static TAutoConsoleVariable<int32> CVarDefaultLockEngineToDisplayRate(
TEXT("LevelSequence.DefaultLockEngineToDisplayRate"),
0,
TEXT("0: Playback locked to playback frames\n1: Unlocked playback with sub frame interpolation"),
ECVF_Default);
static TAutoConsoleVariable<FString> CVarDefaultTickResolution(
TEXT("LevelSequence.DefaultTickResolution"),
TEXT("24000fps"),
TEXT("Specifies the default tick resolution for newly created level sequences. Examples: 30 fps, 120/1 (120 fps), 30000/1001 (29.97), 0.01s (10ms)."),
ECVF_Default);
static TAutoConsoleVariable<FString> CVarDefaultDisplayRate(
TEXT("LevelSequence.DefaultDisplayRate"),
TEXT("30fps"),
TEXT("Specifies the default display frame rate for newly created level sequences; also defines frame locked frame rate where sequences are set to be frame locked. Examples: 30 fps, 120/1 (120 fps), 30000/1001 (29.97), 0.01s (10ms)."),
ECVF_Default);
static TAutoConsoleVariable<int32> CVarDefaultClockSource(
TEXT("LevelSequence.DefaultClockSource"),
0,
TEXT("Specifies the default clock source for newly created level sequences. 0: Tick, 1: Platform, 2: Audio, 3: RelativeTimecode, 4: Timecode, 5: Custom"),
ECVF_Default);
ULevelSequence::ULevelSequence(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, MovieScene(nullptr)
{
bParentContextsAreSignificant = true;
}
void ULevelSequence::Initialize()
{
MovieScene = NewObject<UMovieScene>(this, NAME_None, RF_Transactional);
const bool bFrameLocked = CVarDefaultLockEngineToDisplayRate.GetValueOnGameThread() != 0;
MovieScene->SetEvaluationType( bFrameLocked ? EMovieSceneEvaluationType::FrameLocked : EMovieSceneEvaluationType::WithSubFrames );
FFrameRate TickResolution(60000, 1);
TryParseString(TickResolution, *CVarDefaultTickResolution.GetValueOnGameThread());
MovieScene->SetTickResolutionDirectly(TickResolution);
FFrameRate DisplayRate(30, 1);
TryParseString(DisplayRate, *CVarDefaultDisplayRate.GetValueOnGameThread());
MovieScene->SetDisplayRate(DisplayRate);
int32 ClockSource = CVarDefaultClockSource.GetValueOnGameThread();
MovieScene->SetClockSource((EUpdateClockSource)ClockSource);
#if WITH_EDITOR
UMovieSceneMetaData* MetaData = FindOrAddMetaData<UMovieSceneMetaData>();
MetaData->SetCreated(FDateTime::UtcNow());
MetaData->SetAuthor(FApp::GetSessionOwner());
#endif
}
UObject* ULevelSequence::MakeSpawnableTemplateFromInstance(UObject& InSourceObject, FName ObjectName)
{
return MovieSceneHelpers::MakeSpawnableTemplateFromInstance(InSourceObject, MovieScene, ObjectName);
}
bool ULevelSequence::CanAnimateObject(UObject& InObject) const
{
return InObject.IsA<AActor>() || InObject.IsA<UActorComponent>() || InObject.IsA<UAnimInstance>();
}
#if WITH_EDITOR
ETrackSupport ULevelSequence::IsTrackSupportedImpl(TSubclassOf<class UMovieSceneTrack> InTrackClass) const
{
if (InTrackClass == UMovieScene3DAttachTrack::StaticClass() ||
InTrackClass == UMovieScene3DPathTrack::StaticClass() ||
InTrackClass == UMovieSceneAudioTrack::StaticClass() ||
InTrackClass == UMovieSceneCameraCutTrack::StaticClass() ||
InTrackClass == UMovieSceneCinematicShotTrack::StaticClass() ||
InTrackClass == UMovieSceneEventTrack::StaticClass() ||
InTrackClass == UMovieSceneFadeTrack::StaticClass() ||
InTrackClass == UMovieSceneLevelVisibilityTrack::StaticClass() ||
InTrackClass == UMovieSceneDataLayerTrack::StaticClass() ||
InTrackClass == UMovieSceneMaterialParameterCollectionTrack::StaticClass() ||
InTrackClass == UMovieSceneSkeletalAnimationTrack::StaticClass() ||
InTrackClass == UMovieSceneSlomoTrack::StaticClass() ||
InTrackClass == UMovieSceneSpawnTrack::StaticClass() ||
InTrackClass == UMovieSceneSubTrack::StaticClass() ||
InTrackClass == UMovieSceneCVarTrack::StaticClass() ||
InTrackClass == UMovieSceneBindingLifetimeTrack::StaticClass() ||
InTrackClass == UMovieSceneTimeWarpTrack::StaticClass())
{
return ETrackSupport::Supported;
}
return Super::IsTrackSupportedImpl(InTrackClass);
}
bool ULevelSequence::IsFilterSupportedImpl(const FString& InFilterName) const
{
static const TArray<FString> SupportedFilters = {
TEXT("Audio"),
TEXT("Camera"),
TEXT("CameraCut"),
TEXT("CinematicShot"),
TEXT("Condition"),
TEXT("ControlRigControl"),
TEXT("DataLayer"),
TEXT("Event"),
TEXT("Fade"),
TEXT("Folder"),
TEXT("Keyed"),
TEXT("Group"),
TEXT("Level"),
TEXT("LevelVisibility"),
TEXT("Light"),
TEXT("LiveLink"),
TEXT("Media"),
TEXT("Niagara"),
TEXT("Particle"),
TEXT("Selected"),
TEXT("SelectedAnimLayersFilter"),
TEXT("SelectedControlRigControl"),
TEXT("SkeletalMesh"),
TEXT("Subsequence"),
TEXT("TimeDilation"),
TEXT("TimeWarp"),
TEXT("Unbound")
};
return SupportedFilters.Contains(InFilterName);
}
void ULevelSequence::GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS;
Super::GetAssetRegistryTags(OutTags);
PRAGMA_ENABLE_DEPRECATION_WARNINGS;
}
void ULevelSequence::GetAssetRegistryTags(FAssetRegistryTagsContext Context) const
{
#if WITH_EDITORONLY_DATA
if (DirectorBlueprint)
{
DirectorBlueprint->GetAssetRegistryTags(Context);
}
#endif
for (UObject* MetaData : MetaDataObjects)
{
IMovieSceneMetaDataInterface* MetaDataInterface = Cast<IMovieSceneMetaDataInterface>(MetaData);
if (MetaDataInterface)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS;
TArray<UObject::FAssetRegistryTag> DeprecatedFunctionTags;
MetaDataInterface->ExtendAssetRegistryTags(DeprecatedFunctionTags);
for (UObject::FAssetRegistryTag& Tag : DeprecatedFunctionTags)
{
Context.AddTag(MoveTemp(Tag));
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS;
MetaDataInterface->ExtendAssetRegistryTags(Context);
}
}
Super::GetAssetRegistryTags(Context);
}
void ULevelSequence::GetAssetRegistryTagMetadata(TMap<FName, FAssetRegistryTagMetadata>& OutMetadata) const
{
for (UObject* MetaData : MetaDataObjects)
{
IMovieSceneMetaDataInterface* MetaDataInterface = Cast<IMovieSceneMetaDataInterface>(MetaData);
if (MetaDataInterface)
{
MetaDataInterface->ExtendAssetRegistryTagMetaData(OutMetadata);
}
}
Super::GetAssetRegistryTagMetadata(OutMetadata);
}
void ULevelSequence::ThreadedPostLoadAssetRegistryTagsOverride(FPostLoadAssetRegistryTagsContext& Context) const
{
Super::ThreadedPostLoadAssetRegistryTagsOverride(Context);
// GetAssetRegistryTags appends the DirectorBlueprint tags to the World's tags, so we also have to run the Blueprint ThreadedPostLoadAssetRegistryTagsOverride
UBlueprint::PostLoadBlueprintAssetRegistryTags(Context);
}
void PurgeLegacyBlueprints(UObject* InObject, UPackage* Package)
{
if (UBlueprint* BP = Cast<UBlueprint>(InObject))
{
UPackage* TransientPackage = GetTransientPackage();
{
FString OldName = BP->GetName();
BP->ClearFlags(RF_Public);
BP->SetFlags(RF_Transient);
BP->RemoveFromRoot();
FName NewName = MakeUniqueObjectName(TransientPackage, UBlueprint::StaticClass(), *FString::Printf(TEXT("DEAD_SPAWNABLE_BLUEPRINT_%s"), *BP->GetName()));
BP->Rename(*NewName.ToString(), GetTransientPackage(), REN_NonTransactional | REN_DoNotDirty);
UE_LOG(LogLevelSequence, Log, TEXT("Discarding blueprint '%s' from package '%s'."), *OldName, *Package->GetName());
}
if (BP->GeneratedClass)
{
FName OldName = BP->GeneratedClass->GetFName();
UObject* OldOuter = BP->GeneratedClass->GetOuter();
UClass* SuperClass = BP->GeneratedClass->GetSuperClass();
if (UObject* ClassDefaultObject = BP->GeneratedClass->GetDefaultObject(false))
{
ClassDefaultObject->ClearFlags(RF_Public);
ClassDefaultObject->SetFlags(RF_Transient);
ClassDefaultObject->RemoveFromRoot();
}
BP->GeneratedClass->ClearFlags(RF_Public);
BP->GeneratedClass->SetFlags(RF_Transient);
BP->GeneratedClass->ClassFlags |= CLASS_Deprecated;
BP->GeneratedClass->RemoveFromRoot();
FName NewName = MakeUniqueObjectName(TransientPackage, BP->GeneratedClass, *FString::Printf(TEXT("DEAD_SPAWNABLE_BP_CLASS_%s_C"), *BP->GeneratedClass->ClassGeneratedBy->GetName()));
BP->GeneratedClass->Rename(*NewName.ToString(), GetTransientPackage(), REN_DoNotDirty | REN_NonTransactional);
if (SuperClass)
{
UObjectRedirector* Redirector = NewObject<UObjectRedirector>(OldOuter, OldName);
Redirector->DestinationObject = SuperClass;
UE_LOG(LogLevelSequence, Log, TEXT("Discarding generated class '%s' from package '%s'. Replacing with redirector to '%s'"), *OldName.ToString(), *Package->GetName(), *SuperClass->GetName());
}
else
{
UE_LOG(LogLevelSequence, Log, TEXT("Discarding generated class '%s' from package '%s'. Unable to create redirector due to no super class."), *OldName.ToString(), *Package->GetName());
}
}
}
}
#endif
void ULevelSequence::PostDuplicate(bool bDuplicateForPIE)
{
Super::PostDuplicate(bDuplicateForPIE);
#if WITH_EDITORONLY_DATA
if (DirectorBlueprint)
{
DirectorClass = DirectorBlueprint->GeneratedClass.Get();
// Remove the binding for the director blueprint recompilation and re-add it to be sure there is only one entry in the list
DirectorBlueprint->OnCompiled().RemoveAll(this);
DirectorBlueprint->OnCompiled().AddUObject(this, &ULevelSequence::OnDirectorRecompiled);
}
else
{
DirectorClass = nullptr;
}
#endif
#if WITH_EDITOR
if (PostDuplicateEvent.IsBound())
{
PostDuplicateEvent.Execute(this);
}
#endif
#if WITH_EDITOR
UMovieSceneMetaData* MetaData = FindOrAddMetaData<UMovieSceneMetaData>();
MetaData->SetCreated(FDateTime::UtcNow());
MetaData->SetAuthor(FApp::GetSessionOwner());
MetaData->SetNotes(FString()); // Intentionally clear the notes
#endif
}
void ULevelSequence::PostLoad()
{
Super::PostLoad();
#if WITH_EDITOR
if (MovieScene)
{
// Remove any invalid object bindings. This was moved from PostInitProperties
// because it has to happen after the asset has actually been serialized.
TSet<FGuid> ValidObjectBindings;
for (int32 Index = 0; Index < MovieScene->GetSpawnableCount(); ++Index)
{
ValidObjectBindings.Add(MovieScene->GetSpawnable(Index).GetGuid());
}
for (int32 Index = 0; Index < MovieScene->GetPossessableCount(); ++Index)
{
ValidObjectBindings.Add(MovieScene->GetPossessable(Index).GetGuid());
}
BindingReferences.RemoveInvalidBindings(ValidObjectBindings);
}
if (!DirectorBlueprint)
{
UBlueprint* PhantomDirector = FindObject<UBlueprint>(this, TEXT("SequenceDirector"));
if (!ensureMsgf(!PhantomDirector, TEXT("Phantom sequence director found in sequence '%s' which has a nullptr DirectorBlueprint. Re-assigning to prevent future crash."), *GetName()))
{
DirectorBlueprint = PhantomDirector;
}
}
if (DirectorBlueprint)
{
DirectorBlueprint->ClearFlags(RF_Standalone);
// Remove the binding for the director blueprint recompilation and re-add it to be sure there is only one entry in the list
DirectorBlueprint->OnCompiled().RemoveAll(this);
DirectorBlueprint->OnCompiled().AddUObject(this, &ULevelSequence::OnDirectorRecompiled);
if (DirectorBlueprint->Rename(*GetDirectorBlueprintName(), nullptr, (REN_NonTransactional | REN_DoNotDirty | REN_Test | REN_DontCreateRedirectors)))
{
DirectorBlueprint->Rename(*GetDirectorBlueprintName(), nullptr, (REN_NonTransactional | REN_DoNotDirty | REN_DontCreateRedirectors));
}
}
if (MovieScene)
{
TSet<FGuid> InvalidSpawnables;
for (int32 Index = 0; Index < MovieScene->GetSpawnableCount(); ++Index)
{
FMovieSceneSpawnable& Spawnable = MovieScene->GetSpawnable(Index);
if (!Spawnable.GetObjectTemplate())
{
if (Spawnable.GeneratedClass_DEPRECATED && Spawnable.GeneratedClass_DEPRECATED->ClassGeneratedBy)
{
const FName TemplateName = MakeUniqueObjectName(MovieScene, UObject::StaticClass(), Spawnable.GeneratedClass_DEPRECATED->ClassGeneratedBy->GetFName());
UObject* NewTemplate = NewObject<UObject>(MovieScene, Spawnable.GeneratedClass_DEPRECATED->GetSuperClass(), TemplateName);
if (NewTemplate)
{
Spawnable.CopyObjectTemplate(*NewTemplate, *this);
}
}
}
if (!Spawnable.GetObjectTemplate())
{
InvalidSpawnables.Add(Spawnable.GetGuid());
UE_LOG(LogLevelSequence, Warning, TEXT("Spawnable '%s' with ID '%s' does not have a valid object template"), *Spawnable.GetName(), *Spawnable.GetGuid().ToString());
}
}
}
if (GetLinkerCustomVersion(FSequencerObjectVersion::GUID) < FSequencerObjectVersion::PurgeSpawnableBlueprints)
{
// Remove any old generated classes from the package that will have been left behind from when we used blueprints for spawnables
{
UPackage* Package = GetOutermost();
TArray<UObject*> PackageSubobjects;
GetObjectsWithOuter(Package, PackageSubobjects, false);
for (UObject* ObjectInPackage : PackageSubobjects)
{
PurgeLegacyBlueprints(ObjectInPackage, Package);
}
}
}
for (TPair<FGuid, FLevelSequenceLegacyObjectReference>& Pair : ObjectReferences_DEPRECATED.Map)
{
if (Pair.Value.ObjectId.IsValid())
{
FUniversalObjectLocator NewLocator;
NewLocator.AddFragment<FLegacyLazyObjectPtrFragment>(Pair.Value.ObjectId.GetGuid());
BindingReferences.FMovieSceneBindingReferences::AddBinding(Pair.Key, MoveTemp(NewLocator));
}
else if (Pair.Value.ObjectPath.Len() > 0)
{
FUniversalObjectLocator NewLocator;
NewLocator.AddFragment<FSubObjectLocator>(Pair.Value.ObjectPath);
BindingReferences.FMovieSceneBindingReferences::AddBinding(Pair.Key, MoveTemp(NewLocator));
}
}
ObjectReferences_DEPRECATED.Map.Empty();
if (GetLinkerCustomVersion(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::LevelSequenceUpgradeDynamicBindings)
{
bool bConvertedDynamicBinding = ConvertOldSpawnables();
for (int32 Index = 0; Index < MovieScene->GetPossessableCount(); ++Index)
{
FMovieScenePossessable& Possessable = MovieScene->GetPossessable(Index);
if (Possessable.DynamicBinding_DEPRECATED.Function)
{
bConvertedDynamicBinding = true;
ConvertDynamicBindingPossessable(Possessable);
}
}
if (bConvertedDynamicBinding && FixupDynamicBindingsEvent.IsBound())
{
if (!GetPackage()->GetHasBeenEndLoaded())
{
FCoreUObjectDelegates::OnEndLoadPackage.AddUObject(this, &ULevelSequence::OnEndLoadPackage);
}
else
{
FixupDynamicBindingsEvent.Broadcast(this);
}
}
}
#endif
}
#if WITH_EDITOR
void ULevelSequence::OnEndLoadPackage(const FEndLoadPackageContext& Context)
{
FCoreUObjectDelegates::OnEndLoadPackage.RemoveAll(this);
if (FixupDynamicBindingsEvent.IsBound())
{
FixupDynamicBindingsEvent.Broadcast(this);
}
}
bool ULevelSequence::ConvertOldSpawnables()
{
bool bConvertedDynamicBinding = false;
while (MovieScene->GetSpawnableCount() > 0)
{
FMovieSceneSpawnable& Spawnable = MovieScene->GetSpawnable(0);
FMovieScenePossessable* CreatedPossessable = nullptr;
UObject* ObjectToConvert = Spawnable.GetObjectTemplate();
UClass* CustomBindingType = nullptr;
if (Spawnable.DynamicBinding_DEPRECATED.Function)
{
CustomBindingType = UMovieSceneSpawnableDirectorBlueprintBinding::StaticClass();
}
else
{
// Search through custom binding types to find one that best supports the template type
static TArray<const TSubclassOf<UMovieSceneCustomBinding>> CachedCustomBindingTypes;
static bool CustomBindingTypesCached = false;
if (!CustomBindingTypesCached)
{
CustomBindingTypesCached = true;
MovieSceneHelpers::GetPrioritySortedCustomBindingTypes(CachedCustomBindingTypes);
}
for (const TSubclassOf<UMovieSceneCustomBinding>& CandidateCustomBindingType : CachedCustomBindingTypes)
{
if (CandidateCustomBindingType && CandidateCustomBindingType->IsChildOf(UMovieSceneSpawnableBindingBase::StaticClass()) && CandidateCustomBindingType->GetDefaultObject<UMovieSceneCustomBinding>()->SupportsBindingCreationFromObject(Spawnable.GetObjectTemplate()))
{
CustomBindingType = CandidateCustomBindingType;
break;
}
}
}
if (!CustomBindingType)
{
UE_LOG(LogLevelSequence, Warning, TEXT("Could not upgrade Spawnable '%s' with ID '%s'"), *Spawnable.GetName(), *Spawnable.GetGuid().ToString());
break;
}
UMovieSceneCustomBinding* NewCustomBinding = CustomBindingType->GetDefaultObject<UMovieSceneCustomBinding>()->CreateNewCustomBinding(ObjectToConvert, *MovieScene);
if (!NewCustomBinding)
{
UE_LOG(LogLevelSequence, Warning, TEXT("Could not upgrade Spawnable '%s' with ID '%s'"), *Spawnable.GetName(), *Spawnable.GetGuid().ToString());
break;
}
if (UMovieSceneSpawnableDirectorBlueprintBinding* DirectorBlueprintBinding = Cast<UMovieSceneSpawnableDirectorBlueprintBinding>(NewCustomBinding))
{
// Copy over the binding info
DirectorBlueprintBinding->DynamicBinding = Spawnable.DynamicBinding_DEPRECATED;
bConvertedDynamicBinding = true;
}
else if (UMovieSceneSpawnableActorBinding* SpawnableActorBinding = Cast<UMovieSceneSpawnableActorBinding>(NewCustomBinding))
{
SpawnableActorBinding->bNetAddressableName = Spawnable.bNetAddressableName;
SpawnableActorBinding->LevelName = Spawnable.LevelName;
SpawnableActorBinding->bContinuouslyRespawn = Spawnable.bContinuouslyRespawn;
SpawnableActorBinding->SpawnOwnership = Spawnable.GetSpawnOwnership();
}
FString PossessableName = Spawnable.GetName();
FGuid SpawnableGuid = Spawnable.GetGuid();
FMovieScenePossessable NewPossessable(PossessableName, NewCustomBinding->GetBoundObjectClass());
// Steal guid
NewPossessable.SetGuid(SpawnableGuid);
if (FMovieSceneBinding* SpawnableBinding = MovieScene->FindBinding(SpawnableGuid))
{
// Copy binding and track references to be tied to the new possessable
FMovieSceneBinding PossessableBinding = *SpawnableBinding;
// Add the custom binding. We use the spawnable binding here since it won't have a binding reference yet, and we need to steal the id
BindingReferences.AddOrReplaceBinding(SpawnableGuid, NewCustomBinding, 0);
// Remove the spawnable and all its' sub tracks
if (MovieScene->RemoveSpawnable(SpawnableGuid))
{
// Add the new possessable with the copied binding
MovieScene->AddPossessable(NewPossessable, PossessableBinding);
}
}
}
return bConvertedDynamicBinding;
}
void ULevelSequence::ConvertDynamicBindingPossessable(FMovieScenePossessable& Possessable)
{
UMovieSceneReplaceableDirectorBlueprintBinding* NewCustomBinding = nullptr;
const FName InstancedBindingName = MakeUniqueObjectName(MovieScene, UObject::StaticClass(), *FString(Possessable.GetName() + TEXT("_CustomBinding")));
NewCustomBinding = NewObject<UMovieSceneReplaceableDirectorBlueprintBinding>(MovieScene, UMovieSceneReplaceableDirectorBlueprintBinding::StaticClass(), InstancedBindingName, RF_Transactional);
if (!NewCustomBinding)
{
return;
}
if (UMovieSceneReplaceableDirectorBlueprintBinding* DirectorBlueprintBinding = Cast<UMovieSceneReplaceableDirectorBlueprintBinding>(NewCustomBinding))
{
// Copy over the binding info
DirectorBlueprintBinding->DynamicBinding = Possessable.DynamicBinding_DEPRECATED;
Possessable.DynamicBinding_DEPRECATED = FMovieSceneDynamicBinding();
}
// Replace the current binding with the new one. We call RemoveBinding first because if there were multiple bindings for this track,
// they would have been overridden with the Dynamic Binding anyway, and so we ensure that stays the same by keeping only one binding
BindingReferences.RemoveBinding(Possessable.GetGuid());
BindingReferences.AddOrReplaceBinding(Possessable.GetGuid(), NewCustomBinding, 0);
// Add a binding lifetime track if not present
UMovieSceneBindingLifetimeTrack* BindingLifetimeTrack = Cast<UMovieSceneBindingLifetimeTrack>(MovieScene->FindTrack(UMovieSceneBindingLifetimeTrack::StaticClass(), Possessable.GetGuid(), NAME_None));
if (!BindingLifetimeTrack)
{
BindingLifetimeTrack = Cast<UMovieSceneBindingLifetimeTrack>(MovieScene->AddTrack(UMovieSceneBindingLifetimeTrack::StaticClass(), Possessable.GetGuid()));
}
if (BindingLifetimeTrack && BindingLifetimeTrack->GetAllSections().IsEmpty())
{
UMovieSceneBindingLifetimeSection* BindingLifetimeSection = Cast<UMovieSceneBindingLifetimeSection>(BindingLifetimeTrack->CreateNewSection());
BindingLifetimeSection->SetRange(TRange<FFrameNumber>::All());
BindingLifetimeTrack->AddSection(*BindingLifetimeSection);
}
}
#endif
#if WITH_EDITORONLY_DATA
void ULevelSequence::DeclareConstructClasses(TArray<FTopLevelAssetPath>& OutConstructClasses, const UClass* SpecificSubclass)
{
Super::DeclareConstructClasses(OutConstructClasses, SpecificSubclass);
OutConstructClasses.Add(FTopLevelAssetPath(UObjectRedirector::StaticClass()));
OutConstructClasses.Add(FTopLevelAssetPath(UMovieSceneSpawnableActorBinding::StaticClass()));
OutConstructClasses.Add(FTopLevelAssetPath(UMovieSceneSpawnableDirectorBlueprintBinding::StaticClass()));
}
#endif
void ULevelSequence::PostInitProperties()
{
Super::PostInitProperties();
}
bool ULevelSequence::Rename(const TCHAR* NewName, UObject* NewOuter, ERenameFlags Flags)
{
bool bRetVal = Super::Rename(NewName, NewOuter, Flags);
#if WITH_EDITOR
if (DirectorBlueprint)
{
DirectorBlueprint->Rename(*GetDirectorBlueprintName(), this, Flags);
}
#endif
return bRetVal;
}
void ULevelSequence::BindPossessableObject(const FGuid& ObjectId, UObject& PossessedObject, UObject* Context)
{
if (Context)
{
BindingReferences.AddBinding(ObjectId, &PossessedObject, Context);
}
}
bool ULevelSequence::CanPossessObject(UObject& Object, UObject* InPlaybackContext) const
{
return true;
}
void ULevelSequence::LocateBoundObjects(const FGuid& ObjectId, UObject* Context, const FLevelSequenceBindingReference::FResolveBindingParams& InResolveBindingParams, TArray<UObject*, TInlineAllocator<1>>& OutObjects) const
{
using namespace UE::UniversalObjectLocator;
TResolveParamsWithBuffer<128> ResolveParams;
ResolveParams.AddParameter(FActorLocatorFragmentResolveParameter::ParameterType,
InResolveBindingParams.StreamingWorld,
InResolveBindingParams.WorldPartitionResolveData ? InResolveBindingParams.WorldPartitionResolveData->ContainerID : FActorContainerID(),
InResolveBindingParams.WorldPartitionResolveData ? InResolveBindingParams.WorldPartitionResolveData->SourceWorldAssetPath : InResolveBindingParams.StreamedLevelAssetPath
);
LocateBoundObjects(ObjectId, ResolveParams, nullptr, OutObjects);
}
FGuid ULevelSequence::FindBindingFromObject(UObject* InObject, UObject* Context) const
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
return UMovieSceneSequence::FindBindingFromObject(InObject, Context);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
FGuid ULevelSequence::FindBindingFromObject(UObject* InObject, TSharedRef<const UE::MovieScene::FSharedPlaybackState> SharedPlaybackState) const
{
if (InObject)
{
if (FMovieSceneEvaluationState* EvaluationState = SharedPlaybackState->FindCapability<FMovieSceneEvaluationState>())
{
FMovieSceneSequenceID SequenceID = EvaluationState->FindSequenceId(this);
return EvaluationState->FindCachedObjectId(*InObject, SequenceID, SharedPlaybackState);
}
}
return FGuid();
}
void ULevelSequence::GatherExpiredObjects(const FMovieSceneObjectCache& InObjectCache, TArray<FGuid>& OutInvalidIDs) const
{
using namespace UE::UniversalObjectLocator;
TArrayView<const FMovieSceneBindingReference> References = BindingReferences.GetAllReferences();
for (int32 Index = 0; Index < References.Num(); ++Index)
{
const FMovieSceneBindingReference& Reference = References[Index];
if (Reference.Locator.GetLastFragmentTypeHandle() == FAnimInstanceLocatorFragment::FragmentType)
{
for (TWeakObjectPtr<> WeakObject : InObjectCache.IterateBoundObjects(Reference.ID))
{
UAnimInstance* AnimInstance = Cast<UAnimInstance>(WeakObject.Get());
if (!AnimInstance || !AnimInstance->GetOwningComponent() || AnimInstance->GetOwningComponent()->GetAnimInstance() != AnimInstance)
{
OutInvalidIDs.Add(Reference.ID);
}
}
// Skip over subsequent matched IDs
while (Index < References.Num()-1 && References[Index+1].ID == Reference.ID)
{
++Index;
}
}
}
}
UMovieScene* ULevelSequence::GetMovieScene() const
{
return MovieScene;
}
UObject* ULevelSequence::GetParentObject(UObject* Object) const
{
if (UActorComponent* Component = Cast<UActorComponent>(Object))
{
return Component->GetOwner();
}
if (UAnimInstance* AnimInstance = Cast<UAnimInstance>(Object))
{
if (AnimInstance->GetWorld())
{
return AnimInstance->GetOwningComponent();
}
}
return nullptr;
}
bool ULevelSequence::AllowsSpawnableObjects() const
{
TArray<const TSubclassOf<UMovieSceneCustomBinding>> CustomBindingTypes;
MovieSceneHelpers::GetPrioritySortedCustomBindingTypes(CustomBindingTypes);
for (const TSubclassOf<UMovieSceneCustomBinding>& CustomBindingType : CustomBindingTypes)
{
const bool bIsCustomSpawnableBinding = CustomBindingType->IsChildOf<UMovieSceneSpawnableBindingBase>();
if (bIsCustomSpawnableBinding)
{
return true;
}
}
return false;
}
bool ULevelSequence::AllowsCustomBindings() const
{
return true;
}
bool ULevelSequence::CanRebindPossessable(const FMovieScenePossessable& InPossessable) const
{
return !InPossessable.GetParent().IsValid();
}
void ULevelSequence::UnbindPossessableObjects(const FGuid& ObjectId)
{
BindingReferences.RemoveBinding(ObjectId);
}
void ULevelSequence::UnbindObjects(const FGuid& ObjectId, const TArray<UObject*>& InObjects, UObject* InContext)
{
BindingReferences.RemoveObjects(ObjectId, InObjects, InContext);
}
void ULevelSequence::UnbindInvalidObjects(const FGuid& ObjectId, UObject* InContext)
{
BindingReferences.RemoveInvalidObjects(ObjectId, InContext);
}
const FMovieSceneBindingReferences* ULevelSequence::GetBindingReferences() const
{
return &BindingReferences;
}
#if WITH_EDITOR
UBlueprint* ULevelSequence::GetDirectorBlueprint() const
{
return DirectorBlueprint;
}
FString ULevelSequence::GetDirectorBlueprintName() const
{
return GetDisplayName().ToString() + "_DirectorBP";
}
void ULevelSequence::SetDirectorBlueprint(UBlueprint* NewDirectorBlueprint)
{
if (DirectorBlueprint)
{
DirectorBlueprint->OnCompiled().RemoveAll(this);
}
DirectorBlueprint = NewDirectorBlueprint;
if (DirectorBlueprint)
{
DirectorClass = NewDirectorBlueprint->GeneratedClass.Get();
DirectorBlueprint->OnCompiled().AddUObject(this, &ULevelSequence::OnDirectorRecompiled);
}
else
{
DirectorClass = nullptr;
}
MarkAsChanged();
}
void ULevelSequence::OnDirectorRecompiled(UBlueprint* InCompiledBlueprint)
{
ensure(InCompiledBlueprint == DirectorBlueprint);
DirectorClass = DirectorBlueprint->GeneratedClass.Get();
MarkAsChanged();
}
FGuid ULevelSequence::FindOrAddBinding(UObject* InObject)
{
using namespace UE::MovieScene;
UObject* PlaybackContext = InObject ? InObject->GetWorld() : nullptr;
if (!InObject || !PlaybackContext)
{
return FGuid();
}
AActor* Actor = Cast<AActor>(InObject);
if (Actor && Actor->ActorHasTag("SequencerActor"))
{
TOptional<FMovieSceneSpawnableAnnotation> Annotation = FMovieSceneSpawnableAnnotation::Find(Actor);
if (Annotation.IsSet() && Annotation->OriginatingSequence == this)
{
return Annotation->ObjectBindingID;
}
// If this actor is a spawnable and is not in the same originating sequence, it's likely a spawnable that will be possessed.
// SetSpawnableObjectBindingID will need to be called on that possessable.
}
UObject* ParentObject = GetParentObject(InObject);
FGuid ParentGuid = ParentObject ? FindOrAddBinding(ParentObject) : FGuid();
if (ParentObject && !ParentGuid.IsValid())
{
UE_LOG(LogLevelSequence, Error, TEXT("Unable to possess object '%s' because it's parent could not be bound."), *InObject->GetName());
return FGuid();
}
// Perform a potentially slow lookup of every possessable binding in the sequence to see if we already have this
{
FSharedPlaybackStateCreateParams CreateParams;
CreateParams.PlaybackContext = PlaybackContext;
TSharedRef<FSharedPlaybackState> TransientPlaybackState = MakeShared<FSharedPlaybackState>(*this, CreateParams);
FMovieSceneEvaluationState State;
TransientPlaybackState->AddCapabilityRaw(&State);
State.AssignSequence(MovieSceneSequenceID::Root, *this, TransientPlaybackState);
FGuid ExistingID = State.FindObjectId(*InObject, MovieSceneSequenceID::Root, TransientPlaybackState);
if (ExistingID.IsValid())
{
return ExistingID;
}
}
// We have to possess this object
if (!CanPossessObject(*InObject, PlaybackContext))
{
return FGuid();
}
FString NewName = Actor ? Actor->GetActorLabel() : InObject->GetName();
const FGuid NewGuid = MovieScene->AddPossessable(NewName, InObject->GetClass());
// Attempt to use the parent as a context if necessary
UObject* BindingContext = ParentObject && AreParentContextsSignificant() ? ParentObject : PlaybackContext;
// Set up parent/child guids for possessables within spawnables
if (ParentGuid.IsValid())
{
FMovieScenePossessable* ChildPossessable = MovieScene->FindPossessable(NewGuid);
if (ensure(ChildPossessable))
{
ChildPossessable->SetParent(ParentGuid, MovieScene);
}
}
BindPossessableObject(NewGuid, *InObject, BindingContext);
return NewGuid;
}
FGuid ULevelSequence::CreatePossessable(UObject* ObjectToPossess)
{
return FindOrAddBinding(ObjectToPossess);
}
FGuid ULevelSequence::CreateSpawnable(UObject* ObjectToSpawn)
{
if (!MovieScene || !ObjectToSpawn)
{
return FGuid();
}
FGuid NewGuid = MovieSceneHelpers::TryCreateCustomSpawnableBinding(this, ObjectToSpawn);
UMovieSceneSpawnTrack* NewSpawnTrack = MovieScene->AddTrack<UMovieSceneSpawnTrack>(NewGuid);
if (NewSpawnTrack)
{
NewSpawnTrack->Modify();
NewSpawnTrack->AddSection(*NewSpawnTrack->CreateNewSection());
}
return NewGuid;
}
#endif // WITH_EDITOR
UObject* ULevelSequence::CreateDirectorInstance(TSharedRef<const FSharedPlaybackState> SharedPlaybackState, FMovieSceneSequenceID SequenceID)
{
UObject* DirectorOuter = SharedPlaybackState->GetPlaybackContext();
IMovieScenePlayer* OptionalPlayer = UE::MovieScene::FPlayerIndexPlaybackCapability::GetPlayer(SharedPlaybackState);
#if WITH_EDITOR
if (!UMovieScene::IsTrackClassAllowed(ULevelSequenceDirector::StaticClass()))
{
return nullptr;
}
#endif
if (DirectorClass && DirectorOuter && DirectorClass->IsChildOf(ULevelSequenceDirector::StaticClass()))
{
FName DirectorName = NAME_None;
#if WITH_EDITOR
// Give it a pretty name so it shows up in the debug instances drop down nicely
DirectorName = MakeUniqueObjectName(DirectorOuter, DirectorClass, *(GetFName().ToString() + TEXT("_Director")));
#endif
ULevelSequencePlayer* LevelSequencePlayer = nullptr;
if (OptionalPlayer)
{
LevelSequencePlayer = Cast<ULevelSequencePlayer>(OptionalPlayer->AsUObject());
}
ULevelSequenceDirector* NewDirector = NewObject<ULevelSequenceDirector>(DirectorOuter, DirectorClass, DirectorName, RF_Transient);
NewDirector->SubSequenceID = SequenceID.GetInternalValue();
NewDirector->WeakLinker = SharedPlaybackState->GetLinker();
NewDirector->InstanceID = SharedPlaybackState->GetRootInstanceHandle().InstanceID;
NewDirector->InstanceSerial = SharedPlaybackState->GetRootInstanceHandle().InstanceSerial;
NewDirector->Player = LevelSequencePlayer;
NewDirector->MovieScenePlayerIndex = OptionalPlayer ? OptionalPlayer->GetUniqueIndex() : INDEX_NONE;
NewDirector->OnCreated();
return NewDirector;
}
return nullptr;
}
void ULevelSequence::AddAssetUserData(UAssetUserData* InUserData)
{
if (InUserData != NULL)
{
UAssetUserData* ExistingData = GetAssetUserDataOfClass(InUserData->GetClass());
if (ExistingData != NULL)
{
AssetUserData.Remove(ExistingData);
}
AssetUserData.Add(InUserData);
}
}
UAssetUserData* ULevelSequence::GetAssetUserDataOfClass(TSubclassOf<UAssetUserData> InUserDataClass)
{
for (int32 DataIdx = 0; DataIdx < AssetUserData.Num(); DataIdx++)
{
UAssetUserData* Datum = AssetUserData[DataIdx];
if (Datum != NULL && Datum->IsA(InUserDataClass))
{
return Datum;
}
}
return NULL;
}
void ULevelSequence::RemoveUserDataOfClass(TSubclassOf<UAssetUserData> InUserDataClass)
{
for (int32 DataIdx = 0; DataIdx < AssetUserData.Num(); DataIdx++)
{
UAssetUserData* Datum = AssetUserData[DataIdx];
if (Datum != NULL && Datum->IsA(InUserDataClass))
{
AssetUserData.RemoveAt(DataIdx);
return;
}
}
}
const TArray<UAssetUserData*>* ULevelSequence::GetAssetUserDataArray() const
{
return &ToRawPtrTArrayUnsafe(AssetUserData);
}
#if WITH_EDITOR
void ULevelSequence::IterateDynamicBindings(TFunction<void(const FGuid&, FMovieSceneDynamicBinding&)> InCallback)
{
for (FMovieSceneBindingReference& BindingReference : BindingReferences.GetAllReferences())
{
if (BindingReference.CustomBinding)
{
if (UMovieSceneReplaceableDirectorBlueprintBinding* ReplaceableDirectorBlueprintBinding = Cast<UMovieSceneReplaceableDirectorBlueprintBinding>(BindingReference.CustomBinding))
{
InCallback(BindingReference.ID, ReplaceableDirectorBlueprintBinding->DynamicBinding);
}
// We can't use 'AsSpawnable' here because we don't have playback state and we might not have a world context.
// This should only be called from an editor context though, so we can just check the inner spawnable.
// If the binding is itself a spawnable director blueprint binding, then iterate over it
if (UMovieSceneSpawnableDirectorBlueprintBinding* SpawnableDirectorBlueprintBinding = Cast<UMovieSceneSpawnableDirectorBlueprintBinding>(BindingReference.CustomBinding))
{
InCallback(BindingReference.ID, SpawnableDirectorBlueprintBinding->DynamicBinding);
}
else if (UMovieSceneReplaceableBindingBase* ReplaceableBinding = Cast<UMovieSceneReplaceableBindingBase>(BindingReference.CustomBinding))
{
if (UMovieSceneSpawnableDirectorBlueprintBinding* InnerSpawnableDirectorBlueprintBinding = Cast<UMovieSceneSpawnableDirectorBlueprintBinding>(ReplaceableBinding->PreviewSpawnable))
{
InCallback(BindingReference.ID, InnerSpawnableDirectorBlueprintBinding->DynamicBinding);
}
}
}
}
}
#endif