BLOGブログ

2018.10.13UE4UE/ C++

[UE4]複数のConditionをもつBranchノードを作る

執筆バージョン: Unreal Engine 4.20

こんにちは。アシスタントエンジニアの小倉です。
今回は、複数のConditionをもつBranchノードを作成します。

 

目次

開発経緯

UK2Nodeによる実装

実行結果

ソースコード

開発経緯

複数の条件式の論理積をとってBranchで分岐したいことがあります。しかしUE4では論理演算が短絡評価しないために、上図のような横に伸びる冗長なノードを組むことになります。

この対応として、マクロによってノードを1つにまとめる方法が考えられます。

しかし、マクロを用いると、引数の数に応じたマクロを用意する必要があります。

そこで、上記のマクロとほぼ同じ実装で、動的にConditionピンの数が変わるようなBranchノードを作成します。

UK2Nodeによる実装

UK2Nodeについて記述した弊社記事があります。

[UE4] ノードの入力ピンによって出力ピン等の内容を動的に変える仕組みについて ~実装例編~

[UE4] ノードの入力ピンによって出力ピン等の内容を動的に変える仕組みについて ~解説編~

UK2Nodeの詳細については、これらの記事を参照してください。

ここでは、上記のようなBranchノードの実装について詳しく紹介します。

ノード外観の設定

// ノード説明の設定
FText UK2Node_SEAndBranch::GetTooltipText() const
{
return LOCTEXT(“GetTooltipText”, “Evaluate from the smaller pin number.”);
}

// ノードタイトル色の設定
FLinearColor UK2Node_SEAndBranch::GetNodeTitleColor() const
{
return GetDefault()->ExecBranchNodeTitleColor;
}

// ノードのメニュー上におけるカテゴリの設定
FText UK2Node_SEAndBranch::GetMenuCategory() const
{
return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::FlowControl);
}

// ノードアイコンの設定
FSlateIcon UK2Node_SEAndBranch::GetIconAndTint(FLinearColor& OutColor) const
{
static FSlateIcon Icon(“EditorStyle”, “GraphEditor.Branch_16x”);
return Icon;
}

// メニューアクションの登録(コンテキストメニューからノードをスポーンさせるようにする)
void UK2Node_SEAndBranch::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);
}
}

UK2Nodeの作法その1です。他のUK2Nodeクラスからのコピペがほとんどで、必ずしも記述する必要のないものもあります。

GetMenuActionsに関しては、メニューからノードをスポーンするときに必要なので必ず定義します。

デフォルトピンの作成

// 出力Then実行ピン
UEdGraphPin* TruePin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
TruePin->PinFriendlyName = LOCTEXT(“AllocateDefaultPins_True”, “true”);

// 出力Else実行ピン
UEdGraphPin* FalsePin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Else);
FalsePin->PinFriendlyName = LOCTEXT(“AllocateDefaultPins_False”, “false”);

// 入力Conditionピン
AddUniqueConditionPin();

Super::AllocateDefaultPins();
}

// 未使用番号のConditionピンを作成する
UEdGraphPin * UK2Node_SEAndBranch::AddUniqueConditionPin()
{
UEdGraphPin* Pin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Boolean, GetUniquePinName());
Pin->DefaultValue = TEXT(“true”);
return Pin;
}

// 未使用番号のConditionピンの名前を取得
FName UK2Node_SEAndBranch::GetUniquePinName() const
{
FName NewPinName;
for (int32 i = 0; true; i++)
{
NewPinName = GetPinNameGivenIndex(i);
if (!FindPin(NewPinName))
{
break;
}
}
return NewPinName;
}

// 番号つきConditionピンの名前を取得
FName UK2Node_SEAndBranch::GetPinNameGivenIndex(int32 Index) const
{
return *FString::Printf(TEXT(“%s_%d”), *UEdGraphSchema_K2::PN_Condition.ToString(), Index);
}

ノードを最初にスポーンしたときのピン部分を作成します。図の4つの黄枠部分を作成しています。

今回作るノードは、基本的にはBranchと同じですが、Conditionピンにワイヤーが接続された場合、新しいConditionピンを追加して、任意の数のConditionを引数に取れるようにします。

そこで、複数のConditionピンは、Condition0, Condition1, Condition2, ……といった番号付きの名前で管理します。ここで、GetPinNameGivenIndex()は、前述のような番号つきConditionピン名を作成する関数です。

