Let's Enjoy Unreal Engine

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

UE4 比較的大規模におけるブループリントの運用

今回はいつもと少し違う趣旨の内容となり、『比較的大規模におけるブループリントの運用』という内容で考察してみます。

ブループリントは皆さんも知る通り、UE4における最も標準的なロジック実装のスクリプティング環境です。おそらくほとんどの人はブループリントでロジックを組んでいると思いますが、規模が変われば条件も変わってきます。

そこで大規模になってくるとブループリントがどういうところで問題になってくるか、まずは考えてみましょう。


大規模開発におけるブループリントのデメリット

大規模な開発を行っていくとブループリントでも様々なデメリットが生じてくるはずです。


・大規模ノードによる複雑さの上昇
・膨大なロジックによる、VMオーバーヘッドの上昇
・マージ時のコストの高さ
・依存関係の複雑さの上昇によるコンパイル時間の増加

などなど、様々なものがあります。もちろんこれだけではありません。


これらに対する可能な限りの対策を考えてみます。

その前にUE4ではC++を扱うことが出来ますので、しばしばブループリントとC++のどちらを使えばいいのか?という話題になります。過去にヒストリアさんの記事でこういうものがありました。


historia.co.jp

ここで言われている基本方針は「BPで実現可能なものはBPで実装する」です。

これは揺らぎません。しかし全てをブループリントで実装するのはやはりデメリットがあります。記事にもあるようにベースクラスはC++で実装しておくことです。

実際にRobo RecallのMOD Kitプロジェクトでは以下のような継承関係になっています。


f:id:alwei:20170424160815p:plain

C++でのベースクラスの上にブループリントのベースクラスが実装される形です。そこから様々な系統の派生クラスへと渡っていきます。

C++側には基本的に滅多に調整が必要な実装は入れておかずに、ロジックは全てブループリントへと実装していくのが良いでしょう。この方式が最も取り回ししやすく、かつブループリントのメリットも多大に受けられるはずです。


以下はブループリントとC++を両方使う際に参考になりそうな資料です。


Unreal Engine | ブループリントのプログラミングのガイドライン

Unreal Engine | C++ とブループリント

そもそもなぜUE4でC++したいのか? - 好きなことを形にしていく開発日記

UnrealEngine4 リフレクション(C++とBluePrint連携)まわりの調査 - Qiita

Blueprints vs C++ | Shooter Tutorial


大規模ノードによる複雑さの上昇

ブループリントはノードベーススクリプトなので、規模が大きくなればなるほど複雑なノード構成になっていきます。Robo Recallのプロジェクトを覗くと、豊富なロジックが実装されており、やはり複雑なものとなっています。


f:id:alwei:20170424163323p:plain

確かに複雑なものになっていますが、読み解けないものになっているか?というとそうでもありません。よくよく見てみると、イベントの数こそ多いものの、1つ1つのロジックの流れは非常に短かくなっていることがわかります。

またロジックがどこからどこまでのものになっているのか、コメントで囲まれているために各ロジックがどの部分に付属するものなのかわかりやすくなっています。

関数も非常に細かい単位でわけられており、1関数は小さいものばかりです。ある程度の規模になったところで徹底した折り畳み(Collapse)化が行われていることもわかります。更にワイヤー同士がなるべく重ならないようにRerouteノードも頻繁に使われていることがわかります。

1つ1つ読み解いていけばコードを読むのとそう変わらないということもわかってくるはずです。

ブループリントはコードで書く以上に見た目が重要なものとなってくるので、大規模プロジェクトにおいても細かく分割を行い、後から見やすい形に整理しておくことがとても大切です。

ヒストリアさんが公開している以下のスライドは大規模開発においてとても重要なことが載っているので、必ず一度以上は目を通しておくことをおすすめします。


www.slideshare.net

膨大なロジックによる、VMオーバーヘッドの上昇

こちらはブループリントを使うことにより、Virtual Machineでの処理が必要となることでネイティブで実行されるC++に比べるとかなり速度が落ちるというものです。

ただしこれについては4.15から実装された「ブループリントのネイティブ化」の機能を使うことで大幅にオーバーヘッドを小さくし、パフォーマンスを得る手段が実装されました。


docs.unrealengine.com

Robo Recallにおいてもこの機能が大活躍しており、Oculus Rift推奨CPUであるCore i5相当のCPUがあれば十分な速度が得られそうです。

もちろんネイティブ化を行おうが、重い処理は重い処理となります。ロジックそのものが重い場合はブループリントだろうがC++だろうが、ロジック自体見直します。

UE4はロジックの実装を基本的にイベントドリブンの仕組みで動作させることが前提の設計となっていますので、イベント処理を上手く受けとって動作させるようにしましょう。

またブループリントとC++どちらを使ってもですが、なるべく毎フレーム呼び出されるTickイベントを使わないようにします。


