Let's Enjoy Unreal Engine

Unreal Engineを使って遊んでみましょう

UE4 マクロマジック!ちょっと深いマクロのお話

この記事はUnreal Engine 4 (UE4) Advent Calendar 2015の1日目の記事です。

qiita.com

去年は入門的な内容でしたが、今年は少しコアなネタになります。

 

ブループリントマクロ!

 

C++のマクロの事ではありません。

皆さん使っているでしょうか?正直なところで言えば関数より存在感が薄く、あまり使われているイメージがありませんが、マクロは関数にはない特徴が沢山あります。

今回はマクロの中でも自分の中で「マクロすごい!」と感じたマクロの側面を紹介していきたいと思います。

マクロの呼び出しコスト

マクロはC++のマクロと同じようにノードの置換を行ないます。マクロのノードを配置した場所にそのマクロのノードをそこに展開するというのが特徴です。

関数とは違い、ノードが配置されなければコンパイル対象にはなりません。配置されたノードはインライン関数のように展開されるので関数呼び出しよりも軽いコストで呼び出せるはずです。

というわけで計測してみました。

f:id:alwei:20151125160505j:plain

このようなベクトルの足し算をするのみの関数とマクロです。

f:id:alwei:20151125160833j:plain

これを単純に10万回ループさせます。結果はDate Time型から取得できる秒とミリ秒だけを取得してフォーマットテキスト化して表示しています。

そして結果がこれ。

f:id:alwei:20151125161239j:plain

下側2つがマクロで上側2つが関数です。

値の差分を見るとマクロが『325』ミリ秒で関数が『4,348』ミリ秒ほどあります。

その差は約4秒!やはり膨大な計算量を持つ場合にはマクロの方が有利という事になりますね。高速化するならC++を使うと思いますが、頭に入れておいても悪くないと思います。

追記1:よくよく見てみると関数側の呼び出し回数が10倍多くなっていました…これはフェアどころではないので、改めて計算し直してみましたが、マクロが『321』ミリ秒と関数が『392』ミリ秒という結果になりました。やはりマクロの方が早いですが、気にするほどの差ではないかもしれません。

追記2:複雑なロジックを含むほど関数とマクロの速度差は顕著になるという指摘も受けました。たしかにこの程度の足し算でこれだけの差がでるので、複雑なロジックになれば明らかな速度差がでるかもしれません。

状態を持つマクロのローカル変数

マクロには不思議な性質があります。ローカル変数を使う事が出来ますが、それぞれのマクロの中でローカルに状態を持つ事が出来ます。

状態とは?

つまりマクロの中にはオブジェクト情報が存在しており、ローカルに値を保持する事が出来るのです。よくよく考えるとおかしな話です。これはあまり他のプログラミング言語でも存在しない概念です。

マクロはローカルに情報を保持しているので、次に呼び出された時でも情報を継続して実行する事が出来ます。

追記:あまり存在しない概念と書きましたが、いわゆる『関数オブジェクト』に近いモノかもしれません。

関数オブジェクト - Wikipedia

 

例として"FlipFlop"マクロの中身を覗いてみましょう。

f:id:alwei:20151125162927j:plain

この"Local Boolean"と"Assign"というノードがローカル変数の取得と値の割り当てを行なっています。しかし普通の関数の場合にはこのローカル変数は関数を出ると共に消滅してしまいますが、マクロの場合ではそうではありません。

この値は次回呼び出された時にも保持されており、そのままその状態を見る事ができるのです。これはマクロにのみ許された特権です。しかもこれはマクロ毎に保持されているので、同じマクロを別のところで使用するとそれぞれ別に値を保持します。

これって実は凄い事で使い方によっては素晴らしい有効性をもたらしてくれます。このような性質を利用した素晴らしい例が海外フォーラムにありましたので紹介します。

forums.unrealengine.com

 

これはForLoopを拡張したマクロで、1つのループ処理を"Loops Per Tick"で指定した量だけ実行し、残りはまた次のTickで処理するという分割ループを実現しています。

これにより、ループで大量に処理をしたいという事があってもTickを分割する事で処理落ちを発生させずに処理を継続するという事が可能になります。高速さを求められるゲームでは非常に有効です。

https://forums.unrealengine.com/attachment.php?attachmentid=42616&d=1433633570

※フォーラム内の画像より引用

 

これはもちろん内部で値を保持しており、次回継続時にもそれを参照して、前回の経過からスタートするという事が可能になっています。これぞまさしくマクロの有効活用ですね。

マクロは発想次第でまだまだこういった使い方が沢山できると思います。

マクロから外に出てもマクロに戻る

これはもう本当に目から鱗というようなマクロの性質でした。マクロは上述したようにその場でノードが展開されるという仕組みです。これを逆手にとっているマクロのノードがあります。

"ForLoop"系のマクロと"Sequence"です。

ForLoopってなぜかマクロの外に一度出ているのに、処理が継続していますよね?意識していないと全然わからない事だと思いますが、これっておかしな話です。しかしForLoopの中身を覗くと一発でわかりました。

f:id:alwei:20151125170019j:plain

Sequenceノードの"Then 0"から"Loop Body"で一旦ForLoopのマクロから外に出ます。本来マクロはこれで終了なはずですが、なんとSequenceノードからの継続で"Then 1"のピンに戻ってくるわけです!!

本当にこれを考えた人、凄い!!と思いました。だってマクロから外に出ているのに、マクロに強制的に戻っているんですよ。そりゃ不思議です。

これは絶対に関数では不可能な仕組みです。マクロの性質を理解しているからこそ思いつく方法ですね。この考え方は色々応用が可能です。

マクロから一旦外に出て、マクロの外で処理したものをまたマクロ内部で処理をするという事も可能です。ちょっと普通のプログラミングでは思いつかないようなやり方ですよね。いやはやブループリントのマクロは面白い!

マクロをもっと活用しよう!

マクロは本当に面白いです。

他にもマクロの入出力には自由な数だけ実行ピンを増やしたり、ワイルドカードピンという特殊なものが使えるのもマクロだけの特徴です。ワイルドカードピンにはどんなタイプのピンでも繋げたりする事が可能です。

f:id:alwei:20151125171018j:plain

意外とこういった事を知らず、マクロなんて別になくても関数でいいよねっていう人も多いはずです。それでは勿体無いので、ぜひマクロを知って活用してみてください!

 

明日はshibukawaさんによる「UE4極本を4.9で」という事でよろしくお願いいたします。