You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
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]
980 lines
33 KiB
C++
980 lines
33 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/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::IsTrackSupported(TSubclassOf<class UMovieSceneTrack> InTrackClass) const
|
|
{
|
|
if (!UMovieScene::IsTrackClassAllowed(InTrackClass))
|
|
{
|
|
return ETrackSupport::NotSupported;
|
|
}
|
|
|
|
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())
|
|
{
|
|
return ETrackSupport::Supported;
|
|
}
|
|
|
|
return Super::IsTrackSupported(InTrackClass);
|
|
}
|
|
|
|
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_ForceNoResetLoaders|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( BP->GeneratedClass->ClassDefaultObject )
|
|
{
|
|
BP->GeneratedClass->ClassDefaultObject->ClearFlags(RF_Public);
|
|
BP->GeneratedClass->ClassDefaultObject->SetFlags(RF_Transient);
|
|
BP->GeneratedClass->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|REN_ForceNoResetLoaders));
|
|
|
|
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_ForceNoResetLoaders|REN_DoNotDirty|REN_Test)))
|
|
{
|
|
DirectorBlueprint->Rename(*GetDirectorBlueprintName(), nullptr, (REN_NonTransactional|REN_ForceNoResetLoaders|REN_DoNotDirty));
|
|
}
|
|
}
|
|
|
|
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())
|
|
{
|
|
FixupDynamicBindingsEvent.Broadcast(this);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
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()));
|
|
}
|
|
#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
|
|
{
|
|
return BindingReferences.FindBindingFromObject(InObject, Context);
|
|
}
|
|
|
|
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
|
|
{
|
|
#if WITH_EDITOR
|
|
if (!UMovieScene::IsTrackClassAllowed(UMovieSceneSpawnTrack::StaticClass()))
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void ULevelSequence::IterateDynamicBindings(const TSharedRef<UE::MovieScene::FSharedPlaybackState> SharedPlaybackState, 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);
|
|
}
|
|
|
|
if (UMovieSceneSpawnableDirectorBlueprintBinding* SpawnableDirectorBlueprintBinding = Cast<UMovieSceneSpawnableDirectorBlueprintBinding>(BindingReference.CustomBinding->AsSpawnable(SharedPlaybackState)))
|
|
{
|
|
InCallback(BindingReference.ID, SpawnableDirectorBlueprintBinding->DynamicBinding);
|
|
}
|
|
}
|
|
}
|
|
}
|