void UActorRecording::StartRecordingNewComponents(ULevelSequence* CurrentSequence, float CurrentSequenceTime) { if (GetActorToRecord() != nullptr) { // find the new component(s) TInlineComponentArray<USceneComponent*> NewComponents; TArray<USceneComponent*> SceneComponents; GetSceneComponents(SceneComponents); for(USceneComponent* SceneComponent : SceneComponents) { if(ValidComponent(SceneComponent)) { TWeakObjectPtr<USceneComponent> WeakSceneComponent(SceneComponent); int32 FoundIndex = TrackedComponents.Find(WeakSceneComponent); if(FoundIndex == INDEX_NONE) { // new component! NewComponents.Add(SceneComponent); } } } ProcessNewComponentArray(NewComponents); UMovieScene* MovieScene = CurrentSequence->GetMovieScene(); check(MovieScene); FAnimationRecordingSettings ComponentAnimationSettings = AnimationSettings; ComponentAnimationSettings.bRemoveRootAnimation = false; ComponentAnimationSettings.bRecordInWorldSpace = false; const USequenceRecorderSettings* Settings = GetDefault<USequenceRecorderSettings>(); if (!bRecordToPossessable) { FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(Guid); check(Spawnable); AActor* ObjectTemplate = CastChecked<AActor>(Spawnable->GetObjectTemplate()); for (USceneComponent* SceneComponent : NewComponents) { // new component, so we need to add this to our BP if it didn't come from SCS FName NewName; if (SceneComponent->CreationMethod != EComponentCreationMethod::SimpleConstructionScript) { // Give this component a unique name within its parent NewName = *FString::Printf(TEXT("Dynamic%s"), *SceneComponent->GetFName().GetPlainNameString()); NewName.SetNumber(1); while (FindObjectFast<UObject>(ObjectTemplate, NewName)) { NewName.SetNumber(NewName.GetNumber() + 1); } USceneComponent* TemplateRoot = ObjectTemplate->GetRootComponent(); USceneComponent* AttachToComponent = nullptr; // look for a similar attach parent in the current structure USceneComponent* AttachParent = SceneComponent->GetAttachParent(); if(AttachParent != nullptr) { // First off, check if we're attached to a component that has already been duplicated into this object // If so, the name lookup will fail, so we use a direct reference if (TWeakObjectPtr<USceneComponent>* DuplicatedComponent = DuplicatedDynamicComponents.Find(AttachParent)) { AttachToComponent = DuplicatedComponent->Get(); } // If we don't have an attachment parent duplicated already, perform a name lookup if (!AttachToComponent) { FName AttachName = SceneComponent->GetAttachParent()->GetFName(); TInlineComponentArray<USceneComponent*> AllChildren; ObjectTemplate->GetComponents(AllChildren); for (USceneComponent* Child : AllChildren) { CA_SUPPRESS(28182); // Dereferencing NULL pointer. 'Child' contains the same NULL value as 'AttachToComponent' did. if (Child->GetFName() == AttachName) { AttachToComponent = Child; break; } } } } if (!AttachToComponent) { AttachToComponent = ObjectTemplate->GetRootComponent(); } USceneComponent* NewTemplateComponent = Cast<USceneComponent>(StaticDuplicateObject(SceneComponent, ObjectTemplate, NewName, RF_AllFlags & ~RF_Transient)); NewTemplateComponent->AttachToComponent(AttachToComponent, FAttachmentTransformRules::KeepRelativeTransform, SceneComponent->GetAttachSocketName()); ObjectTemplate->AddInstanceComponent(NewTemplateComponent); DuplicatedDynamicComponents.Add(SceneComponent, NewTemplateComponent); } else { NewName = SceneComponent->GetFName(); } StartRecordingComponentProperties(NewName, SceneComponent, GetActorToRecord(), CurrentSequence, CurrentSequenceTime, ComponentAnimationSettings); bNewComponentAddedWhileRecording = true; } SyncTrackedComponents(); } else { for (USceneComponent* SceneComponent : NewComponents) { // new component, start recording StartRecordingComponentProperties(SceneComponent->GetFName(), SceneComponent, GetActorToRecord(), CurrentSequence, CurrentSequenceTime, ComponentAnimationSettings); } SyncTrackedComponents(); } } }
void UActorRecording::ProcessNewComponentArray(TInlineComponentArray<USceneComponent*>& ProspectiveComponents) const { // Only iterate as far as the current size of the array (it may grow inside the loop) int32 LastIndex = ProspectiveComponents.Num(); for(int32 Index = 0; Index < LastIndex; ++Index) { USceneComponent* NewComponent = ProspectiveComponents[Index]; USceneComponent* Parent = ProspectiveComponents[Index]->GetAttachParent(); while (Parent) { TWeakObjectPtr<USceneComponent> WeakParent(Parent); if (TrackedComponents.Contains(WeakParent) || ProspectiveComponents.Contains(Parent) || Parent->GetOwner() != NewComponent->GetOwner()) { break; } else { ProspectiveComponents.Add(Parent); } Parent = Parent->GetAttachParent(); } } // Sort parent first, to ensure that attachments get added properly TMap<USceneComponent*, int32> AttachmentDepths; for (USceneComponent* Component : ProspectiveComponents) { AttachmentDepths.Add(Component, GetAttachmentDepth(Component)); } ProspectiveComponents.Sort( [&](USceneComponent& A, USceneComponent& B) { return *AttachmentDepths.Find(&A) < *AttachmentDepths.Find(&B); } ); }
void USimpleConstructionScript::ExecuteScriptOnActor(AActor* Actor, const FTransform& RootTransform, bool bIsDefaultTransform) { if(RootNodes.Num() > 0) { TSet<UActorComponent*> AllComponentsCreatedBySCS; TInlineComponentArray<UActorComponent*> InstancedComponents; for(auto NodeIt = RootNodes.CreateIterator(); NodeIt; ++NodeIt) { USCS_Node* RootNode = *NodeIt; if(RootNode != nullptr) { // Get all native scene components TInlineComponentArray<USceneComponent*> Components; Actor->GetComponents(Components); for (int32 Index = Components.Num()-1; Index >= 0; --Index) { USceneComponent* SceneComponent = Components[Index]; if (SceneComponent->CreationMethod == EComponentCreationMethod::Instance) { Components.RemoveAt(Index); } else { // Handle the native sub-component of an instance component case USceneComponent* ParentSceneComponent = SceneComponent->GetTypedOuter<USceneComponent>(); if (ParentSceneComponent && ParentSceneComponent->CreationMethod == EComponentCreationMethod::Instance) { Components.RemoveAt(Index); } } } // Get the native root component; if it's not set, the first native scene component will be used as root. This matches what's done in the SCS editor. USceneComponent* RootComponent = Actor->GetRootComponent(); if(RootComponent == nullptr && Components.Num() > 0) { RootComponent = Components[0]; } // If the root node specifies that it has a parent USceneComponent* ParentComponent = nullptr; if(RootNode->ParentComponentOrVariableName != NAME_None) { // Get the Actor class object UClass* ActorClass = Actor->GetClass(); check(ActorClass != nullptr); // If the root node is parented to a "native" component (i.e. in the 'Components' array) if(RootNode->bIsParentComponentNative) { for(int32 CompIndex = 0; CompIndex < Components.Num(); ++CompIndex) { // If we found a match, remember the index if(Components[CompIndex]->GetFName() == RootNode->ParentComponentOrVariableName) { ParentComponent = Components[CompIndex]; break; } } } else { // In the non-native case, the SCS node's variable name property is used as the parent identifier UObjectPropertyBase* Property = FindField<UObjectPropertyBase>(ActorClass, RootNode->ParentComponentOrVariableName); if(Property != nullptr) { // If we found a matching property, grab its value and use that as the parent for this node ParentComponent = Cast<USceneComponent>(Property->GetObjectPropertyValue_InContainer(Actor)); } } } // Create the new component instance and any child components it may have UActorComponent* InstancedComponent = RootNode->ExecuteNodeOnActor(Actor, ParentComponent != nullptr ? ParentComponent : RootComponent, &RootTransform, bIsDefaultTransform); if(InstancedComponent != nullptr) { InstancedComponents.Add(InstancedComponent); } // get list of every component SCS created, in case some of them aren't in the attachment hierarchy any more (e.g. rigid bodies) TInlineComponentArray<USceneComponent*> ComponentsAfterSCS; Actor->GetComponents(ComponentsAfterSCS); for (USceneComponent* C : ComponentsAfterSCS) { if (Components.Contains(C) == false) { AllComponentsCreatedBySCS.Add(C); } } } } // Register all instanced SCS components once SCS execution has finished; sorted in order to register the scene component hierarchy first, followed by the remaining actor components (in case they happen to depend on something in the scene hierarchy) InstancedComponents.Sort([](const UActorComponent& A, const UActorComponent& B) { return A.IsA<USceneComponent>(); }); for(auto InstancedComponent : InstancedComponents) { RegisterInstancedComponent(InstancedComponent); } // now that the instanced components in the attachment hierarchy are registered, register any other components that SCS made but aren't in the attachment hierarchy for whatever reason. for (auto C : AllComponentsCreatedBySCS) { if (C->IsRegistered() == false) { C->RegisterComponent(); } } } else if(Actor->GetRootComponent() == NULL) // Must have a root component at the end of SCS, so if we don't have one already (from base class), create a SceneComponent now { USceneComponent* SceneComp = NewObject<USceneComponent>(Actor); SceneComp->SetFlags(RF_Transactional); SceneComp->CreationMethod = EComponentCreationMethod::SimpleConstructionScript; SceneComp->SetWorldTransform(RootTransform); Actor->SetRootComponent(SceneComp); SceneComp->RegisterComponent(); } }
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); } } } } } }