アンリアる! C++入門編 ~対話形式で学ぶUnreal Engine~
BOOTHでUnreal Engine C++入門書を販売していますUnreal Engine上でC++を使って開発するために必要となる基礎知識を一冊の本にまとめた本です。 対話形式によるわかりやすい説明を目指しました。無料の試し読みも可能ですので、ぜひ読んでみてください!
[UE5] Create Your Own Blueprint Node by Inheriting K2Node (2) Execution Pin

This article explains how to create a blueprint node with an execution pin.
The explanation assumes that you are familiar with the basic contents of creating the Blueprint node.
Please check the Basic article if necessary.

Source Code

The complete source code is available on GitHub.

https://github.com/colory-games/techblog-sample-projects/tree/main/nutti/20220626

To run the source code, follow these steps.

  1. Place the directory MyCompareInt into Plugins in your Unreal Engine project directory.
  2. Right click on the .uproject file and create a project file for Visual Studio.
  3. Open the project file and build the project.
  4. Run Unreal Engine from the .uproject file and activate the "MyCompareInt" plugin.

Specification of the Blueprint Node

The specification of the Blueprint node "MyCompareInt" is as follows.

  • Receives two integers "A" and "B" as inputs.
  • Changes the output execution pin to be executed depending on the inputs "A" and "B".
    • Execution pin "Greater": Execute when A>B
    • Execution pin "Equal": Execute when A=B
    • Execution pin "Less": Execute when A<B

This specification is similar to the macro "Compare Int" provided in the Blueprint.
Please check the implementation of the macro "Compare Int" if necessary.

Create a Module

Create a module by following to the description in the Basic section.
For the concreate instructions to create a module, please refer to the article for the basic.

Create a Blueprint Node

We can create a Blueprint node by the following steps.

  1. Inherit class UK2Node.
  2. Setup basic information.
  3. Add pins.
  4. Add internal pins.
  5. Define the process of execution pins.

Steps 1 and 2 are the almost same as in the article for the basic, so we will start from step 3.

3. Add Pins

Add the following pins according to the specification.

Pin Name Input/Output Role
Input Execute the node.
A Input Input of integer.
B Input Input of integer.
Greater Output Execute when A>B
Equal Output Execute when A=B
Less Output Execute when A<B

We need to add those pins.
AllocateDefaultPins is the member funciton which defines the process of adding pins to the node.

CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Int, APinName);
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Int, BPinName);
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GreaterPinName);
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, EqualPinName);
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, LessPinName);

To add an execution pin, specify UEdGraphSchema_K2::PC_Exec for the second argument.
If you want to add execution pin on the input side that triggers node execution, the third argument needs to be UEdGraphSchema_K2::PN_Execute.

Add three execution pins on the output side as well.
We add execution pins whose names are Greater, Equal, and Less.

4. Add Internal Pins

The node "MyCompareInt" needs to determine the pin on the output side to be executed according to the value of the input integers "A" and "B".

  • When "A>B", execute the pin "Greater".
  • When "A=B", execute the pin "Equal".
  • When "A<B", execute the pin "Less".

We can implement this in Blueprint as follows.

Implementation in Blueprint 1

However, we will implement as follows.

  1. Pass the inputs ‘A’ and ‘B’ to the library function UKismetMathLibrary::GreaterEqual_IntInt. If the result is False, execute the node connected to the execution pin ‘Less’.
  2. If the result of 1 is True, pass the inputs ‘A’ and ‘B’ to the library function UKismetMathLibrary::LessEqual_IntInt. If the result is False, execute the node connected to the execution pin ‘Greater’.
  3. If the result of 2 is True, execute the node connected to the execution pin Equal.

To realize in Blueprint, you can implement this as follows.

Implementation in Blueprint 2

To achieve, you need to understand the following two things.

  • Add an internal pin, which is required to call the library function.
  • Determine the execution pin to execute by using the output result of the added library function.

First, we will desribe the process of adding the internal pins required for a library function call.
Use the member function CreatePin is used to add the internal pins.

UEdGraphPin* FunctionPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UKismetMathLibrary::StaticClass(), TEXT("GreaterEqual_IntInt"));
FunctionPin->bDefaultValueIsReadOnly = true;
FunctionPin->bNotConnectable = true;
FunctionPin->bHidden = true;

