Published on

创建自定义编辑器

Authors
  • avatar
    Name
    东哥
    Twitter

前言

很久以前项目需求Paper2D动画, Paper2D切换动画不是很方便, 最好还是用类似动画蓝图的状态机,但是UE目前的状态机只有动画蓝图和AI行为树, 于是那个时候就想做一个自定义的状态机,但是后来项目搁浅就没动机继续研究了, 现在凑空来搞一搞这个蛮有用的东西(也很利于了解UE编辑器)

image-20210421113343319

录制_2021_04_21_15_01_58_451

基础概念

自定义编辑器涉及到的类特别的多, 刚入手非常头疼, 我们先大概罗列一下需要用到的类及用途

  • UBlueprint: 编辑器界面的资源, 注意: 这个类不是被编辑和生成的对象

  • Instance类: 一般继承自UObject, 是UBlueprint的ParentClass, 这个是编辑器编辑的类和生成的对象

  • AssetEdtor: 资源编辑器,双击资源以后第一时间打开的界面的总管理者

  • ApplicationMode: 界面模式, 用于自定义布局和添加自定义界面, 在AssetEdtor中注册

  • AssetTypeAction: 定义创建资源的样式, 包括分类,颜色,对应的资源类和关键的OpenAssetEditor()操作

  • Factory: 创建和托管资源, 定义了点击图标创建蓝图资源的过程

  • Graph : Editor中的视图,比如蓝图中带网格的那种; 在Editor中创建和获取

  • Schema: 定义视图中的规则, 比如连线规则,创建Node规则等; 在Graph创建的时候指定, 与Graph一般是组合出现

  • ActionMenu: Slate类, 右键点击视图的菜单,用于选择节点

  • SchemaAction: ActionMenu中的每一个功能节点, 用于创建Node等

  • Node: 视图中的蓝图节点, 表示的是一个函数或者状态机

    • UEdGraphNode : Node对象
    • SGraphNode: Slate类, 定义Node的显示样式
  • GraphXXXXFactory: 实现节点/引脚/连线的创建的工厂类, 同时也定义了Node的对象与于此对应Slate类

  • Pin: Node之间连接的引脚

  • Connection Drawing Policy: Pin之间连接的样式定义,包含颜色,线条样式等

  • INameValidatorInterface : 负责Node重命名逻辑的类接口, 通过重写Node中的MakeNameValidator()方法指定重命名对象;如果要判断名字有效性和重复等就需要用到此类

创建资源

第一部, 我们需要创建资源, 这一步我们参考我之前的文章自定义资源, 如果需要自定义样式如图标/缩略图等, 请参考使用自定义Slate笔刷

AssetEditor

我们在打开资源的地方对Editor进行初始化

void FVSM_AssetTypeAction::OpenAssetEditor(const `TArray<UObject*>`& InObjects, `TSharedPtr<class IToolkitHost>` EditWithinLevelEditor /*= `TSharedPtr<IToolkitHost>`()*/)
{
	EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone;

	for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt)
	{
		auto Blueprint = `Cast<UBlueprint>`(*ObjIt);
		if (Blueprint && Blueprint->SkeletonGeneratedClass && Blueprint->GeneratedClass && Blueprint->`IsA<UVSM_Blueprint>`())
		{
			FVSMEditorModule& VSMEModule = FModuleManager::`LoadModuleChecked<FVSMEditorModule>`("VSMEditor");
			VSMEModule.CreateEditor(Mode,EditWithinLevelEditor,Blueprint);

		}
		else
		{
			FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("OpenStateMachineBlueprint Failed!!", "State machine Blueprint could not be loaded because it derives from an invalid class.\nCheck to make sure the parent class for this blueprint hasn't been removed!"));

		}
	}
}
`TSharedRef<FVSM_Editor>` FVSMEditorModule::CreateEditor(const EToolkitMode::Type Mode, const `TSharedPtr< class IToolkitHost >`& InitToolkitHost, UBlueprint* BP)
{

	CreateClassCache();//用于记录特定资源类
	`TSharedRef<FVSM_Editor>` editor(new FVSM_Editor());
	editor->InitEditor(Mode, InitToolkitHost, BP);
	return editor;
}

初始化的内容比较多, 重点就是如下几条, 其实可以模仿动画蓝图的初始化或者蓝图编辑器的初始化过程

    //创建自定义视图
if (!SMBP->Graph)
	{
		UVSM_Graph* StateMachineGraph = `CastChecked<UVSM_Graph>`(FBlueprintEditorUtils::CreateNewGraph(SMBP, NAME_None, UVSM_Graph::StaticClass(), UVSM_Schema::StaticClass()));
		if (StateMachineGraph)
			SMBP->Graph = StateMachineGraph;
	}
//初始化视图

GetGraph()->Init(SMBP, ThisPtr);

