UE4C++泛型蓝图节点之K2Node蓝图节点

UE4C++泛型蓝图节点之K2Node蓝图节点

  • 前言:

上篇我们讲解了以CustomThunk方式创建自定义泛型节点,今天在此我们讲解以K2Node实现自定义蓝图节点。

首先,我们来比较这俩种方法,CustomThunk创建泛型节点时,代码简洁,调试起来也方便但是灵活度没有K2Node高,K2Node可以动态的改变的节点数量, 是更灵活的动态类型。就如K2Node可以用来创建下拉列表,比如UE源码中UK2Node_GetDataTableRow就是很经典的以K2Node开发的。我们可以看到它这个节点输入的时候是以下拉列表形式,可以选择不同的Datatable表,并输出不同类型的数据结构,这就是K2Node的比较灵活的地方,在更加灵活的开发上,就可以选择以K2Node的方式进行开发。K2Node的存在是用来优化蓝图到C++的远程调用方式(RPC)。
在这里插入图片描述
K2Node本质上与CustomThunk类型一致,都是以DECLARE_FUNCTION函数的类型进行的,只不过K2Node所对应的DECLARE_FUNCTION比较难找而已。继续拿UK2Node_GetDataTableRow举例,他的DECLARE_FUNCTION函数在UDataTableFunctionLibrary的蓝图函数库里面。
在这里插入图片描述

在遇到增加删除pin的时候,也会选择以K2Node进行开发实现,例如逻辑操作And节点就能动态的添加删除pin,而此刻用CustomThunk的Thunk函数体实现形式就完全不行,因此,在此我给大家分享一下K2Node的一些功能实现。
在这里插入图片描述
创建一个类继承自UK2Node
在这里插入图片描述

//鼠标放到上面的说明/注释
	virtual FText GetTooltipText()const override;
	//节点名称
	virtual FText GetNodeTitle(ENodeTitleType::Type TitleType)const override;
	//将节点添加到蓝图视图
	virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegister)const override;
	//蓝图节点的标签
	virtual FText GetMenuCategory()const;
	//展开节点
	virtual void ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)override;
	//分配默认引脚
	virtual void AllocateDefaultPins()override;
	//引脚更改时会调用
	virtual void PinDefaultValueChanged(UEdGraphPin* ChangedPin)override;
	//连接情况更改以后
	virtual void NotifyPinConnectionListChanged(UEdGraphPin* Pin)override;

	//创建一个可视小部件来在图形编辑器或图形面板中表示这个节点。如果没有实现,则将使用默认的节点工厂
	virtual TSharedPtr<SGraphNode> CreateVisualWidget() { return TSharedPtr<SGraphNode>(); }
	// 为表示此节点的小部件创建背景图像
	virtual TSharedPtr<SWidget> CreateNodeImage() const { return TSharedPtr<SWidget>(); }
	//右键菜单, 比如添加RemovePin
	virtual void GetNodeContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const override;

知道这些了,我们再将UK2Node_GetDataTableRow中的代码全拷贝到自己的代码中,例如:

  • .h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "K2Node.h"
#include "KismetCompiler.h"

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "Textures/SlateIcon.h"
#include "EdGraph/EdGraphNodeUtils.h"
#include "Test_K2Node.generated.h"

/**
 *
 */
class FBlueprintActionDatabaseRegistrar;
 class UDataTable;
 class UEdGraph;