We specify the second argument of the UEdGraphSchema_K2::PC_Object.
We specify the blueprint library function to the third argument, and specify the name of the library function to the fourth argument.

Use the return value of the library function CreatePin and apply various configuration to make the internal pin read-only.
The pin to execute the library function has been created, but there is no library function to execute.

The process for setting the library function is shown below.

UFunction* Function = FindUField(UKismetMathLibrary::StaticClass(), TEXT("GreaterEqual_IntInt"));
if (Function != nullptr && Function->HasAllFunctionFlags(FUNC_Static))
{
    UBlueprint* Blueprint = GetBlueprint();
    if (Blueprint != nullptr)
    {
        UClass* FunctionOwnerClass = Function->GetOuterUClass();
        if (!Blueprint->SkeletonGeneratedClass->IsChildOf(FunctionOwnerClass))
        {
            FunctionPin->DefaultObject = FunctionOwnerClass->GetDefaultObject();
        }
    }
}

Call the function FindUField to get the UFunction of the library function UKismetMathLibrary::GreaterEqual_IntInt.
Then, set UFunction to the member variable DefaultObject of the created inner pin FunctionPin.
This allows the inner pin to execute the library function UKismetMathLibrary::GreaterEqual_IntInt.

In the same way, you can create an inner pin to execute the library function UKismetMathLibrary::LessEqual_IntInt.

5. Define the Process of Execution Pins

We will implement a process that uses the output result of a library function to determine the execution pin to be executed.
We defined a computation graph by using the member function ExpandNode.
However, the member function ExpandNode cannot realize the process of selecting a pin to execute based on a specific condition.
To realize this, you need to define a class FKCHandler_MyCompareInt inherited from class FNodeHandlingFunctor.

class FKCHandler_MyCompareInt : public FNodeHandlingFunctor
{
    TMap BoolTermMap;
public:
    FKCHandler_MyCompareInt(FKismetCompilerContext& InCompilerContext) : FNodeHandlingFunctor(InCompilerContext)
    {
    }
    virtual void RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node) override
    {
        // snip
    }
    virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
    {
        // snip
    }
};

Create Variables

The member function RegisterNets defines the process of creating temporary variables to store the results of the library functions you have executed.
The temporary variables will be used later.

virtual void RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
    FNodeHandlingFunctor::RegisterNets(Context, Node);
    {
        // Store the result of library function "UKismetMathLibrary::GreaterEqual_IntInt"
        // to the temporary boolean variable.
        FBPTerminal* BoolTerm = Context.CreateLocalTerminal();
        BoolTerm->Type.PinCategory = UEdGraphSchema_K2::PC_Boolean;
        BoolTerm->Source = Node;
        BoolTerm->Name = Context.NetNameMap->MakeValidName(Node, TEXT("IsGreaterEqual"));
        BoolTermMap.Add(TEXT("IsGreaterEqual"), BoolTerm);
    }
}

The member function RegisterNets must first call RegisterNets of the parent class.
After calling RegisterNets of the parent class, create a boolean variable by calling the member function CreateLocalTerminal with the argument Context.
We repeat this process twice, to create the variables IsGreaterEqual and IsLessEqual for storing the results of UKismetMathLibrary::LessEqual_IntInt.

The created variables are registered in the member variable BoolTermMap to be used while the Blueprint compilation.

Blueprint Compilation

The member function Compile defines the process to be called while the Blueprint compilation.
This function will add a Blueprint statement FKismetCompiledStatement.

The member function Compile defined here can be called in the Compile Functions phase. The concreate process of Blueprint compilation is described in the official documentation Blueprint Compiler Overview.

The Blueprint statement type can be found in the EKismetCompiledStatementType.
We will use the following statement types.

Statement Type Meaning
KCST_CallFunction Calls a function (UFunction)
KCST_GotoIfNot Transfer the control of execution to a specific execution pin if the specified condition is False
KCST_UnconditionalGoto Unconditionally transfer the control of execution to a specific execution pin

