目标

UE4编辑器中的蓝图编辑器材质编辑器等都是继承自UEdGraph的图表编辑器。插件中是可以拓展一种新的图表编辑器以实现特定目的的界面的,例如官方内建插件中的NiagaraAssetManagerEditor都拓展了新的图表编辑器。

本篇的目标是观察相关的基础概念并创建一个最简单的图表窗口。我主要参考了AssetManagerEditor中的ReferenceViewer(引用关系查看器),因为这是我目前发现的最简单的图表编辑器。

图表编辑器相关的基本概念

UEdGraph
UCLASS()
class ENGINE_API UEdGraph : public UObject

UEdGraph代表了一个图表对象。
不出意外,可以看到节点列表是它的成员:

/** Set of all nodes in this graph */
UPROPERTY()
TArray<class UEdGraphNode*> Nodes;

为了创建一个新的图表,需要定义一个新的UEdGraph

UEdGraphNode
UCLASS()
class ENGINE_API UEdGraphNode : public UObject

UEdGraphNode代表了图表对象中的一个节点。
它有节点上的引脚信息:

TArray<UEdGraphPin*> Pins;

为了创建一个新的图表,需要定义一个新的UEdGraphNode

UEdGraphSchema
UCLASS(abstract)
class ENGINE_API UEdGraphSchema : public UObject

暂时没有想到准确的对于Schema的翻译,我暂时的理解是它描述了图表对象的一种“规则”,例如:

/**
 * Get all actions that can be performed when right clicking on a graph or drag-releasing on a graph from a pin
 *
 * @param [in,out]	ContextMenuBuilder	The context (graph, dragged pin, etc...) and output menu builder.
 */
 virtual void GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const;

GetGraphContextActions得到了所有“鼠标右键点击”或者“拖拽一个引脚并释放”所能进行的操作。

每个UEdGraph都必须指定一个UEdGraphSchema

SGraphNode
class GRAPHEDITOR_API SGraphNode : public SNodePanel::SNode

SGraphNode是节点在图表编辑器中具体的UI界面。想要改变节点的界面,就重新定义一个新的SGraphNode

SGraphEditor
class SGraphEditor : public SCompoundWidget

SGraphEditor封装了图表编辑器的界面。
创建控件时需要指定图表对象:

SLATE_ARGUMENT( UEdGraph*, GraphToEdit )

0.新建插件

Editor Standalone Window为模板创建一个插件,这将提供一个编辑器独立窗口,方便之后的实验。
在这里插入图片描述
插件名为YaksueGraphPlg

随后关卡编辑器上方工具栏会有一个新的按钮:
在这里插入图片描述
点击后会创建窗口:
在这里插入图片描述
正如文字中显示,之后的界面内容可以放在FYaksueGraphPlgModule::OnSpawnPluginTab
在这里插入图片描述

1.空白的图表界面

1.1 定义一个空白的UEdGraph

EdGraph_Yaksue.h

#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "EdGraph/EdGraph.h"

#include "EdGraph_Yaksue.generated.h"


UCLASS()
class UEdGraph_Yaksue : public UEdGraph
{
	GENERATED_UCLASS_BODY()
};

EdGraph_Yaksue.cpp

#include "EdGraph_Yaksue.h"


UEdGraph_Yaksue::UEdGraph_Yaksue(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
}
1.2 定义一个空白的UEdGraphSchema

YaksueGraphSchema.h

#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "EdGraph/EdGraphSchema.h"

#include "YaksueGraphSchema.generated.h"


UCLASS(MinimalAPI)
class UYaksueGraphSchema : public UEdGraphSchema
{
	GENERATED_UCLASS_BODY()
};

YaksueGraphSchema.cpp

#include "YaksueGraphSchema.h"


UYaksueGraphSchema::UYaksueGraphSchema(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
}
1.3 定义一个窗口界面

SYaksueGraphWindow.h

#pragma once

#include "CoreMinimal.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SCompoundWidget.h"
#include "GraphEditor.h"


class SYaksueGraphWindow : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS(SYaksueGraphWindow){}

	SLATE_END_ARGS()

	/** Constructs this widget with InArgs */
	void Construct( const FArguments& InArgs );

	//图表编辑器控件
	TSharedPtr<SGraphEditor> GraphEditorPtr;

	//图表对象
	class UEdGraph_Yaksue* GraphObj;
};

其中GraphEditorPtrSGraphEditor控件。而GraphObj是图表对象。

SYaksueGraphWindow.cpp

#include "SYaksueGraphWindow.h"
#include "EdGraph_Yaksue.h"
#include "YaksueGraphSchema.h"


