一、创建空Node
1、创建两个空类:
SMyGraphNode_HelloWorld
FMyGraphPanelNodeFactory
SMyGraphNode_HelloWorld
需要添加模块:EditorStyle
SMyGraphNode_HelloWorld.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "EdGraphUtilities.h"
/**
*
*/
class TESTGRAPHANDBLUEPRINT_API FMyGraphPanelNodeFactory:public FGraphPanelNodeFactory
{
public:
FMyGraphPanelNodeFactory();
~FMyGraphPanelNodeFactory();
//鼠标左键点击后创建Node时调用
virtual TSharedPtr<class SGraphNode> CreateNode(class UEdGraphNode* Node) const override;
};
SMyGraphNode_HelloWorld.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "FMyGraphPanelNodeFactory.h"
#include "TestNode_HelloWorld.h"
#include "SMyGraphNode_HelloWorld.h"
FMyGraphPanelNodeFactory::FMyGraphPanelNodeFactory()
{
}
FMyGraphPanelNodeFactory::~FMyGraphPanelNodeFactory()
{
}
TSharedPtr<class SGraphNode> FMyGraphPanelNodeFactory::CreateNode(class UEdGraphNode* Node) const
{
//当要创建的Node是我们自定义Node时才将Node格式改为自定义格式
if (UTestNode_HelloWorld* MarkerNode=Cast<UTestNode_HelloWorld>(Node))
{
return SNew(SMyGraphNode_HelloWorld, MarkerNode);
}
//其他Node,默认格式
return nullptr;
}
SMyGraphNode_HelloWorld.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "SGraphNode.h"
class UTestNode_HelloWorld;
/**
*
*/
class TESTGRAPHANDBLUEPRINT_API SMyGraphNode_HelloWorld:public SGraphNode
{
public:
SLATE_BEGIN_ARGS(SMyGraphNode_HelloWorld)
{
}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, UTestNode_HelloWorld* MarkerNode);
//设置Node样式
virtual void UpdateGraphNode() override;
virtual void CreatePinWidgets() override;
protected:
TSharedPtr<class SBox> PinBox;
};
SMyGraphNode_HelloWorld.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SMyGraphNode_HelloWorld.h"
#include "TestNode_HelloWorld.h"
#include "SBox.h"
#include "SBoxPanel.h"
void SMyGraphNode_HelloWorld::Construct(const FArguments& InArgs, UTestNode_HelloWorld* MarkerNode)
{
GraphNode = MarkerNode;
//设置鼠标类型为小手
this->SetCursor(EMouseCursor::Hand);
//执行更新逻辑
this->UpdateGraphNode();
}
void SMyGraphNode_HelloWorld::UpdateGraphNode()
{
InputPins.Empty();
OutputPins.Empty();
RightNodeBox.Reset();
LeftNodeBox.Reset();
const FSlateBrush* MyNodeIcon = FEditorStyle::GetBrush(TEXT("Graph.StateNode.Icon"));
//使用Slate创建Node
this->GetOrAddSlot(ENodeZone::Center)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SAssignNew(PinBox, SBox)
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SBorder).BorderBackgroundColor_Lambda([&]() {
return FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.5f));
})
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.AutoWidth()
[
//Node中必须含名为LeftNodeBox/RightNodeBox的容器
SAssignNew(LeftNodeBox, SVerticalBox)
]
+ SHorizontalBox::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.AutoWidth()
[
SAssignNew(RightNodeBox, SVerticalBox)
]
]
]
];
PinBox->SetWidthOverride(100.f);
PinBox->SetHeightOverride(60.f);
}
void SMyGraphNode_HelloWorld::CreatePinWidgets()
{
}
2、将FMyGraphPanelNodeFactory设置为NodeFactory
在FTestGraphAndBlueprintModule::StartupModule()中添加
//注册VisualNode工厂
FEdGraphUtilities::RegisterVisualNodeFactory(MakeShareable(new FMyGraphPanelNodeFactory()));
3、运行结果
附:添加背景图片
a) 在TestGraphAndBlueprintStyle中创建一个资源加载函数
TestGraphAndBlueprintStyle.h
static FSlateImageBrush* GetImageBrush(const FString& RelativePath, FVector2D Size);
TestGraphAndBlueprintStyle.cpp
该函数需要用到 IMAGE_BRUSH 宏,不能放在该宏前面
//IMAGE_BRUSH:行35
FSlateImageBrush* FTestGraphAndBlueprintStyle::GetImageBrush(const FString& RelativePath, FVector2D Size)
{
TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("TestGraphAndBlueprintStyle"));
//获取资源路径:插件位置/Resource
Style->SetContentRoot(IPluginManager::Get().FindPlugin("TestGraphAndBlueprint")->GetBaseDir() / TEXT("Resources"));
return new IMAGE_BRUSH(RelativePath,Size);
}
b) 在SGraphNode中加载图片并作为Slate背景
SMyGraphNode_HelloWorld::UpdateGraphNode()
添加位置:
.BorderImage_Lambda([&]()
{
const FVector2D ICOSize(64.f, 64.f);
return FTestGraphAndBlueprintStyle::GetImageBrush(TEXT("ButtonIcon_40x"), ICOSize);
})
效果
二、创建Node里面的Pin
1、新建SMyGraphPin_HelloWorld类,继承自SGraphPin
SMyGraphPin_HelloWorld.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "SGraphPin.h"
/**
*
*/
class TESTGRAPHANDBLUEPRINT_API SMyGraphPin_HelloWorld: public SGraphPin
{
public:
SLATE_BEGIN_ARGS(SMyGraphPin_HelloWorld){}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, UEdGraphPin* InPin);
};
SMyGraphPin_HelloWorld.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SMyGraphPin_HelloWorld.h"
#include "TestGraphAndBlueprintStyle.h"
void SMyGraphPin_HelloWorld::Construct(const FArguments& InArgs, UEdGraphPin* InPin)
{
this->SetCursor(EMouseCursor::Hand);
//可编辑
IsEditable = true;
//获取到逻辑Pin并检查
GraphPinObj=InPin;
check(GraphPinObj);
//获取到当前Shema并检查
const UEdGraphSchema* Schema = GraphPinObj->GetSchema();
check(Schema);
//创建VisualPin
SBorder::Construct(SBorder::FArguments()
//背景图片
// .BorderImage_Lambda([&](){
// const FVector2D ICOSize(64.f, 64.f);
// return FTestGraphAndBlueprintStyle::GetImageBrush(TEXT("ButtonIcon_40x"), ICOSize);})
//背景颜色
.BorderBackgroundColor_Lambda([&]() {
return FSlateColor(FLinearColor(0.f, 1.f, 1.f, 0.5f));})
//鼠标按下事件 Geometry:几何信息 PointerEvent:按键信息
.OnMouseButtonDown_Lambda([&](const FGeometry& Geometry, const FPointerEvent& PointerEvent)
{
return FReply::Handled();
})
);
}
2、在创建VisualNode时创建VisualPin
SMyGraphNode_HelloWorld::UpdateGraphNode()
当VisualNode创建完成时运行
//创建Widget
CreatePinWidgets();
SMyGraphNode_HelloWorld::CreatePinWidgets()
void SMyGraphNode_HelloWorld::CreatePinWidgets()
{
//获取到Node
UTestNode_HelloWorld* HelloNode = CastChecked<UTestNode_HelloWorld>(GraphNode);
if (HelloNode)
{
//遍历所有的Pin一一创建VirtualPin
for (UEdGraphPin *CurPin:HelloNode->Pins)
{
//新建VisualPin
TSharedPtr<SGraphPin> NewPin = SNew(SMyGraphPin_HelloWorld, CurPin);
NewPin->SetIsEditable(IsEditable);
//添加到系统对应的Box中,In/Out与UpdateGraphNode中的LeftNodeBox/RightNodeBox对应
this->AddPin(NewPin.ToSharedRef());
//添加到自定义的对应集合中
if (CurPin->Direction==EEdGraphPinDirection::EGPD_Input)
{
InputPins.Add(NewPin.ToSharedRef());
}
else if (CurPin->Direction == EEdGraphPinDirection::EGPD_Output)
{
OutputPins.Add(NewPin.ToSharedRef());
}
}
}
}
3、创建拖拽(Alt、Ctrl)
SMyGraphNode_HelloWorld.h
//用来获取到protected修饰的OwnerNodePtr
TSharedPtr<SGraphNode> GetGraphNode() { return OwnerNodePtr.Pin(); }
SMyGraphNode_HelloWorld::Construct()
添加鼠标点击事件
//鼠标按下事件 Geometry:几何信息 PointerEvent:按键信息
.OnMouseButtonDown_Lambda([&](const FGeometry& Geometry, const FPointerEvent& PointerEvent)
{
//?干啥的
bIsMovingLinks = false;
//判断是否是鼠标左键&&该节点是可编辑的
if (PointerEvent.GetEffectingButton() == EKeys::LeftMouseButton&& IsEditingEnabled())
{
//该GraphPin可连接
if (!GraphPinObj->bNotConnectable)
{
/**
* OwnerNodePtr.Pin():
* Converts this weak pointer to a shared pointer that you can use to access the object (if it
* hasn't expired yet.) Remember, if there are no more shared references to the object, the
* returned shared pointer will not be valid. You should always check to make sure the returned
* pointer is valid before trying to dereference the shared pointer!
*
* @return Shared pointer for this object (will only be valid if still referenced!)
*/
//获取到当前Pin所属的VisualNode?: Node为什么不能放在Lambda外面被赋值,会引发断点
TSharedPtr<SGraphNode> OwnerGraphNode = OwnerNodePtr.Pin();
//同时按了Alt时,断开连接的所有引脚
if (PointerEvent.IsAltDown())
{
//?: 同理,为什么使用同样代码的OwnerSchema调用时会引发断点,而这里获取的不会
const UEdGraphSchema* Schema = GraphPinObj->GetSchema();
/**BreakPinLinks()
* Breaks all links from/to a single pin
*
* @param TargetPin The pin to break links on
* @param bSendsNodeNotifcation whether to send a notification to the node post pin connection change
*/
//断掉该GraphPin的所有连接
Schema->BreakPinLinks(*GraphPinObj, true);
return FReply::Handled();
}
//同时按了Ctrl/Cmd时,可将连接线拖动至别的引脚(是否按下Controller&&该引脚连接数目>0)
if (PointerEvent.IsControlDown() && (GraphPinObj->LinkedTo.Num() > 0))
{
//获取当前面板中到所有的VisualPin
TSet<TSharedRef<SWidget>> AllVisualPins;
OwnerGraphNode->GetOwnerPanel()->GetAllPins(AllVisualPins);
//有连线的Pin的Map<GraphPin,VisualPin>
TMap<FGraphPinHandle, TSharedRef<SGraphPin>> AllPinsMap;
//将所有Pin中有至少一个连接的塞进AllPinsMap
for (const TSharedRef<SWidget>& VisualPinRef : AllVisualPins)
{
UEdGraphPin* GraphPinPtr = static_cast<const SGraphPin&>(VisualPinRef.Get()).GetPinObj();
if (GraphPinPtr->LinkedTo.Num() > 0)
{
AllPinsMap.Add(FGraphPinHandle(GraphPinPtr), StaticCastSharedRef<SGraphPin>(VisualPinRef));
}
}
//存储Pin信息的struct(名字、所处GraphNode)
struct FLinkedPinInfo {
FString PinName;
TWeakObjectPtr<UEdGraphNode> OwnerGraphNode;
};
//通过在AllPinsMap中名字配对的方式找到哪些是直接连接在当前Pin上的Pin,并将其塞进LinkedPinInfoArray中
TArray<FLinkedPinInfo> LinkedPinInfoArray;
for (UEdGraphPin* Pin : GetPinObj()->LinkedTo)
{
if (auto PinWidget = AllPinsMap.Find(Pin))
{
FLinkedPinInfo PinInfo;
PinInfo.PinName = (*PinWidget)->GetPinObj()->PinName.ToString();
//!: 一旦某一个->为空则崩溃
PinInfo.OwnerGraphNode = StaticCastSharedRef<SMyGraphPin_HelloWorld>(*PinWidget)->GetGraphNode()->GetNodeObj();
LinkedPinInfoArray.Add(MoveTemp(PinInfo));
}
}
TArray<TSharedRef<SGraphPin>> PinArray;
for (FLinkedPinInfo PinInfo : LinkedPinInfoArray)
{
//遍历Pin所属Node的所有GraphPin
for (UEdGraphPin* Pin : PinInfo.OwnerGraphNode.Get()->Pins)
{
//判断两者名字是否相同
if (Pin->PinName.ToString() == PinInfo.PinName)
{
//将对应名字的VisualPin塞入PinArray中
if (TSharedRef<SGraphPin>* pWidget = AllPinsMap.Find(FGraphPinHandle(Pin)))
{
PinArray.Add(*pWidget);
}
}
}
}
//生成所有连线的拖拽
TSharedPtr<FDragDropOperation> DragEvent;
if (PinArray.Num() > 0)
{
DragEvent = SpawnPinDragEvent(OwnerGraphNode->GetOwnerPanel().ToSharedRef(), PinArray);
}
//?: 为什么又要GetShema一次
const UEdGraphSchema* Schema = GraphPinObj->GetSchema();
//断开原有Pin
Schema->BreakPinLinks(*GraphPinObj, true);
//进行新的PinDrag
if (DragEvent.IsValid())
{
bIsMovingLinks = true;
return FReply::Handled().BeginDragDrop(DragEvent.ToSharedRef());
}
else {
return FReply::Handled();
}
}
//如果是正常的鼠标拖拽
if (!ensure(OwnerGraphNode.IsValid()))
{
return FReply::Unhandled();
}
else
{
TArray<TSharedRef<SGraphPin>> PinArray;
PinArray.Add(SharedThis(this));
TSharedPtr<FDragDropOperation> DragEvent = SpawnPinDragEvent(OwnerGraphNode->GetOwnerPanel().ToSharedRef(), PinArray);
return FReply::Handled().BeginDragDrop(DragEvent.ToSharedRef());
}
}
}
return FReply::Handled();
}));
4、自定义Pin的样式
重写SGraphNode的AddPin函数
SMyGraphNode_HelloWorld.h
//重写AddPin
virtual void AddPin(const TSharedRef<SGraphPin>& PinToAdd) override;
SMyGraphNode_HelloWorld.cpp
void SMyGraphNode_HelloWorld::AddPin(const TSharedRef<SGraphPin>& PinToAdd)
{
PinToAdd->SetOwner(SharedThis(this));
const UEdGraphPin* GraphPinObj = PinToAdd->GetPinObj();
if (GraphPinObj&&GraphPinObj->bAdvancedView)
{
PinToAdd->SetVisibility(TAttribute<EVisibility>(PinToAdd, &SGraphPin::IsPinVisibleAsAdvanced));
}
PinToAdd->SetDesiredSizeScale(FVector2D(7.f, 10.f));
if (PinToAdd->GetDirection() == EEdGraphPinDirection::EGPD_Input)
{
LeftNodeBox->AddSlot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.FillHeight(1.f)
.Padding(5.f, 0.f)
[
PinToAdd
];
}
if (PinToAdd->GetDirection() == EEdGraphPinDirection::EGPD_Output)
{
RightNodeBox->AddSlot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.FillHeight(1.f)
.Padding(23.f, 0.f)
[
PinToAdd
];
}
}
5、运行结果:
6、总结
1) 由FMyGraphPanelNodeFactory创建Node工厂,以此根据不同情况创建对应Node
2) 由SGraphNode构造VisualNode,注意容器中应由LeftNodeBox和RightNodeBox
3) 当完成VisualNode的构建后开始根据Pin构建SGraphPin,由系统自动将InputPin放入到上文的
LeftNodeBox中,OutputPin放入到RightNodeBox中(this->AddPin(NewPin.ToSharedRef()))
4) 给Pin添加鼠标正常点击、ctrl+点击、Alt+点击事件处理
5) 自定义Pin的样式