GetPinNameGivenIndex()によって作成したConditionピン名を0から順にFindPin()で検索すると、使用されていないConditionピン名でnullptrになります。これを利用して、GetUniquePinName()は使用されていないConditionピン名を取得します。AddUniqueConditionPin()は、使用されていないConditionピン名から新しいConditionピンを追加する関数です。

ノードピンの取得

// Elseピンの取得(主にExpandNode内や、SpawnIntermediateNodeでUK2Node_SEAndBranchが使われるときに使用する)
UEdGraphPin * UK2Node_SEAndBranch::GetElsePin() const
{
UEdGraphPin* Pin = FindPin(UEdGraphSchema_K2::PN_Else);
check(Pin);
return Pin;
}

// Conditionピンの取得(主にExpandNode内や、SpawnIntermediateNodeでUK2Node_SEAndBranchが使われるときに使用する)
UEdGraphPin * UK2Node_SEAndBranch::GetConditionPinGivenIndex(const int32 Index)
{
return FindPin(GetPinNameGivenIndex(Index));
}

UK2Nodeの作法その2です。ピンを取得するだけであれば、FindPin()を直接呼び出しても問題ありません。

しかし、外部のUK2NodeのExpandNode上でこのノードがスポーンされたとき、繋ぐことのできるピンを明示するという意味合いもあるため、慣習としてピンの取得関数は記述した方がよいです(そうでなくとも、適切に関数に分けるべきですね)。

ピンの動的追加・削除

// ワイヤーで繋がったピンしかないとき、ピンを追加
if (!IsHaveUnlinkConditionPin())
{
AddUniqueConditionPin();
}

// ノード外観を更新する
GetGraph()->NotifyGraphChanged();
}

Super::PinConnectionListChanged(Pin);
}

// 指定したピンがConditionピンか取得
bool UK2Node_SEAndBranch::IsConditionPin(UEdGraphPin* TargetPin) const
{
return TargetPin && TargetPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Boolean;
}

// ピンの削除
void UK2Node_SEAndBranch::RemovePin(UEdGraphPin * TargetPin)
{
// 指定したピンを削除する
DestroyPin(TargetPin);
Pins.Remove(TargetPin);

// Conditionピンの番号を詰める
int32 ThenIndex = 0;
for (auto Pin : Pins)
{
if (IsConditionPin(Pin))
{
Pin->PinName = GetPinNameGivenIndex(ThenIndex++);
}
}
}

// ワイヤーの繋がっていないConditionピンを含むか
bool UK2Node_SEAndBranch::IsHaveUnlinkConditionPin() const
{
for (const auto& Pin : Pins)
{
if (IsConditionPin(Pin) && Pin->LinkedTo.Num() == 0)
{
return true;
}
}
return false;
}

PinConnectionListChanged()は、ピン同士が接続・切断されたときに実行されます。

この関数はConditionピンだけでなく、実行ピンが接続・切断されたときにも実行されます。そこで、IsConditionPin()でピンを調べることで、Conditionピンの接続・切断のみを取り扱うようにフィルタリングします。

ピンの切断は、LinkedTo配列の要素が空かどうかで判断できます。もし切断された場合は、RemovePinによって切断されたピンを削除します。

ノードの要件として、必ず1つデフォルトピンを持つようにしたいので、IsHaveUnlinkConnectionPin()によって、接続されていないピンがあるか確認します。もしなかった場合は、新しいピンを追加します。

ExpandNod

全てのConditionピンを列挙

// Conditionピンを取得
TArray<UEdGraphPin*> ConditionPins;
for (int32 i = 0; true; i++)
{
// TODO
// 必要であればデフォルトピンの値による自明なフロー制御を行う
// ・Trueデフォルトピンは、ConditionPinsに含めない
// ・Falseデフォルトピンは、結果が自明(必ずElseに実行が流れる)
UEdGraphPin* ConditionPin = FindPin(GetPinNameGivenIndex(i));
if (!ConditionPin)
{
break;
}
ConditionPins.Add(ConditionPin);
}

ExpandNodeでノードの内部の挙動を作成します。主に以下を行います。

  • SpawnIntermediateNode()で既存のUK2Nodeをスポーンし、スポーンしたノードのピン同士をMakeLinkToで接続
  • MovePinLinksToIntermediate()で、ExpandNodeするノードの入力ピンと出力ピンのリンクを、スポーンしたピンに移動
  • BreakAllNodeLinks()で、ExpandNodeするノードにつながるリンクを切断

上記のコードでは、ノードが持っている全てのConditionピンを配列にキャッシュしています。

この時点でのExpandNodeのイメージ図は次のようになります。

Rerouteノードをスポーン

