@mkloczko1
Do you still remember how you did this ?
Because I am finding myself with the same problem.
What I want is do add a component to an existing Spawnable actor that is inside the level sequence.
So, inside the level sequence, I added a spawnable MetaHuman. I can add animation to it without problem, because the animation is on the MetaHuman itself (the root/skeletal mesh component).
But now, I want to add the Face component of the MetaHuman to its parent in the level sequence, then add an animation to the Face.
Right now, I can add the Face and the animation, but there seem to be some kind of problem as the face animation is not playing, and the Face track is grayed out….
Here is what I am doing in blueprint to add the Metahuman, its animation, and the Face and animation I want.
Here is all my code :
#include "RuntimeSequenceManager.h"
#include "EngineUtils.h"
#include "LevelSequence.h"
#include "LevelSequenceActor.h"
#include "MovieScene.h"
#include "Animation/AnimSequence.h"
#include "Camera/CameraActor.h"
#include "Camera/CameraComponent.h"
#include "MoviePipelineExecutor.h"
#include "MoviePipelineInProcessExecutor.h"
#include "MoviePipelineQueueEngineSubsystem.h"
#include "Sections/MovieSceneCameraCutSection.h"
#include "Tracks/MovieScene3DTransformTrack.h"
#include "Tracks/MovieSceneCameraCutTrack.h"
#include "Tracks/MovieSceneSkeletalAnimationTrack.h"
// ===================== Private =====================
FGuid URuntimeSequenceManager::CreateBinding(ULevelSequence* Sequence, UObject* Object, bool bSpawnable)
{
if (!Sequence || !Object)
{
UE_LOG(LogTemp, Error, TEXT("CreateBinding: Invalid Sequence or Object."));
return FGuid();
}
UMovieSceneSequence* MovieSceneSequence = Cast<UMovieSceneSequence>(Sequence);
if (!MovieSceneSequence)
{
UE_LOG(LogTemp, Error, TEXT("CreateBinding: Sequence is not a MovieSceneSequence."));
return FGuid();
}
FGuid Guid;
if (bSpawnable)
{
// Duplicate and clean the duplicate (to avoid modifying the actual object (because it's a pointer to the actual object)
UObject* Copy = DuplicateObjectForSequencer(Sequence, Object);
PrepareObjectForSequencer(Copy);
// UObject* Template = Sequence->MakeSpawnableTemplateFromInstance(*Copy, *Copy->GetName());
UObject* Template = MakeSpawnableTemplateFromInstance(*Copy, Sequence->MovieScene, *Copy->GetName());
Guid = MovieSceneSequence->CreateSpawnable(Template);
// UObject* Template = Sequence->MakeSpawnableTemplateFromInstance(*Object, *Object->GetName());
// Guid = MovieSceneSequence->CreateSpawnable(Template);
// Remove animation blueprint on TEMPLATE
if (Guid.IsValid()) AddedBindings.Add(Guid, Template);
UE_LOG(LogTemp, Log, TEXT("CreateBinding: Created SPAWNABLE %s with GUID %s."), *Template->GetName(), *Guid.ToString());
}
else
{
Guid = MovieSceneSequence->CreatePossessable(Object);
if (Guid.IsValid()) AddedBindings.Add(Guid, Object);
UE_LOG(LogTemp, Log, TEXT("CreateBinding: Created POSSESSABLE %s with GUID %s."), *Object->GetName(), *Guid.ToString());
}
return Guid;
}
FGuid URuntimeSequenceManager::AddCameraBinding(ULevelSequence* Sequence, bool bSpawnable)
{
if (!Sequence)
{
UE_LOG(LogTemp, Error, TEXT("AddCameraBinding: Sequence is null."));
return FGuid();
}
// Create the camera TEMPLATE inside the LevelSequence (for both spawnable and possessable)
ACameraActor* TemplateCamera = NewObject<ACameraActor>(Sequence, ACameraActor::StaticClass(), NAME_None, RF_Transactional);
if (!TemplateCamera)
{
UE_LOG(LogTemp, Error, TEXT("AddCameraBinding: Failed to create camera template."));
return FGuid();
}
TemplateCamera->SetActorLabel(TEXT("RuntimeCamera"));
const FGuid CameraGuid = CreateBinding(Sequence, TemplateCamera, bSpawnable);
return CameraGuid;
}
void URuntimeSequenceManager::CopyCameraComponentSettings(const UCameraComponent* Source, const ACameraActor* DestinationActor)
{
if (!Source || !DestinationActor)
{
UE_LOG(LogTemp, Error, TEXT("CopyCameraComponentSettings: Source or Destination is null."));
return;
}
// Copy camera properties from source to destination
if (UCameraComponent* Destination = DestinationActor->GetCameraComponent())
{
Destination->FieldOfView = Source->FieldOfView;
Destination->AspectRatio = Source->AspectRatio;
Destination->bConstrainAspectRatio = Source->bConstrainAspectRatio;
Destination->ProjectionMode = Source->ProjectionMode;
Destination->PostProcessSettings = Source->PostProcessSettings;
Destination->PostProcessBlendWeight = Source->PostProcessBlendWeight;
Destination->bConstrainAspectRatio = Source->bConstrainAspectRatio;
Destination->bUsePawnControlRotation = Source->bUsePawnControlRotation;
}
UE_LOG(LogTemp, Log, TEXT("CopyCameraComponentSettings: Copied camera settings from template."));
}
void URuntimeSequenceManager::CopyTransformToSpawnable(ULevelSequence* Sequence, FGuid Guid, const FTransform& TransformToApply, int KeyInterpolation)
{
if (!Sequence || !Guid.IsValid())
return;
// IMPORTANT: Because we used "CreateSpawnable" before, the object was added to the "Possessables" Array (and not "Spawnables" !!!),
// SO we use "FindPossessable". Otherwise, "FindSpawnable" will return null. (tested before, and indeed, it returns null.....)
FMovieScenePossessable* Possessable = Sequence->MovieScene->FindPossessable(Guid);
if (!Possessable)
{
UE_LOG(LogTemp, Error, TEXT("CopyTransformToSpawnable: Possessable/Spawnable not found."));
return;
}
UObject* TemplateObject = nullptr;
if (TWeakObjectPtr<UObject>* WeakObjPtr = AddedBindings.Find(Guid))
TemplateObject = WeakObjPtr->Get();
if (!TemplateObject)
{
UE_LOG(LogTemp, Error, TEXT("CopyTransformToSpawnable: Failed to get actor from AddedBindings."));
return;
}
// Add transform, section and transform key frame
UMovieScene3DTransformTrack* TransformTrack = AddTransformTrack(Sequence, TemplateObject);
// todo: have some kind of variables that makes it so that I can choose the start and end frame here
UMovieScene3DTransformSection* TransformSection = AddTransformSection(Sequence, TransformTrack, 0, 100, EMovieSceneBlendType::Absolute);
AddTransformKeyFrame(Sequence, TemplateObject, TransformSection, 0, 0, TransformToApply, KeyInterpolation);
UE_LOG(LogTemp, Log, TEXT("CopyTransformToSpawnable: Applied transform key to spawnable %s."), *Guid.ToString());
}
UObject* URuntimeSequenceManager::DuplicateObjectForSequencer(ULevelSequence* Sequence, UObject* OriginalObject)
{
return StaticDuplicateObject(
OriginalObject,
Sequence, // Outer = LevelSequence to avoid GC
NAME_None
);
}
void URuntimeSequenceManager::PrepareObjectForSequencer(UObject* Object)
{
AActor* ActorCopy = Cast<AActor>(Object);
if (!ActorCopy)
{
UE_LOG(LogTemp, Error, TEXT("PrepareObjectForSequence: Object is not an actor."));
return;
}
if (USkeletalMeshComponent* Sk = ActorCopy->FindComponentByClass<USkeletalMeshComponent>())
{
Sk->SetAnimationMode(EAnimationMode::AnimationCustomMode);
Sk->AnimationData.AnimToPlay = nullptr; // to be safe
Sk->AnimClass = nullptr; // to be safe
/*
* Because we remove the animation, and so we are removing the code that was attaching both hands to the wheel,
* we should add a Control rig track to the character so that it is used at runtime in the level sequence.
*/
}
}
UObject* URuntimeSequenceManager::MakeSpawnableTemplateFromInstance(UObject& InSourceObject, UMovieScene* InMovieScene, FName InName)
{
// UObject* NewInstance = NewObject<UObject>(InMovieScene, InSourceObject.GetClass(), InName);
UObject* NewInstance = NewObject<UObject>(InMovieScene, InSourceObject.GetClass(), InName, RF_NoFlags, &InSourceObject, true);
UEngine::FCopyPropertiesForUnrelatedObjectsParams CopyParams;
CopyParams.bNotifyObjectReplacement = false;
CopyParams.bPreserveRootComponent = false;
CopyParams.bPerformDuplication = true;
UEngine::CopyPropertiesForUnrelatedObjects(&InSourceObject, NewInstance, CopyParams);
AActor* Actor = CastChecked<AActor>(NewInstance);
// Remove tags that may have gotten stuck on- for spawnables/replaceables these tags will be added after spawning
static const FName SequencerActorTag(TEXT("SequencerActor"));
static const FName SequencerPreviewActorTag(TEXT("SequencerPreviewActor"));
Actor->Tags.Remove(SequencerActorTag);
Actor->Tags.Remove(SequencerPreviewActorTag);
if (Actor->GetAttachParentActor() != nullptr)
{
// We don't support spawnables and attachments right now
// @todo: map to attach track?
Actor->DetachFromActor(FDetachmentTransformRules(FAttachmentTransformRules(EAttachmentRule::KeepRelative, false), false));
}
// The spawnable source object was created with RF_Transient. The object generated from that needs its
// component flags cleared of RF_Transient so that the template object can be saved to the level sequence.
for (UActorComponent* Component : Actor->GetComponents())
{
if (Component)
{
Component->ClearFlags(RF_Transient);
}
}
return NewInstance;
}
void URuntimeSequenceManager::AddKeyFrameToDoubleChannel(UMovieSceneSection* Section, int ChannelIndex, int Frame, double Value, int KeyInterpolation)
{
if (!Section)
{
UE_LOG(LogTemp, Error, TEXT("AddKeyFrameToDoubleChannel: Section not found."));
return;
}
FMovieSceneDoubleChannel* Channel = Section->GetChannelProxy().GetChannel<FMovieSceneDoubleChannel>(ChannelIndex);
if (!Channel)
{
UE_LOG(LogTemp, Error, TEXT("AddKeyFrameToDoubleChannel: Channel not found."));
return;
}
// Calculate the tick value for the input frame numbers
// For example, 100 ticks for one frame
ULevelSequence* Sequence = Cast<ULevelSequence>(Section->GetOutermostObject());
int FrameTickValue = Sequence->MovieScene->GetTickResolution().AsDecimal() / Sequence->MovieScene->GetDisplayRate().AsDecimal();
FFrameNumber FrameNumber = FFrameNumber(Frame * FrameTickValue);
// Add key to channel
if (KeyInterpolation == 0)
Channel->AddCubicKey(FrameNumber, Value);
else if (KeyInterpolation == 1)
Channel->AddLinearKey(FrameNumber, Value);
else
Channel->AddConstantKey(FrameNumber, Value);
}
void URuntimeSequenceManager::RemoveKeyFrameToDoubleChannel(UMovieSceneSection* Section, int ChannelIndex, int Frame)
{
if (!Section)
{
UE_LOG(LogTemp, Error, TEXT("AddKeyFrameToDoubleChannel: Section not found."));
return;
}
FMovieSceneDoubleChannel* Channel = Section->GetChannelProxy().GetChannel<FMovieSceneDoubleChannel>(ChannelIndex);
if (!Channel)
{
UE_LOG(LogTemp, Error, TEXT("AddKeyFrameToDoubleChannel: Channel not found."));
return;
}
// Calculate the tick value for the input frame numbers
// For example, 100 ticks for one frame
ULevelSequence* Sequence = Cast<ULevelSequence>(Section->GetOutermostObject());
int FrameTickValue = Sequence->MovieScene->GetTickResolution().AsDecimal() / Sequence->MovieScene->GetDisplayRate().AsDecimal();
FFrameNumber FrameNumber = FFrameNumber(Frame * FrameTickValue);
// Find the key we want to remove
TArray<FFrameNumber> UnusedKeyTimes;
TArray<FKeyHandle> KeyHandles;
Channel->GetKeys(TRange<FFrameNumber>(FrameNumber, FrameNumber), &UnusedKeyTimes, &KeyHandles);
// Remove key
Channel->DeleteKeys(KeyHandles);
}
FFrameNumber URuntimeSequenceManager::FindLastFrameInSequence(ULevelSequence* Sequence)
{
// Get all the sections inside the level sequence
TArray<UMovieSceneSection*> AllSections = Sequence->MovieScene->GetAllSections();
FFrameNumber LastFrame = 0;
for (UMovieSceneSection* Section : AllSections)
{
if (!Section) continue;
// Get the start and end frame value
TRange<FFrameNumber> SectionRange = Section->GetRange();
if (SectionRange.HasUpperBound())
{
const FFrameNumber SectionEnd = SectionRange.GetUpperBoundValue();
if (SectionEnd > LastFrame)
LastFrame = SectionEnd;
}
}
return LastFrame;
}
void URuntimeSequenceManager::SetPlaybackRangeToLastFrame(ULevelSequence* Sequence, FFrameNumber LastFrame)
{
// Change the end of the level sequence to the last frame available from any section
Sequence->MovieScene->SetPlaybackRange(TRange<FFrameNumber>(0, LastFrame));
}
void URuntimeSequenceManager::AddFinalStaticKeyFrames(ULevelSequence* Sequence, FFrameNumber LastFrame)
{
if (!Sequence)
{
UE_LOG(LogTemp, Error, TEXT("AddFinalStaticKeyFrames: Sequence is null."));
return;
}
if (LastFrame < 0)
{
UE_LOG(LogTemp, Error, TEXT("AddFinalStaticKeyFrames: Last frame is negative."));
return;
}
if (StaticObjects.IsEmpty())
{
UE_LOG(LogTemp, Warning, TEXT("AddFinalStaticKeyFrames: Static objects list is empty."));
return;
}
// Get the display rate to convert ticks in frames
FFrameRate DisplayRate = Sequence->MovieScene->GetDisplayRate();
FFrameRate TickResolution = Sequence->MovieScene->GetTickResolution();
FFrameNumber LastFrameInFrames = FFrameRate::TransformTime(FFrameTime(LastFrame), TickResolution, DisplayRate).FloorToFrame();
for (const TPair<TWeakObjectPtr<UObject>, FTransform>& Pair : StaticObjects)
{
TWeakObjectPtr<UObject> Object = Pair.Key;
FTransform Transform = Pair.Value;
// Get the transform track
FGuid OutGuid;
UMovieScene3DTransformTrack* Track = GetTransformTrackFromSequence(Sequence, Object.Get(), OutGuid);
if (!Track)
{
UE_LOG(LogTemp, Warning, TEXT("AddFinalStaticKeyFrames: Transform track not found for object."));
continue;
}
// Get the section (always index 0 for static)
UMovieScene3DTransformSection* Section = GetTransformSectionFromSequence(Sequence, Object.Get(), Track, 0);
if (!Section)
{
UE_LOG(LogTemp, Warning, TEXT("AddFinalStaticKeyFrames: Section not found for object."));
continue;
}
// Extend the section until LastFrame
if (Section->GetRange().GetUpperBoundValue() <= LastFrame)
Section->SetRange(TRange<FFrameNumber>(Section->GetRange().GetLowerBoundValue(), LastFrame));
// Add the last keyframe
AddTransformKeyFrame(Sequence, Object.Get(), Section, 0, LastFrameInFrames.Value, Transform, 0);
UE_LOG(LogTemp, Log, TEXT("AddFinalStaticKeyFrames: Static key added at last frame %d for object %s"), LastFrameInFrames.Value, *Object->GetName());
}
UE_LOG(LogTemp, Log, TEXT("AddFinalStaticKeyFrames: Finished adding final static key frames."));
}
void URuntimeSequenceManager::ExtendAllTracksToFrame(ULevelSequence* Sequence, FFrameNumber Frame)
{
if (!Sequence)
{
UE_LOG(LogTemp, Error, TEXT("ExtendAllTracksToFrame: Sequence is null."));
return;
}
for (const FMovieSceneBinding& Binding : Sequence->MovieScene->GetBindings())
{
for (UMovieSceneTrack* Track : Binding.GetTracks())
{
if (!Track) continue;
for (UMovieSceneSection* Section : Track->GetAllSections())
{
if (!Section) continue;
TRange<FFrameNumber> Range = Section->GetRange();
FFrameNumber Lower = Range.HasLowerBound() ? Range.GetLowerBoundValue() : 0; // fallback
FFrameNumber Upper = Range.HasUpperBound() ? Range.GetUpperBoundValue() : 0;
if (!Range.HasUpperBound() || Upper < Frame)
Section->SetRange(TRange<FFrameNumber>(Lower, Frame));
}
}
}
// Don't forget the camera cut track, which is specific
if (UMovieSceneCameraCutTrack* CutTrack = Cast<UMovieSceneCameraCutTrack>(Sequence->MovieScene->GetCameraCutTrack()))
{
for (UMovieSceneSection* Section : CutTrack->GetAllSections())
{
if (!Section) continue;
TRange<FFrameNumber> Range = Section->GetRange();
FFrameNumber Lower = Range.GetLowerBoundValue();
if (Range.GetUpperBoundValue() < Frame)
Section->SetRange(TRange<FFrameNumber>(Lower, Frame));
}
}
}
// ===================== Protected =====================
// ---------- Tracks ----------
UMovieSceneCameraCutTrack* URuntimeSequenceManager::GetCameraCutTrackFromSequence(ULevelSequence* Sequence)
{
if (!Sequence)
{
UE_LOG(LogTemp, Error, TEXT("GetCameraCutTrackFromSequence: The Level Sequence is null."));
return nullptr;
}
// Get the camera cut track
UMovieSceneCameraCutTrack* CameraCutTrack = Cast<UMovieSceneCameraCutTrack>(Sequence->MovieScene->GetCameraCutTrack());
return CameraCutTrack;
}
UMovieScene3DTransformTrack* URuntimeSequenceManager::GetTransformTrackFromSequence(ULevelSequence* Sequence, UObject* Object, FGuid& OutGuid)
{
FGuid Guid = GetGuidFromObjectInSequence(Sequence, Object); // we already check if object and sequence are null inside this method
if (!Guid.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("GetTransformTrackFromSequence: Actor is not in the sequence."));
OutGuid = Guid;
return nullptr;
}
OutGuid = Guid;
// Get the transform track
UMovieScene3DTransformTrack* TransformTrack = Sequence->MovieScene->FindTrack<UMovieScene3DTransformTrack>(Guid);
return TransformTrack;
}
UMovieSceneSkeletalAnimationTrack* URuntimeSequenceManager::GetOrAddAnimationTrackFromSequence(ULevelSequence* Sequence, UObject* Object, FGuid& OutGuid)
{
FGuid Guid = GetGuidFromObjectInSequence(Sequence, Object); // we already check if object and sequence are null inside this method
if (!Guid.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("GetAnimationTrackFromSequence: Object is not in the sequence."));
OutGuid = Guid;
return nullptr;
}
OutGuid = Guid;
// Get the animation track
UMovieSceneSkeletalAnimationTrack* AnimTrack = Sequence->MovieScene->FindTrack<UMovieSceneSkeletalAnimationTrack>(Guid);
if (AnimTrack == nullptr)
{
// Create the animation track if it doesn't exist
AnimTrack = Sequence->MovieScene->AddTrack<UMovieSceneSkeletalAnimationTrack>(Guid);
if (!AnimTrack)
{
UE_LOG(LogTemp, Error, TEXT("GetAnimationTrackFromSequence: Was not able to create track."));
return nullptr;
}
UE_LOG(LogTemp, Log, TEXT("GetAnimationTrackFromSequence: Created track for object %s."), *Object->GetName());
}
return AnimTrack;
}
UMovieScene3DTransformTrack* URuntimeSequenceManager::AddTransformTrack(ULevelSequence* Sequence, UObject* Object)
{
FGuid Guid = FGuid();
UMovieScene3DTransformTrack* TransformTrack = GetTransformTrackFromSequence(Sequence, Object, Guid);
if (TransformTrack == nullptr)
{
// Create the transform track if it doesn't exist
TransformTrack = Sequence->MovieScene->AddTrack<UMovieScene3DTransformTrack>(Guid);
if (!TransformTrack)
{
UE_LOG(LogTemp, Error, TEXT("AddTransformKey: Was not able to create track."));
return nullptr;
}
UE_LOG(LogTemp, Log, TEXT("AddTransformKey: Created track for object %s."), *Object->GetName());
}
return TransformTrack;
}
void URuntimeSequenceManager::AddCameraCutTrack(ULevelSequence* Sequence, FGuid CamGuid, int StartFrame, int EndFrame)
{
// ------ Add the Camera cut track and the section for this camera ------
UMovieSceneCameraCutTrack* CameraCutTrack = GetCameraCutTrackFromSequence(Sequence); // we already check here if the sequence is valid
if (CameraCutTrack == nullptr)
{
// Add the camera cut track if it doesn't exist
CameraCutTrack = Cast<UMovieSceneCameraCutTrack>(Sequence->MovieScene->AddCameraCutTrack(UMovieSceneCameraCutTrack::StaticClass()));
}
// Calculate the tick value for the input frame numbers
// For example, 100 ticks for one frame
int FrameTickValue = Sequence->MovieScene->GetTickResolution().AsDecimal() / Sequence->MovieScene->GetDisplayRate().AsDecimal();
UMovieSceneCameraCutSection* Section = CameraCutTrack->AddNewCameraCut(UE::MovieScene::FRelativeObjectBindingID(CamGuid), FFrameNumber(StartFrame * FrameTickValue));
if (!Section)
{
UE_LOG(LogTemp, Error, TEXT("AddCameraToSequence: Was not able to create section."));
}
if (EndFrame > StartFrame)
{
Section->SetEndFrame(FFrameNumber(EndFrame * FrameTickValue));
}
UE_LOG(LogTemp, Log, TEXT("AddCameraCutTrack: Added camera cut track for camera %s."), *CamGuid.ToString());
}
// ---------- Sections ----------
UMovieScene3DTransformSection* URuntimeSequenceManager::GetTransformSectionFromSequence(ULevelSequence* Sequence, UObject* Object, UMovieScene3DTransformTrack* TransformTrack, int SectionIndex)
{
if (!TransformTrack)
{
UE_LOG(LogTemp, Error, TEXT("GetTransformSectionFromSequence: Transform track is null."));
return nullptr;
}
// Get all sections
TArray<UMovieSceneSection*> AllSections = TransformTrack->GetAllSections();
// Make sure the index is valid
if (SectionIndex < 0 || SectionIndex >= AllSections.Num())
{
UE_LOG(LogTemp, Error, TEXT("GetTransformSectionFromSequence: Section index out of bounds."));
return nullptr;
}
return Cast<UMovieScene3DTransformSection>(AllSections[SectionIndex]);
}
UMovieScene3DTransformSection* URuntimeSequenceManager::AddTransformSection(ULevelSequence* Sequence, UMovieScene3DTransformTrack* Track, int StartFrame, int EndFrame, EMovieSceneBlendType BlendType)
{
// Create the section
UMovieScene3DTransformSection* TransformSection = Cast<UMovieScene3DTransformSection>(Track->CreateNewSection());
if (!TransformSection)
{
UE_LOG(LogTemp, Error, TEXT("AddTransformKey: Was not able to create section."));
return nullptr;
}
// Calculate the tick value for the input frame numbers
// For example, 100 ticks for one frame
int FrameTickValue = Sequence->MovieScene->GetTickResolution().AsDecimal() / Sequence->MovieScene->GetDisplayRate().AsDecimal();
// Set the frame range of the section
TransformSection->SetRange((TRange<FFrameNumber>(FFrameNumber(StartFrame * FrameTickValue), FFrameNumber(EndFrame * FrameTickValue))));
// Set the blend type
TransformSection->SetBlendType(BlendType);
int RowIndex = -1;
for (UMovieSceneSection* ExistingSection : Track->GetAllSections())
{
RowIndex = FMath::Max(RowIndex, ExistingSection->GetRowIndex());
}
TransformSection->SetRowIndex(RowIndex + 1);
// Add Section to track
Track->AddSection(*TransformSection);
return TransformSection;
}
// ===================== Public =====================
// ---------- Setup ----------
ALevelSequenceActor* URuntimeSequenceManager::GetOrSpawnSequenceActor(UWorld* World, ULevelSequence* Sequence)
{
if (!World || !Sequence) return nullptr;
// Search for a LevelSequenceActor existing which plays this sequence
for (TActorIterator<ALevelSequenceActor> It(World); It; ++It)
{
if (It->GetSequence() == Sequence)
return *It;
}
// If none are found
FActorSpawnParameters SpawnParams;
SpawnParams.Name = FName(TEXT("RuntimeSequenceActor"));
ALevelSequenceActor* NewActor = World->SpawnActor<ALevelSequenceActor>(ALevelSequenceActor::StaticClass(), FTransform::Identity, SpawnParams);
if (NewActor)
{
NewActor->SetSequence(Sequence);
NewActor->InitializePlayer(); // Assure que SequencePlayer est créé
}
return NewActor;
}
FGuid URuntimeSequenceManager::GetGuidFromObjectInSequence(ULevelSequence* Sequence, UObject* Object)
{
if (!Object)
{
UE_LOG(LogTemp, Error, TEXT("GetObjectGuidFromSequence: The Object you want to get is null."));
return FGuid();
}
if (!Sequence)
{
UE_LOG(LogTemp, Error, TEXT("GetObjectGuidFromSequence: The Level Sequence is null."));
return FGuid();
}
// First: fast path using our local map (recommended for runtime usage)
for (const auto& Pair : AddedBindings)
{
if (Pair.Value.IsValid() && Pair.Value.Get() == Object)
{
UE_LOG(LogTemp, Log, TEXT("GetObjectGuidFromSequence: Object found in local mapping!"));
return Pair.Key;
}
}
// If we don't own the mapping (Object might have been added by other code), we can't reliably resolve it
// without engine internals (SharedPlaybackState / Linker). So we return the invalid and let the caller add it.
UE_LOG(LogTemp, Warning, TEXT("GetObjectGuidFromSequence: Object not found in local mapping."));
return FGuid();
}
void URuntimeSequenceManager::ClearSequence(ULevelSequence *Sequence)
{
if (!Sequence)
{
UE_LOG(LogTemp, Error, TEXT("ClearSequence: Sequence is null."));
return;
}
if (AddedBindings.Num() == 0)
{
UE_LOG(LogTemp, Log, TEXT("ClearSequence: No stored bindings to clear."));
return;
}
UMovieScene* MovieScene = Sequence->GetMovieScene();
if (!MovieScene) return;
// ----- Clear all bindings created by this runtime builder -----
TArray<FGuid> Keys;
AddedBindings.GenerateKeyArray(Keys);
// Iterate over all stored bindings and remove them
for (const FGuid& Guid : Keys)
{
Sequence->UnbindPossessableObjects(Guid); // Unbind any objects that were bound at this guid
MovieScene->RemovePossessable(Guid); // Remove the possessable from the movie scene (safe in runtime)
}
AddedBindings.Empty(); // Clear the map
// ----- Clear camera cuts -----
UMovieSceneCameraCutTrack* CameraCutTrack = GetCameraCutTrackFromSequence(Sequence);
if (CameraCutTrack && CameraCutTrack->GetAllSections().Num() > 0)
{
CameraCutTrack->RemoveAllAnimationData();
Sequence->MovieScene->RemoveCameraCutTrack();
}
UE_LOG(LogTemp, Log, TEXT("ClearSequence: Sequence cleared and all stored bindings removed."));
}
void URuntimeSequenceManager::RenderSequence(ULevelSequence* Sequence, const FString& PresetPath)
{
if (bIsRendering)
{
UE_LOG(LogTemp, Error, TEXT("RenderSequence: Already rendering a sequence."));
return;
}
UMoviePipelineQueueEngineSubsystem* Subsystem = GEngine->GetEngineSubsystem<UMoviePipelineQueueEngineSubsystem>();
UMoviePipelineQueue* Queue = Subsystem->GetQueue();
Queue->DeleteAllJobs();
// New job
UMoviePipelineExecutorJob* Job = Queue->AllocateNewJob(UMoviePipelineExecutorJob::StaticClass());
Job->Sequence = FSoftObjectPath(Sequence);
Job->Map = FSoftObjectPath(GetWorld());
Job->JobName = TEXT("RuntimeRender");
if (!PresetPath.IsEmpty())
{
UMoviePipelinePrimaryConfig* Preset = LoadObject<UMoviePipelinePrimaryConfig>(nullptr, *PresetPath);
if (Preset)
Job->SetConfiguration(Preset);
else
UE_LOG(LogTemp, Error, TEXT("RenderSequence: Could not load movie pipeline configuration at path %s."), *PresetPath);
}
// Create an executor that will render the queue in the current level !!
UMoviePipelineInProcessExecutor* Executor = NewObject<UMoviePipelineInProcessExecutor>();
Executor->bUseCurrentLevel = true;
// Add a callbakcs to notify us when the executor is finished
Executor->OnExecutorFinished().AddLambda([this](UMoviePipelineExecutorBase*, bool)
{
UE_LOG(LogTemp, Log, TEXT("RenderSequence: Render finished."));
bIsRendering = false;
});
// Execute
bIsRendering = true;
Subsystem->RenderQueueWithExecutorInstance(Executor);
UE_LOG(LogTemp, Log, TEXT("RenderSequence: Render started."));
}
// ---------- Add Objects ----------
FGuid URuntimeSequenceManager::AddCameraActorToSequence(ULevelSequence *Sequence, ACameraActor *SourceCameraActor, int StartFrame, int EndFrame, bool bSpawnable, bool bStaticTransform)
{
// todo:
// check if a camera is not already in the level sequence
// we could add multiple camera, but it is not usefull right now
FGuid CamGuid = AddCameraBinding(Sequence, bSpawnable);
if (!CamGuid.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("AddCameraActorToSequence: Failed to add camera spawnable to level sequence."));
return FGuid();
}
// ------ Copy the camera components settings to the spawnable camera actor ------
// Get the actor associated with the Guid
ACameraActor* TemplateCamera = Cast<ACameraActor>(AddedBindings[CamGuid].Get());
if (!TemplateCamera)
{
UE_LOG(LogTemp, Error, TEXT("AddCameraActorToSequence: Template camera not found in AddedBindings."));
return CamGuid;
}
// Copy settings
CopyCameraComponentSettings(SourceCameraActor->GetCameraComponent(), TemplateCamera);
// Add camera cut
AddCameraCutTrack(Sequence, CamGuid, StartFrame, EndFrame);
// Add transform track and key
FTransform SourceTransform = SourceCameraActor->GetTransform();
UMovieScene3DTransformTrack* TransformTrack = AddTransformTrack(Sequence, TemplateCamera);
UMovieScene3DTransformSection* TransformSection = AddTransformSection(Sequence, TransformTrack, StartFrame, EndFrame, EMovieSceneBlendType::Absolute);
AddTransformKeyFrame(Sequence, TemplateCamera, TransformSection, 0, StartFrame, SourceTransform, 0);
UE_LOG(LogTemp, Log, TEXT("AddCameraActorToSequence: Added camera component to sequence."));
if (bStaticTransform)
StaticObjects.Add(TemplateCamera, SourceTransform); // Add to the map to add the last keyframe later (in SetSequenceEndToLastKey)
return CamGuid;
}
FGuid URuntimeSequenceManager::AddCameraComponentToSequence(ULevelSequence* Sequence, UCameraComponent* SourceCameraComponent, int StartFrame, int EndFrame, bool bSpawnable, bool bStaticTransform)
{
// todo:
// check if a camera is not already in the level sequence
// we could add multiple camera, but it is not usefull right now
FGuid CamGuid = AddCameraBinding(Sequence, bSpawnable);
if (!CamGuid.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("AddCameraComponentToSequence: Failed to add camera spawnable to level sequence."));
return FGuid();
}
// ------ Copy the camera components settings to the spawnable camera actor ------
// Get the actor associated with the Guid
ACameraActor* TemplateCamera = Cast<ACameraActor>(AddedBindings[CamGuid].Get());
if (!TemplateCamera)
{
UE_LOG(LogTemp, Error, TEXT("AddCameraComponentToSequence: Failed to get camera actor from spawnable."));
return FGuid();
}
// Copy settings
CopyCameraComponentSettings(SourceCameraComponent, TemplateCamera);
// Add camera cut
AddCameraCutTrack(Sequence, CamGuid, StartFrame, EndFrame);
// Add transform track and key
FTransform SourceTransform = SourceCameraComponent->GetComponentTransform();
UMovieScene3DTransformTrack* TransformTrack = AddTransformTrack(Sequence, TemplateCamera);
UMovieScene3DTransformSection* TransformSection = AddTransformSection(Sequence, TransformTrack, StartFrame, EndFrame, EMovieSceneBlendType::Absolute);
AddTransformKeyFrame(Sequence, TemplateCamera, TransformSection, 0, StartFrame, SourceTransform, 0);
UE_LOG(LogTemp, Log, TEXT("AddCameraComponentToSequence: Added camera component to sequence."));
if (bStaticTransform)
StaticObjects.Add(TemplateCamera, SourceTransform); // Add to the map to add the last keyframe later (in SetSequenceEndToLastKey)
return CamGuid;
}
FGuid URuntimeSequenceManager::AddObjectToSequence(ULevelSequence *Sequence, UObject* Object, UObject*& OutTemplateObject, bool bSpawnable = true, bool bSyncTransform = true, bool bStaticTransform = false, FTransform Transform = FTransform::Identity)
{
OutTemplateObject = nullptr;
FGuid Guid = GetGuidFromObjectInSequence(Sequence, Object); // we already check if object and sequence are null inside this method
if (Guid.IsValid())
{
UE_LOG(LogTemp, Log, TEXT("AddObjectToSequence: Object %s already exists!"), *Object->GetName());
if (TWeakObjectPtr<UObject>* WeakObjPtr = AddedBindings.Find(Guid))
OutTemplateObject = WeakObjPtr->Get();
return Guid;
}
FGuid BindingGuid = CreateBinding(Sequence, Object, bSpawnable);
if (bSyncTransform)
CopyTransformToSpawnable(Sequence, BindingGuid, Transform, 0);
if (TWeakObjectPtr<UObject>* WeakObjPtr = AddedBindings.Find(BindingGuid))
OutTemplateObject = WeakObjPtr->Get();
if (bStaticTransform)
{
if (OutTemplateObject)
StaticObjects.Add(OutTemplateObject, Transform); // Add to the map to add the last keyframe later (in SetSequenceEndToLastKey)
else
{
UE_LOG(LogTemp, Error, TEXT("AddObjectToSequence: Failed to get template object from binding guid."));
}
}
return BindingGuid;
}
FGuid URuntimeSequenceManager::AddChildBindingToSequence(ULevelSequence* Sequence, const FGuid& ParentGuid, UObject* ChildObject, UObject*& OutChildTemplateObject)
{
OutChildTemplateObject = nullptr;
if (!Sequence || !ChildObject || !ParentGuid.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("AddChildBindingToSequence: Invalid input"));
return FGuid();
}
// Create a possessable, then it will be parented to the ParentGuid object
FGuid ChildGuid = Sequence->MovieScene->AddPossessable(ChildObject->GetName(), ChildObject->GetClass());
if (!ChildGuid.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("AddChildBindingToSequence: Failed to add child binding to sequence."));
return FGuid();
}
Sequence->BindPossessableObject(ChildGuid, *ChildObject, ChildObject->GetWorld());
FMovieScenePossessable* Possessable = Sequence->MovieScene->FindPossessable(ChildGuid);
if (Possessable)
{
Possessable->SetParent(ParentGuid, Sequence->MovieScene);
// Add the child object to the map to later be able to get it with GetGuidFromObjectInSequence()
AddedBindings.Add(ChildGuid, ChildObject);
}
else
{
UE_LOG(LogTemp, Error, TEXT("AddChildBindingToSequence: Failed to find possessable."));
return FGuid();
}
// Get the child template
OutChildTemplateObject = ChildObject;
UE_LOG(LogTemp, Log, TEXT("AddChildBindingToSequence: Child %s added under parent GUID %s"), *ChildObject->GetName(), *ParentGuid.ToString());
return ChildGuid;
}
void URuntimeSequenceManager::AddAnimationToSequence(ULevelSequence *Sequence, UObject* Object, UAnimSequence *Anim, int StartFrame, int EndFrame)
{
if (!Anim)
{
UE_LOG(LogTemp, Error, TEXT("AddAnimationToSequence: Animation is not valid."));
return;
}
FGuid Guid = FGuid();
UMovieSceneSkeletalAnimationTrack* AnimTrack = GetOrAddAnimationTrackFromSequence(Sequence, Object, Guid);
// Calculate the tick value for the input frame numbers
// For example, 100 ticks for one frame
int FrameTickValue = Sequence->MovieScene->GetTickResolution().AsDecimal() / Sequence->MovieScene->GetDisplayRate().AsDecimal();
// Add animation Track
UMovieSceneSection* Section = AnimTrack->AddNewAnimation(FFrameNumber(StartFrame * FrameTickValue), Anim);
if (!Section)
{
UE_LOG(LogTemp, Error, TEXT("AddAnimationToSequence: Was not able to create section."));
}
if (EndFrame > StartFrame)
{
Section->SetEndFrame(FFrameNumber(EndFrame * FrameTickValue));
}
}
// ---------- Keyframing ----------
void URuntimeSequenceManager::AddTransformKeyFrame(ULevelSequence* Sequence, UObject* Object, UMovieScene3DTransformSection* Section, int SectionIndex, int Frame, FTransform Transform, int KeyInterpolation)
{
if (!Section)
{
UE_LOG(LogTemp, Error, TEXT("AddTransformKey: Section is null."));
return;
}
RemoveTransformKeyFrame(Section, Frame);
// Location
AddKeyFrameToDoubleChannel(Section, 0, Frame, Transform.GetLocation().X, KeyInterpolation);
AddKeyFrameToDoubleChannel(Section, 1, Frame, Transform.GetLocation().Y, KeyInterpolation);
AddKeyFrameToDoubleChannel(Section, 2, Frame, Transform.GetLocation().Z, KeyInterpolation);
// Rotation
AddKeyFrameToDoubleChannel(Section, 3, Frame, Transform.Rotator().Roll, KeyInterpolation);
AddKeyFrameToDoubleChannel(Section, 4, Frame, Transform.Rotator().Pitch, KeyInterpolation);
AddKeyFrameToDoubleChannel(Section, 5, Frame, Transform.Rotator().Yaw, KeyInterpolation);
// Location
AddKeyFrameToDoubleChannel(Section, 6, Frame, Transform.GetScale3D().X, KeyInterpolation);
AddKeyFrameToDoubleChannel(Section, 7, Frame, Transform.GetScale3D().Y, KeyInterpolation);
AddKeyFrameToDoubleChannel(Section, 8, Frame, Transform.GetScale3D().Z, KeyInterpolation);
UE_LOG(LogTemp, Log, TEXT("AddTransformKey: Added key frame to section %d."), SectionIndex);
}
void URuntimeSequenceManager::RemoveTransformKeyFrame(UMovieScene3DTransformSection* Section, int Frame)
{
if (!Section)
{
UE_LOG(LogTemp, Error, TEXT("AddTransformKey: Section not found."));
return;
}
// Location
RemoveKeyFrameToDoubleChannel(Section, 0, Frame);
RemoveKeyFrameToDoubleChannel(Section, 1, Frame);
RemoveKeyFrameToDoubleChannel(Section, 2, Frame);
// Rotation
RemoveKeyFrameToDoubleChannel(Section, 3, Frame);
RemoveKeyFrameToDoubleChannel(Section, 4, Frame);
RemoveKeyFrameToDoubleChannel(Section, 5, Frame);
// Location
RemoveKeyFrameToDoubleChannel(Section, 6, Frame);
RemoveKeyFrameToDoubleChannel(Section, 7, Frame);
RemoveKeyFrameToDoubleChannel(Section, 8, Frame);
UE_LOG(LogTemp, Log, TEXT("AddTransformKey: Removed key frame from section %d."), Section->GetRowIndex());
}
// ---------- Finalize ----------
void URuntimeSequenceManager::FinalizeSequence(ULevelSequence* Sequence)
{
FFrameNumber LastFrame = FindLastFrameInSequence(Sequence);
SetPlaybackRangeToLastFrame(Sequence, LastFrame);
AddFinalStaticKeyFrames(Sequence, LastFrame);
ExtendAllTracksToFrame(Sequence, LastFrame);
}
void URuntimeSequenceManager::DebugTest(ULevelSequence* Sequence)
{
FFrameNumber LastFrame = FindLastFrameInSequence(Sequence);
ExtendAllTracksToFrame(Sequence, LastFrame);
}
If you know what’s happening, please let me know.