UCLASS()
class GAME_SANDBOX_API UTest_K2Node : public UK2Node
{
	GENERATED_BODY()
public:
	/*
	//鼠标放到上面的说明/注释
	virtual FText GetTooltipText()const override;
	//节点名称
	virtual FText GetNodeTitle(ENodeTitleType::Type TitleType)const override;
	//将节点添加到蓝图视图
	virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegister)const override;
	//蓝图节点的标签
	virtual FText GetMenuCategory()const;
	//展开节点
	virtual void ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)override;
	//分配默认引脚
	virtual void AllocateDefaultPins()override;
	//引脚更改时会调用
	virtual void PinDefaultValueChanged(UEdGraphPin* ChangedPin)override;
	//连接情况更改以后
	virtual void NotifyPinConnectionListChanged(UEdGraphPin* Pin)override;

	//创建一个可视小部件来在图形编辑器或图形面板中表示这个节点。如果没有实现,则将使用默认的节点工厂
	virtual TSharedPtr<SGraphNode> CreateVisualWidget() { return TSharedPtr<SGraphNode>(); }
	// 为表示此节点的小部件创建背景图像
	virtual TSharedPtr<SWidget> CreateNodeImage() const { return TSharedPtr<SWidget>(); }
	//右键菜单, 比如添加RemovePin
	virtual void GetNodeContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const override;
	*/
	UTest_K2Node(const FObjectInitializer& ObjectInitializer);
	//~ Begin UEdGraphNode Interface.
	//分配默认引脚
	virtual void AllocateDefaultPins() override;
	//节点名称
	virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
	//引脚更改时会调用
	virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override;
	//鼠标放到上面的说明/注释
	virtual FText GetTooltipText() const override;
	//展开节点
	virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
	virtual FSlateIcon GetIconAndTint(FLinearColor& OutColor) const override;
	virtual void PostReconstructNode() override;
	//~ End UEdGraphNode Interface.

	//~ Begin UK2Node Interface
	virtual bool IsNodeSafeToIgnore() const override { return true; }
	virtual void ReallocatePinsDuringReconstruction(TArray<UEdGraphPin*>& OldPins) override;
	//将节点添加到蓝图视图
	virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
	//蓝图节点的标签
	virtual FText GetMenuCategory() const override;
	virtual bool IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const override;
	virtual void EarlyValidation(class FCompilerResultsLog& MessageLog) const override;
	virtual void PreloadRequiredAssets() override;
	//连接情况更改以后
	virtual void NotifyPinConnectionListChanged(UEdGraphPin* Pin) override;
	//~ End UK2Node Interface

	/** Get the return type of our struct */
	UScriptStruct* GetReturnTypeForStruct();

	/** Get the then output pin */
	UEdGraphPin* GetThenPin() const;
	/** Get the Data Table input pin */
	UEdGraphPin* GetDataTablePin(const TArray<UEdGraphPin*>* InPinsToSearch = NULL) const;
	/** Get the spawn transform input pin */
	UEdGraphPin* GetRowNamePin() const;
	/** Get the exec output pin for when the row was not found */
	UEdGraphPin* GetRowNotFoundPin() const;
	/** Get the result output pin */
	UEdGraphPin* GetResultPin() const;

	/** Get the type of the TableRow to return */
	UScriptStruct* GetDataTableRowStructType() const;

	void OnDataTableRowListChanged(const UDataTable* DataTable);
private:
	/**
	 * Takes the specified "MutatablePin" and sets its 'PinToolTip' field (according
	 * to the specified description)
	 *
	 * @param   MutatablePin	The pin you want to set tool-tip text on
	 * @param   PinDescription	A string describing the pin's purpose
	 */
	void SetPinToolTip(UEdGraphPin& MutatablePin, const FText& PinDescription) const;

	/** Set the return type of our struct */
	void SetReturnTypeForStruct(UScriptStruct* InClass);
	/** Queries for the authoritative return type, then modifies the return pin to match */
	void RefreshOutputPinType();
	/** Triggers a refresh which will update the node's widget; aimed at updating the dropdown menu for the RowName input */
	void RefreshRowNameOptions();

	// 此节点的Tooltip text 
	FText NodeTooltip;

	// 构造FText字符串的开销很大,所以我们缓存节点的标题
	FNodeTextCache CachedNodeTitle;
};
  • .cpp
// Fill out your copyright notice in the Description page of Project Settings.


#include "Test_K2Node.h"
#include "Engine/DataTable.h"
#include "EdGraphSchema_K2.h"
#include "K2Node_CallFunction.h"
#include "K2Node_IfThenElse.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "KismetCompiler.h"
#include "DataTableEditorUtils.h"
#include "Kismet/DataTableFunctionLibrary.h"
#include "BlueprintNodeSpawner.h"
#include "EditorCategoryUtils.h"
#include "BlueprintActionDatabaseRegistrar.h"

