You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
This fixes OnInitialize() getting called (which has a workaround for edit modes, unfortunately) when the day sequence is active. Moved callbacks to within OnInitialize since they don't need to run unless the track editor is active. Note, derived class of MovieSceneSequence that need the Skeletal Animation Track Editor, should implement IsTrackSupported. #rb mike.zyracki [FYI] lonnie.li #preflight 636a9787dc30a4ce96aa2fa2 [CL 23069801 by max chen in ue5-main branch]
758 lines
24 KiB
C++
758 lines
24 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "LevelSequence.h"
|
|
#include "ILevelSequenceMetaData.h"
|
|
#include "Engine/EngineTypes.h"
|
|
#include "HAL/IConsoleManager.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/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 "Modules/ModuleManager.h"
|
|
#include "LevelSequencePlayer.h"
|
|
#include "Compilation/MovieSceneCompiledDataManager.h"
|
|
#include "Evaluation/MovieSceneEvaluationTemplateInstance.h"
|
|
#include "Engine/AssetUserData.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(LevelSequence)
|
|
|
|
|
|
#if WITH_EDITOR
|
|
#include "UObject/SequencerObjectVersion.h"
|
|
#include "UObject/ObjectRedirector.h"
|
|
|
|
ULevelSequence::FPostDuplicateEvent ULevelSequence::PostDuplicateEvent;
|
|
|
|
#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);
|
|
}
|
|
|
|
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())
|
|
{
|
|
return ETrackSupport::Supported;
|
|
}
|
|
|
|
return Super::IsTrackSupported(InTrackClass);
|
|
}
|
|
|
|
void ULevelSequence::GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (DirectorBlueprint)
|
|
{
|
|
DirectorBlueprint->GetAssetRegistryTags(OutTags);
|
|
}
|
|
#endif
|
|
|
|
for (UObject* MetaData : MetaDataObjects)
|
|
{
|
|
ILevelSequenceMetaData* MetaDataInterface = Cast<ILevelSequenceMetaData>(MetaData);
|
|
if (MetaDataInterface)
|
|
{
|
|
MetaDataInterface->ExtendAssetRegistryTags(OutTags);
|
|
}
|
|
}
|
|
|
|
Super::GetAssetRegistryTags(OutTags);
|
|
}
|
|
|
|
void ULevelSequence::GetAssetRegistryTagMetadata(TMap<FName, FAssetRegistryTagMetadata>& OutMetadata) const
|
|
{
|
|
for (UObject* MetaData : MetaDataObjects)
|
|
{
|
|
ILevelSequenceMetaData* MetaDataInterface = Cast<ILevelSequenceMetaData>(MetaData);
|
|
if (MetaDataInterface)
|
|
{
|
|
MetaDataInterface->ExtendAssetRegistryTagMetaData(OutMetadata);
|
|
}
|
|
}
|
|
|
|
Super::GetAssetRegistryTagMetadata(OutMetadata);
|
|
}
|
|
|
|
void ULevelSequence::PostLoadAssetRegistryTags(const FAssetData& InAssetData, TArray<FAssetRegistryTag>& OutTagsAndValuesToUpdate) const
|
|
{
|
|
Super::PostLoadAssetRegistryTags(InAssetData, OutTagsAndValuesToUpdate);
|
|
|
|
// GetAssetRegistryTags appends the DirectorBlueprint tags to the World's tags, so we also have to run the Blueprint PostLoadAssetRegistryTags
|
|
UBlueprint::PostLoadBlueprintAssetRegistryTags(InAssetData, OutTagsAndValuesToUpdate);
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
void ULevelSequence::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
#if WITH_EDITOR
|
|
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));
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
#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();
|
|
|
|
#if WITH_EDITOR
|
|
if (MovieScene)
|
|
{
|
|
// Remove any invalid object bindings
|
|
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);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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::ConvertPersistentBindingsToDefault(UObject* FixupContext)
|
|
{
|
|
if (PossessedObjects_DEPRECATED.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
MarkPackageDirty();
|
|
for (auto& Pair : PossessedObjects_DEPRECATED)
|
|
{
|
|
UObject* Object = Pair.Value.GetObject();
|
|
if (Object)
|
|
{
|
|
FGuid ObjectId;
|
|
FGuid::Parse(Pair.Key, ObjectId);
|
|
BindingReferences.AddBinding(ObjectId, Object, FixupContext);
|
|
}
|
|
}
|
|
PossessedObjects_DEPRECATED.Empty();
|
|
}
|
|
|
|
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 Object.IsA<AActor>() || Object.IsA<UActorComponent>() || Object.IsA<UAnimInstance>();
|
|
}
|
|
|
|
void ULevelSequence::LocateBoundObjects(const FGuid& ObjectId, UObject* Context, TArray<UObject*, TInlineAllocator<1>>& OutObjects) const
|
|
{
|
|
LocateBoundObjects(ObjectId, Context, {}, OutObjects);
|
|
}
|
|
|
|
void ULevelSequence::LocateBoundObjects(const FGuid& ObjectId, UObject* Context, const FTopLevelAssetPath& StreamedLevelAssetPath, TArray<UObject*, TInlineAllocator<1>>& OutObjects) const
|
|
{
|
|
// Handle legacy object references
|
|
UObject* Object = Context ? ObjectReferences.ResolveBinding(ObjectId, Context) : nullptr;
|
|
if (Object)
|
|
{
|
|
OutObjects.Add(Object);
|
|
}
|
|
|
|
BindingReferences.ResolveBinding(ObjectId, Context, StreamedLevelAssetPath, OutObjects);
|
|
}
|
|
|
|
FGuid ULevelSequence::FindBindingFromObject(UObject* InObject, UObject* Context) const
|
|
{
|
|
return BindingReferences.FindBindingFromObject(InObject, Context);
|
|
}
|
|
|
|
void ULevelSequence::GatherExpiredObjects(const FMovieSceneObjectCache& InObjectCache, TArray<FGuid>& OutInvalidIDs) const
|
|
{
|
|
for (const FGuid& ObjectId : BindingReferences.GetBoundAnimInstances())
|
|
{
|
|
for (TWeakObjectPtr<> WeakObject : InObjectCache.IterateBoundObjects(ObjectId))
|
|
{
|
|
UAnimInstance* AnimInstance = Cast<UAnimInstance>(WeakObject.Get());
|
|
if (!AnimInstance || !AnimInstance->GetOwningComponent() || AnimInstance->GetOwningComponent()->GetAnimInstance() != AnimInstance)
|
|
{
|
|
OutInvalidIDs.Add(ObjectId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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::CanRebindPossessable(const FMovieScenePossessable& InPossessable) const
|
|
{
|
|
return !InPossessable.GetParent().IsValid();
|
|
}
|
|
|
|
void ULevelSequence::UnbindPossessableObjects(const FGuid& ObjectId)
|
|
{
|
|
BindingReferences.RemoveBinding(ObjectId);
|
|
|
|
// Legacy object references
|
|
ObjectReferences.Map.Remove(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);
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
UBlueprint* ULevelSequence::GetDirectorBlueprint() const
|
|
{
|
|
return DirectorBlueprint;
|
|
}
|
|
|
|
FString ULevelSequence::GetDirectorBlueprintName() const
|
|
{
|
|
return GetDisplayName().ToString() + " (Director BP)";
|
|
}
|
|
|
|
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)
|
|
{
|
|
UObject* PlaybackContext = InObject ? InObject->GetWorld() : nullptr;
|
|
if (!InObject || !PlaybackContext)
|
|
{
|
|
return FGuid();
|
|
}
|
|
|
|
AActor* Actor = Cast<AActor>(InObject);
|
|
// @todo: sequencer-python: need to figure out how we go from a spawned object to an object binding without the spawn register or any IMovieScenePlayer interface
|
|
// Normally this process would happen through sequencer, since it has more context than just the level sequence asset.
|
|
// For now we cannot possess spawnables or anything within them since we have no way of retrieving the spawnable from the object
|
|
if (Actor && Actor->ActorHasTag("SequencerActor"))
|
|
{
|
|
TOptional<FMovieSceneSpawnableAnnotation> Annotation = FMovieSceneSpawnableAnnotation::Find(Actor);
|
|
if (Annotation.IsSet() && Annotation->OriginatingSequence == this)
|
|
{
|
|
return Annotation->ObjectBindingID;
|
|
}
|
|
|
|
UE_LOG(LogLevelSequence, Error, TEXT("Unable to possess object '%s' since it is, or is part of a spawnable that is not in this sequence."), *InObject->GetName());
|
|
return FGuid();
|
|
}
|
|
|
|
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
|
|
{
|
|
class FTransientPlayer : public IMovieScenePlayer
|
|
{
|
|
public:
|
|
FMovieSceneRootEvaluationTemplateInstance Template;
|
|
virtual FMovieSceneRootEvaluationTemplateInstance& GetEvaluationTemplate() override { check(false); return Template; }
|
|
virtual void UpdateCameraCut(UObject* CameraObject, const EMovieSceneCameraCutParams& CameraCutParams) override {}
|
|
virtual void SetViewportSettings(const TMap<FViewportClient*, EMovieSceneViewportParams>& ViewportParamsMap) override {}
|
|
virtual void GetViewportSettings(TMap<FViewportClient*, EMovieSceneViewportParams>& ViewportParamsMap) const override {}
|
|
virtual EMovieScenePlayerStatus::Type GetPlaybackStatus() const { return EMovieScenePlayerStatus::Stopped; }
|
|
virtual void SetPlaybackStatus(EMovieScenePlayerStatus::Type InPlaybackStatus) override {}
|
|
} Player;
|
|
|
|
Player.State.AssignSequence(MovieSceneSequenceID::Root, *this, Player);
|
|
|
|
FGuid ExistingID = Player.FindObjectId(*InObject, MovieSceneSequenceID::Root);
|
|
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);
|
|
}
|
|
|
|
FMovieSceneSpawnable* ParentSpawnable = MovieScene->FindSpawnable(ParentGuid);
|
|
if (ParentSpawnable)
|
|
{
|
|
ParentSpawnable->AddChildPossessable(NewGuid);
|
|
}
|
|
}
|
|
|
|
BindPossessableObject(NewGuid, *InObject, BindingContext);
|
|
|
|
return NewGuid;
|
|
|
|
}
|
|
|
|
FGuid ULevelSequence::CreatePossessable(UObject* ObjectToPossess)
|
|
{
|
|
return FindOrAddBinding(ObjectToPossess);
|
|
}
|
|
|
|
FGuid ULevelSequence::CreateSpawnable(UObject* ObjectToSpawn)
|
|
{
|
|
if (!MovieScene || !ObjectToSpawn)
|
|
{
|
|
return FGuid();
|
|
}
|
|
|
|
TArray<TSharedRef<IMovieSceneObjectSpawner>> ObjectSpawners;
|
|
|
|
// In order to create a spawnable, we have to instantiate all the relevant object spawners for level sequences, and try to create a spawnable from each
|
|
FLevelSequenceModule& LevelSequenceModule = FModuleManager::LoadModuleChecked<FLevelSequenceModule>("LevelSequence");
|
|
LevelSequenceModule.GenerateObjectSpawners(ObjectSpawners);
|
|
|
|
// The first object spawner to return a valid result will win
|
|
for (TSharedRef<IMovieSceneObjectSpawner> Spawner : ObjectSpawners)
|
|
{
|
|
TValueOrError<FNewSpawnable, FText> Result = Spawner->CreateNewSpawnableType(*ObjectToSpawn, *MovieScene, nullptr);
|
|
if (Result.IsValid())
|
|
{
|
|
FNewSpawnable& NewSpawnable = Result.GetValue();
|
|
|
|
NewSpawnable.Name = MovieSceneHelpers::MakeUniqueSpawnableName(MovieScene, NewSpawnable.Name);
|
|
|
|
FGuid NewGuid = MovieScene->AddSpawnable(NewSpawnable.Name, *NewSpawnable.ObjectTemplate);
|
|
|
|
UMovieSceneSpawnTrack* NewSpawnTrack = MovieScene->AddTrack<UMovieSceneSpawnTrack>(NewGuid);
|
|
if (NewSpawnTrack)
|
|
{
|
|
NewSpawnTrack->AddSection(*NewSpawnTrack->CreateNewSection());
|
|
}
|
|
return NewGuid;
|
|
}
|
|
}
|
|
|
|
return FGuid();
|
|
}
|
|
|
|
#endif // WITH_EDITOR
|
|
|
|
UObject* ULevelSequence::CreateDirectorInstance(IMovieScenePlayer& Player, FMovieSceneSequenceID SequenceID)
|
|
{
|
|
ULevelSequencePlayer* LevelSequencePlayer = Cast<ULevelSequencePlayer>(Player.AsUObject());
|
|
UObject* DirectorOuter = LevelSequencePlayer ? LevelSequencePlayer : Player.GetPlaybackContext();
|
|
|
|
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
|
|
|
|
ULevelSequenceDirector* NewDirector = NewObject<ULevelSequenceDirector>(DirectorOuter, DirectorClass, DirectorName, RF_Transient);
|
|
NewDirector->Player = LevelSequencePlayer;
|
|
NewDirector->MovieScenePlayerIndex = Player.GetUniqueIndex();
|
|
NewDirector->SubSequenceID = SequenceID.GetInternalValue();
|
|
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);
|
|
}
|
|
|