関連ブログ
- [UE4][UE5]開発環境の容量を少しでも減らす 2024.08.14UE
- [UE5] PushModel型のReplicationを使い、ネットワーク最適化を図る 2024.05.29UE
- [UE5]マテリアルでメッシュをスケールする方法 2024.01.17UE
CATEGORY
2018.10.13UE4UE/ C++
執筆バージョン: Unreal Engine 4.20 |
こんにちは。アシスタントエンジニアの小倉です。
今回は、複数のConditionをもつBranchノードを作成します。
複数の条件式の論理積をとってBranchで分岐したいことがあります。しかしUE4では論理演算が短絡評価しないために、上図のような横に伸びる冗長なノードを組むことになります。
この対応として、マクロによってノードを1つにまとめる方法が考えられます。
しかし、マクロを用いると、引数の数に応じたマクロを用意する必要があります。
そこで、上記のマクロとほぼ同じ実装で、動的にConditionピンの数が変わるようなBranchノードを作成します。
UK2Nodeについて記述した弊社記事があります。
[UE4] ノードの入力ピンによって出力ピン等の内容を動的に変える仕組みについて ~実装例編~
[UE4] ノードの入力ピンによって出力ピン等の内容を動的に変える仕組みについて ~解説編~
UK2Nodeの詳細については、これらの記事を参照してください。
ここでは、上記のようなBranchノードの実装について詳しく紹介します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
// ノードタイトルの設定 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& amp; OutColor) const { static FSlateIcon Icon("EditorStyle", "GraphEditor.Branch_16x"); return Icon; } // メニューアクションの登録(コンテキストメニューからノードをスポーンさせるようにする) void UK2Node_SEAndBranch::GetMenuActions(FBlueprintActionDatabaseRegistrar& amp; 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に関しては、メニューからノードをスポーンするときに必要なので必ず定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
// ノードのデフォルトのピン定義 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(); } // 未使用番号の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ピンを追加する関数です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 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)); } |
UK2Nodeの作法その2です。ピンを取得するだけであれば、FindPin()を直接呼び出しても問題ありません。
しかし、外部のUK2NodeのExpandNode上でこのノードがスポーンされたとき、繋ぐことのできるピンを明示するという意味合いもあるため、慣習としてピンの取得関数は記述した方がよいです(そうでなくとも、適切に関数に分けるべきですね)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
// ピンの追加と削除(ピンとワイヤーが接続されたり切断されたときに呼ばれる) 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); } // 指定したピンがConditionピンか取得 bool UK2Node_SEAndBranch::IsConditionPin(UEdGraphPin* TargetPin) const { return TargetPin & amp; & 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& amp; Pin: Pins) { if (IsConditionPin(Pin) & amp; & Pin - > LinkedTo.Num() == 0) { return true; } } return false; } |
PinConnectionListChanged()は、ピン同士が接続・切断されたときに実行されます。
この関数はConditionピンだけでなく、実行ピンが接続・切断されたときにも実行されます。そこで、IsConditionPin()でピンを調べることで、Conditionピンの接続・切断のみを取り扱うようにフィルタリングします。
ピンの切断は、LinkedTo配列の要素が空かどうかで判断できます。もし切断された場合は、RemovePinによって切断されたピンを削除します。
ノードの要件として、必ず1つデフォルトピンを持つようにしたいので、IsHaveUnlinkConnectionPin()によって、接続されていないピンがあるか確認します。もしなかった場合は、新しいピンを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void UK2Node_SEAndBranch::ExpandNode(FKismetCompilerContext & amp; CompilerContext, UEdGraph * SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); // Conditionピンを取得 TArray& lt; UEdGraphPin*& gt; ConditionPins; for (int32 i = 0; true; i++) { // TODO // 必要であればデフォルトピンの値による自明なフロー制御を行う // ・Trueデフォルトピンは、ConditionPinsに含めない // ・Falseデフォルトピンは、結果が自明(必ずElseに実行が流れる) UEdGraphPin* ConditionPin = FindPin(GetPinNameGivenIndex(i)); if (!ConditionPin) { break; } ConditionPins.Add(ConditionPin); } |
ExpandNodeでノードの内部の挙動を作成します。主に以下を行います。
上記のコードでは、ノードが持っている全てのConditionピンを配列にキャッシュしています。
この時点でのExpandNodeのイメージ図は次のようになります。
1 2 3 4 |
// Rerouteノードをスポーンする UK2Node_Knot* RerouteNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); RerouteNode->AllocateDefaultPins(); UEdGraphPin* RerouteExecPin = RerouteNode->GetInputPin(); |
次にRerouteノードをSpawnIntermediateNode()を使ってスポーンします。
スポーンしたノードは、ピンが初期化されていないため、必ずAllocateDefaultPin()を呼び出して初期化します。
この時点でのExpandNodeのイメージ図は次のようになります。Rerouteノードが配置されました。
1 2 3 4 5 6 7 8 9 10 |
// Branchノードをスポーンする TArray & lt; UK2Node_IfThenElse*& gt; 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ノードをスポーンします。BranchノードはConditionの数だけ必要なので、for文でConditionピンの数だけスポーン処理を行います。
Branchノードをスポーンしたら、BranchのFalse(Else)出力ピンと、Rerouteノードの入力実行ピンをMakeLinkTo()で接続します。
また、ConditionピンとスポーンしたBranchノードの実行入力ピンをMovePinLinksToIntermediate()で移動します。
この時点でのExpandNodeのイメージ図は次のようになります。MakeLinkTo()とMovePinLinksToIntermediate()は、イメージ図ではどちらもピンの接続をしている処理で、使い分けが難しいように感じます。ですが基本的には、「スポーンしたノードのピン同士を接続するときはMakeLinkTo()を使う」「Expandするノードのピンとスポーンしたノードのピンを接続するときはMovePinLinksToIntermediate()を使う」ぐらいに考えてしまっても問題はありません。
1 2 3 4 5 6 |
// Branchノード同士を接続する for (int32 i = 0; i & lt; BranchNodes.Num() - 1; i++) { UEdGraphPin* PrevBranchThenPin = BranchNodes[i] - > GetThenPin(); UEdGraphPin* PostBranchExecPin = BranchNodes[i + 1] - > GetExecPin(); PrevBranchThenPin - > MakeLinkTo(PostBranchExecPin); } |
スポーンしたBranchノードのTrue出力ピンと、次にスポーンしたBranchノードの実行入力ピンをMakeLinkTo()で接続します。
この時点でのExpandNodeのイメージ図は次のようになります。
1 2 3 4 |
// ピンリンクの移動 CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *BranchNodes[0]->GetExecPin()); CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *BranchNodes.Last()->GetThenPin()); CompilerContext.MovePinLinksToIntermediate(*GetElsePin(), *RerouteNode->GetOutputPin()); |
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 |
正しく分岐し、また短絡評価もしていることが確認できました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// Fill out your copyright notice in the Description page of Project Settings. 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", }); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// Fill out your copyright notice in the Description page of Project Settings. #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& amp; 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& amp; ActionRegistrar) const override; virtual FText GetMenuCategory() const override; virtual void ReallocatePinsDuringReconstruction(TArray& lt; UEdGraphPin*& gt; & OldPins) override; virtual void ExpandNode(class FKismetCompilerContext& amp; 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); }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
#include "K2Node_SEAndBranch.h" #include "EdGraphSchema_K2.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintNodeSpawner.h" #include "GraphEditorSettings.h" #include "EditorCategoryUtils.h" #include "KismetCompiler.h" #include "K2Node_IfThenElse.h" #include "K2Node_Knot.h" #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& amp; 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 & amp; & 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& amp; Pin: Pins) { if (IsConditionPin(Pin) & amp; & 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& lt; UEdGraphPin*& gt; & OldPins) { Super::ReallocatePinsDuringReconstruction(OldPins); // 内部でAllocateDefaultPins()が呼ばれる const int32 AlreadyPinNum = Pins.Num(); for (int i = 0; i & lt; OldPins.Num() - AlreadyPinNum; i++) { AddUniqueConditionPin(); } } // メニューアクションの登録(コンテキストメニューからノードをスポーンさせるようにする) void UK2Node_SEAndBranch::GetMenuActions(FBlueprintActionDatabaseRegistrar& amp; 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& amp; CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); // Conditionピンを取得 TArray& lt; UEdGraphPin*& gt; 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& lt; UK2Node_IfThenElse*& gt; 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 & lt; 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 |