#define LOCTEXT_NAMESPACE "K2Node_TestGetDataTableRow"

namespace GetTestDataTableRowHelper
{

const FName DataTablePinName = "TestDataTable";
const FName RowNotFoundPinName = "TestRowNotFound";
const FName RowNamePinName = "TestRowName";

}

UTest_K2Node::UTest_K2Node(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	NodeTooltip = LOCTEXT("NodeTooltip", "Attempts to retrieve a Test TableRow from a DataTable via it's RowName");
}

void UTest_K2Node::AllocateDefaultPins()
{
	const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();

	// Add execution pins
	CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
	UEdGraphPin* RowFoundPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
	RowFoundPin->PinFriendlyName = LOCTEXT("GetDataTableRow Row Found Exec pin", "Test Row Found");
	CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GetTestDataTableRowHelper::RowNotFoundPinName);

	// Add DataTable pin
	UEdGraphPin* DataTablePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UDataTable::StaticClass(), GetTestDataTableRowHelper::DataTablePinName);
	SetPinToolTip(*DataTablePin, LOCTEXT("DataTablePinDescription", "Test The DataTable you want to retreive a row from"));

	// Row Name pin
	UEdGraphPin* RowNamePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Name, GetTestDataTableRowHelper::RowNamePinName);
	SetPinToolTip(*RowNamePin, LOCTEXT("RowNamePinDescription", "Test The name of the row to retrieve from the DataTable"));

	// Result pin
	UEdGraphPin* ResultPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Wildcard, UEdGraphSchema_K2::PN_ReturnValue);
	ResultPin->PinFriendlyName = LOCTEXT("GetDataTableRow Output Row", "Test Out Row");
	SetPinToolTip(*ResultPin, LOCTEXT("ResultPinDescription", "Test The returned TableRow, if found"));

	Super::AllocateDefaultPins();
}

void UTest_K2Node::SetPinToolTip(UEdGraphPin& MutatablePin, const FText& PinDescription) const
{
	MutatablePin.PinToolTip = UEdGraphSchema_K2::TypeToText(MutatablePin.PinType).ToString();

	UEdGraphSchema_K2 const* const K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
	if (K2Schema != nullptr)
	{
		MutatablePin.PinToolTip += TEXT(" ");
		MutatablePin.PinToolTip += K2Schema->GetPinDisplayName(&MutatablePin).ToString();
	}

	MutatablePin.PinToolTip += FString(TEXT("\n")) + PinDescription.ToString();
}

void UTest_K2Node::RefreshOutputPinType()
{
	UScriptStruct* OutputType = GetDataTableRowStructType();
	SetReturnTypeForStruct(OutputType);
}

void UTest_K2Node::RefreshRowNameOptions()
{
	// When the DataTable pin gets a new value assigned, we need to update the Slate UI so that SGraphNodeCallParameterCollectionFunction will update the ParameterName drop down
	UEdGraph* Graph = GetGraph();
	Graph->NotifyGraphChanged();
}


void UTest_K2Node::SetReturnTypeForStruct(UScriptStruct* NewRowStruct)
{
	UScriptStruct* OldRowStruct = GetReturnTypeForStruct();
	if (NewRowStruct != OldRowStruct)
	{
		UEdGraphPin* ResultPin = GetResultPin();

		if (ResultPin->SubPins.Num() > 0)
		{
			GetSchema()->RecombinePin(ResultPin);
		}

		// NOTE: purposefully not disconnecting the ResultPin (even though it changed type)... we want the user to see the old
		//       connections, and incompatible connections will produce an error (plus, some super-struct connections may still be valid)
		ResultPin->PinType.PinSubCategoryObject = NewRowStruct;
		ResultPin->PinType.PinCategory = (NewRowStruct == nullptr) ? UEdGraphSchema_K2::PC_Wildcard : UEdGraphSchema_K2::PC_Struct;

		CachedNodeTitle.Clear();
	}
}

