GetSpawnableCount() always returns 0

This question was created in reference to: [How to change the class of a spawnable actor track in a level [Content removed]

It looks like the code in the question referenced above doesn’t work in the latest version of the engine. The issue seems to be that MovieScene->GetSpawnableCount() always returns 0. I’ve attached a project for repro purposes. Is this a bug or has the code changed? Is there a different way to get spawnable tracks now?

Steps to Reproduce

  • In the project provided, right click on the Level Sequence LS_Test and select Scripted Asset Actions -> Print Spawnables And Possessables
  • In the Output log you’ll see that it says there are 0 spawnable tracks and 4 possessable tracks, but if you open the level sequence you’ll see there are 2 possessable tracks and 2 spawnable tracks

Hi Guillermo,

Indeed, UE 5.5 has a new way to handle Spawnables. Now, spawnables can be based on either FMovieSceneSpawnable (as before) or UMovieSceneSpawnableActorBinding. And the method MovieScene->GetSpawnableCount() only counts the old FMovieSceneSpawnables.

The good news is that there is now much greater support for scripting Sequencer using Blueprint Editor Utilities or Python. For example, you can find a spawnable by name and change its template class in BP as follows:

[Image Removed]You can also get all bindings in a sequence with the GetBindings() node, and query the name of each binding with its GetName() node:

[Image Removed]The somewhat bad news is that node “Change Actor Template Class” only works for a sequence that is currently open for editing in Sequencer.

If you need to work with sequences while they are not open for editing, you will need to use C++ as before. However, unfortunately, many useful C++ functions for the new implementation are inside class UMovieSceneSequenceExtensions, which has been exported to BP and Python but not for the DLL public interface for usage by other C++ modules. This means that you may currently need to copy whole methods from that class, such as GetBindings(), GetSpawnables() and FindBindingByName(). Moreover, you will probably need to copy ULevelSequenceEditorSubsystem::ChangeActorTemplateClass() and some of the functions it calls internally, but adapting them to use a given MovieScene* rather than accessing the sequence that is currently open in Sequencer.

Please let me know if this helps you get back the functionality you need, or if you need any further assistance.

Best regards,

Vitor

Thanks Vitor. Our tool is meant to edit multiple sequences at once (basically all takes recorded during a shoot), so having to manually open each sequence would take a long time. Is there any chance that this code can be exposed in C++ in future versions of the engine? I’d rather not copy the methods into our plugin, since this is part of our toolset and it would be hard to maintain as we update the engine code. Also, how come MovieScene->GetPossessableCount() counts UMovieSceneSpawnableActorBinding as possessable?

Hi Guillermo,

If you find it helpful, you can programmatically open and close the Sequencer Editor for a given Sequence by using the functions from ULevelSequenceEditorBlueprintLibrary:

ULevelSequenceEditorBlueprintLibrary::OpenLevelSequence(); ULevelSequenceEditorBlueprintLibrary::CloseLevelSequence(); ULevelSequenceEditorBlueprintLibrary::GetCurrentLevelSequence();On the other hand, if you change your mind about grabbing some code from the engine, here are some functions you can use. You will find that some of them are similar to the ones I provided you on the referenced topic, while also handling the new MovieSceneSpawnableActorBindings.

`FString UMyEditorLibrary::GetBindingName (const FMovieSceneBindingProxy& InBinding)
{
UMovieScene* MovieScene = InBinding.Sequence ? InBinding.Sequence->GetMovieScene() : nullptr;
if (MovieScene && InBinding.BindingID.IsValid())
{
FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(InBinding.BindingID);
if (Spawnable)
{
return Spawnable->GetName();
}

FMovieScenePossessable* Possessable = MovieScene->FindPossessable(InBinding.BindingID);
if (Possessable)
{
return Possessable->GetName();
}
}

return FString();
}

TArray UMyEditorLibrary::GetBindings (UMovieSceneSequence* Sequence)
{
if (!Sequence)
return {};

UMovieScene* MovieScene = Sequence->GetMovieScene();
if (!MovieScene)
return {};

TArray Found;

const TArray& Bindings = MovieScene->GetBindings();
for (const FMovieSceneBinding& Binding : Bindings)
{
Found.Emplace(Binding.GetObjectGuid(), Sequence);
}

return Found;
}

TArray UMyEditorLibrary::GetSpawnables (UMovieSceneSequence* Sequence)
{
if (!Sequence)
return {};

TArray Found;

// Find MovieSceneSpawnableActorBindings
if (const FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences())
{
for (const FMovieSceneBindingReference& BindingReference : BindingReferences->GetAllReferences())
{
if (BindingReference.CustomBinding && BindingReference.CustomBinding->IsA())
{
Found.Emplace(BindingReference.ID, Sequence);
}
}
}

// Find MovieSceneSpawnables
UMovieScene* MovieScene = Sequence->GetMovieScene();
if (MovieScene)
{
int32 Count = MovieScene->GetSpawnableCount();
for (int32 i = 0; i < Count; ++i)
{
const FMovieSceneSpawnable& Spawnable = MovieScene->GetSpawnable(i);
Found.Emplace(Spawnable.GetGuid(), Sequence);
}
}

return Found;
}

TArray UMyEditorLibrary::GetPossessables (UMovieSceneSequence* Sequence)
{
if (!Sequence)
return {};

UMovieScene* MovieScene = Sequence->GetMovieScene();
if (!MovieScene)
return {};

TArray Found;

if (const FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences())
{
for (const FMovieSceneBindingReference& BindingReference : BindingReferences->GetAllReferences())
{
if (!BindingReference.CustomBinding)
{
Found.Emplace(BindingReference.ID, Sequence);
}
}
}
else
{
int32 Count = MovieScene->GetPossessableCount();
for (int32 i = 0; i < Count; ++i)
{
const FMovieScenePossessable& Possessable = MovieScene->GetPossessable(i);
Found.Emplace(Possessable.GetGuid(), Sequence);
}
}

return Found;
}

TArray UMyEditorLibrary::FindBindingsByName(UMovieSceneSequence* Sequence, FString NameToFind)
{
if (!Sequence)
return {};

UMovieScene* MovieScene = Sequence->GetMovieScene();
if (!MovieScene)
return {};

TArray Found;

const TArray& Bindings = MovieScene->GetBindings();
for (const FMovieSceneBinding& Binding : Bindings)
{
if (Binding.GetName() == NameToFind)
{
Found.Emplace(Binding.GetObjectGuid(), Sequence);
}
}

return Found;
}

TArray UMyEditorLibrary::FindSpawnablesByName (UMovieSceneSequence* Sequence, FString NameToFind)
{
TArray Found;

TArray Spawnables = GetSpawnables(Sequence);
for (const FMovieSceneBindingProxy& Spawnable : Spawnables)
{
if (GetBindingName(Spawnable) == NameToFind)
{
Found.Add(Spawnable);
}
}

return Found;
}

TArray UMyEditorLibrary::FindPossessablesByName (UMovieSceneSequence* Sequence, FString NameToFind)
{
TArray Found;

TArray Possessables = GetPossessables(Sequence);
for (const FMovieSceneBindingProxy& Possessable : Possessables)
{
if (GetBindingName(Possessable) == NameToFind)
{
Found.Add(Possessable);
}
}

return Found;
}

void UMyEditorLibrary::ChangeNamedSpawnableClass (UMovieSceneSequence* Sequence, FString NameToFind, TSubclassOf NewClass)
{
// Get the MovieScene contained in this Sequence
UMovieScene* MovieScene = Sequence->GetMovieScene();
check(MovieScene);

ULevelSequenceEditorSubsystem* LevelSequenceEditorSubsystem = GEditor->GetEditorSubsystem();

// Iterate over the spawnables of this Sequence
TArray Spawnables = FindSpawnablesByName(Sequence, NameToFind);
for (const FMovieSceneBindingProxy& Spawnable : Spawnables)
{
DoChangeSpawnableClass(Sequence, Spawnable, NewClass);
}
}

void UMyEditorLibrary::DoChangeSpawnableClass(UMovieSceneSequence* Sequence, const FMovieSceneBindingProxy& Binding, TSubclassOf NewClass)
{
ILevelSequenceModule& LevelSequenceModule = FModuleManager::LoadModuleChecked(“LevelSequence”);

// Get the MovieScene contained in this Sequence
UMovieScene* MovieScene = Sequence->GetMovieScene();
check(MovieScene);

// Generate object spawners
TArray<TSharedRef> ObjectSpawners;
LevelSequenceModule.GenerateObjectSpawners(ObjectSpawners);

// Iterate over the spawners until we find one that works
for (TSharedRef Spawner : ObjectSpawners)
{
// Run the spawner
TValueOrError<FNewSpawnable, FText> Result = Spawner->CreateNewSpawnableType(*NewClass.Get(), *MovieScene, nullptr);

// Check if it worked
if (Result.IsValid())
{
UObject* ObjectTemplate = Result.GetValue().ObjectTemplate;

// Mark MovieScene dirty
MovieScene->Modify();

// Handle FMovieSceneSpawnable
if (FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(Binding.BindingID))
{
Spawnable->SetObjectTemplate(ObjectTemplate);
}
// Handle MovieSceneSpawnableActorBinding
else if (FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences())
{
for (const FMovieSceneBindingReference& BindingReference : BindingReferences->GetAllReferences())
{
if (UMovieSceneSpawnableBindingBase* SpawnableBinding = (UMovieSceneSpawnableBindingBase*)(BindingReference.CustomBinding))
{
SpawnableBinding->SetObjectTemplate(ObjectTemplate);
}
}
}
}
}
}`If you are building the engine from source, you can also expose the sequencer extensions classes (such as UMovieSceneBindingExtensions) yourself. Simply open file “MovieSceneBindingExtensions.h”, for example, and add the macro “SEQUENCERSCRIPTING_API” on the class definition (between the “class” keyword and the name of the class). If you wish, I can ask someone from Epic if there are any plans to expose them on a future official release. I can tell you right away, though, that they are not currently exposed even on the latest commit of the main developmente branch, so even if there are plans to do so in the future, it might take a while until the change ends up in a release.

Oh, and about your question, yes, from the point-of-view of the UMovieScene class, UMovieSceneSpawnableActorBinding is indeed a Possessable, but one with a custom binding that creates a Spawnable to possess when evaluated. I also think this is kind of confusing, but apparently this change was meant to add more flexibility to spawnables, as per the comment above class UMovieSceneSpawnableBindingBase:

`/**

  • The base class for custom spawnable bindings. A spawnable binding will spawn an object upon resolution or return a cached previously spawned object.
  • UMovieSceneSpawnableActorBinding is the reimplementation of previous FMovieSceneSpawnable features and spawns an actor based on a saved template and actor class.
  • Otherwise, projects are free to implement their own Spawnable bindings by overriding this class.
  • In doing so, they could choose to just override GetSpawnObjectClass, PostSpawnObject, and PreDestroyObject for example to do custom post-spawn setup on a character mesh,
  • or they could choose to fully override SpawnObject and DestroySpawnedObject and do their own custom logic for spawning completely.
    */`Best regards,

Vitor