After creating graph view, you need to create graph node to organize the logic inside the graph.
Create your own graph node class, derived form UEdGraphNode.
UCLASS()
class UTestGraphNode : public UEdGraphNode
{
GENERATED_UCLASS_BODY()
public:
//~ Begin UEdGraphNode Interface
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
virtual FText GetTooltipText() const override;
//~ End UEdGraphNode Interface
...
}
//Create pins
void UTestGraphNode::AllocateDefaultPins()
{
CreatePin(EGPD_Output, UTestEditorTypes::PinCategory_SingleNode, FString(), nullptr, TEXT(""));
}
Create node instance and add to graph.
UEdGraphNode* CreateGraphNode(class UEdGraph* ParentGraph, const FVector2D Location)
{
if(ParentGraph == NULL) return NULL;
UEdGraphNode* ResultNode = NewObject<UTestGraphNode>(ParentGraph);
ParentGraph->Modify();
ResultNode->SetFlags(RF_Transactional);
// set outer to be the graph so it doesn't go away
ResultNode->Rename(NULL, ParentGraph, REN_NonTransactional);
ResultNode->AddNode(NodeTemplate, true);
ResultNode->CreateNewGuid();
NodeTemplate->NodePosX = Location.X;
NodeTemplate->NodePosY = Location.Y;
ResultNode->SnapToGrid(AE_SNAP_GRID);
// setup pins after placing node
NodeTemplate->AllocateDefaultPins();
return ResultNode;
}
In the schema class (Introduced in previous) article) , you can determine the node pins connection condition.
const FPinConnectionResponse UEdGraphSchema_Test::CanCreateConnection(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const
{
// Make sure the pins are not on the same node
if (PinA->GetOwningNode() == PinB->GetOwningNode())
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinErrorSameNode", "Both are on the same node"));
}
// Make sure the pins are not on the same node
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE, LOCTEXT("PinConnect", "Connect nodes"));
}
Also, in the schema class, you can add context menu for graph node as follow.
void UEdGraphSchema_Test::GetContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const
{
if (Context->Node)
{
/* before UE4.24
MenuBuilder->BeginSection("TestGraphSchemaNodeActions", LOCTEXT("ClassActionsMenuHeader", "Node Actions"));
{
MenuBuilder->AddMenuEntry(FGenericCommands::Get().Delete);
MenuBuilder->AddMenuEntry(FGenericCommands::Get().Cut);
MenuBuilder->AddMenuEntry(FGenericCommands::Get().Copy);
MenuBuilder->AddMenuEntry(FGenericCommands::Get().Duplicate);
MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().BreakNodeLinks);
}
MenuBuilder->EndSection();
*/
FToolMenuSection& Section = Menu->AddSection("TestGraphSchemaNodeActions", LOCTEXT("ClassActionsMenuHeader", "Node Actions"));
{
MenuBuilder->AddMenuEntry(FGenericCommands::Get().Delete);
MenuBuilder->AddMenuEntry(FGenericCommands::Get().Cut);
MenuBuilder->AddMenuEntry(FGenericCommands::Get().Copy);
MenuBuilder->AddMenuEntry(FGenericCommands::Get().Duplicate);
MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().BreakNodeLinks);
}
}
Super::GetContextMenuActions(Menu, Context);
}
If you need to customize graph node style, you can add style factory to do this.
For example (Build-in AnimationGraph):
struct ANIMATIONBLUEPRINTEDITOR_API FAnimationGraphNodeFactory : public FGraphPanelNodeFactory
{
virtual TSharedPtr<class SGraphNode> CreateNode(class UEdGraphNode* InNode) const override;
};
struct ANIMATIONBLUEPRINTEDITOR_API FAnimationGraphPinFactory : public FGraphPanelPinFactory
{
public:
virtual TSharedPtr<class SGraphPin> CreatePin(class UEdGraphPin* Pin) const override;
};
struct ANIMATIONBLUEPRINTEDITOR_API FAnimationGraphPinConnectionFactory : public FGraphPanelPinConnectionFactory
{
public:
virtual class FConnectionDrawingPolicy* CreateConnectionPolicy(const class UEdGraphSchema* Schema, int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const class FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const override;
};
Register these factories in StartupModule().
void FAnimationBlueprintEditorModule::StartupModule()
{
...
AnimGraphNodeFactory = MakeShareable(new FAnimationGraphNodeFactory());
FEdGraphUtilities::RegisterVisualNodeFactory(AnimGraphNodeFactory);
AnimGraphPinFactory = MakeShareable(new FAnimationGraphPinFactory());
FEdGraphUtilities::RegisterVisualPinFactory(AnimGraphPinFactory);
AnimGraphPinConnectionFactory = MakeShareable(new FAnimationGraphPinConnectionFactory());
FEdGraphUtilities::RegisterVisualPinConnectionFactory(AnimGraphPinConnectionFactory);
...
}
For graph node style, you need to create your own class, derived from SGraphNode. SGraphNode is the visual widget shown in graph.
For example:
class SGraphNodeAnimState : public SGraphNode
{
public:
SLATE_BEGIN_ARGS(SGraphNodeAnimState){}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, UAnimStateNodeBase* InNode);
// SNodePanel::SNode interface
virtual void GetNodeInfoPopups(FNodeInfoContext* Context, TArray<FGraphInformationPopupInfo>& Popups) const override;
// End of SNodePanel::SNode interface
// SGraphNode interface
virtual void UpdateGraphNode() override;
virtual void CreatePinWidgets() override;
virtual void AddPin(const TSharedRef<SGraphPin>& PinToAdd) override;
virtual TSharedPtr<SToolTip> GetComplexTooltip() override;
// End of SGraphNode interface
...
};
Determine which SGraphNode you want to used in the node factory.
For example:
TSharedPtr<class SGraphNode> FAnimationGraphNodeFactory::CreateNode(class UEdGraphNode* InNode) const
{
...
if (UAnimStateNode* StateNode = Cast<UAnimStateNode>(InNode))
{
return SNew(SGraphNodeAnimState, StateNode);
}
return nullptr;
}
Here, the UAnimStateNode is corresponding to UTestGraphNode (derived form UEdGraphNode) above.
For the pin connection style, you need to create a drawing policy class, derived from FKismetConnectionDrawingPolicy;
For example:
// This class draws the connections for an UEdGraph with an animation schema
class FAnimGraphConnectionDrawingPolicy : public FKismetConnectionDrawingPolicy
{
public:
// Constructor
FAnimGraphConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraphObj);
// FKismetConnectionDrawingPolicy interface
virtual bool TreatWireAsExecutionPin(UEdGraphPin* InputPin, UEdGraphPin* OutputPin) const override;
virtual void BuildExecutionRoadmap() override;
virtual void DetermineStyleOfExecWire(float& Thickness, FLinearColor& WireColor, bool& bDrawBubbles, const FTimePair& Times) override;
// End of FKismetConnectionDrawingPolicy interface
};
Determine which drawing policy used in the graph in the factory.
For example:
class FConnectionDrawingPolicy* FAnimationGraphPinConnectionFactory::CreateConnectionPolicy(const class UEdGraphSchema* Schema, int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const class FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const
{
if (Schema->IsA(UAnimationGraphSchema::StaticClass()))
{
return new FAnimGraphConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, ZoomFactor, InClippingRect, InDrawElements, InGraphObj);
}
else if (Schema->IsA(UAnimationStateMachineSchema::StaticClass()))
{
return new FStateMachineConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, ZoomFactor, InClippingRect, InDrawElements, InGraphObj);
}
return nullptr;
}
Notice that you can also determine the drawing policy class in the schema class. It will override the policy from the connection factory class.
UCLASS()
class UEdGraphSchema_Test : public UEdGraphSchema
{
GENERATED_UCLASS_BODY()
//~ Begin EdGraphSchema Interface
virtual class FConnectionDrawingPolicy* CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const override;
...
};
For the pin style, you need to create a pin class, derived from SGraphPin;
For example:
class SGraphPinPose : public SGraphPin
{
public:
SLATE_BEGIN_ARGS(SGraphPinPose) {}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, UEdGraphPin* InPin);
protected:
//~ Begin SGraphPin Interface
virtual const FSlateBrush* GetPinIcon() const override;
//~ End SGraphPin Interface
mutable const FSlateBrush* CachedImg_Pin_ConnectedHovered;
mutable const FSlateBrush* CachedImg_Pin_DisconnectedHovered;
};
Determine which pin style used in the graph in the factory.
For example:
TSharedPtr<class SGraphPin> FAnimationGraphPinFactory::CreatePin(class UEdGraphPin* InPin) const
{
if (InPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct)
{
if ((InPin->PinType.PinSubCategoryObject == FPoseLink::StaticStruct()) || (InPin->PinType.PinSubCategoryObject == FComponentSpacePoseLink::StaticStruct()))
{
return SNew(SGraphPinPose, InPin);
}
}
...
return nullptr;
}
The default sytle will be used if return nullptr.