次にRerouteノードをSpawnIntermediateNode()を使ってスポーンします。

スポーンしたノードは、ピンが初期化されていないため、必ずAllocateDefaultPin()を呼び出して初期化します。

この時点でのExpandNodeのイメージ図は次のようになります。Rerouteノードが配置されました。

Branchノードをスポーン

次に、Branchノードをスポーンします。BranchノードはConditionの数だけ必要なので、for文でConditionピンの数だけスポーン処理を行います。

Branchノードをスポーンしたら、BranchのFalse(Else)出力ピンと、Rerouteノードの入力実行ピンをMakeLinkTo()で接続します。

また、ConditionピンとスポーンしたBranchノードの実行入力ピンをMovePinLinksToIntermediate()で移動します。

この時点でのExpandNodeのイメージ図は次のようになります。MakeLinkTo()とMovePinLinksToIntermediate()は、イメージ図ではどちらもピンの接続をしている処理で、使い分けが難しいように感じます。ですが基本的には、「スポーンしたノードのピン同士を接続するときはMakeLinkTo()を使う」「Expandするノードのピンとスポーンしたノードのピンを接続するときはMovePinLinksToIntermediate()を使う」ぐらいに考えてしまっても問題はありません。

Branchノード同士を接続

スポーンしたBranchノードのTrue出力ピンと、次にスポーンしたBranchノードの実行入力ピンをMakeLinkTo()で接続します。

この時点でのExpandNodeのイメージ図は次のようになります。

ピンリンクの移動

BreakAllNodeLinks();

まだ接続していない残ったピンをMovePinLinksToIntermediate()で接続します。

まず、ExpandNodeするノードの実行入力ピンと、最初のBranchノード実行入力ピンに接続します。

次に、最後にスポーンしたBranchノードのTrue実行出力ピンと、このノードのTrue実行出力ピンを接続します。

次に、Rerouteノードの実行出力ピンと、このノードのFalse実行出力ピンを接続します。

最後に、BreakAllLinks()で、このノードの全てのリンクを切断します。

最終的なExpandNodeのイメージ図は次のようになります。

実行結果

作成したノードを使用します。レベルブループリント上で次のようにしました。

Func1, Func2, Func3ノードは、それぞれPrintStringでノードが実行されたことを表示して、Bool値を返します。

これを、Funcの返り値をそれぞれ変えたときの結果は、次のようになりました。

Func1 False True False True False True False True
Func2 False False True True False False True True
Func3 False False False False True True True True
表示 call func1

False

 

 

call func1

call func2

False

 

call func1

False

 

 

call func1

call func2

call func3

False

call func1

False

 

 

call func1

call func2

False

 

call func1

False

 

 

call func1

call func2

call func3

True

正しく分岐し、また短絡評価もしていることが確認できました。

ソースコード

using UnrealBuildTool;

public class BlogTest420BEditor : ModuleRules
{
public BlogTest420BEditor(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

PublicDependencyModuleNames.AddRange(new string[] {
“Core”,
“CoreUObject”,
“Engine”,
“InputCore”,
“Slate”,
“EditorStyle”,
});

PrivateDependencyModuleNames.AddRange(new string[] {
“EditorStyle”,
“KismetCompiler”,
“UnrealEd”,
“GraphEditor”,
“SlateCore”,
“Kismet”,
“KismetWidgets”,
“PropertyEditor”,
“BlueprintGraph”,
});
}
}

#pragma once

#include “K2Node.h”
#include “EdGraph/EdGraphNodeUtils.h”
#include “K2Node_SEAndBranch.generated.h”

UCLASS()
class BLOGTEST420BEDITOR_API UK2Node_SEAndBranch : public UK2Node
{
GENERATED_BODY()

public:
// Begin UEdGraphNode interface.
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
virtual FText GetTooltipText() const override;
virtual FLinearColor GetNodeTitleColor() const override;
virtual FSlateIcon GetIconAndTint(FLinearColor& OutColor) const override;
virtual void PinConnectionListChanged(UEdGraphPin* Pin) override;
// End UEdGraphNode interface.

// Begin UK2Node interface
virtual bool IsNodeSafeToIgnore() const override { return true; }
virtual bool CanEverInsertExecutionPin() const override { return true; }
virtual bool CanEverRemoveExecutionPin() const override { return true; }
virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
virtual FText GetMenuCategory() const override;
virtual void ReallocatePinsDuringReconstruction(TArray<UEdGraphPin*>& OldPins) override;
virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
// End UK2Node interface

public:
FName GetPinNameGivenIndex(int32 Index) const; // 名前つきConditionピン名の取得
FName GetUniquePinName() const; // 未使用番号のConditionピン名の取得
UEdGraphPin* AddUniqueConditionPin(); // 未使用番号のConditionピンの追加
void RemovePin(UEdGraphPin* TargetPin); // 指定ピンの削除
bool IsHaveUnlinkConditionPin() const; // ノードがリンクのないConditionピンを持っているか取得
bool IsConditionPin(UEdGraphPin* TargetPin) const; // 指定ピンがConditionピンか取得

UEdGraphPin* GetThenPin() const;
UEdGraphPin* GetElsePin() const;
UEdGraphPin* GetConditionPinGivenIndex(const int32 Index);
};

