1.4 从零创建蓝图编辑器-创建自定义Node显示

一、创建空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、运行结果:

在这里插入图片描述
在这里插入图片描述在这里插入图片描述
5、自定义Pin

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的样式

附:

类之间关系XMind

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值