//创建工具栏

Toolbar = MakeShareable(new FVSM_Toolbar(ThisPtr));

//创建并设置模式
	AddApplicationMode(FVSM_AppMode_BlueprintEditor::ModeName, MakeShareable(new FVSM_AppMode_BlueprintEditor(ThisPtr, FVSM_AppMode_BlueprintEditor::ModeName)));
	SetCurrentMode(FVSM_AppMode_BlueprintEditor::ModeName);

我们这里用了自定义的细节面板, 就需要使用Persona模块, 因为选中Node以后细节面板对应的信息我们需要自定义

FPersonaModule& PersonaModule = FModuleManager::`GetModuleChecked<FPersonaModule>`("Persona");
//这一步需要放到最后, 否则Inspector还没创建
PersonaModule.CustomizeBlueprintEditorDetails(Inspector->GetPropertyView().ToSharedRef(), FOnInvokeTab::CreateSP(this, &FAssetEditorToolkit::InvokeTab));

ApplicationMode

模式在Editor初始化的时候创建和添加

class FVSM_AppMode_BlueprintEditor : public FBlueprintEditorApplicationMode

我们直接继承自蓝图编辑器模式(没必要自己造轮子)

关键就俩函数, 构造函数和RegisterTabFactories()

构造函数需要传入Editor和模式名称

FVSM_AppMode_BlueprintEditor::FVSM_AppMode_BlueprintEditor(`TSharedPtr<class FVSM_Editor>` InStateMachineEditor, FName InModeName)
	: FBlueprintEditorApplicationMode(InStateMachineEditor, InModeName, FVSM_AppMode_BlueprintEditor::GetLocalizedMode, false, false)
{
	
	Editor = InStateMachineEditor;

	TabLayout = FTabManager::NewLayout("WidgetBlueprintEditor_Graph_Layout_v1")
		->AddArea
		(
			FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
			->Split
			(
				FTabManager::NewStack()
				->SetSizeCoefficient(0.186721f)
				->SetHideTabWell(true)
				->AddTab(InStateMachineEditor->GetToolbarTabId(), ETabState::OpenedTab)
			)
			->Split
			(
				FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)
				->Split
				(
					FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
					->SetSizeCoefficient(0.15f)
					->Split
					(
						FTabManager::NewStack()->SetSizeCoefficient(0.5f)
						->AddTab(FBlueprintEditorTabs::MyBlueprintID, ETabState::OpenedTab)
					)
					->Split
					(
						FTabManager::NewStack()->SetSizeCoefficient(0.5f)
						->AddTab(FBlueprintEditorTabs::DetailsID, ETabState::OpenedTab)
					)
				)
				->Split
				(
					FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
					->SetSizeCoefficient(0.70f)
					->Split
					(
						FTabManager::NewStack()
						->SetSizeCoefficient(0.80f)
						
						->AddTab("Document", ETabState::ClosedTab)
						->AddTab(FVSM_Editor::TabId_GraphCanvas, ETabState::OpenedTab)
						->SetForegroundTab(FVSM_Editor::TabId_GraphCanvas)
					)
					->Split
					(
						FTabManager::NewStack()
						->SetSizeCoefficient(0.20f)
						->AddTab(FBlueprintEditorTabs::CompilerResultsID, ETabState::OpenedTab)
						->AddTab(FBlueprintEditorTabs::FindResultsID, ETabState::OpenedTab)
					)
				)
				->Split
				(
					FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
					->SetSizeCoefficient(0.15f)
					->Split
					(
						FTabManager::NewStack()
						->AddTab(FBlueprintEditorTabs::PaletteID, ETabState::ClosedTab)
					)
				)
			)
		);

	//InStateMachineEditor->GetToolbarBuilder()->AddWidgetBlueprintEditorModesToolbar(ToolbarExtender);
	if (UToolMenu* Toolbar = InStateMachineEditor->RegisterModeToolbarIfUnregistered(GetModeName()))
	{
		InStateMachineEditor->GetToolbarBuilder()->AddCompileToolbar(Toolbar);
		InStateMachineEditor->GetToolbarBuilder()->AddBlueprintGlobalOptionsToolbar(Toolbar);
		InStateMachineEditor->GetToolbarBuilder()->AddDebuggingToolbar(Toolbar);
		InStateMachineEditor->GetToolbarBuilder()->AddScriptingToolbar(Toolbar);
		InStateMachineEditor->GetToolbarBuilder()->AddNewToolbar(Toolbar);

	}
	
	
   }