#define LOCTEXT_NAMESPACE “K2Node_SEAndBranch”

// ノードのデフォルトのピン定義
void UK2Node_SEAndBranch::AllocateDefaultPins()
{
// 入力実行ピン
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);

// 出力Then実行ピン
UEdGraphPin* TruePin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
TruePin->PinFriendlyName = LOCTEXT(“AllocateDefaultPins_True”, “true”);

// 出力Else実行ピン
UEdGraphPin* FalsePin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Else);
FalsePin->PinFriendlyName = LOCTEXT(“AllocateDefaultPins_False”, “false”);

// 入力Conditionピン
AddUniqueConditionPin();

Super::AllocateDefaultPins();
}

// ノードタイトルの設定
FText UK2Node_SEAndBranch::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
return LOCTEXT(“GetNodeTitleDefault”, “SE And Branch”);
}

// ノード説明の設定
FText UK2Node_SEAndBranch::GetTooltipText() const
{
return LOCTEXT(“GetTooltipText”, “Evaluate from the smaller pin number.”);
}

// ノードタイトル色の設定
FLinearColor UK2Node_SEAndBranch::GetNodeTitleColor() const
{
return GetDefault()->ExecBranchNodeTitleColor;
}

// ノードのメニュー上におけるカテゴリの設定
FText UK2Node_SEAndBranch::GetMenuCategory() const
{
return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::FlowControl);
}

// ノードアイコンの設定
FSlateIcon UK2Node_SEAndBranch::GetIconAndTint(FLinearColor& OutColor) const
{
static FSlateIcon Icon(“EditorStyle”, “GraphEditor.Branch_16x”);
return Icon;
}

// 番号つきConditionピンの名前を取得
FName UK2Node_SEAndBranch::GetPinNameGivenIndex(int32 Index) const
{
return *FString::Printf(TEXT(“%s_%d”), *UEdGraphSchema_K2::PN_Condition.ToString(), Index);
}

// 指定したピンがConditionピンか取得
bool UK2Node_SEAndBranch::IsConditionPin(UEdGraphPin* TargetPin) const
{
return TargetPin && TargetPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Boolean;
}

// Thenピンの取得(主にExpandNode内や、SpawnIntermediateNodeでUK2Node_SEAndBranchが使われるときに使用する)
UEdGraphPin * UK2Node_SEAndBranch::GetThenPin() const
{
UEdGraphPin* Pin = FindPin(UEdGraphSchema_K2::PN_Then);
check(Pin);
return Pin;
}

// Elseピンの取得(主にExpandNode内や、SpawnIntermediateNodeでUK2Node_SEAndBranchが使われるときに使用する)
UEdGraphPin * UK2Node_SEAndBranch::GetElsePin() const
{
UEdGraphPin* Pin = FindPin(UEdGraphSchema_K2::PN_Else);
check(Pin);
return Pin;
}

// Conditionピンの取得(主にExpandNode内や、SpawnIntermediateNodeでUK2Node_SEAndBranchが使われるときに使用する)
UEdGraphPin * UK2Node_SEAndBranch::GetConditionPinGivenIndex(const int32 Index)
{
return FindPin(GetPinNameGivenIndex(Index));
}

// ワイヤーの繋がっていないConditionピンを含むか
bool UK2Node_SEAndBranch::IsHaveUnlinkConditionPin() const
{
for (const auto& Pin : Pins)
{
if (IsConditionPin(Pin) && Pin->LinkedTo.Num() == 0)
{
return true;
}
}
return false;
}

// 未使用番号のConditionピンの名前を取得
FName UK2Node_SEAndBranch::GetUniquePinName() const
{
FName NewPinName;
for (int32 i = 0; true; i++)
{
NewPinName = GetPinNameGivenIndex(i);
if (!FindPin(NewPinName))
{
break;
}
}
return NewPinName;
}