UScriptStruct* UTest_K2Node::GetReturnTypeForStruct()
{
	UScriptStruct* ReturnStructType = (UScriptStruct*)(GetResultPin()->PinType.PinSubCategoryObject.Get());

	return ReturnStructType;
}

UScriptStruct* UTest_K2Node::GetDataTableRowStructType() const
{
	UScriptStruct* RowStructType = nullptr;

	UEdGraphPin* DataTablePin = GetDataTablePin();
	if(DataTablePin && DataTablePin->DefaultObject != nullptr && DataTablePin->LinkedTo.Num() == 0)
	{
		if (const UDataTable* DataTable = Cast<const UDataTable>(DataTablePin->DefaultObject))
		{
			RowStructType = DataTable->RowStruct;
		}
	}

	if (RowStructType == nullptr)
	{
		UEdGraphPin* ResultPin = GetResultPin();
		if (ResultPin && ResultPin->LinkedTo.Num() > 0)
		{
			RowStructType = Cast<UScriptStruct>(ResultPin->LinkedTo[0]->PinType.PinSubCategoryObject.Get());
			for (int32 LinkIndex = 1; LinkIndex < ResultPin->LinkedTo.Num(); ++LinkIndex)
			{
				UEdGraphPin* Link = ResultPin->LinkedTo[LinkIndex];
				UScriptStruct* LinkType = Cast<UScriptStruct>(Link->PinType.PinSubCategoryObject.Get());

				if (RowStructType->IsChildOf(LinkType))
				{
					RowStructType = LinkType;
				}
			}
		}
	}
	return RowStructType;
}

void UTest_K2Node::OnDataTableRowListChanged(const UDataTable* DataTable)
{
	UEdGraphPin* DataTablePin = GetDataTablePin();
	if (DataTable && DataTablePin && DataTable == DataTablePin->DefaultObject)
	{
		UEdGraphPin* RowNamePin = GetRowNamePin();
		const bool TryRefresh = RowNamePin && !RowNamePin->LinkedTo.Num();
		const FName CurrentName = RowNamePin ? FName(*RowNamePin->GetDefaultAsString()) : NAME_None;
		if (TryRefresh && RowNamePin && !DataTable->GetRowNames().Contains(CurrentName))
		{
			if (UBlueprint* BP = GetBlueprint())
			{
				FBlueprintEditorUtils::MarkBlueprintAsModified(BP);
			}
		}
	}
}

void UTest_K2Node::ReallocatePinsDuringReconstruction(TArray<UEdGraphPin*>& OldPins) 
{
	Super::ReallocatePinsDuringReconstruction(OldPins);

	if (UEdGraphPin* DataTablePin = GetDataTablePin(&OldPins))
	{
		if (UDataTable* DataTable = Cast<UDataTable>(DataTablePin->DefaultObject))
		{
			// make sure to properly load the data-table object so that we can 
			// farm the "RowStruct" property from it (below, in GetDataTableRowStructType)
			PreloadObject(DataTable);
		}
	}
}

void UTest_K2Node::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
	// actions get registered under specific object-keys; the idea is that 
	// actions might have to be updated (or deleted) if their object-key is  
	// mutated (or removed)... here we use the node's class (so if the node 
	// type disappears, then the action should go with it)
	UClass* ActionKey = GetClass();
	// to keep from needlessly instantiating a UBlueprintNodeSpawner, first   
	// check to make sure that the registrar is looking for actions of this type
	// (could be regenerating actions for a specific asset, and therefore the 
	// registrar would only accept actions corresponding to that asset)
	if (ActionRegistrar.IsOpenForRegistration(ActionKey))
	{
		UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
		check(NodeSpawner != nullptr);

		ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner);
	}
}

FText UTest_K2Node::GetMenuCategory() const
{
	return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Utilities);
}

