// Copyright Epic Games, Inc. All Rights Reserved. #include "Bindings/MovieSceneSpawnableBinding.h" #include "MovieSceneSpawnRegister.h" #include "EntitySystem/MovieSceneSharedPlaybackState.h" #include "MovieScene.h" #include "Engine/World.h" #include "Engine/Level.h" #include "MovieSceneBindingReferences.h" #include "Tracks/MovieSceneBindingLifetimeTrack.h" #include "Sections/MovieSceneBindingLifetimeSection.h" #include "Styling/SlateBrush.h" #include "Styling/AppStyle.h" #include "Internationalization/Internationalization.h" #include "Tracks/MovieSceneSpawnTrack.h" #include "Sections/MovieSceneBoolSection.h" #include "MovieSceneSequence.h" #define LOCTEXT_NAMESPACE "FPossessableModel" static TAutoConsoleVariable CVarEnableReadableActorLabelsForSpawnables( TEXT("LevelSequence.EnableReadableActorLabelsForSpawnables"), 1, TEXT("If true, in the editor during PIE, Sequencer will set the DisplayName of spawned actors to match their Spawnable name in Sequencer, mimicking edit-time behavior. This helps with identifying spawnables more reliably, but isn't available in packaged builds. Try disabling this if you see async loads being flushed during actor spawning in PIE.\n") TEXT("0: off, 1: on"), ECVF_Default); UObject* UMovieSceneSpawnableBindingBase::SpawnObject(const FGuid& BindingId, int32 BindingIndex, UMovieScene& MovieScene, FMovieSceneSequenceIDRef TemplateID, TSharedRef SharedPlaybackState) { UWorld* WorldContext = GetWorldContext(SharedPlaybackState); if (WorldContext == nullptr) { UE_LOG(LogMovieScene, Warning, TEXT("Can't find world to spawn '%s' into, defaulting to Persistent level"), *MovieScene.GetName()); WorldContext = GWorld; } FName SpawnName = GetSpawnName(BindingId, MovieScene, TemplateID, SharedPlaybackState); // If there's an object that already exists with the requested name, it needs to be renamed (it's probably pending kill) if (!SpawnName.IsNone()) { UObject* ExistingObject = StaticFindObjectFast(nullptr, WorldContext->PersistentLevel.Get(), SpawnName); if (ExistingObject) { FName DefunctName = MakeUniqueObjectName(WorldContext->PersistentLevel.Get(), ExistingObject->GetClass()); ExistingObject->Rename(*DefunctName.ToString(), nullptr); } } // Spawn Object Internal UObject* SpawnedObject = SpawnObjectInternal(WorldContext, SpawnName, BindingId, BindingIndex, MovieScene, TemplateID, SharedPlaybackState); if (!SpawnedObject) { return nullptr; } #if WITH_EDITOR if (GIsEditor) { // Explicitly set RF_Transactional on spawned objects so we can undo/redo properties on them. SpawnedObject->SetFlags(RF_Transactional); } #endif // If have spawned an actor, do some actor-specific setup if (AActor* SpawnedActor = Cast(SpawnedObject)) { // Ensure this spawnable is not a preview actor. Preview actors will not have BeginPlay() called on them. #if WITH_EDITOR SpawnedActor->bIsEditorPreviewActor = false; #endif static const FName SequencerActorTag(TEXT("SequencerActor")); // tag this actor so we know it was spawned by sequencer SpawnedActor->Tags.AddUnique(SequencerActorTag); #if WITH_EDITOR if (GIsEditor) { // Explicitly set RF_Transactional on spawned actors so we can undo/redo properties on them. // This particular UObject will be marked RF_Transactional by the caller, but we need to set it on the components. for (UActorComponent* Component : SpawnedActor->GetComponents()) { if (Component) { Component->SetFlags(RF_Transactional); } } } #endif } // Allows derived classes to perform post-spawn logic such as mesh setup on actors. PostSpawnObject(SpawnedObject, WorldContext, BindingId, BindingIndex, MovieScene, TemplateID, SharedPlaybackState); return SpawnedObject; } void UMovieSceneSpawnableBindingBase::DestroySpawnedObject(UObject* Object) { if (!Object) { return; } #if WITH_EDITOR if (GIsEditor) { // Explicitly remove RF_Transactional on spawned objects since we don't want to transact spawn/destroy events Object->ClearFlags(RF_Transactional); } if (AActor* Actor = Cast(Object)) { // Explicitly remove RF_Transactional on spawned actors since we don't want to trasact spawn/destroy events // This particular UObject will have RF_Transactional cleared by the caller, but we need to cleared it on the components. for (UActorComponent* Component : Actor->GetComponents()) { if (Component) { Component->ClearFlags(RF_Transactional); } } } #endif DestroySpawnedObjectInternal(Object); } #if WITH_EDITOR void UMovieSceneSpawnableBindingBase::SetupDefaults(UObject* SpawnedObject, FGuid ObjectBindingId, UMovieScene& OwnerMovieScene) { Super::SetupDefaults(SpawnedObject, ObjectBindingId, OwnerMovieScene); // TODO: For now we are not using binding lifetime track for this, though it will support it. We continue to use spawn track until we improve UX of splitting sections // // // //// Ensure it has a binding lifetime track //UMovieSceneBindingLifetimeTrack* BindingLifetimeTrack = Cast(OwnerMovieScene.FindTrack(UMovieSceneBindingLifetimeTrack::StaticClass(), ObjectBindingId, NAME_None)); //if (!BindingLifetimeTrack) //{ // BindingLifetimeTrack = Cast(OwnerMovieScene.AddTrack(UMovieSceneBindingLifetimeTrack::StaticClass(), ObjectBindingId)); //} //if (BindingLifetimeTrack && BindingLifetimeTrack->GetAllSections().IsEmpty()) //{ // UMovieSceneBindingLifetimeSection* BindingLifetimeSection = Cast(BindingLifetimeTrack->CreateNewSection()); // BindingLifetimeSection->SetRange(TRange::All()); // BindingLifetimeTrack->AddSection(*BindingLifetimeSection); //} UMovieSceneSpawnTrack* SpawnTrack = Cast(OwnerMovieScene.FindTrack(UMovieSceneSpawnTrack::StaticClass(), ObjectBindingId, NAME_None)); if (!SpawnTrack) { SpawnTrack = Cast(OwnerMovieScene.AddTrack(UMovieSceneSpawnTrack::StaticClass(), ObjectBindingId)); } if (SpawnTrack && SpawnTrack->GetAllSections().Num() == 0) { SpawnTrack->Modify(); UMovieSceneBoolSection* SpawnSection = Cast(SpawnTrack->CreateNewSection()); SpawnSection->GetChannel().SetDefault(true); SpawnSection->SetRange(TRange::All()); SpawnTrack->AddSection(*SpawnSection); SpawnTrack->SetObjectId(ObjectBindingId); } } FSlateIcon UMovieSceneSpawnableBindingBase::GetBindingTrackCustomIconOverlay() const { return FSlateIcon(FAppStyle::GetAppStyleSetName(), "Sequencer.SpawnableIconOverlay"); } FText UMovieSceneSpawnableBindingBase::GetBindingTrackIconTooltip() const { return LOCTEXT("CustomSpawnableTooltip", "This item is spawned by sequencer by a custom spawnable binding according to this object's spawn track."); } #endif UWorld* UMovieSceneSpawnableBindingBase::GetWorldContext(TSharedRef SharedPlaybackState) const { UObject* PlaybackContext = SharedPlaybackState->GetPlaybackContext(); return PlaybackContext ? PlaybackContext->GetWorld() : nullptr; } FMovieSceneBindingResolveResult UMovieSceneSpawnableBindingBase::ResolveBinding(const FMovieSceneBindingResolveParams& ResolveParams, int32 BindingIndex, TSharedRef SharedPlaybackState) const { FMovieSceneBindingResolveResult Result; const FMovieSceneSpawnRegister* SpawnRegister = SharedPlaybackState->FindCapability(); UObject* SpawnedObject = SpawnRegister ? SpawnRegister->FindSpawnedObject(ResolveParams.ObjectBindingID, ResolveParams.SequenceID, BindingIndex).Get() : nullptr; if (SpawnedObject) { Result.Object = SpawnedObject; } return Result; } const UMovieSceneSpawnableBindingBase* UMovieSceneSpawnableBindingBase::AsSpawnable(TSharedRef SharedPlaybackState) const { return Cast(this); } #undef LOCTEXT_NAMESPACE