void FVSM_AppMode_BlueprintEditor::RegisterTabFactories(`TSharedPtr<FTabManager>` InTabManager)
{
	`TSharedPtr<FVSM_Editor>` ed = GetEditor();

	 auto icon = FVSM_Style::GetBrush("VSM.StateMachineGraphSmall");
	WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_StateMachineEditor", "State Machine"));
	auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef();
	InTabManager->RegisterTabSpawner(FVSM_Editor::TabId_GraphCanvas, FOnSpawnTab::CreateSP(ed.Get(), &FVSM_Editor::SpawnTab_GraphCanvas))
		.SetDisplayName(LOCTEXT("GraphCanvasTab", "State Machine Graph"))
		.SetGroup(WorkspaceMenuCategoryRef)
		//.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.EventGraph_16x"));
		.SetIcon(FSlateIcon(FVSM_Style::GetStyleSetName(), "VSM.StateMachineGraphSmall"));

	ed->RegisterToolbarTab(InTabManager.ToSharedRef());
	ed->PushTabFactories(CoreTabFactories);
	//如debug,details等所有常用标签
	ed->PushTabFactories(BlueprintEditorTabFactories);
	ed->PushTabFactories(TabFactories);
}

Graph

class VSMEDITOR_API UVSM_Graph : public UEdGraph

由Editor初始化, 主要负责Node的操作

如对与一般State节点

void UVSM_Graph::RegisterNode(UVSM_State_Base* State, UVSM_Node* NewNode, UEdGraphPin* FromPin)
{
	Modify();
	this->SMBP->Modify();
	//Register node to the nodes array
	this->SMBP->Nodes.Add(NewNode);
	State->GraphNode = NewNode;
	NewNode->State = State;
	NewNode->ReallocateDefaultPins();

	// Previous node 
	if (FromPin)
	{
		UVSM_Node* FromNode = `Cast<UVSM_Node>`(FromPin->GetOwningNode());

		// apply current blueprint edited
		this->SetCurrentStateMachineBlueprintToNode(FromNode);

				
		if (FromNode->IsRootNode())
		{
			this->SetEntryState(State);
		}
			
	}
	if (!NewNode->IsRootNode())
	{
		NewNode->CreateBoundGraph();
	}
	this->SetCurrentStateMachineBlueprintToNode(NewNode);

	// linking between pins
	NewNode->AutowireNewNode(FromPin);

	NotifyGraphChanged();
}

Schema

class VSMEDITOR_API UVSM_Schema : public UEdGraphSchema

Schema一般分两种, 还有一种K2版本, 如果是一般蓝图中右键出现的菜单那种都是K2版本的, 能使用K2Node

//~ Begin EdGraphSchema Interface
	virtual void GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const override;
	virtual void GetContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const override;
	virtual const FPinConnectionResponse CanCreateConnection(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const override;
	virtual bool TryCreateConnection(UEdGraphPin* PinA, UEdGraphPin* PinB) const override;
	virtual bool CreateAutomaticConversionNodeAndConnections(UEdGraphPin* PinA, UEdGraphPin* PinB) const override;
	virtual bool ShouldHidePinDefaultValue(UEdGraphPin* Pin) const override;
	virtual FLinearColor GetPinTypeColor(const FEdGraphPinType& PinType) const override;
	virtual void BreakNodeLinks(UEdGraphNode& TargetNode) const override;
	virtual void BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotifcation) const override;
	virtual void BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) const override;
	//~ End EdGraphSchema Interface

重写了很多方法, 从函数名称能看出来, 都是用来控制节点或者引脚之类的东西

重点说明几个函数

  • GetContextMenuActions()

右键点击Node以后的操作, 比如这里我们只需要重命名和删除

void UVSM_Schema::GetContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const
{
	if (Context->Pin)
	{
		FToolMenuSection& Section = Menu->AddSection("StateMachineGraphSchemaPinActions", LOCTEXT("PinActionsMenuHeader", "Actions"));
		
		Section.AddMenuEntry(FGenericCommands::Get().Delete);

		if (Context->Node->bCanRenameNode)
		{
			Section.AddMenuEntry(FGenericCommands::Get().Rename);
		}
	}

	Super::GetContextMenuActions(Menu, Context);
}
  • GetGraphContextActions()

右键视图的操作, 我们需要讲自定义的Node添加到这里

void UVSM_Schema::GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const
{
	`TSharedPtr<FVSM_SchemaAction_NewNode>` NewStateAction(new FVSM_SchemaAction_NewNode(
		LOCTEXT("DefaultCategory","Default"),
		LOCTEXT("NewState", "New State ..."),
		LOCTEXT("NewStateTooltip", "Add a new Default State"),
		0
	));
	ContextMenuBuilder.AddAction(NewStateAction);



	FCategorizedGraphActionListBuilder TasksBuilder(TEXT("Task"));
	FVSMEditorModule& EditorModule = FModuleManager::`GetModuleChecked<FVSMEditorModule>`(TEXT("VSMEditor"));
	FVSM_NodeClassHelper* ClassCache = EditorModule.GetClassCache().Get();
	`TArray<FVSM_NodeClassData>` NodeClasses;
	ClassCache->GatherClasses(false, UVSM_TaskInstance::StaticClass(), NodeClasses);
	for (const FVSM_NodeClassData& NodeClass : NodeClasses)
	{
		//auto data = NodeClass;
		const FText NodeTypeName = FText::FromString(FName::NameToDisplayString(NodeClass.ToString()+TEXT(" ..."), false));
		`TSharedPtr<FVSM_SchemaAction_NewTaskNode>` AddOpAction = UVSM_Schema::AddNewTaskNode(TasksBuilder, NodeClass.GetCategory(), NodeTypeName, FText::GetEmpty(), NodeClass);
	}
	ContextMenuBuilder.Append(TasksBuilder);
}