bool UTest_K2Node::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const
{
	if (MyPin == GetResultPin() && MyPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard)
	{
		bool bDisallowed = true;
		if (OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct)
		{
			if (UScriptStruct* ConnectionType = Cast<UScriptStruct>(OtherPin->PinType.PinSubCategoryObject.Get()))
			{
				bDisallowed = !FDataTableEditorUtils::IsValidTableStruct(ConnectionType);
			}
		}
		else if (OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard)
		{
			bDisallowed = false;
		}

		if (bDisallowed)
		{
			OutReason = TEXT("Must be a struct that can be used in a DataTable");
		}
		return bDisallowed;
	}
	return false;
}

void UTest_K2Node::PinDefaultValueChanged(UEdGraphPin* ChangedPin) 
{
	if (ChangedPin && ChangedPin->PinName == GetTestDataTableRowHelper::DataTablePinName)
	{
		RefreshOutputPinType();

		UEdGraphPin* RowNamePin = GetRowNamePin();
		UDataTable*  DataTable = Cast<UDataTable>(ChangedPin->DefaultObject);
		if (RowNamePin)
		{
			if (DataTable && (RowNamePin->DefaultValue.IsEmpty() || !DataTable->GetRowMap().Contains(*RowNamePin->DefaultValue)))
			{
				if (auto Iterator = DataTable->GetRowMap().CreateConstIterator())
				{
					RowNamePin->DefaultValue = Iterator.Key().ToString();
				}
			}	

			RefreshRowNameOptions();
		}
	}
}

FText UTest_K2Node::GetTooltipText() const
{
	return NodeTooltip;
}

UEdGraphPin* UTest_K2Node::GetThenPin()const
{
	const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();

	UEdGraphPin* Pin = FindPinChecked(UEdGraphSchema_K2::PN_Then);
	check(Pin->Direction == EGPD_Output);
	return Pin;
}

UEdGraphPin* UTest_K2Node::GetDataTablePin(const TArray<UEdGraphPin*>* InPinsToSearch /*= NULL*/) const
{
	const TArray<UEdGraphPin*>* PinsToSearch = InPinsToSearch ? InPinsToSearch : &Pins;
    
	UEdGraphPin* Pin = nullptr;
	for (UEdGraphPin* TestPin : *PinsToSearch)
	{
		if (TestPin && TestPin->PinName == GetTestDataTableRowHelper::DataTablePinName)
		{
			Pin = TestPin;
			break;
		}
	}
	check(Pin == nullptr || Pin->Direction == EGPD_Input);
	return Pin;
}

UEdGraphPin* UTest_K2Node::GetRowNamePin() const
{
	UEdGraphPin* Pin = FindPinChecked(GetTestDataTableRowHelper::RowNamePinName);
	check(Pin->Direction == EGPD_Input);
	return Pin;
}

UEdGraphPin* UTest_K2Node::GetRowNotFoundPin() const
{
	UEdGraphPin* Pin = FindPinChecked(GetTestDataTableRowHelper::RowNotFoundPinName);
	check(Pin->Direction == EGPD_Output);
	return Pin;
}

UEdGraphPin* UTest_K2Node::GetResultPin() const
{
	const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();

	UEdGraphPin* Pin = FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue);
	check(Pin->Direction == EGPD_Output);
	return Pin;
}

FText UTest_K2Node::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	if (TitleType == ENodeTitleType::MenuTitle)
	{
		return LOCTEXT("ListViewTitle", "Get Data Table Row");
	}
	else if (UEdGraphPin* DataTablePin = GetDataTablePin())
	{
		if (DataTablePin->LinkedTo.Num() > 0)
		{
			return NSLOCTEXT("TestK2Node", "DataTable_Title_Unknown", "Get Test Data Table Row");
		}
		else if (DataTablePin->DefaultObject == nullptr)
		{
			return NSLOCTEXT("TestK2Node", "DataTable_Title_None", "Get Test Data Table Row NONE");
		}
		else if (CachedNodeTitle.IsOutOfDate(this))
		{
			FFormatNamedArguments Args;
			Args.Add(TEXT("DataTableName"), FText::FromString(DataTablePin->DefaultObject->GetName()));

			FText LocFormat = NSLOCTEXT("TestK2Node", "DataTable", "Get Test Data Table Row {DataTableName}");
			// FText::Format() is slow, so we cache this to save on performance
			CachedNodeTitle.SetCachedText(FText::Format(LocFormat, Args), this);
		}
	}
	else
	{
		return NSLOCTEXT("TestK2Node", "DataTable_Title_None", "Get Test Data Table Row NONE");
	}	
	return CachedNodeTitle;
}