f:id:alwei:20170424170649p:plain

もしTickを使わないのであれば、プロパティから"Start with Tick Enabled"のチェックを外す、もしくは頻繁に呼ぶ必要がなければ"Tick Interval"を設定して最小限の呼び出しにしましょう。これだけでオーバーヘッドはかなり下がるはずです。

マージ時のコストの高さ

ブループリントは複数人で開発した時のマージがなかなか大変です。しかもブループリントはバイナリーデータなので、通常の方法では差分をとったり、マージすることができません。

しかしSVN等を使った状態であれば、エディター側が対応してくれているので、ビジュアル化された状態の差分をとったり、マージツールを使ったマージといった高度な機能が用意されています。


f:id:alwei:20170424171252p:plain

差分ツールを起動した状態。


f:id:alwei:20170424171305p:plain

マージツールを起動した状態。

この辺りについては私の著書である『Unreal Engine 4 ブループリント逆引きリファレンス ゲーム・映像制作現場で役立つビジュアルスクリプトガイド』でも詳しく解説しています。


Unreal Engine 4 ブループリント逆引きリファレンス ゲーム・映像制作現場で役立つビジュアルスクリプトガイド | alwei |本 | 通販 | Amazon


ブループリントのノードはテキストとしても保存が可能です。ノードを選択して、コピーペーストすればテキストとして貼り付けが可能です。

f:id:alwei:20170424171732p:plain

最悪バイナリーデータで不都合が起きた場合でもテキストベースのマージを行うことも可能です。

大規模になってきた時には作業が衝突(Conflict)してしまうことは恐らくあるでしょう。その時は焦らずにエディターの機能に頼ることで十分対応が可能なはずです。

依存関係の複雑さの上昇によるコンパイル時間の増加

最も大規模で問題になりやすいのはここかもしれません。ブループリントを使っていると、特定の別のブループリントに依存するようになってしまい、芋づる式に複雑な依存関係を持ってしまうことが必然的に起きてしまいます。

この依存関係が複雑になると、ブループリントの読み込みがどんどん重くなったり、コンパイル速度がどんどん遅くなってしまう等デメリットが多々あります。

対策として、まず考えられるのはブループリント インターフェースを使うことです。


unrealengine.hatenablog.com

このブループリント インターフェースは非常に重要です。自分以外のブループリント上でキャストを行ったり、自分が持っている関数が外から呼び出されると、それだけで依存関係が発生します。そこで依存関係を発生させないようにするためにインターフェースを使います。

インターフェース経由の呼び出しはカプセル化されているので依存関係が発生せず、読み込みが発生せず、コンパイル速度も落ちません。

実際に依存関係が発生しているかはブループリントのアセットを右クリックして、"Reference Viewer"を確認してみましょう。


f:id:alwei:20170424172930p:plain


f:id:alwei:20170424173017p:plain

シンプルな構成が保てていれば、読み込みやコンパイル時間での待ちが少なくなるはずです。

またマクロ化なども注意が必要です。便利だからと言ってなんでもかんでもマクロライブラリーにしてしまったりすると、マクロはその場でノードが展開されるという仕様なために様々なブループリント上で依存関係が発生します。

ヒストリアさんの以下の記事でも解説されています。


historia.co.jp

以下引用。

ブループリントはアセット参照などを含めると依存関係が発生します。これは関数ライブラリ、マクロライブラリも例外ではありません。そしてライブラリは色んなアクターが使う事が想定されるので、そのライブラリに不必要なアセットが依存して含まれてしまうと、ゲーム中にアクターを生成する際、芋ずる式にアセットがロードされてしまい、実際には読み込む必要がないアセットまでもが読み込まれて無駄にメモリを圧迫したり、ロードに無駄な時間がかかってしまいます。なので、極力依存関係が発生しないように作るか、もし依存関係が発生してしまう場合には、ライブラリを分けた方がよいです。

依存関係の発生は大規模になればなるほど問題が大きくなります。プロジェクトが大規模になる前から手を打っておかないと大変なことになるかもしれないので、しっかりと事前予防をしておきましょう。

マクロは便利ですが、なんでもかんでもマクロにしてしまうのは危険です。Robo Recallでもマクロライブラリーは最低限のものしか用意されていませんでした。ブループリントローカルマクロとマクロライブラリーは似ているようで微妙な違いもあります。その辺りにも注意してください。

まとめ

ブループリントを大規模で使うのはなかなか大変かもしれませんが、今のUE4には上手く付き合うための手段が十分用意されているという印象です。

UE4を使った大規模タイトルが増えていますが、C++だけでやるのは実際もっとしんどいことが多いです。なによりもブループリントは調整がしやすく、トライ&エラーが非常にやりやすいので、ゲームプレイロジックの部分だけでも使うことをおすすめいたします。