这里用到一个类FVSM_NodeClassHelper, 写法完全参考行为树的一个类似的Helper类(FGraphNodeClassHelper), 主要就是检索资源中的类用于创建自定义节点(参考行为树的Task)

我们可以完全复制其代码过来, 然后就会发现引擎会崩溃, 只需要在构造函数和析构函数中加入一些判断, 毕竟我们的模块不是引擎亲生的, 如

if (GEditor)
{
   GEditor->OnBlueprintCompiled().AddRaw(this, &FVSM_NodeClassHelper::InvalidateCache);
		GEditor->OnClassPackageLoadedOrUnloaded().AddRaw(this, &FVSM_NodeClassHelper::InvalidateCache); 
}

在析构的时候对资源模块进行判断再引用, 否则在关闭编辑器后来个无聊的崩溃

	if (FModuleManager::Get().IsModuleLoaded(TEXT("AssetRegistry")))
	{
		FAssetRegistryModule& AssetRegistryModule = FModuleManager::`GetModuleChecked<FAssetRegistryModule>`(TEXT("AssetRegistry"));
		AssetRegistryModule.Get().OnFilesLoaded().RemoveAll(this);
		AssetRegistryModule.Get().OnAssetAdded().RemoveAll(this);
		AssetRegistryModule.Get().OnAssetRemoved().RemoveAll(this);
	}

其余基本抄袭

连接和绘制规则

需要定义一个FConnectionDrawingPolicy类,表述连接线的样式和连接规则等

ECanCreateConnectionResponse/ 节点连接规则

ECanCreateConnectionResponse描述
CONNECT_RESPONSE_MAKEAB能随意连接(N:N)
CONNECT_RESPONSE_DISALLOWAB不能连接(0:0)
CONNECT_RESPONSE_BREAK_OTHERS_AA只能连接一次(1:N)
CONNECT_RESPONSE_BREAK_OTHERS_BB只能连接一次(N:1)
CONNECT_RESPONSE_BREAK_OTHERS_ABAB都只能连接一次(1:1)
CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE通过中间强制转换节点或其他转换节点进行连接

GraphFactory

节点/引脚/连线的创建需要创建对应的工厂类来完成

class VSMEDITOR_API FVSM_GraphNodeFactory : public FGraphPanelNodeFactory
{
	virtual `TSharedPtr<class SGraphNode>` CreateNode(class UEdGraphNode* InNode) const override;
};

struct VSMEDITOR_API FVSM_PinConnectionFactory : 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;
};

class FConnectionDrawingPolicy* FVSM_PinConnectionFactory::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(UVSM_Schema::StaticClass()))
		return new FVSM_ConnectionPolicy(InBackLayerID, InFrontLayerID, ZoomFactor, InClippingRect, InDrawElements, InGraphObj);

	return nullptr;
}

`TSharedPtr<class SGraphNode>` FVSM_GraphNodeFactory::CreateNode(class UEdGraphNode* InNode) const
{
	if (UVSM_Node_Entry* EntryNode = `Cast<UVSM_Node_Entry>`(InNode))
		return SNew(SVSM_Node_Entry, EntryNode);
	else if (UVSM_Node_Transition* TransitionNode = `Cast<UVSM_Node_Transition>`(InNode))
		return SNew(SVSM_Node_Transition, TransitionNode);
	else if (UVSM_Node_Task* TaskNode = `Cast<UVSM_Node_Task>`(InNode))
		return SNew(SVSM_Node_Task, TaskNode);
	else if (UVSM_Node* Node = `Cast<UVSM_Node>`(InNode))
		return SNew(SVSM_Node, Node);

	return nullptr;
}

然后在模块启动时注册到编辑器

void FVSMEditorModule::RegisterFactories()
{
	StateMachineGraphFactory = MakeShareable(new FVSM_GraphNodeFactory());
	StateMachineGraphPinConnectionFactory = MakeShareable(new FVSM_PinConnectionFactory());
	FEdGraphUtilities::RegisterVisualNodeFactory(StateMachineGraphFactory);
	FEdGraphUtilities::RegisterVisualPinConnectionFactory(StateMachineGraphPinConnectionFactory);
}

