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ノードの実装について詳しく紹介します。

ノード外観の設定

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

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

デフォルトピンの作成

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

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

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

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

ノードピンの取得

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

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

ピンの動的追加・削除

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

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

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

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

ExpandNod

全てのConditionピンを列挙

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

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

ソースコード

 

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