void SYaksueGraphWindow::Construct(const FArguments& InArgs)
{
	//创建图表对象
	GraphObj = NewObject<UEdGraph_Yaksue>();
	GraphObj->Schema = UYaksueGraphSchema::StaticClass();
	GraphObj->AddToRoot();
	

	//创建图表编辑器控件
	GraphEditorPtr = SNew(SGraphEditor)
		.GraphToEdit(GraphObj);
		

	//指定本控件的UI:
	ChildSlot
	[
		GraphEditorPtr.ToSharedRef()
	];
}
1.4 将窗口界面加入Tab界面中
#include"SYaksueGraphWindow.h"
TSharedRef<SDockTab> FYaksueGraphPlgModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs)
{
	return SNew(SDockTab)
		.TabRole(ETabRole::NomadTab)
		[
			// Put your tab content here!
			SNew(SYaksueGraphWindow)
		];
}

效果:
在这里插入图片描述

2.创建一个图表节点

2.1 定义一个空白的图表节点

EdGraphNode_Yaksue.h

#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "EdGraph/EdGraphNode.h"

#include "EdGraphNode_Yaksue.generated.h"


UCLASS()
class UEdGraphNode_Yaksue : public UEdGraphNode
{
	GENERATED_UCLASS_BODY()
};

EdGraphNode_Yaksue.cpp

#include "EdGraphNode_Yaksue.h"


UEdGraphNode_Yaksue::UEdGraphNode_Yaksue(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
}
2.2在图表中加入节点

UEdGraph_Yaksue中加入一个新的接口:

//重新构建图表
void RebuildGraph();

RebuildGraph所做的就是创建一个新的节点:

void UEdGraph_Yaksue::RebuildGraph()
{
	//创建一个节点
	CreateNode(UEdGraphNode_Yaksue::StaticClass());
}
2.3 在窗口中调用RebuildGraph
GraphObj->RebuildGraph();

效果:
在这里插入图片描述

3.自定义节点UI

虽然没有SGraphNodeEdGraphNode_Yaksue对应,但是再上一步中已经能看到节点的界面了,我想这大概是因为在没有对应SGraphNode时,会给一个默认的SGraphNode

而在这一步中,我将创建一个新的SGraphNode来自定义节点UI。

3.1 定义一个新的节点控件SGraphNode

SYaksueGraphNode.h

#pragma once

#include "CoreMinimal.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "SGraphNode.h"

class UEdGraphNode_Yaksue;

class SYaksueGraphNode : public SGraphNode
{
public:
	SLATE_BEGIN_ARGS(SYaksueGraphNode){}

	SLATE_END_ARGS()

	/** Constructs this widget with InArgs */
	void Construct( const FArguments& InArgs, UEdGraphNode_Yaksue* InNode );

	// SGraphNode implementation
	virtual void UpdateGraphNode() override;
	// End SGraphNode implementation
};

SYaksueGraphNode.cpp

#include "SYaksueGraphNode.h"
#include "EdGraphNode_Yaksue.h"
#include "Widgets/Input/SButton.h"

void SYaksueGraphNode::Construct( const FArguments& InArgs, UEdGraphNode_Yaksue* InNode )
{
	GraphNode = InNode;
	UpdateGraphNode();
}

void SYaksueGraphNode::UpdateGraphNode()
{
	GetOrAddSlot( ENodeZone::Center )
	[
		SNew(SButton).Text(FText::FromString("Hello Yaksue"))
	];
}

UpdateGraphNode()中将指定界面内容,这里很简单,就是一个按钮,写着"Hello Yaksue"。

3.2 将节点UI和节点类型对应

先定义一个FGraphPanelNodeFactory(图表节点工厂),将UEdGraphNode_YaksueSYaksueGraphNode对应起来:

//定义图表节点工厂
class FYaksueGraphNodeFactory : public FGraphPanelNodeFactory
{
	virtual TSharedPtr<class SGraphNode> CreateNode(UEdGraphNode* Node) const override
	{
		if (UEdGraphNode_Yaksue* YaksueGraphNode = Cast<UEdGraphNode_Yaksue>(Node))
		{
			return SNew(SYaksueGraphNode, YaksueGraphNode);
		}

		return nullptr;
	}
};

然后在插件模块的启动函数中注册这个工厂:

//注册图表节点工厂
FEdGraphUtilities::RegisterVisualNodeFactory(MakeShareable(new FYaksueGraphNodeFactory()));

现在,节点的界面变成了:
在这里插入图片描述

总结

至此,“创建一个最简单的图表编辑器”这一目标已经达成,不过关于UE4的图表编辑器的学习只是一个开始,后续还有很多问题,例如:

  • 节点之间的连接关系是怎样的系统?
  • 如何安排右键菜单中的选择?
  • 等等。。。
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