投稿

4月, 2017の投稿を表示しています

[UMG]ディゾルブエフェクトもできる、RetainerBoxについて

イメージ
RetainerBoxについて紹介します。これを使うと、例えば図1のようなUMG用ディゾルブエフェクトを作成することも可能です。

RetainerBoxとは Optimization(最適化)に分類されるウィジェットです。Retainer Boxの子ウィジェットの内容を、テクスチャ(RenderTarget)に指定間隔でオフスクリーンレンダリング(OSRと略します)し、その結果のテクスチャをスクリーンに描画するウィジェットです。OSRは毎フレーム行われるわけではなく、OSRが行われないフレームの間は最後にOSRされたテクスチャを再利用することでスクリーンへの描画負荷を減らします。 子ウィジェットの内容が複雑なものである場合、OSRの頻度が少ないほど描画パフォーマンスが向上する可能性があります。ただし、OSRの頻度を「毎フレーム」に設定したりすると、OSRされたテクスチャをスクリーンに描画する「二度手間」がある分、逆にパフォーマンスは悪化すると考えられます。今回は最適化の話に焦点を合わせませんが、使うときはパフォーマンスに留意する必要があると思われます。
今回は、Retainer Boxを用いたエフェクトを主に紹介します。実は、Retainer BoxはOSRされたテクスチャをスクリーンに描画する際、マテリアルによって描画内容を変えることが可能です。これはBorderやImageウィジェットのBrushにエフェクト用マテリアルを適用するのとは異なり、Retainer Boxにエフェクト用マテリアルを適用すると、子ウィジェット全てに対してエフェクトが適用されるのと同じ表現ができます。

Retainer Boxを使ってみる 前準備として、図2のようなUser Widgetを作成します。今回は、これをRetainer Boxの子ウィジェットにして全体にエフェクトを適用します。

まず、図2のウィジェットをRetainer Boxでラップして、図3のようにします。余談ですが、ウィジェットをラップするときはコピー&ペーストで作るより、Wrap With...の中から親としたいパネルウィジェットを選ぶ方が簡単です。

