To be brief, K2Node can be considered as a dynamic, complicated and advanced blueprint node, when declare a function of a UObject, we usually use UFUNCTION(Blueprintcallabe) specifier, it will also generate a blueprint node for it. Sometimes we need an advanced blueprint node for a function, such as a node which will change its output pin by changing the input. To do that, we need the K2Node.
1. What are K2Nodes
For those who are new to Slate, K2Node is not a familiar name, but you may already using K2Node even through you don’t know what K2Nodes are.
For example, the Branch node in blueprint, is a K2Node:
Branch node is of the type UK2Node_IfThenElse, you can find the other build-in K2Nodes for engine in the path Engine/Source/Editor/BlueprintGraph/Classes, where you will also find many other familiar nodes, such as SpawnActor, Event, Self and so on.
By the way, if you would like to accomplish a node like the branch node, using two output pins or more outpins if the output is of bool type or an enumeration type, you don’t need to use a K2Node, here is a tip for that:
- Using the specifier ExpandBoolAsExec of UFUNCTION for bool output, using the specifier ExpandEnumAsExec= of UFUNCTION for enumeration output, note that they are meta specifier
UFUNCTION(BlueprintCallable, meta=(ExpandBoolAsExecs="ReturnValue"))
static bool ReturnBoolValue(const bool InputValue);
The can also be written as:
UFUNCTION(BlueprintCallable, meta=(ExpandBoolAsExecs="Output"))
static void ReturnBoolValue(const int InputValue, bool& Output);
One way to consider K2Node is that consider it as a node wrapped two or more functions, one takes the input and pass them to N functions, and another is used to get the output.
2. An example of K2Node
We take the UK2Node_IfThenElse as an example, it is the simplest K2Node, the declaration of it is as followed:
class FBlueprintActionDatabaseRegistrar;
class UEdGraphPin;
class UObject;
UCLASS(MinimalAPI, meta=(Keywords = "if bool branch"))
class UK2Node_IfThenElse : public UK2Node
{
GENERATED_UCLASS_BODY()
//~ Begin UEdGraphNode Interface
virtual void AllocateDefaultPins() override;
virtual FLinearColor GetNodeTitleColor() const override;
virtual FText GetTooltipText() const override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
virtual FSlateIcon GetIconAndTint(FLinearColor& OutColor) const override;
//~ End UEdGraphNode Interface
//~ Begin K2Node Interface.
virtual class FNodeHandlingFunctor* CreateNodeHandler(class FKismetCompilerContext& CompilerContext) const override;
virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
virtual FText GetMenuCategory() const override;
//~ End K2Node Interface.
/** Get the then output pin */
BLUEPRINTGRAPH_API UEdGraphPin* GetThenPin() const;
/** Get the return value pin */
BLUEPRINTGRAPH_API UEdGraphPin* GetElsePin() const;
/** Get the condition pin */
BLUEPRINTGRAPH_API UEdGraphPin* GetConditionPin() const;
};
You may find some methods familiar to us, like AllocateDefaultPins, GetNodeTitleColor and so on, indeed, UK2Node inherits from UEdGraphNode, the latter has already been discussed for many times in the last posts.
If you are not familiar with the UEdGraphNode, please make sure you have read the other posts of this series:
The interface UEdGraphNode has been implemented as:
void UK2Node_IfThenElse::AllocateDefaultPins()
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
UEdGraphPin* ConditionPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Boolean, UEdGraphSchema_K2::PN_Condition);
K2Schema->SetPinAutogeneratedDefaultValue(ConditionPin, TEXT("true"));
UEdGraphPin* TruePin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
TruePin->PinFriendlyName = LOCTEXT("true", "true");
UEdGraphPin* FalsePin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Else);
FalsePin->PinFriendlyName = LOCTEXT("false", "false");
Super::AllocateDefaultPins();
}
FText UK2Node_IfThenElse::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
return LOCTEXT("Branch", "Branch");
}
FLinearColor UK2Node_IfThenElse::GetNodeTitleColor() const
{
return GetDefault<UGraphEditorSettings>()->ExecBranchNodeTitleColor;
}
FSlateIcon UK2Node_IfThenElse::GetIconAndTint(FLinearColor& OutColor) const
{
static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "GraphEditor.Branch_16x");
return Icon;
}
FText UK2Node_IfThenElse::GetTooltipText() const
{
return LOCTEXT("BranchStatement_Tooltip", "Branch Statement\nIf Condition is true, execution goes to True, otherwise it goes to False");
}
Here you can find that it uses a special Schema called UEdGraphSchema_K2, when creating the pins, it also uses a special pin UEdGraphSchema_K2::PN_Execute, a special pin category UEdGraphSchema::PC_Exec.
In fact, they are just special build-in names, it is used for blueprint graph system, nothing special. In your name it whatever you want. In the following contents, we will find how these names work.
const FName UEdGraphSchema_K2::PC_SoftClass(TEXT("softclass"));
const FName UEdGraphSchema_K2::PSC_Self(TEXT("self"));
const FName UEdGraphSchema_K2::PSC_Index(TEXT("index"));
const FName UEdGraphSchema_K2::PSC_Bitmask(TEXT("bitmask"));
const FName UEdGraphSchema_K2::PN_Execute(TEXT("execute"));
const FName UEdGraphSchema_K2::PN_Then(TEXT("then"));
const FName UEdGraphSchema_K2::PN_Completed(TEXT("Completed"));
There are three shortcut methods, they are used to get the pin:
/** Get the then output pin */
BLUEPRINTGRAPH_API UEdGraphPin* GetThenPin() const;
/** Get the return value pin */
BLUEPRINTGRAPH_API UEdGraphPin* GetElsePin() const;
/** Get the condition pin */
BLUEPRINTGRAPH_API UEdGraphPin* GetConditionPin() const;
which is implemented as:
UEdGraphPin* UK2Node_IfThenElse::GetThenPin() const
{
UEdGraphPin* Pin = FindPin(UEdGraphSchema_K2::PN_Then);
check(Pin);
return Pin;
}
UEdGraphPin* UK2Node_IfThenElse::GetElsePin() const
{
UEdGraphPin* Pin = FindPin(UEdGraphSchema_K2::PN_Else);
check(Pin);
return Pin;
}
UEdGraphPin* UK2Node_IfThenElse::GetConditionPin() const
{
UEdGraphPin* Pin = FindPin(UEdGraphSchema_K2::PN_Condition);
check(Pin);
return Pin;
}
What is worth mentioning here is the remaining three methods, they are used to implement the interface UK2Node:
//~ Begin K2Node Interface.
virtual class FNodeHandlingFunctor* CreateNodeHandler(class FKismetCompilerContext& CompilerContext) const override;
virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
virtual FText GetMenuCategory() const override;
//~ End K2Node Interface.
They are been implemented as:
FNodeHandlingFunctor* UK2Node_IfThenElse::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const
{
return new FKCHandler_Branch(CompilerContext);
}
void UK2Node_IfThenElse::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 UK2Node_IfThenElse::GetMenuCategory() const
{
return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::FlowControl);
}
No too hard to understand GetMenuCategory() and GetMenuActions(), but the CreateNodeHandler is a big one, it returns a value of the type FNodeHandlingFunctor.
3. NodeHandlingFunctor
A NodeHandlingFunctor is used to decribes how to compile the node, the node handler of Branch node is as followed:
class FKCHandler_Branch : public FNodeHandlingFunctor
{
public:
FKCHandler_Branch(FKismetCompilerContext& InCompilerContext)
: FNodeHandlingFunctor(InCompilerContext)
{
}
virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
// For imperative nodes, make sure the exec function was actually triggered and not just included due to an output data dependency
FEdGraphPinType ExpectedExecPinType;
ExpectedExecPinType.PinCategory = UEdGraphSchema_K2::PC_Exec;
FEdGraphPinType ExpectedBoolPinType;
ExpectedBoolPinType.PinCategory = UEdGraphSchema_K2::PC_Boolean;
UEdGraphPin* ExecTriggeringPin = Context.FindRequiredPinByName(Node, UEdGraphSchema_K2::PN_Execute, EGPD_Input);
if ((ExecTriggeringPin == NULL) || !Context.ValidatePinType(ExecTriggeringPin, ExpectedExecPinType))
{
CompilerContext.MessageLog.Error(*LOCTEXT("NoValidExecutionPinForBranch_Error", "@@ must have a valid execution pin @@").ToString(), Node, ExecTriggeringPin);
return;
}
else if (ExecTriggeringPin->LinkedTo.Num() == 0)
{
CompilerContext.MessageLog.Warning(*LOCTEXT("NodeNeverExecuted_Warning", "@@ will never be executed").ToString(), Node);
return;
}
// Generate the output impulse from this node
UEdGraphPin* CondPin = Context.FindRequiredPinByName(Node, UEdGraphSchema_K2::PN_Condition, EGPD_Input);
UEdGraphPin* ThenPin = Context.FindRequiredPinByName(Node, UEdGraphSchema_K2::PN_Then, EGPD_Output);
UEdGraphPin* ElsePin = Context.FindRequiredPinByName(Node, UEdGraphSchema_K2::PN_Else, EGPD_Output);
if (Context.ValidatePinType(ThenPin, ExpectedExecPinType) &&
Context.ValidatePinType(ElsePin, ExpectedExecPinType) &&
Context.ValidatePinType(CondPin, ExpectedBoolPinType))
{
UEdGraphPin* PinToTry = FEdGraphUtilities::GetNetFromPin(CondPin);
FBPTerminal** CondTerm = Context.NetMap.Find(PinToTry);
if (CondTerm != NULL)
{
// First skip the if, if the term is false
{
FBlueprintCompiledStatement& SkipIfGoto = Context.AppendStatementForNode(Node);
SkipIfGoto.Type = KCST_GotoIfNot;
SkipIfGoto.LHS = *CondTerm;
Context.GotoFixupRequestMap.Add(&SkipIfGoto, ElsePin);
}
// Now go to the If branch
{
FBlueprintCompiledStatement& GotoThen = Context.AppendStatementForNode(Node);
GotoThen.Type = KCST_UnconditionalGoto;
GotoThen.LHS = *CondTerm;
Context.GotoFixupRequestMap.Add(&GotoThen, ThenPin);
}
}
else
{
CompilerContext.MessageLog.Error(*LOCTEXT("ResolveTermPassed_Error", "Failed to resolve term passed into @@").ToString(), CondPin);
}
}
}
};
By reading this we can understand how the node being compiled.
This post focus on how to achieve a node with complex flow control than average UFUNCTION() node, but sometimes we used K2Node for editor style, we would not to change the flow control but only need to change the apperence of the node.
For that, the following content can be helpful:
Im pretty pleased to find this great site. I wanted to thank you for ones time for this wonderful read!! I definitely appreciated every little bit of it and i also have you book-marked to check out new things on your website.