FGuid FSequencer::AddSpawnableForAssetOrClass( UObject* Object, UObject* CounterpartGamePreviewObject ) { FGuid NewSpawnableGuid; if( ObjectBindingManager->AllowsSpawnableObjects() ) { // Grab the MovieScene that is currently focused. We'll add our Blueprint as an inner of the // MovieScene asset. UMovieScene* OwnerMovieScene = GetFocusedMovieScene(); // @todo sequencer: Undo doesn't seem to be working at all const FScopedTransaction Transaction( LOCTEXT("UndoAddingObject", "Add Object to MovieScene") ); // Use the class as the spawnable's name if this is an actor class, otherwise just use the object name (asset) const bool bIsActorClass = Object->IsA( AActor::StaticClass() ) && !Object->HasAnyFlags( RF_ArchetypeObject ); const FName AssetName = bIsActorClass ? Object->GetClass()->GetFName() : Object->GetFName(); // Inner objects don't need a name (it will be auto-generated by the UObject system), but we want one in this case // because the class of any actors that are created from this Blueprint may end up being user-facing. const FName BlueprintName = MakeUniqueObjectName( OwnerMovieScene, UBlueprint::StaticClass(), AssetName ); // Use the asset name as the initial spawnable name const FString NewSpawnableName = AssetName.ToString(); // @todo sequencer: Need UI to allow user to rename these slots // Create our new blueprint! UBlueprint* NewBlueprint = NULL; { // @todo sequencer: Add support for forcing specific factories for an asset UActorFactory* FactoryToUse = NULL; if( bIsActorClass ) { // Placing an actor class directly:: FactoryToUse = GEditor->FindActorFactoryForActorClass( Object->GetClass() ); } else { // Placing an asset FactoryToUse = FActorFactoryAssetProxy::GetFactoryForAssetObject( Object ); } if( FactoryToUse != NULL ) { // Create the blueprint NewBlueprint = FactoryToUse->CreateBlueprint( Object, OwnerMovieScene, BlueprintName ); } else if( bIsActorClass ) { // We don't have a factory, but we can still try to create a blueprint for this actor class NewBlueprint = FKismetEditorUtilities::CreateBlueprint( Object->GetClass(), OwnerMovieScene, BlueprintName, EBlueprintType::BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass() ); } } if( ensure( NewBlueprint != NULL ) ) { if( NewBlueprint->GeneratedClass != NULL && FBlueprintEditorUtils::IsActorBased( NewBlueprint ) ) { AActor* ActorCDO = CastChecked< AActor >( NewBlueprint->GeneratedClass->ClassDefaultObject ); // If we have a counterpart object, then copy the properties from that object back into our blueprint's CDO // @todo sequencer livecapture: This isn't really good enough to handle complex actors. The dynamically-spawned actor could have components that // were created in its construction script or via straight-up C++ code. Instead what we should probably do is duplicate the PIE actor and generate // our CDO from that duplicate. It could get pretty complex though. if( CounterpartGamePreviewObject != NULL ) { AActor* CounterpartGamePreviewActor = CastChecked< AActor >( CounterpartGamePreviewObject ); CopyActorProperties( CounterpartGamePreviewActor, ActorCDO ); } else { // Place the new spawnable in front of the camera (unless we were automatically created from a PIE actor) PlaceActorInFrontOfCamera( ActorCDO ); } } NewSpawnableGuid = OwnerMovieScene->AddSpawnable( NewSpawnableName, NewBlueprint, CounterpartGamePreviewObject ); if (IsShotFilteringOn()) { AddUnfilterableObject(NewSpawnableGuid); } } } return NewSpawnableGuid; }
void UActorRecording::StartRecordingActorProperties(ULevelSequence* CurrentSequence, float CurrentSequenceTime) { if(CurrentSequence != nullptr) { // set up our spawnable or possessable for this actor UMovieScene* MovieScene = CurrentSequence->GetMovieScene(); AActor* Actor = GetActorToRecord(); if (bRecordToPossessable) { Guid = MovieScene->AddPossessable(Actor->GetActorLabel(), Actor->GetClass()); CurrentSequence->BindPossessableObject(Guid, *Actor, Actor->GetWorld()); } else { FString TemplateName = GetUniqueSpawnableName(MovieScene, Actor->GetName()); AActor* ObjectTemplate = CastChecked<AActor>(CurrentSequence->MakeSpawnableTemplateFromInstance(*Actor, *TemplateName)); if (ObjectTemplate) { TInlineComponentArray<USkeletalMeshComponent*> SkeletalMeshComponents; ObjectTemplate->GetComponents(SkeletalMeshComponents); for (USkeletalMeshComponent* SkeletalMeshComponent : SkeletalMeshComponents) { SkeletalMeshComponent->SetAnimationMode(EAnimationMode::AnimationSingleNode); SkeletalMeshComponent->bEnableUpdateRateOptimizations = false; SkeletalMeshComponent->MeshComponentUpdateFlag = EMeshComponentUpdateFlag::AlwaysTickPoseAndRefreshBones; SkeletalMeshComponent->ForcedLodModel = 1; } Guid = MovieScene->AddSpawnable(TemplateName, *ObjectTemplate); } } // now add tracks to record if(Guid.IsValid()) { // add our folder FindOrAddFolder(MovieScene); // force set recording to record translations as we need this with no animation UMovieScene3DTransformSectionRecorderSettings* TransformSettings = ActorSettings.GetSettingsObject<UMovieScene3DTransformSectionRecorderSettings>(); check(TransformSettings); TransformSettings->bRecordTransforms = true; // grab components so we can track attachments // don't include non-CDO here as they wont be part of our initial BP (duplicated above) // we will catch these 'extra' components on the first tick const bool bIncludeNonCDO = false; SyncTrackedComponents(bIncludeNonCDO); TInlineComponentArray<USceneComponent*> SceneComponents(GetActorToRecord()); // check if components need recording TInlineComponentArray<USceneComponent*> ValidSceneComponents; for(TWeakObjectPtr<USceneComponent>& SceneComponent : TrackedComponents) { if(ValidComponent(SceneComponent.Get())) { ValidSceneComponents.Add(SceneComponent.Get()); // add all parent components too TArray<USceneComponent*> ParentComponents; SceneComponent->GetParentComponents(ParentComponents); for(USceneComponent* ParentComponent : ParentComponents) { ValidSceneComponents.AddUnique(ParentComponent); } } } ProcessNewComponentArray(ValidSceneComponents); TSharedPtr<FMovieSceneAnimationSectionRecorder> FirstAnimRecorder = nullptr; for(USceneComponent* SceneComponent : ValidSceneComponents) { TSharedPtr<FMovieSceneAnimationSectionRecorder> AnimRecorder = StartRecordingComponentProperties(SceneComponent->GetFName(), SceneComponent, GetActorToRecord(), CurrentSequence, CurrentSequenceTime, AnimationSettings); if(!FirstAnimRecorder.IsValid() && AnimRecorder.IsValid() && GetActorToRecord()->IsA<ACharacter>()) { FirstAnimRecorder = AnimRecorder; } } // we need to create a transform track even if we arent recording transforms if (FSequenceRecorder::Get().GetTransformRecorderFactory().CanRecordObject(GetActorToRecord())) { TSharedPtr<IMovieSceneSectionRecorder> Recorder = FSequenceRecorder::Get().GetTransformRecorderFactory().CreateSectionRecorder(TransformSettings->bRecordTransforms, FirstAnimRecorder); if(Recorder.IsValid()) { Recorder->CreateSection(GetActorToRecord(), MovieScene, Guid, CurrentSequenceTime); Recorder->Record(CurrentSequenceTime); SectionRecorders.Add(Recorder); } } TArray<IMovieSceneSectionRecorderFactory*> ModularFeatures = IModularFeatures::Get().GetModularFeatureImplementations<IMovieSceneSectionRecorderFactory>(MovieSceneSectionRecorderFactoryName); for (IMovieSceneSectionRecorderFactory* Factory : ModularFeatures) { if (Factory->CanRecordObject(GetActorToRecord())) { TSharedPtr<IMovieSceneSectionRecorder> Recorder = Factory->CreateSectionRecorder(ActorSettings); if (Recorder.IsValid()) { Recorder->CreateSection(GetActorToRecord(), MovieScene, Guid, CurrentSequenceTime); Recorder->Record(CurrentSequenceTime); SectionRecorders.Add(Recorder); } } } } } }