void UTest_K2Node::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
    Super::ExpandNode(CompilerContext, SourceGraph);
    
    UEdGraphPin* OriginalDataTableInPin = GetDataTablePin();
    UDataTable* Table = (OriginalDataTableInPin != NULL) ? Cast<UDataTable>(OriginalDataTableInPin->DefaultObject) : NULL;
    if((nullptr == OriginalDataTableInPin) || (0 == OriginalDataTableInPin->LinkedTo.Num() && nullptr == Table))
    {
        CompilerContext.MessageLog.Error(*LOCTEXT("GetDataTableRowNoDataTable_Error", "GetTestDataTableRow must have a DataTable specified.").ToString(), this);
        // we break exec links so this is the only error we get
        BreakAllNodeLinks();
        return;
    }

	// FUNCTION NODE
	const FName FunctionName = GET_FUNCTION_NAME_CHECKED(UDataTableFunctionLibrary, GetDataTableRowFromName);
	UK2Node_CallFunction* GetDataTableRowFunction = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph);
	GetDataTableRowFunction->FunctionReference.SetExternalMember(FunctionName, UDataTableFunctionLibrary::StaticClass());
	GetDataTableRowFunction->AllocateDefaultPins();
    CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *(GetDataTableRowFunction->GetExecPin()));

	// Connect the input of our GetDataTableRow to the Input of our Function pin
    UEdGraphPin* DataTableInPin = GetDataTableRowFunction->FindPinChecked(TEXT("Table"));
	if(OriginalDataTableInPin->LinkedTo.Num() > 0)
	{
		// Copy the connection
		CompilerContext.MovePinLinksToIntermediate(*OriginalDataTableInPin, *DataTableInPin);
	}
	else
	{
		// Copy literal
		DataTableInPin->DefaultObject = OriginalDataTableInPin->DefaultObject;
	}
	UEdGraphPin* RowNameInPin = GetDataTableRowFunction->FindPinChecked(TEXT("RowName"));
	CompilerContext.MovePinLinksToIntermediate(*GetRowNamePin(), *RowNameInPin);

	// Get some pins to work with
	UEdGraphPin* OriginalOutRowPin = FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue);
	UEdGraphPin* FunctionOutRowPin = GetDataTableRowFunction->FindPinChecked(TEXT("OutRow"));
    UEdGraphPin* FunctionReturnPin = GetDataTableRowFunction->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue);
    UEdGraphPin* FunctionThenPin = GetDataTableRowFunction->GetThenPin();
        
    // Set the type of the OutRow pin on this expanded mode to match original
    FunctionOutRowPin->PinType = OriginalOutRowPin->PinType;
	FunctionOutRowPin->PinType.PinSubCategoryObject = OriginalOutRowPin->PinType.PinSubCategoryObject;
        
    //BRANCH NODE
    UK2Node_IfThenElse* BranchNode = CompilerContext.SpawnIntermediateNode<UK2Node_IfThenElse>(this, SourceGraph);
    BranchNode->AllocateDefaultPins();
    // Hook up inputs to branch
    FunctionThenPin->MakeLinkTo(BranchNode->GetExecPin());
    FunctionReturnPin->MakeLinkTo(BranchNode->GetConditionPin());
        
    // Hook up outputs
    CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *(BranchNode->GetThenPin()));
    CompilerContext.MovePinLinksToIntermediate(*GetRowNotFoundPin(), *(BranchNode->GetElsePin()));
    CompilerContext.MovePinLinksToIntermediate(*OriginalOutRowPin, *FunctionOutRowPin);

	BreakAllNodeLinks();
}

FSlateIcon UTest_K2Node::GetIconAndTint(FLinearColor& OutColor) const
{
	OutColor = GetNodeTitleColor();
	static FSlateIcon Icon("EditorStyle", "Kismet.AllClasses.FunctionIcon");
	return Icon;
}

