Files
UnrealEngine/Engine/Source/Runtime/MovieScene/Private/Bindings/MovieSceneSpawnableBinding.cpp
2025-05-18 13:04:45 +08:00

209 lines
7.9 KiB
C++

// 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<int32> 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<const UE::MovieScene::FSharedPlaybackState> 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<AActor>(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<AActor>(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<UMovieSceneBindingLifetimeTrack>(OwnerMovieScene.FindTrack(UMovieSceneBindingLifetimeTrack::StaticClass(), ObjectBindingId, NAME_None));
//if (!BindingLifetimeTrack)
//{
// BindingLifetimeTrack = Cast<UMovieSceneBindingLifetimeTrack>(OwnerMovieScene.AddTrack(UMovieSceneBindingLifetimeTrack::StaticClass(), ObjectBindingId));
//}
//if (BindingLifetimeTrack && BindingLifetimeTrack->GetAllSections().IsEmpty())
//{
// UMovieSceneBindingLifetimeSection* BindingLifetimeSection = Cast<UMovieSceneBindingLifetimeSection>(BindingLifetimeTrack->CreateNewSection());
// BindingLifetimeSection->SetRange(TRange<FFrameNumber>::All());
// BindingLifetimeTrack->AddSection(*BindingLifetimeSection);
//}
UMovieSceneSpawnTrack* SpawnTrack = Cast<UMovieSceneSpawnTrack>(OwnerMovieScene.FindTrack(UMovieSceneSpawnTrack::StaticClass(), ObjectBindingId, NAME_None));
if (!SpawnTrack)
{
SpawnTrack = Cast<UMovieSceneSpawnTrack>(OwnerMovieScene.AddTrack(UMovieSceneSpawnTrack::StaticClass(), ObjectBindingId));
}
if (SpawnTrack && SpawnTrack->GetAllSections().Num() == 0)
{
SpawnTrack->Modify();
UMovieSceneBoolSection* SpawnSection = Cast<UMovieSceneBoolSection>(SpawnTrack->CreateNewSection());
SpawnSection->GetChannel().SetDefault(true);
SpawnSection->SetRange(TRange<FFrameNumber>::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<const UE::MovieScene::FSharedPlaybackState> SharedPlaybackState) const
{
UObject* PlaybackContext = SharedPlaybackState->GetPlaybackContext();
return PlaybackContext ? PlaybackContext->GetWorld() : nullptr;
}
FMovieSceneBindingResolveResult UMovieSceneSpawnableBindingBase::ResolveBinding(const FMovieSceneBindingResolveParams& ResolveParams, int32 BindingIndex, TSharedRef<const UE::MovieScene::FSharedPlaybackState> SharedPlaybackState) const
{
FMovieSceneBindingResolveResult Result;
const FMovieSceneSpawnRegister* SpawnRegister = SharedPlaybackState->FindCapability<FMovieSceneSpawnRegister>();
UObject* SpawnedObject = SpawnRegister ? SpawnRegister->FindSpawnedObject(ResolveParams.ObjectBindingID, ResolveParams.SequenceID, BindingIndex).Get() : nullptr;
if (SpawnedObject)
{
Result.Object = SpawnedObject;
}
return Result;
}
const UMovieSceneSpawnableBindingBase* UMovieSceneSpawnableBindingBase::AsSpawnable(TSharedRef<const UE::MovieScene::FSharedPlaybackState> SharedPlaybackState) const
{
return Cast<UMovieSceneSpawnableBindingBase>(this);
}
#undef LOCTEXT_NAMESPACE