// 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 CVarDefaultLockEngineToDisplayRate( TEXT("LevelSequence.DefaultLockEngineToDisplayRate"), 0, TEXT("0: Playback locked to playback frames\n1: Unlocked playback with sub frame interpolation"), ECVF_Default); static TAutoConsoleVariable 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 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 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(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(); 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() || InObject.IsA() || InObject.IsA(); } #if WITH_EDITOR ETrackSupport ULevelSequence::IsTrackSupportedImpl(TSubclassOf 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 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& 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(MetaData); if (MetaDataInterface) { PRAGMA_DISABLE_DEPRECATION_WARNINGS; TArray 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& OutMetadata) const { for (UObject* MetaData : MetaDataObjects) { IMovieSceneMetaDataInterface* MetaDataInterface = Cast(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(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(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(); 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 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(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 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(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 PackageSubobjects; GetObjectsWithOuter(Package, PackageSubobjects, false); for (UObject* ObjectInPackage : PackageSubobjects) { PurgeLegacyBlueprints(ObjectInPackage, Package); } } } for (TPair& Pair : ObjectReferences_DEPRECATED.Map) { if (Pair.Value.ObjectId.IsValid()) { FUniversalObjectLocator NewLocator; NewLocator.AddFragment(Pair.Value.ObjectId.GetGuid()); BindingReferences.FMovieSceneBindingReferences::AddBinding(Pair.Key, MoveTemp(NewLocator)); } else if (Pair.Value.ObjectPath.Len() > 0) { FUniversalObjectLocator NewLocator; NewLocator.AddFragment(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> CachedCustomBindingTypes; static bool CustomBindingTypesCached = false; if (!CustomBindingTypesCached) { CustomBindingTypesCached = true; MovieSceneHelpers::GetPrioritySortedCustomBindingTypes(CachedCustomBindingTypes); } for (const TSubclassOf& CandidateCustomBindingType : CachedCustomBindingTypes) { if (CandidateCustomBindingType && CandidateCustomBindingType->IsChildOf(UMovieSceneSpawnableBindingBase::StaticClass()) && CandidateCustomBindingType->GetDefaultObject()->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()->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(NewCustomBinding)) { // Copy over the binding info DirectorBlueprintBinding->DynamicBinding = Spawnable.DynamicBinding_DEPRECATED; bConvertedDynamicBinding = true; } else if (UMovieSceneSpawnableActorBinding* SpawnableActorBinding = Cast(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(MovieScene, UMovieSceneReplaceableDirectorBlueprintBinding::StaticClass(), InstancedBindingName, RF_Transactional); if (!NewCustomBinding) { return; } if (UMovieSceneReplaceableDirectorBlueprintBinding* DirectorBlueprintBinding = Cast(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(MovieScene->FindTrack(UMovieSceneBindingLifetimeTrack::StaticClass(), Possessable.GetGuid(), NAME_None)); if (!BindingLifetimeTrack) { BindingLifetimeTrack = Cast(MovieScene->AddTrack(UMovieSceneBindingLifetimeTrack::StaticClass(), Possessable.GetGuid())); } if (BindingLifetimeTrack && BindingLifetimeTrack->GetAllSections().IsEmpty()) { UMovieSceneBindingLifetimeSection* BindingLifetimeSection = Cast(BindingLifetimeTrack->CreateNewSection()); BindingLifetimeSection->SetRange(TRange::All()); BindingLifetimeTrack->AddSection(*BindingLifetimeSection); } } #endif #if WITH_EDITORONLY_DATA void ULevelSequence::DeclareConstructClasses(TArray& 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>& 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 SharedPlaybackState) const { if (InObject) { if (FMovieSceneEvaluationState* EvaluationState = SharedPlaybackState->FindCapability()) { FMovieSceneSequenceID SequenceID = EvaluationState->FindSequenceId(this); return EvaluationState->FindCachedObjectId(*InObject, SequenceID, SharedPlaybackState); } } return FGuid(); } void ULevelSequence::GatherExpiredObjects(const FMovieSceneObjectCache& InObjectCache, TArray& OutInvalidIDs) const { using namespace UE::UniversalObjectLocator; TArrayView 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(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(Object)) { return Component->GetOwner(); } if (UAnimInstance* AnimInstance = Cast(Object)) { if (AnimInstance->GetWorld()) { return AnimInstance->GetOwningComponent(); } } return nullptr; } bool ULevelSequence::AllowsSpawnableObjects() const { TArray> CustomBindingTypes; MovieSceneHelpers::GetPrioritySortedCustomBindingTypes(CustomBindingTypes); for (const TSubclassOf& CustomBindingType : CustomBindingTypes) { const bool bIsCustomSpawnableBinding = CustomBindingType->IsChildOf(); 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& 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(InObject); if (Actor && Actor->ActorHasTag("SequencerActor")) { TOptional 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 TransientPlaybackState = MakeShared(*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(NewGuid); if (NewSpawnTrack) { NewSpawnTrack->Modify(); NewSpawnTrack->AddSection(*NewSpawnTrack->CreateNewSection()); } return NewGuid; } #endif // WITH_EDITOR UObject* ULevelSequence::CreateDirectorInstance(TSharedRef 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(OptionalPlayer->AsUObject()); } ULevelSequenceDirector* NewDirector = NewObject(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 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 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* ULevelSequence::GetAssetUserDataArray() const { return &ToRawPtrTArrayUnsafe(AssetUserData); } #if WITH_EDITOR void ULevelSequence::IterateDynamicBindings(TFunction InCallback) { for (FMovieSceneBindingReference& BindingReference : BindingReferences.GetAllReferences()) { if (BindingReference.CustomBinding) { if (UMovieSceneReplaceableDirectorBlueprintBinding* ReplaceableDirectorBlueprintBinding = Cast(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(BindingReference.CustomBinding)) { InCallback(BindingReference.ID, SpawnableDirectorBlueprintBinding->DynamicBinding); } else if (UMovieSceneReplaceableBindingBase* ReplaceableBinding = Cast(BindingReference.CustomBinding)) { if (UMovieSceneSpawnableDirectorBlueprintBinding* InnerSpawnableDirectorBlueprintBinding = Cast(ReplaceableBinding->PreviewSpawnable)) { InCallback(BindingReference.ID, InnerSpawnableDirectorBlueprintBinding->DynamicBinding); } } } } } #endif