void UTest_K2Node::PostReconstructNode()
{
	Super::PostReconstructNode();

	RefreshOutputPinType();
}

void UTest_K2Node::EarlyValidation(class FCompilerResultsLog& MessageLog) const
{
	Super::EarlyValidation(MessageLog);

	const UEdGraphPin* DataTablePin = GetDataTablePin();
	const UEdGraphPin* RowNamePin = GetRowNamePin();
	if (!DataTablePin || !RowNamePin)
	{
		MessageLog.Error(*LOCTEXT("MissingPins", "Missing pins in @@").ToString(), this);
		return;
	}

	if (DataTablePin->LinkedTo.Num() == 0)
	{
		const UDataTable* DataTable = Cast<UDataTable>(DataTablePin->DefaultObject);
		if (!DataTable)
		{
			MessageLog.Error(*LOCTEXT("NoDataTable", "No DataTable in @@").ToString(), this);
			return;
		}

		if (!RowNamePin->LinkedTo.Num())
		{
			const FName CurrentName = FName(*RowNamePin->GetDefaultAsString());
			if (!DataTable->GetRowNames().Contains(CurrentName))
			{
				const FString Msg = FText::Format(
					LOCTEXT("WrongRowNameFmt", "'{0}' row name is not stored in '{1}'. @@"),
					FText::FromString(CurrentName.ToString()),
					FText::FromString(GetFullNameSafe(DataTable))
				).ToString();
				MessageLog.Error(*Msg, this);
				return;
			}
		}
	}	
}

void UTest_K2Node::PreloadRequiredAssets()
{
	if (UEdGraphPin* DataTablePin = GetDataTablePin())
	{
		if (UDataTable* DataTable = Cast<UDataTable>(DataTablePin->DefaultObject))
		{
			// make sure to properly load the data-table object so that we can 
			// farm the "RowStruct" property from it (below, in GetDataTableRowStructType)
			PreloadObject(DataTable);
		}
	}
	return Super::PreloadRequiredAssets();
}

void UTest_K2Node::NotifyPinConnectionListChanged(UEdGraphPin* Pin)
{
	Super::NotifyPinConnectionListChanged(Pin);

	if (Pin == GetResultPin())
	{
		UEdGraphPin* TablePin = GetDataTablePin();
		// this connection would only change the output type if the table pin is undefined
		const bool bIsTypeAuthority = (TablePin->LinkedTo.Num() > 0 || TablePin->DefaultObject == nullptr);
		if (bIsTypeAuthority)
		{
			RefreshOutputPinType();
		}		
	}
	else if (Pin == GetDataTablePin())
	{
		const bool bConnectionAdded = Pin->LinkedTo.Num() > 0;
		if (bConnectionAdded)
		{
			// if a connection was made, then we may need to rid ourselves of the row dropdown
			RefreshRowNameOptions();
			// if the output connection was previously, incompatible, it now becomes the authority on this node's output type
			RefreshOutputPinType();
		}
	}
}

#undef LOCTEXT_NAMESPACE

如果代码改的发现还有错误,这时候添加模块,此处我选择偷懒,是将UK2Node_GetDataTableRow所在的模块添加进入.build.cs文件中,在将其中的private中的文件拷贝到.build.cs中,因为,私有模块在添加之后,其他模块是无法使用的。我之前的模块添加应该有讲到,感兴趣的可以稍微看看。

在这里插入图片描述
以下便是添加好的模块

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "BlueprintGraph" });

        PrivateDependencyModuleNames.AddRange(
            new string[]
            {
                "Slate",
                "SlateCore", 
                "ApplicationCore",
                "EditorStyle",
                "KismetCompiler",
                "UnrealEd",
                "GraphEditor",
                "Kismet",
                "KismetWidgets",
                "PropertyEditor",
                "ToolMenus",
            });

运行之后选择Datatable和不选择Datatable便是这俩种情况
在这里插入图片描述

在这里插入图片描述

这时候我们再深入研究代码即可

  • 创建输入引脚
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
  • 创建输出引脚
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);