次に、Retainer BoxのEffect Materialにマテリアルを設定します。ここではCreate Materialを選んで、新しいマテリアル(アセット名は…

[UMG]カーソル入力を阻害する-前編-

イメージ
はじめに 今回は、UMGのテクニックを紹介しようと思います。
図1を見てください。Open Dialogボタンをクリックすると、図2のようなダイアログが表示されます。


このダイアログシステムには、ダイアログ表示中にOpen Dialogボタンを再クリックできてしまうという問題があります。
Open Dialogボタンを再クリックさせないための方法はいくつかあります。はじめに思いつくのは、Open Dialogボタンがクリックされたとき、ボタンのVisibilityをHit Test Invisibleに変更して、クリック不能にする方法です。しかし、この方法はHUDに新しいボタンが追加されるたびに、それぞれのボタンのVisibilityを変更しなければならず、あまり良い設計とはいえません。

カーソル入力を阻害する これを解決するために、ダイアログの後ろにカーソル入力を阻害するウィジェットを配置する方法で、再クリックさせないようにします。ウィジェットは何でもいいのですが、今回は見た目のないSpacerを使います。まず、図3のように、ダイアログの後ろにSpacerを配置します。

次に、図4のように、VisibilityをVisibleに変更し、配置したSpacerを画面全体に広げます。画面全体に広げるのは、Spacerより背後にある全てのウィジェットを覆い、その全てのウィジェットのカーソル入力を阻害するためです。今回はSpacerの親がOverlayなので、AlignmentをFillに設定すれば画面全体に広げられます。親がCanvas Panel等の場合は、別途Anchors等を変更する必要があります。これだけで、Spacerより後ろにあるボタン{1}とのインタラクティブが阻害され、Open Dialogボタンは再クリックできなくなります。

何故カーソル入力が阻害されるか 次の表はVisibilityによる違いをまとめたものです。Visibleだけが背後のウィジェットとのインタラクティブを阻害するという特徴をもっていることがわかります。前にSpacerをVisibleに変更したのは、Spacerが遮る背後のウィジェット(ここでは、Open Dialogボタン)とのインタラクティブを打ち切るためだったわけです。
また、「ウィジェットは何でもいい」と書きましたが、これは…

[BP]Blueprintアンチパターン その3 -Select+SetByRefVar-

イメージ
今回は、ゲーム制作を通して実際に遭遇したBlueprintのアンチパターンについて紹介したいと思います。

SelectノードをIncrementノードに繋げる まず、図1のように繋ぎます。 ピン形状的に問題はなさそうに見えます。また、これはPIE上で正しく動作(IndexがFalseならTestVarAが1に、TrueならTestVarBが1にインクリメントされる)します。 しかし、この繋ぎ方をするとパッケージ化に失敗します。
おそらくバグかと思われますが、調べてみると類似の問題がUE4.11から発生しているらしく、修正が困難な例なのかもしれません。
問題の単純化 問題はIncrementノードに繋いでいるから発生しているわけではありません。同じようなことはDecrementでも起こります。 大本の原因は、図2のようにSetBy-RefVarノード(「リファレンス渡し変数を設定」ノード)に直接Selectノードを繋げることで起きています。Incrementノードは内部的にSetBy-RefVarノードを用いるマクロですから、マクロが展開されたときに問題の繋ぎ方をしてしまい、パッケージ化に失敗してしまいます。
回避方法 問題は「直接」繋ぐことで起きています。 これは図3のように関数でラップすれば回避できることを意味しています。 このような関数を代わりに使うと、パッケージ化で失敗する問題を回避できるようです。 もしくは、単純にブランチでインクリメントする変数を分岐する方が良いかもしれません。

この記事は次のバージョンで作成されました。 Unreal Editor(4.15.1-3348071+++UE4+Release-4.15)

[BP]Blueprintアンチパターン その2 -Pure関数+ForEachLoop-

イメージ
今回は、ゲーム制作を通して実際に遭遇したBlueprintのアンチパターンについて紹介したいと思います。

Pure関数をForEachLoopに繋ぐ これはよく言われる話ですが、実にUE4を始めて1年近く気づかなかったものです。図1を見てください。
HeavyPureFuncノードの結果をForEachLoopに繋いでいるだけです。一見すると問題なさそうに見えます。しかし、これでは駄目です。ForEachLoopをダブルクリックして実装を見てみると、図2のようになっています。
Inputの引数Arrayを辿ってみると、LengthとGetノードで使われているのがわかります。これを見るとForEachLoopは特別なことをしている訳ではなく、与えられたArrayの要素を順に取り出しているだけなのです。なので、ここにPure関数を繋いでしまうとLengthとGetが評価される度にPure関数も再評価してしまうことになります。
実際に、HeavyPureFunc関数に適当なPrintStringを繋いで確かめてみましょう。ReturnValueが返す配列の要素数は3としました。
要素数に対し、LengthとGetノードの評価回数だけHeavyPureFunc関数が呼び出されていることが確認できます。もしPure関数の計算量が非常に大きい場合は、再評価が重なってボトルネックとなり得ます。
また、ボトルネックだけなら最適化の段階で気づきますが、もう一つ別の問題があります。それは『Pure関数が返す配列の要素が変わってもそのままイテレーションし続ける』という点です。
例を1つ挙げます。この例を実行すると、配列の範囲外にアクセスしてしまいます。
OutRangePureFunc関数は、ForEachLoop内のLengthノードが評価される瞬間には要素数3の配列を返し、Getノードが評価される瞬間には要素数2の配列を返します。よって、図8を実行すると、3回目のループ時に配列3番目の要素にアクセスできずIsValidの結果がFalseになります。UE4における配列のGetノードは範囲外にアクセスしてもNullを返すだけなのでエラーが起きることはありませんが、ForEachLoopが最初にきたときの配列と別物の配列がイテレーションされる可能性があるというのは結構怖いことだと思います…

[Material]自動で傾斜の見た目を変える

イメージ
World Aligned Blendについて紹介します。これを使うと、図1のようなものが出来上がります。
 詳細はここに書いてあります。

はじめに ランドスケープで傾斜を作る時、その傾斜に沿って岩肌をペイントをしたりすると思います。しかし、やった人にはわかりますが、これはかなりの苦行です。同じような傾斜をひたすら塗って塗って……塗り続ける。塗ったは良いけどムラがあって綺麗じゃない。しかもスカルプトしなおすと傾斜でなくなったところを消さなければならない。凄まじい労力です。 しかし、よく考えてみたら傾斜の法線をみて、部分的に岩肌が露出するようにしてくれるマテリアルがあれば、わざわざペイントする必要はありません。そういうアイデアを実現するのがWorld Aligned Blendノードです。
作る まずStarterContentを入れたプロジェクトを作成して、新規マテリアルで図2のように繋ぎます。 テクスチャはStarterContetのものを使いました。WorldAlignedBlendのBlendSharpnessに-16を繋ぎ、BlendBiasに6を繋ぎます。 次に、このマテリアルを使ってランドスケープを作成します。今回は図3のようにデフォルトのままで設定を変更しません。 これで作成したランドスケープでスカルプトすると、図4のように傾斜で岩肌が露出するようになります。 おわりに今回の例ではマテリアルが簡素なので見た目が悪いのですが、ちゃんと組めば便利で見た目もいいものができると思います。
マテリアルはやや複雑になりますが、これを使えばランドスケープ制作のワークフローを改善することができそうです。

この記事は次のバージョンで作成されました。 Unreal Editor(4.14.3-3249277+++UE4+Release-4.14)

[UE4]Blueprintアンチパターン その1 -IsValidノード+Selectノード-

イメージ
今回は、ゲーム制作を通して実際に遭遇したBlueprintのアンチパターンについて紹介したいと思います。

IsValidノードをSelectノードに繋ぐ まず、次のようなnull safe constなGetTestStringという関数を作ります。
次に、レベルブループリント上で次のように繋ぎます。
NullActorがValid(非Nullでキル保留中でない)ならGetTestString関数の結果を、Invalid(Nullもしくはキル保留中)なら定数のNULLを画面に出力する、という一見自然そうな実装に見えます……が、これを実行すると、NullActorがNullのとき、エラーを起こします。 何故でしょうか?
実はSelectノードはTrueとFalseの両方の結果を一度に評価します。よって、NullActorがNullであってもGetTestStringを無理やり評価してエラーを起こしてしまっているのです。 ところが、次のような場合にはエラーを起こしません。
さきほどと違う点は、GetTestStringの代わりにGetDisplayNameを繋げているところです。これを実行しても、エラーが起きることはありません。
何故でしょうか?
それはGetDisplayNameが静的なnull safe関数であるからです。この『静的な』が非常に重要で、例えばGetTestStringをnull safe const関数であっても、Blueprint上で呼び出すと{1}エラーを起こしてしまいますが、static関数であればエラーを起こさず呼び出すことができるようです。
実はIsValidも同じように静的なnull safe関数として実装されていて、これがNullActorがNullであってもエラーを起こさない理由となっています。 話は戻りますが、このようなパターンに遭遇した場合、どのように解決すべきでしょうか。私は次のようにしました。
実行ピン数が増えてしまうのが少し気になりますが、これならNullTestActorがNullのとき、GetTestStringを評価することはありません。 もしくは、GetTestStringをBlueprintFunctionLibrary上に実装することでも実現できます。


{1} C++の場合、nullポインタをデリファレンスしな…

[Unreal C++]意図しないガベージコレクションを起こす

イメージ
ガベージコレクション(以下GCと略す)とは、使われなくなったオブジェクトを自動で検知・破棄しメモリの無駄を減らす機構のことです。
UE4にもこれが実装されていますが、どのように動作しているかはいまいちわかりません。
今回は、GCによってオブジェクトが破棄される様を確認することで、リバースエンジニアリング的にその挙動を見ていきたいと思います。
UE4におけるガベージコレクションアンリアルのオブジェクトのハンドリングに書いてありました。
オブジェクトをGCの対象から外す方法は、UPROPERTY指定子を付けた変数によって保持すること、だそうです。 Blueprint上の変数は全てUPROPERTY指定子がついたものといえます。よって、Blueprint OnlyのプロジェクトでGCを意識することはありません。更に、ActorやComponent等のクラスは明らかにレベルやActorによって参照が保持されているので、意図しないGCの対象になることはないでしょう。意図しないGCの対象にするためのポイントをまとめます。
動的に生成する(コード上でNewObjectを使う)UObjectクラスを継承したクラス(ActorやComponentではない)UPROPERTY指定子をつけた変数で保持しない
オブジェクトを生成する まず、プロジェクトを立ち上げて、新規C++クラスを作成します。 「全てのクラスを表示」して「Object」を選択して次へをクリックします。 クラス名はGameVarとしておき、何も変更しません。

GCによって破棄されたオブジェクトにアクセスする UPROPERTY指定子をつけた変数によって保持しないことで、生成したオブジェクトがGCによって破棄されることを確認してみます。今回はGameModeに実装してみました。
変数gvsは、GCの対象外にするためにUPROPERTY指定子を付けていません。CreateAndAddGameVar関数は、UGameVarオブジェクトを新たに生成し、gvsに格納する関数です。これらをレベルブループリント上で次のようにします。 オブジェクトが破棄されていない間はIsValidの結果がTrueになり、破棄された後はFalseになることが期待されます。
このレベルのWorldSettingsでGameModeをBlogTestGame…