A statement can be created by calling the member function AppendStatementForNode with the argument Context.
The return value is the structure FBlueprintCompiledStatement.
The member variable Type is a statement type, and LHS and RHS are variables to be passed to the statement.
The meaning of the variables depends on the statement type, see EKismetCompiledStatementType for detail.

Get Required Variables for the Compilation

The member function Compile first retrieves variables needed at compilation time.

virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
    UK2Node_MyCompareInt* MyCompareIntNode = CastChecked(Node);
    // Get execution pin on the output side.
    UEdGraphPin* GreaterPin = MyCompareIntNode->FindPin(GreaterPinName);
    UEdGraphPin* EqualPin = MyCompareIntNode->FindPin(EqualPinName);
    UEdGraphPin* LessPin = MyCompareIntNode->FindPin(LessPinName);
    // Get temporary variables.
    FBPTerminal* IsGreaterEqualTerm = BoolTermMap.FindRef(TEXT("IsGreaterEqual"));
    FBPTerminal* IsLessEqualTerm = BoolTermMap.FindRef(TEXT("IsLessEqual"));
    // Get variables of input pin "A".
    UEdGraphPin* APin = MyCompareIntNode->FindPin(APinName);
    UEdGraphPin* ANet = FEdGraphUtilities::GetNetFromPin(APin);
    FBPTerminal* ATerm = Context.NetMap.FindRef(ANet);
    // Get variables of input pin "B".
    UEdGraphPin* BPin = MyCompareIntNode->FindPin(BPinName);
    UEdGraphPin* BNet = FEdGraphUtilities::GetNetFromPin(BPin);
    FBPTerminal* BTerm = Context.NetMap.FindRef(BNet);
}

FBPTerminal is the equivalent of a variable in the statement.
Note that class UEdGraphPin does not represent the execution pin.
We will get the following variables.

Variable Name Meaning
IsGreaterEqualTerm Store the result of the library function UKismetMathLibrary::GreaterEqual_IntInt
IsLessEqualTerm Store the result of library function UKismetMathLibrary::LessEqual_IntInt
ATerm Input pin "A"
BTerm input pin "B"

Create Blueprint Statement

We will create Blueprint statements.
As we mentioned earlier, there is no statement type that transfers execution control to a specific execution pin if the specified condition is True.
In step 4, we changed the method of implementation to take into account this constraint.

  1. Pass the inputs "A" and "B" to the library function UKismetMathLibrary::GreaterEqual_IntInt. If the result is False, execute the node connected to the execution pin "Less".
  2. If the result of 1 is True, pass the inputs "A" and "B" to the library function UKismetMathLibrary::LessEqual_IntInt. If the result is False, execute the node connected to the execution pin "Greater".
  3. If the result of 2 is True, execute the node connected to the execution pin "Equal".

We will show the pseudo code for a C++ implementation.

IsGreaterEqualTerm = UKismetMathLibrary::GreaterEqual_IntInt(ATerm, BTerm);
if (!IsGreaterEqualTerm) {
    goto LessPin;
}
IsLessEqualTerm = UKismetMathLibrary::LessEqual_IntInt(ATerm, BTerm);
if (!IsLessEqualTerm) {
    goto GreaterPin;
}
goto EqualPin;

This can be rewritten into a blueprint statement as follows.

KCST_CallFunction IsGreaterEqualTerm [ATerm, BTerm]
KCST_GotoIfNot IsGreaterEqualTerm -> LessPin
KCST_CallFunction IsLessEqualTerm [ATerm, BTerm]
KCST_GotoIfNot IsLessEqualTerm -> GreaterPin
KCST_UnconditionalGoto -> EqualPin

We will create these statements in the member function Compile.

virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
    // snip
    {
        // Get UFunction of the library function "UKismetMathLibrary::GreaterEqual_IntInt".
        UEdGraphPin* FunctionPin = MyCompareIntNode->FindPin(TEXT("GreaterEqual_IntInt"));
        FBPTerminal* FunctionContext = Context.NetMap.FindRef(FunctionPin);
        UClass* FunctionClass = Cast(FunctionPin->PinType.PinSubCategoryObject.Get());
        UFunction* FunctionPtr = FindUField(FunctionClass, FunctionPin->PinName);
        // Create a statement that calls a library function.
        FBlueprintCompiledStatement& CallFuncStatement = Context.AppendStatementForNode(MyCompareIntNode);
        CallFuncStatement.Type = KCST_CallFunction;
        CallFuncStatement.FunctionToCall = FunctionPtr;
        CallFuncStatement.FunctionContext = FunctionContext;
        CallFuncStatement.bIsParentContext = false;
        CallFuncStatement.LHS = IsGreaterEqualTerm;
        CallFuncStatement.RHS.Add(ATerm);
        CallFuncStatement.RHS.Add(BTerm);
        // Create a statement that transfers execution control to the execution pin "LessPin" when the result of a library function call is false.
        FBlueprintCompiledStatement& GotoStatement = Context.AppendStatementForNode(MyCompareIntNode);
        GotoStatement.Type = KCST_GotoIfNot;
        GotoStatement.LHS = IsGreaterEqualTerm;
        Context.GotoFixupRequestMap.Add(&GotoStatement, LessPin);
    }
    {
        // snip
        // Create a statement that transfers execution control to the execution pin "GreaterPin" when the result of a library function call is false.
        FBlueprintCompiledStatement& GotoStatement = Context.AppendStatementForNode(MyCompareIntNode);
        GotoStatement.Type = KCST_GotoIfNot;
        GotoStatement.LHS = IsLessEqualTerm;
        Context.GotoFixupRequestMap.Add(&GotoStatement, GreaterPin);
    }
    // If none of the conditions are met, create a statement that unconditionally transfers execution control to the execution pin "EqualPin".
    GenerateSimpleThenGoto(Context, *MyCompareIntNode, EqualPin);
}

First, get the UFunction of the library function UKismetMathLibrary::GreaterEqual_IntInt to be used for creating Blueprint statements.
It can be obtained from the internal pin created in step 4.

Next, call the member function AppendStatementForNode of Context to create a statement KCST_CallFunction that calls the library function.
Set the member variable Type of the structure FBlueprintCompiledStatement to KCST_CallFunction.
This indicates that this statement calls a library function.
The member variable FunctionToCall is set to the UFunction of the library function to be called in this statement.
Set the member variable LHS to the variable IsGreaterEqualTerm that stores the return value of the library function.
Set the member variable RHS to the values to be passed as the argument of the library function in the correct order.
In this case, ATerm and BTerm are arguments.

Then, we will create the statement KCST_GotoIfNot, which transfers execution control to a specific execution pin if the specified condition is False.
Specify IsGreaterEqualTerm to the member variable LHS so that execution control is transferred when IsGreaterEqualTerm is False ("A<B").
Since the destination of the execution control is the execution pin LessPin, call Context.GotoFixupRequestMap.Add to register the destination of the execution control.

In the same way, create a statement that transfers control of execution to the execution pin GreaterPin.

Finally, the function GenerateSimpleThenGoto is called to create a statement that transfers control of execution to the execution pin EqualPin if none of the conditions are met ("A=B").
The function GenerateSimpleThenGoto is a convenience function that internally creates the statement KCST_UnconditionalGoto and calls Context.GotoFixupRequestMap.Add to register the pin to which execution control is transferred.

Register the Function Executed on the Execution Pin

The class FKCHandler_MyCompareInt, which defines the process of the execution pin, must be associated with the class UK2Node_MyCompareInt of the node.
This can be done by overriding the member function CreateNodeHandler which returns the class FKCHandler_MyCompareInt.

FNodeHandlingFunctor* UK2Node_MyCompareInt::CreateNodeHandler(class FKismetCompilerContext& CompilerContext) const
{
    return new FKCHandler_MyCompareInt(CompilerContext);
}

Use the Created Node

Let’s use the node you created.
Open the Blueprint Editor, right-click to display the menu, and enter "mycompareint" in the search box.

Search for node "MyCompareInt"

Select and place the node "MyComponentInt" in the category "Flow Control".

Create Actor

When the created Actor is placed in the level, we can see that the execution result will be changed depending on the input values of the node.

Execution of node "MyCompareInt"

Here is the execution pin "Greater".
You can try to change the values of input pins "A" and "B".

References