void FVSMEditorModule::UnregisterFactories()
{
	FEdGraphUtilities::UnregisterVisualNodeFactory(StateMachineGraphFactory);
	FEdGraphUtilities::UnregisterVisualPinConnectionFactory(StateMachineGraphPinConnectionFactory);
}

Node

节点继承自UEdGraphNode, 下列函数大多都是字面意思

	// UEdGraphNode interface.
	virtual void ReconstructNode() override;
	virtual	void AllocateDefaultPins() override;
	virtual bool CanCreateUnderSpecifiedSchema(const UEdGraphSchema* Schema) const override;
	virtual bool CanUserDeleteNode() const override{return true; };
	virtual void AutowireNewNode(UEdGraphPin* FromPin);
	virtual void DestroyNode() override;
	virtual void SaveNode();
	virtual void OnNotifyGraphChanged();
	virtual void OnPropertyUpdated(const FPropertyChangedEvent& PropertyChangedEvent, FProperty* PropertyThatChanged);
	virtual FLinearColor GetNodeTitleColor() const override;
	virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
//重命名类
	virtual `TSharedPtr<class INameValidatorInterface>` MakeNameValidator() const override;
	virtual void OnRenameNode(const FString& NewName) override;
	// End of UEdGraphNode interface.

//三个函数跟双击以后的操作有关系, 比如跳转到某个视图或者打开某个资源
	virtual UObject* GetJumpTargetForDoubleClick() const override;
	virtual bool CanJumpToDefinition() const override;
	virtual void JumpToDefinition() const override;

Node与一个Runtime状态类(UVSM_State_Base*)同时存在, 选中Node以后, 细节面板显示的就是这个类,编辑的属性改的也是这个类,最终反馈到状态机类UVSM_StateMachine

SlateNode

每个Node都对应一个Slate类, 用于在视图中显示, 在GraphFactory中确定对应关系

class VSMEDITOR_API SVSM_Node : public SGraphNode
{

public:
	
	SLATE_BEGIN_ARGS(SVSM_Node) {}
	SLATE_END_ARGS()

	/************************************************************************/
	/* FUNCTIONS                                                            */
	/************************************************************************/
	
	void Construct(const FArguments& InArgs, UVSM_Node* InNode);
	
	// SGraphNode interface
	virtual void UpdateGraphNode() override;
	virtual void AddPin(const `TSharedRef<SGraphPin>`& PinToAdd) override;
	virtual void CreatePinWidgets() override;
	virtual const FSlateBrush* GetShadowBrush(bool bSelected) const override;
	void OnNameTextCommited(const FText& InText, ETextCommit::Type CommitInfo);
	bool OnVerifyNameTextChanged(const FText& InText, FText& OutErrorMessage);
	virtual void GetNodeInfoPopups(FNodeInfoContext* Context, `TArray<FGraphInformationPopupInfo>`& Popups) const override;
	// End of SGraphNode interface

	virtual FSlateColor GetNodeColor() const;
	virtual FSlateColor GetNodeBackgroundColor() const;
	virtual FText GetNodeTitle() const;
	FText GetPreviewCornerText() const;
	virtual const FSlateBrush* GetNameIcon() const;
	FSlateColor GetBorderBackgroundColor() const;



	 const  FVSM_SlateStyle* Style;

};

如果需要对debug的时候进行一些操作, 可以对一些函数进行改动, 比如如果要在Debug的时候提示一个信息框, 可以对GetNodeInfoPopups()函数进行如下操作

void SVSM_Node::GetNodeInfoPopups(FNodeInfoContext* Context, `TArray<FGraphInformationPopupInfo>`& Popups) const
{
	UVSM_Blueprint* StateMachineBlueprint = `Cast<UVSM_Blueprint>`(`Cast<UVSM_Graph>`(GraphNode->GetGraph())->SMBP);
	UVSM_Node* StateMachineGraphNode = `Cast<UVSM_Node>`(GraphNode);

	if (StateMachineGraphNode->IsRootNode() || !StateMachineGraphNode->State)
		return; 

	if (StateMachineBlueprint)
	{
		if (UVSM_StateMachine* StateMachine = `Cast<UVSM_StateMachine>`(StateMachineBlueprint->GetObjectBeingDebugged()))
		{
			if (StateMachine->CurrentState.IsValid())
			{
				FString StateName = StateMachine->CurrentState.Name;
				if (StateName != StateMachineGraphNode->State->Data.Name)
					return;
				
				FLinearColor CurrentStateColor(1.f, 0.5f, 0.25f);
				FString StateText = FString::Printf(TEXT("%s ACTIVE"), *StateName);
				new (Popups) FGraphInformationPopupInfo(nullptr, CurrentStateColor, StateText);
			}
		}
	}
}