// 未使用番号のConditionピンを作成する
UEdGraphPin * UK2Node_SEAndBranch::AddUniqueConditionPin()
{
UEdGraphPin* Pin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Boolean, GetUniquePinName());
Pin->DefaultValue = TEXT(“true”);
return Pin;
}

// ピンの削除
void UK2Node_SEAndBranch::RemovePin(UEdGraphPin * TargetPin)
{
// 指定したピンを削除する
DestroyPin(TargetPin);
Pins.Remove(TargetPin);

// Conditionピンの番号を詰める
int32 ThenIndex = 0;
for (auto Pin : Pins)
{
if (IsConditionPin(Pin))
{
Pin->PinName = GetPinNameGivenIndex(ThenIndex++);
}
}
}

// ピンの追加と削除(ピンとワイヤーが接続されたり切断されたときに呼ばれる)
void UK2Node_SEAndBranch::PinConnectionListChanged(UEdGraphPin * Pin)
{
if (IsConditionPin(Pin))
{
// Conditionピンからワイヤーが切断されたとき、切断されたワイヤーを削除
if (Pin->LinkedTo.Num() == 0)
{
RemovePin(Pin);
}

// ワイヤーで繋がったピンしかないとき、ピンを追加
if (!IsHaveUnlinkConditionPin())
{
AddUniqueConditionPin();
}

// ノード外観を更新する
GetGraph()->NotifyGraphChanged();
}

Super::PinConnectionListChanged(Pin);
}

// ピンの再構築(主にこのノードがあるBlueprintGraphがロードされたときに呼ばれる関数)
void UK2Node_SEAndBranch::ReallocatePinsDuringReconstruction(TArray<UEdGraphPin*>& OldPins)
{
Super::ReallocatePinsDuringReconstruction(OldPins); // 内部でAllocateDefaultPins()が呼ばれる

const int32 AlreadyPinNum = Pins.Num();
for (int i = 0; i < OldPins.Num() – AlreadyPinNum; i++)
{
AddUniqueConditionPin();
}
}

// メニューアクションの登録(コンテキストメニューからノードをスポーンさせるようにする)
void UK2Node_SEAndBranch::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);
}
}

void UK2Node_SEAndBranch::ExpandNode(FKismetCompilerContext & CompilerContext, UEdGraph * SourceGraph)
{
Super::ExpandNode(CompilerContext, SourceGraph);

// Conditionピンを取得
TArray<UEdGraphPin*> ConditionPins;
for (int32 i = 0; true; i++)
{
// TODO
// 必要であればデフォルトピンの値による自明なフロー制御を行う
// ・Trueデフォルトピンは、ConditionPinsに含めない
// ・Falseデフォルトピンは、結果が自明(必ずElseに実行が流れる)
UEdGraphPin* ConditionPin = FindPin(GetPinNameGivenIndex(i));
if (!ConditionPin)
{
break;
}
ConditionPins.Add(ConditionPin);
}

// Rerouteノードをスポーンする
UK2Node_Knot* RerouteNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph);
RerouteNode->AllocateDefaultPins();
UEdGraphPin* RerouteExecPin = RerouteNode->GetInputPin();

// Branchノードをスポーンする
TArray<UK2Node_IfThenElse*> BranchNodes;
for (auto ConditionPin : ConditionPins)
{
UK2Node_IfThenElse* BranchNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph);
BranchNode->AllocateDefaultPins();
BranchNode->GetElsePin()->MakeLinkTo(RerouteExecPin);
CompilerContext.MovePinLinksToIntermediate(*ConditionPin, *BranchNode->GetConditionPin());
BranchNodes.Add(BranchNode);
}

// Branchノード同士を接続する
for (int32 i = 0; i < BranchNodes.Num() – 1; i++) { UEdGraphPin* PrevBranchThenPin = BranchNodes[i]->GetThenPin();
UEdGraphPin* PostBranchExecPin = BranchNodes[i + 1]->GetExecPin();
PrevBranchThenPin->MakeLinkTo(PostBranchExecPin);
}

// ピンリンクの移動
CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *BranchNodes[0]->GetExecPin());
CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *BranchNodes.Last()->GetThenPin());
CompilerContext.MovePinLinksToIntermediate(*GetElsePin(), *RerouteNode->GetOutputPin());

BreakAllNodeLinks();
}

#undef LOCTEXT_NAMESPACE

 

この記事は次のバージョンで作成されました。
Unreal Editor Version: 4.20.3-4369336+++UE4+Release-4.20