// Copyright Epic Games, Inc. All Rights Reserved. #include "LevelSequenceActorSpawner.h" #include "Misc/PackageName.h" #include "MovieSceneSpawnable.h" #include "GameFramework/Actor.h" #include "Engine/World.h" #include "Particles/ParticleSystemComponent.h" #include "Engine/Engine.h" #include "Engine/Level.h" #include "Engine/LevelStreaming.h" #include "LevelUtils.h" #include "UObject/Package.h" #include "UObject/UObjectGlobals.h" #include "Evaluation/MovieSceneEvaluationTemplateInstance.h" #include "EntitySystem/MovieSceneEntitySystemLinker.h" #include "Systems/MovieSceneDeferredComponentMovementSystem.h" #if WITH_EDITOR #include "Editor.h" #endif static const FName SequencerActorTag(TEXT("SequencerActor")); TSharedRef FLevelSequenceActorSpawner::CreateObjectSpawner() { return MakeShareable(new FLevelSequenceActorSpawner); } UClass* FLevelSequenceActorSpawner::GetSupportedTemplateType() const { return AActor::StaticClass(); } ULevelStreaming* GetLevelStreaming(const FName& DesiredLevelName, const UWorld* World) { if (DesiredLevelName == NAME_None) { return nullptr; } const TArray& StreamingLevels = World->GetStreamingLevels(); FString SafeLevelNameString = DesiredLevelName.ToString(); if (FPackageName::IsShortPackageName(SafeLevelNameString)) { // Make sure MyMap1 and Map1 names do not resolve to a same streaming level SafeLevelNameString.InsertAt(0, '/'); } #if WITH_EDITOR FWorldContext* WorldContext = GEngine->GetWorldContextFromWorld(World); if (WorldContext && WorldContext->PIEInstance != INDEX_NONE) { SafeLevelNameString = UWorld::ConvertToPIEPackageName(SafeLevelNameString, WorldContext->PIEInstance); } #endif for (ULevelStreaming* LevelStreaming : StreamingLevels) { if (LevelStreaming && LevelStreaming->GetWorldAssetPackageName().EndsWith(SafeLevelNameString, ESearchCase::IgnoreCase)) { return LevelStreaming; } } return nullptr; } UObject* FLevelSequenceActorSpawner::SpawnObject(FMovieSceneSpawnable& Spawnable, FMovieSceneSequenceIDRef TemplateID, TSharedRef SharedPlaybackState) { AActor* ObjectTemplate = Cast(Spawnable.GetObjectTemplate()); if (!ObjectTemplate) { return nullptr; } if (!ensure(!ObjectTemplate->GetClass()->HasAnyClassFlags(CLASS_NewerVersionExists))) { return nullptr; } const EObjectFlags ObjectFlags = RF_Transient | RF_Transactional; // @todo sequencer livecapture: Consider using SetPlayInEditorWorld() and RestoreEditorWorld() here instead // @todo sequencer actors: We need to make sure puppet objects aren't copied into PIE/SIE sessions! They should be omitted from that duplication! UObject* PlaybackContext = SharedPlaybackState->GetPlaybackContext(); UWorld* WorldContext = PlaybackContext ? PlaybackContext->GetWorld() : nullptr; FName DesiredLevelName = Spawnable.GetLevelName(); if (DesiredLevelName != NAME_None) { if (WorldContext && WorldContext->GetFName() == DesiredLevelName) { // done, spawn into this world } else { ULevelStreaming* LevelStreaming = GetLevelStreaming(DesiredLevelName, WorldContext); if (LevelStreaming && LevelStreaming->GetWorldAsset().IsValid()) { WorldContext = LevelStreaming->GetWorldAsset().Get(); } else { UE_LOG(LogMovieScene, Warning, TEXT("Can't find sublevel '%s' to spawn '%s' into, defaulting to Persistent level"), *DesiredLevelName.ToString(), *Spawnable.GetName()); } } } if (WorldContext == nullptr) { UE_LOG(LogMovieScene, Warning, TEXT("Can't find world to spawn '%s' into, defaulting to Persistent level"), *Spawnable.GetName()); WorldContext = GWorld; } // We use the net addressable name for spawnables on any non-editor, non-standalone world (ie, all clients, servers and PIE worlds) const bool bUseNetAddressableName = Spawnable.bNetAddressableName && (WorldContext->WorldType != EWorldType::Editor) && (WorldContext->GetNetMode() != ENetMode::NM_Standalone); FName SpawnName = bUseNetAddressableName ? Spawnable.GetNetAddressableName(SharedPlaybackState, TemplateID) : #if WITH_EDITOR // Construct the object with the same name that we will set later on the actor to avoid renaming it inside SetActorLabel MakeUniqueObjectName(WorldContext->PersistentLevel, ObjectTemplate->GetClass(), *Spawnable.GetName()); #else NAME_None; #endif // 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, SpawnName); if (ExistingObject) { FName DefunctName = MakeUniqueObjectName(WorldContext->PersistentLevel, ExistingObject->GetClass()); ExistingObject->Rename(*DefunctName.ToString(), nullptr); } } FStringView SpawnLabel; #if WITH_EDITOR const IConsoleVariable* AllowSetActorLabelCvar = IConsoleManager::Get().FindConsoleVariable(TEXT("LevelSequence.EnableReadableActorLabelsForSpawnables")); const bool bAllowSetActorLabel = AllowSetActorLabelCvar && AllowSetActorLabelCvar->GetBool(); // Historically, setting the actor label has caused performance issues in some scenarios (by causing async loading flushes); however, there's no // evidence for this anymore, so the cvar is here to turn off this behavior if needed. if ((WorldContext->WorldType == EWorldType::Editor) || bAllowSetActorLabel) { SpawnLabel = Spawnable.GetName(); } #endif // Spawn the puppet actor FActorSpawnParameters SpawnInfo; { SpawnInfo.Name = SpawnName; #if WITH_EDITOR SpawnInfo.InitialActorLabel = SpawnLabel; #endif SpawnInfo.ObjectFlags = ObjectFlags; SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; // @todo: Spawning with a non-CDO template is fraught with issues //SpawnInfo.Template = ObjectTemplate; // allow pre-construction variables to be set. SpawnInfo.bDeferConstruction = true; SpawnInfo.Template = ObjectTemplate; SpawnInfo.OverrideLevel = WorldContext->PersistentLevel; } FTransform SpawnTransform; if (USceneComponent* RootComponent = ObjectTemplate->GetRootComponent()) { SpawnTransform.SetTranslation(RootComponent->GetRelativeLocation()); SpawnTransform.SetRotation(RootComponent->GetRelativeRotation().Quaternion()); SpawnTransform.SetScale3D(RootComponent->GetRelativeScale3D()); } else { SpawnTransform = Spawnable.SpawnTransform; } { // Disable all particle components so that they don't auto fire as soon as the actor is spawned. The particles should be triggered through the particle track. for (UActorComponent* Component : ObjectTemplate->GetComponents()) { if (UParticleSystemComponent* ParticleComponent = Cast(Component)) { // The particle needs to be set inactive in case its template was active. ParticleComponent->SetActiveFlag(false); Component->bAutoActivate = false; } } } AActor* SpawnedActor = WorldContext->SpawnActorAbsolute(ObjectTemplate->GetClass(), SpawnTransform, SpawnInfo); if (!SpawnedActor) { return nullptr; } //UEngine::FCopyPropertiesForUnrelatedObjectsParams CopyParams; //CopyParams.bPreserveRootComponent = false; //CopyParams.bNotifyObjectReplacement = false; //SpawnedActor->UnregisterAllComponents(); //UEngine::CopyPropertiesForUnrelatedObjects(ObjectTemplate, SpawnedActor, CopyParams); //SpawnedActor->RegisterAllComponents(); // Ensure this spawnable is not a preview actor. Preview actors will not have BeginPlay() called on them. #if WITH_EDITOR SpawnedActor->bIsEditorPreviewActor = false; #endif // tag this actor so we know it was spawned by sequencer SpawnedActor->Tags.AddUnique(SequencerActorTag); if (bUseNetAddressableName) { SpawnedActor->SetNetAddressable(); } #if WITH_EDITOR if (GIsEditor) { // Explicitly set RF_Transactional on spawned actors so we can undo/redo properties on them. SpawnedActor->SetFlags(RF_Transactional); for (UActorComponent* Component : SpawnedActor->GetComponents()) { if (Component) { Component->SetFlags(RF_Transactional); } } } #endif if (UMovieSceneEntitySystemLinker* Linker = SharedPlaybackState->GetLinker()) { if (UMovieSceneDeferredComponentMovementSystem* DeferredMovementSystem = Linker->FindSystem()) { for (UActorComponent* ActorComponent : SpawnedActor->GetComponents()) { if (USceneComponent* SceneComponent = Cast(ActorComponent)) { DeferredMovementSystem->DeferMovementUpdates(SceneComponent); } } } } const bool bIsDefaultTransform = true; SpawnedActor->FinishSpawning(SpawnTransform, bIsDefaultTransform); return SpawnedActor; } void FLevelSequenceActorSpawner::DestroySpawnedObject(UObject& Object) { AActor* Actor = Cast(&Object); if (!ensure(Actor)) { return; } #if WITH_EDITOR if (GIsEditor) { // Explicitly remove RF_Transactional on spawned actors since we don't want to trasact spawn/destroy events Actor->ClearFlags(RF_Transactional); for (UActorComponent* Component : Actor->GetComponents()) { if (Component) { Component->ClearFlags(RF_Transactional); } } } #endif UWorld* World = Actor->GetWorld(); if (World) { const bool bNetForce = false; const bool bShouldModifyLevel = false; World->DestroyActor(Actor, bNetForce, bShouldModifyLevel); } }