INameValidatorInterface

用于重命名的类, 通过重写Node的函数 virtual TSharedPtr MakeNameValidator() const override;指定对象

一般需要重写下面几个方法

	virtual EValidatorResult IsValid(const FString& Name, bool bOriginal) override;
	virtual EValidatorResult IsValid(const FName& Name, bool bOriginal) override;
	virtual bool IsCurrentName(const FName& Name) const;
	virtual bool IsExistingName(const FName& Name) const;
EValidatorResult FVSM_NameValidatorBase::IsValid(const FName & Name, bool bOriginal)
{
	if (Name.IsNone()) {
		return EValidatorResult::EmptyName;
	}
	if (!Name.IsValidXName()) {
		return EValidatorResult::ContainsInvalidCharacters;
	}
	if (IsCurrentName(Name)) {
		return EValidatorResult::ExistingName;
	}
	if (IsExistingName(Name)) {
		return EValidatorResult::AlreadyInUse;
	}
	return EValidatorResult::Ok;
}

外部通过调用IsValid()来获取返回类型, 重命名的时候决定是成功还是失败

Utilities

一个比较重要的静态库类, 定义了所有创建节点和视图操作


class VSMEDITOR_API FVSM_EditorUtilities
{

	/************************************************************************/
	/* FUNCTIONS                                                            */
	/************************************************************************/

public:

	template<typename T>
	static T* CreateNode(UEdGraph* Graph, int32 NodePosX, int32 NodePosY, bool bSelectNewNode = true)
	{
		`FGraphNodeCreator<T>` NodeCreator(*Graph);
		T* GraphNode = NodeCreator.CreateNode();
		GraphNode->NodePosX = NodePosX;
		GraphNode->NodePosY = NodePosY;
		NodeCreator.Finalize();

		return GraphNode;
	};
};









class VSMEDITOR_API FVSM_StateMachineUtilities : public FVSM_EditorUtilities
{

	/************************************************************************/
	/* PROPERTIES															*/
	/************************************************************************/

protected:
	static const FString PREFIX_NAME;
	static const FString BEGIN_NAME;
	static const FString UPDATE_NAME;
	static const FString FINISH_NAME;
	static const FString SUFFIX_BEGIN_NAME;
	static const FString SUFFIX_UPDATE_NAME;
	static const FString SUFFIX_FINISH_NAME;

public:

	/************************************************************************/
	/* FUNCTIONS                                                            */
	/************************************************************************/


	template<typename NodeType, typename StateType>
	static NodeType* AddNewDefaultState(int32 NodePosX, int32 NodePosY, UVSM_Graph* Graph, UEdGraphPin* FromPin = nullptr, bool bSelectNewNode = true)
	{
		NodeType* NewNode = `CreateNode<NodeType>`(Graph, NodePosX, NodePosY, bSelectNewNode);

		StateType* State = `NewObject<StateType>`(Graph->PrepareOuter());

		Graph->RegisterNode(State, NewNode, FromPin);

		return NewNode;
	};


	static UVSM_Node_Entry* AddNewEntry(int32 NodePosX, int32 NodePosY, UVSM_Graph* Graph, bool bSelectNewNode = true)
	{
		UVSM_Node_Entry* NewNode = `CreateNode<UVSM_Node_Entry>`(Graph, NodePosX, NodePosY, bSelectNewNode);

		UVSM_State_Entry* Entry = `NewObject<UVSM_State_Entry>`(Graph->PrepareOuter());
		Entry->Data.Name = TEXT("Entry");
		Graph->RegisterEntryNode(Entry, NewNode);

		return NewNode;
	};

	static UVSM_Node_Task* AddNewTask(int32 NodePosX, int32 NodePosY, UVSM_Graph* Graph, FVSM_NodeClassData InClassData, UEdGraphPin* FromPin = nullptr, bool bSelectNewNode = true)
	{

		UVSM_Node_Task* NewNode = `CreateNode<UVSM_Node_Task>`(Graph, NodePosX, NodePosY, bSelectNewNode);

		UVSM_State_Task* Task = `NewObject<UVSM_State_Task>`(Graph->PrepareOuter());
		Task->TaskBP = InClassData.GetBlueprint();
		Task->Data.TaskClass = InClassData.GetClass();
		Task->Data.Name = "Task_" + FString::FromInt(Task->GetUniqueID());
		UVSM_Blueprint* Blueprint = Graph->SMBP;
		Blueprint->GetSM()->AddOrUpdateState(Task->Data);
		Graph->RegisterTaskNode(Task, NewNode, FromPin);


		return NewNode;
	};