数组类型的输出引脚

// Create the output pin
UEdGraphNode::FCreatePinParams PinParams;
PinParams.ContainerType = EPinContainerType::Array;
PinParams.bIsReference = false;
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Byte, UEdGraphSchema_K2::PN_ReturnValue, PinParams);

继承 IK2Node_AddPinInterface接口后重写virtual void AddInputPin() override函数添加Pin

void UK2Node_ConvertScriptCallValue::AddInputPin()
{
	CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, GetPinName(NumInputs));
	NumInputs++;
}

添加移除Pin的操作

void UK2Node_ConvertScriptCallValue::GetNodeContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const
{
	Super::GetNodeContextMenuActions(Menu, Context);
 
	if (!Context->bIsDebugging)
	{
		static FName CommutativeAssociativeBinaryOperatorNodeName = FName("K2Node_ConvertScriptCallValue");
		FText CommutativeAssociativeBinaryOperatorStr = LOCTEXT("K2Node_ConvertScriptCallValue", "Operator Node");
		if (Context->Pin != NULL)
		{
			if (CanRemovePin(Context->Pin))
			{
				FToolMenuSection& Section =
					Menu->AddSection(CommutativeAssociativeBinaryOperatorNodeName, CommutativeAssociativeBinaryOperatorStr);
				Section.AddMenuEntry("RemovePin", LOCTEXT("RemovePin", "Remove pin"),
					LOCTEXT("RemovePinTooltip", "Remove this input pin"), FSlateIcon(),
					FUIAction(FExecuteAction::CreateUObject(const_cast<UK2Node_ConvertScriptCallValue*>(this),
						&UK2Node_ConvertScriptCallValue::RemoveInputPin, const_cast<UEdGraphPin*>(Context->Pin))));
 
			}
		}
	}
}

移除pin

void UK2Node_ConvertScriptCallValue::RemoveInputPin(UEdGraphPin * Pin)
{
	FScopedTransaction Transaction(FText::FromString("ConvertScriptCallValue_RemoveInputPin"));
	Modify();
	--NumInputs;
	RemovePin(Pin);
	SyncPinNames();
	FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint());
}

次篇幅就到此为止,下章讲解详细的使用,大家喜欢就点个赞吧!

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
在 Vue 3 + TypeScript 中,泛型是一个非常常用的技巧,可以帮助我们编写出更加通用、可复用的代码。下面是一个泛型的高级运用实例: 假设我们有一个表单组件,它的数据模型是一个对象,其中每个属性都有一个对应的输入框,我们可以编写一个 `Form` 组件来实现这个表单。为了让 `Form` 组件更加通用,我们可以使用泛型来实现: ```typescript // 定义表单数据模型的类型 interface FormModel { [key: string]: any } // 定义表单项的类型 interface FormItem<T> { label: string prop: keyof T } // 定义表单组件的 props 类型 interface FormProps<T extends FormModel> { model: T items: Array<FormItem<T>> } // 定义表单组件 export default defineComponent<FormProps<FormModel>>({ name: 'Form', props: { model: Object as PropType<FormModel>, items: Array as PropType<Array<FormItem<FormModel>>> }, setup(props) { const handleSubmit = () => { // 处理表单提交逻辑 } return { handleSubmit } } }) ``` 在上面的代码中,我们定义了一个 `Form` 组件,它接收两个 props:`model` 和 `items`,其中 `model` 是一个泛型类型,它可以是任意一个类似于表单数据模型的对象,`items` 是一个数组,其中每个元素都是一个 `FormItem` 类型的对象,表示一个表单项。在组件中,我们使用 `keyof` 关键字来获取泛型类型 `T` 的所有属性名,然后使用 `Array<FormItem<T>>` 来表示 `items` 的类型,这样就可以在使用组件时传入任意一个数据模型和表单项,从而实现组件的通用性和灵活性。 以上是一个 Vue 3 + TypeScript 中泛型的高级运用实例,通过使用泛型,我们可以编写出更加通用、可复用的组件和方法,从而提高代码的复用性和灵活性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值