// FNodeSpawnInfo interface virtual void GetActions(UEdGraph* InDestGraph, FVector2D& InOutDestPosition, TArray<UEdGraphNode*>& OutNodes) override { const UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(InDestGraph); USelection* SelectedLvlActors = GEditor->GetSelectedActors(); if ((Blueprint != nullptr) && Blueprint->ParentClass->IsChildOf<ALevelScriptActor>() && (SelectedLvlActors->Num() > 0)) { for (FSelectionIterator LvlActorIt(*SelectedLvlActors); LvlActorIt; ++LvlActorIt) { IBlueprintNodeBinder::FBindingSet Bindings; UK2Node_Literal* TemplateRefNode = Cast<UK2Node_Literal>(UBlueprintNodeSpawner::Create(UK2Node_Literal::StaticClass())->Invoke(InDestGraph, Bindings, InOutDestPosition)); TemplateRefNode->SetObjectRef(*LvlActorIt); OutNodes.Add(TemplateRefNode); } } }
UEdGraphNode* FEdGraphSchemaAction_K2AddCallOnActor::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode/* = true*/) { const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>(); // Snap the node placement location to the grid, ensures calculations later match up better FVector2D LocalLocation; LocalLocation.X = FMath::GridSnap( Location.X, SNAP_GRID ); LocalLocation.Y = FMath::GridSnap( Location.Y, SNAP_GRID ); // First use the base functionality to spawn the 'call function' node UEdGraphNode* CallNode = FEdGraphSchemaAction_K2NewNode::PerformAction(ParentGraph, FromPin, LocalLocation); const float FunctionNodeHeightUnsnapped = UEdGraphSchema_K2::EstimateNodeHeight( CallNode ); // this is the guesstimate of the function node's height, snapped to grid units const float FunctionNodeHeight = FMath::GridSnap( FunctionNodeHeightUnsnapped, SNAP_GRID ); // this is roughly the middle of the function node height const float FunctionNodeMidY = LocalLocation.Y + FunctionNodeHeight * 0.5f; // this is the offset up from the mid point at which we start placing nodes const float StartYOffset = (float((LevelActors.Num() > 0) ? LevelActors.Num()-1 : 0) * -NodeLiteralHeight) * 0.5f; // The Y location we start placing nodes from const float ReferencedNodesPlacementYLocation = FunctionNodeMidY + StartYOffset; // Now we need to create the actor literal to wire up for ( int32 ActorIndex = 0; ActorIndex < LevelActors.Num(); ActorIndex++ ) { AActor* LevelActor = LevelActors[ActorIndex]; if(LevelActor != NULL) { UK2Node_Literal* LiteralNode = NewObject<UK2Node_Literal>(ParentGraph); ParentGraph->AddNode(LiteralNode, false, bSelectNewNode); LiteralNode->SetFlags(RF_Transactional); LiteralNode->SetObjectRef(LevelActor); LiteralNode->AllocateDefaultPins(); LiteralNode->NodePosX = LocalLocation.X - FunctionNodeLiteralReferencesXOffset; // this is the current offset down from the Y start location to place the next node at float CurrentNodeOffset = NodeLiteralHeight * float(ActorIndex); LiteralNode->NodePosY = ReferencedNodesPlacementYLocation + CurrentNodeOffset; LiteralNode->SnapToGrid(SNAP_GRID); // Connect the literal out to the self of the call UEdGraphPin* LiteralOutput = LiteralNode->GetValuePin(); UEdGraphPin* CallSelfInput = CallNode->FindPin(K2Schema->PN_Self); if(LiteralOutput != NULL && CallSelfInput != NULL) { LiteralOutput->MakeLinkTo(CallSelfInput); } } } return CallNode; }
void UK2Node_PlayMovieScene::CreatePinForBoundObject( FMovieSceneBoundObject& BoundObject ) { const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>(); // We use the GUID as the pin name as this uniquely identifies it const FString PinName = BoundObject.GetPossessableGuid().ToString( EGuidFormats::DigitsWithHyphens ); // For the friendly name, we use the possessable name from the MovieScene asset that is associated with this node FText PinFriendlyName = FText::FromString(PinName); { UMovieScene* MovieScene = GetMovieScene(); if( MovieScene != NULL ) // @todo sequencer: Need to refresh the PinFriendlyName if the MovieScene asset changes, or if the possessable slot is renamed within Sequencer { for( auto PossessableIndex = 0; PossessableIndex < MovieScene->GetPossessableCount(); ++PossessableIndex ) { auto& Possessable = MovieScene->GetPossessable( PossessableIndex ); if( Possessable.GetGuid() == BoundObject.GetPossessableGuid() ) { // Found a name for this possessable PinFriendlyName = FText::FromString(Possessable.GetName()); break; } } } } const FString PinSubCategory(TEXT("")); UObject* PinSubCategoryObject = AActor::StaticClass(); const bool bIsArray = false; const bool bIsReference = false; UEdGraphPin* NewPin = CreatePin( EGPD_Input, K2Schema->PC_Object, PinSubCategory, PinSubCategoryObject, bIsArray, bIsReference, PinName ); check( NewPin != NULL ); // Set the friendly name for this pin NewPin->PinFriendlyName = PinFriendlyName; // Place a literal for the bound object and hook it up to the pin // @todo sequencer: Should we instead set the default on the pin to the object instead? const TArray< UObject* >& Objects = BoundObject.GetObjects(); if( Objects.Num() > 0 ) { for( auto ObjectIter( Objects.CreateConstIterator() ); ObjectIter; ++ObjectIter ) { auto* Object = *ObjectIter; if( ensure( Object != NULL ) ) { // Check to see if we have a literal for this object already UK2Node_Literal* LiteralNode = NULL; { TArray< UK2Node_Literal* > LiteralNodes; GetGraph()->GetNodesOfClass( LiteralNodes ); for( auto NodeIt( LiteralNodes.CreateConstIterator() ); NodeIt; ++NodeIt ) { const auto CurLiteralNode = *NodeIt; if( CurLiteralNode->GetObjectRef() == Object ) { // Found one! LiteralNode = CurLiteralNode; break; } } } if( LiteralNode == NULL ) { // No literal for this object yet, so we'll make one now. UK2Node_Literal* LiteralNodeTemplate = NewObject<UK2Node_Literal>(); LiteralNodeTemplate->SetObjectRef( Object ); // Figure out a decent place to stick the node // @todo sequencer: The placement of these is really unacceptable. We should setup new logic specific for moviescene pin objects. const FVector2D NewNodePos = GetGraph()->GetGoodPlaceForNewNode(); LiteralNode = FEdGraphSchemaAction_K2NewNode::SpawnNodeFromTemplate<UK2Node_Literal>(GetGraph(), LiteralNodeTemplate, NewNodePos); } // Hook up the object reference literal to our pin LiteralNode->GetValuePin()->MakeLinkTo( NewPin ); } } } }
void UK2Node_PlayMovieScene::PinConnectionListChanged( UEdGraphPin* InPin ) { // Call parent implementation Super::PostReconstructNode(); // @todo sequencer: What about the "default" value for the pin? Right now we don't do anything with that. Might feel buggy. // --> Actually, we can't use that yet because Actor references won't be fixed up for PIE except for literals CreateBindingsIfNeeded(); // Update the MovieScene bindings for any changes that were made to the node // @todo sequencer: Only a single incoming Pin is changing, but we're refreshing all pin states here. Could be simplified bool bAnyBindingsChanged = false; { auto& BoundObjects = MovieSceneBindings->GetBoundObjects(); for( auto BoundObjectIter( BoundObjects.CreateIterator() ); BoundObjectIter; ++BoundObjectIter ) { auto& BoundObject = *BoundObjectIter; TArray< UObject* > OtherObjects; // Find the pin that goes with this object auto* Pin = FindPinChecked( BoundObject.GetPossessableGuid().ToString( EGuidFormats::DigitsWithHyphens ) ); // Is the pin even linked to anything? if( Pin->LinkedTo.Num() > 0 ) { // @todo sequencer major: Add support for multiple connections to a single input pin to be treated as an "unordered array" for( auto OtherPinIter( Pin->LinkedTo.CreateIterator() ); OtherPinIter; ++OtherPinIter ) { auto* OtherPin = *OtherPinIter; if( OtherPin != NULL ) { // Look for an object bound to a literal. We can use these for scrub preview in the editor! UK2Node_Literal* OtherLiteralPin = Cast< UK2Node_Literal >( OtherPin->GetOwningNode() ); if( OtherLiteralPin != NULL ) { // @todo sequencer: Because we only recognize object literals, any dynamically bound actor won't be scrubable // in the editor. Maybe we should support a "preview actor" that can be hooked up just for scrubbing and puppeting? // ==> Maybe use the pin's default value as the preview actor, even when overridden with a dynamically spawned actor? UObject* OtherObject = OtherLiteralPin->GetObjectRef(); if ( OtherObject != NULL ) { OtherObjects.Add( OtherObject ); } } } } } // Update our bindings to match the state of the node if( BoundObject.GetObjects() != OtherObjects ) // @todo sequencer: Kind of weird to compare entire array (order change shouldn't cause us to invalidate). Change to a set compare? { // @todo sequencer: No type checking is happening here. Should we? (interactive during pin drag?) Modify(); BoundObject.SetObjects( OtherObjects ); bAnyBindingsChanged = true; } } } if( bAnyBindingsChanged ) { OnBindingsChangedEvent.Broadcast(); } }