	template<typename NodeType, typename TransitionType>
	static NodeType* AddNewTransition(int32 NodePosX, int32 NodePosY, UVSM_Graph* Graph, bool bSelectNewNode = true)
	{
		NodeType* NewNode = `CreateNode<NodeType>`(Graph, NodePosX, NodePosY, bSelectNewNode);

		TransitionType* Transition = `NewObject<TransitionType>`(Graph->PrepareOuter());

		Graph->RegisterTransitionNode(Transition, NewNode);

		return NewNode;
	};


	static void AddDefaultStateGraph(UVSM_Graph* Graph, UVSM_Node* StateNode)
	{

		if (StateNode->IsRootNode()) return;
			

		UVSM_Blueprint* Blueprint = Graph->SMBP;
		UVSM_State_Base* SmState = StateNode->State;
		FVSM_StateData& State = SmState->Data;

		// 创建名称
		State.Name = "State_" + FString::FromInt(SmState->GetUniqueID());

		// 创建视图, 视图名称与状态名称相同
		SmState->StateGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, *State.Name, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
		SmState->StateGraph->bAllowDeletion = false;
		SmState->StateGraph->bAllowRenaming = false;


		//添加新蓝图视图
		FBlueprintEditorUtils::AddUbergraphPage(Blueprint, SmState->StateGraph);
		
		State.BeginFunctionName = PREFIX_NAME + State.Name + SUFFIX_BEGIN_NAME;
		State.UpdateFunctionName = PREFIX_NAME + State.Name + SUFFIX_UPDATE_NAME;
		State.FinishFunctionName = PREFIX_NAME + State.Name + SUFFIX_FINISH_NAME;

		// 创建新事件节点
		UFunction* BeginFunction = `FindUField<UFunction>`(Blueprint->GetSM()->GetClass(), "BeginState_Internal");
		UK2Node_CustomEvent* BeginEvent = UK2Node_CustomEvent::CreateFromFunction(FVector2D(0, 0), SmState->StateGraph, State.BeginFunctionName, BeginFunction, false);
		BeginEvent->bCanRenameNode = false;

		

		UFunction* UpdateFunction = `FindUField<UFunction>`(Blueprint->GetSM()->GetClass(), "UpdateState_Internal");
		UK2Node_CustomEvent* UpdateEvent = UK2Node_CustomEvent::CreateFromFunction(FVector2D(0, 100.f), SmState->StateGraph, State.UpdateFunctionName, UpdateFunction, false);

		`TSharedPtr<FUserPinInfo>` PinDefinition = MakeShareable(new FUserPinInfo);
		FEdGraphPinType PinType;
		const UEdGraphSchema_K2* K2Schema = `GetDefault<UEdGraphSchema_K2>`();
		PinType.bIsReference = false;
		PinType.PinCategory = K2Schema->PC_Float;
		PinDefinition->PinName = "DeltaTime";
		PinDefinition->PinType = PinType;
		UEdGraphPin* NewPin = UpdateEvent->CreatePinFromUserDefinition(PinDefinition);
		UpdateEvent->ReconstructNode();
		UpdateEvent->bCanRenameNode = false;

		UFunction* FinishFunction = `FindUField<UFunction>`(Blueprint->GetSM()->GetClass(), "FinishState_Internal");
		UK2Node_CustomEvent* FinishEvent = UK2Node_CustomEvent::CreateFromFunction(FVector2D(0, 250.f), SmState->StateGraph, State.FinishFunctionName, FinishFunction, false);
		FinishEvent->bCanRenameNode = false;

		Blueprint->GetSM()->AddOrUpdateState(State);
	};

