Custom importer for Level sequence can no longer fix value changes into spawnable using CopyObjectTemplate

We use a custom import tool to generate Level sequences from Maya export files, this involves creating spawnable bindings and modifying component parameters on the actor and then calling CopyObjectTemplate to fix those param changes to the spawnable. The next time the Level sequence loads and the actor gets spawned it would spawn with the changes made earlier.

However In UE5.6 (maybe earlier) When we create a spawnable binding we use ISequencer::CreateBinding, this now no longer creates a standard spawnable, it creates a custom possessable (change made for fortnight i believe) so we can no longer use UMovieScene::FindSpawnable (it returns Null) and CopyObjectTemplate.

Steps to Reproduce
Use c++ to load level sequence created In Unreal 4.#.

Get Spawnable using UMovieScene::FindSpawnable

Get Spawnable Actor

Modify Actor component

Use FMovieSceneSpawnable::CopyObjectTemplate to preserve component changes in spawnable.

UMovieScene::FindSpawnable now fails because of changes made to accommodate FortNite (spawnables converted to custom possessable bindings)

I can find the possessable, but does anyone know the equivalent of CopyObjectTemplate for these custom possessable bindings?

Thank you in advance for any help :slight_smile:

Hello [mention removed]​,

I took a look at the ISequencer and SequencerUtilities code, and there is a way to create a standard spawnable binding so you can continue using FindSpawnable and CopyObjectTemplate as before.

Instead of calling ISequencer::CreateBinding directly, try using FSequencer::CreateBinding with a FCreateBindingParams where bSpawnable is set to true. This forces the creation of the “old-style” spawnable rather than the newer possessable bindings.

For example:

UE::Sequencer::FCreateBindingParams BindingParams;
BindingParams.BindingNameOverride = BindName;
BindingParams.bSpawnable = true; // Use old-style spawnable
 
FGuid BindingGuid = FSequencer::CreateBinding(Object, BindingParams);

With bSpawnable set, CreateOrReplaceBinding will call MakeNewSpawnable() internally, giving you a valid spawnable GUID. You should be able to then use that GUID with CopyObjectTemplate just like in your previous workflow.

The rest of the available FCreateBindingParams are listed in SequencerUtilities.h

Please let me know if this information helps.

Best,

Francisco

Thank you for the info, however I believe there is an issue with using the old style spawnables, I need to double check this but I believe the old style spawnables no longer give you the option to right click on the binding and change the actor class! (Menu option is missing), will get back to you with more info.

Regards

Paul

After investigation I find that setting bSpawnable to true still creates a custom binding and that the MovieScene spawnables array remains empty. This means I cant use MovieScene::FindSpawnable to then call CopyObjectTemplate.

Hello [mention removed]​,

After some digging, I ran into the problem you described. It appears the bSpawnable property still results in a custom binding and the spawnables array stays empty.

What worked for me was calling UMovieScene::AddSpawnable directly. That function does populate the Spawnables array so you can then call FindSpawnable and use CopyObjectTemplate like before.

Here’s a minimal wrapper I tested from an Editor Utility Widget:

bool UMySequencerHelper::CreateSpawnable(ULevelSequence* Sequence, UClass* ClassToSpawn, const FString& BindingDisplayName)
{
    if (!Sequence || !ClassToSpawn)
    {
        UE_LOG(LogTemp, Warning, TEXT("CreateSpawnable: invalid Sequence or Class"));
        return false;
    }
 
    UMovieScene* MovieScene = Sequence->GetMovieScene();
    if (!MovieScene)
    {
        UE_LOG(LogTemp, Warning, TEXT("CreateSpawnable: MovieScene is null"));
        return false;
    }
 
    AActor* Template = NewObject<AActor>(Sequence, ClassToSpawn, NAME_None, RF_Transactional);
    if (!Template)
    {
        UE_LOG(LogTemp, Error, TEXT("CreateSpawnable: Failed to create template for %s"), *ClassToSpawn->GetName());
        return false;
    }
 
    FMovieSceneSpawnable::MarkSpawnableTemplate(*Template);
 
    // Add to the MovieScene
    FGuid SpawnableGuid = MovieScene->AddSpawnable(BindingDisplayName, *Template);
    if (!SpawnableGuid.IsValid())
    {
        UE_LOG(LogTemp, Error, TEXT("CreateSpawnable: AddSpawnable failed for %s"), *BindingDisplayName);
        return false;
    }
 
    // You may add tracks here 
    if (UMovieSceneSpawnTrack* SpawnTrack = MovieScene->AddTrack<UMovieSceneSpawnTrack>(SpawnableGuid))
    {
        if (UMovieSceneSpawnSection* SpawnSection = Cast<UMovieSceneSpawnSection>(SpawnTrack->CreateNewSection()))
        {
            SpawnTrack->AddSection(*SpawnSection);
            SpawnSection->GetChannel().SetDefault(true); 
        }
    }
 
#if WITH_EDITOR
    Sequence->MarkPackageDirty();
#endif
 
    return true;
}

With this approach, MovieScene->FindSpawnable returns a valid entry. Please let me know if this helps.

Best,

Francisco