	static void AddNewTransitionGraph(UVSM_Graph* Graph, UVSM_Node_Transition* TransNode)
	{
		UVSM_Blueprint* Blueprint = Graph->SMBP;
		if ((!TransNode->GetInputPin() || TransNode->GetInputPin()->LinkedTo.Num() == 0)
			|| (!TransNode->GetOutputPin() && TransNode->GetOutputPin()->LinkedTo.Num() == 0))
			return;

		UVSM_Node* InputState = `CastChecked<UVSM_Node>`(TransNode->GetInputPin()->LinkedTo[0]->GetOwningNode());
		UVSM_Node* OutputState = `CastChecked<UVSM_Node>`(TransNode->GetOutputPin()->LinkedTo[0]->GetOwningNode());
		UVSM_State_Transition* Transition = TransNode->Transition;
		FVSM_TransitionData& RuntimeData = Transition->Data;

		if (InputState && OutputState)
		{
			// 创建过渡节点名称
			RuntimeData.Name = InputState->State->Data.Name + "_To_" + OutputState->State->Data.Name;
			RuntimeData.FromState = InputState->State->Data.Name;
			RuntimeData.ToState = OutputState->State->Data.Name;
			InputState->State->Data.TransitionNames.Add(RuntimeData.Name);
			// 创建函数视图,名字从RuntimeData获取
			Transition->TransitionGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, *RuntimeData.Name, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
			Transition->TransitionGraph->bAllowDeletion = false;
			Transition->TransitionGraph->bAllowRenaming = false;
			FBlueprintEditorUtils::`AddFunctionGraph<UClass>`(Blueprint, Transition->TransitionGraph, true, nullptr);

			UK2Node_FunctionEntry* EntryNode = nullptr;
			if (Transition->TransitionGraph)
			{
				
				//获取所有输入节点
				`TArray<UK2Node_FunctionEntry*>` EntryNodes;
				Transition->TransitionGraph->GetNodesOfClass(EntryNodes);

				if ((EntryNodes.Num() > 0) && EntryNodes[0]->IsEditable())
					EntryNode = EntryNodes[0];

				// 创建返回节点
				if (EntryNode)
				{
					EntryNode->AddExtraFlags(FUNC_BlueprintPure);
					UK2Node_FunctionResult* ResultNode = FBlueprintEditorUtils::FindOrCreateFunctionResultNode(EntryNode);
					if (ResultNode)
					{
						
						const UEdGraphSchema_K2* K2Schema = `GetDefault<UEdGraphSchema_K2>`();

						FEdGraphPinType PinType;
						PinType.bIsReference = false;
						PinType.PinCategory = K2Schema->PC_Boolean;
						FName NewPinName = ResultNode->CreateUniquePinName(TEXT("Result"));
						UEdGraphPin* NewPin = ResultNode->CreateUserDefinedPin(NewPinName, PinType, EGPD_Input, false);

						ResultNode->ReconstructNode();
					}
				}
			}

			Blueprint->GetSM()->AddOrUpdateTransition(RuntimeData);
		}
	};


	static void RenameStateGraph(UVSM_State_Base* State, FString OldName, FString NewName)
	{
		if (State)
		{
			FVSM_StateData& RuntimeState = State->Data;
			RuntimeState.Name = NewName;
			if (State->StateGraph)
			{

				FBlueprintEditorUtils::RenameGraph(State->StateGraph, RuntimeState.Name);
				//重命名函数名称
				for (UEdGraphNode* Node : State->StateGraph->Nodes)
				{
					FString NodeName = Node->GetNodeTitle(ENodeTitleType::MenuTitle).ToString();
					if (NodeName == PREFIX_NAME + OldName + SUFFIX_BEGIN_NAME)
						Node->OnRenameNode(PREFIX_NAME + NewName + SUFFIX_BEGIN_NAME);
					else if (NodeName == PREFIX_NAME + OldName + SUFFIX_UPDATE_NAME)
						Node->OnRenameNode(PREFIX_NAME + NewName + SUFFIX_UPDATE_NAME);
					else if (NodeName == PREFIX_NAME + OldName + SUFFIX_FINISH_NAME)
						Node->OnRenameNode(PREFIX_NAME + NewName + SUFFIX_FINISH_NAME);
				}

				RuntimeState.BeginFunctionName = PREFIX_NAME + RuntimeState.Name + SUFFIX_BEGIN_NAME;
				RuntimeState.UpdateFunctionName = PREFIX_NAME + RuntimeState.Name + SUFFIX_UPDATE_NAME;
				RuntimeState.FinishFunctionName = PREFIX_NAME + RuntimeState.Name + SUFFIX_FINISH_NAME;
			}
			
			State->SMBP->GetSM()->AddOrUpdateState(RuntimeState, OldName);


			
		}
	};


	static void RenameStateGraph(UVSM_State_Transition* Transition)
	{
		if (Transition && Transition->TransitionGraph)
		{
			UVSM_Node_Transition* TransNode = `CastChecked<UVSM_Node_Transition>`(Transition->GraphNode);
			FVSM_TransitionData& RuntimeData = Transition->Data;


			if ((!TransNode->GetInputPin() || TransNode->GetInputPin()->LinkedTo.Num() == 0)
				|| (!TransNode->GetOutputPin() && TransNode->GetOutputPin()->LinkedTo.Num() == 0))
				return;

			UVSM_Node* InputState = `CastChecked<UVSM_Node>`(TransNode->GetInputPin()->LinkedTo[0]->GetOwningNode());
			UVSM_Node* OutputState = `CastChecked<UVSM_Node>`(TransNode->GetOutputPin()->LinkedTo[0]->GetOwningNode());
			
			FString OldName = Transition->TransitionGraph->GetName();

			RuntimeData.Name = InputState->State->Data.Name + "_To_" + OutputState->State->Data.Name;
			RuntimeData.FromState = InputState->State->Data.Name;
			RuntimeData.ToState = OutputState->State->Data.Name;

			FBlueprintEditorUtils::RenameGraph(Transition->TransitionGraph, RuntimeData.Name);
			Transition->SMBP->GetSM()->AddOrUpdateTransition(RuntimeData, OldName);
		}
	};

};