はじめに
この記事は、私がUnrealEngine(以下UE)を使った実際のコンシューマゲーム開発業務を経験した中でBlueprint(以下BP)とC++のバランスについて思い悩んだことについて書いてみました。
UEを使ったゲームの制作事例の一つの意見、として参考にしていただければ幸いです。
なお、使用していたUEのバージョンはUE4(4.20~4.26) です。
これ以降のバージョンにて、以下に書いているものが改善された機能追加などがあるかもしれません。
対象読者
この記事はUEを使っている方全員に向けて書いているわけではなく、以下の方を対象にしています。
・コンシューマゲーム開発を行っている方
・多人数で開発をされている方
特にこれからUEを使おうとしている方に参考にしていただきたいです。
お一人でPC用の小規模なゲーム開発をしている方にはあまり関係がない話かもしれません。
(こんな事あるんだ~と読んでいただけるとうれしいです)
まず結論から
基本的にはC++で書き、部分的にBPで書く
これが結論です。
インターネットなどで調べているとよく言われているのが、基本はBPで書き必要に応じてC++に書き換える だと思います。
ですが、コンシューマーゲーム開発のような多人数大規模、かつスイッチなどのゲーム機用のアプリケーション開発にて、プログラマがロジックの記述にBPをメインに使ってゲームを作るのは、トータルでは 非効率高コスト だと思いました。
以下に、実際に感じたデメリットを上げてみます。
多人数開発で感じたBPでロジックを書くデメリット
1,BPは多人数で同時編集がやりづらい
BPは バイナリデータのスクリプトデータアセット です。
そのため、テキストよりも同時に多人数で編集することには向いていません。
UE4にはSVNなどのSourceControl機能を使ってアセットをロックして多人数による編集を制限できる機能がありますが、あくまで制限でありテキスト編集とまったく同様に、というのは難しいかと思います。
2,BPは差分を確認しづらい
BPはバイナリデータのため、例えばSVNの差分表示やWinMeargeでは確認できません。
SourceControlが有効になっていればUEの機能で差分を見ることができます。
ただし、テキストの差分比較よりも圧倒的に手間です。
これが影響があるのが、 「コードレビューや差分検索のやりずらさ」 です。
SVNのコミット履歴から、他人の変更を検索して参考にしたり、ダメだししたりということがとてもやりづらく感じました。
3,BPは処理が重い
BPの処理はそれ自体がC++よりも処理負荷が重いです(使い方によって重さに差はあります)
また、処理負荷を高めてしまうような罠も多いです。(例えばForEachに配列を渡すやり方など)
パフォーマンス比較については、Blueprints vs. C++ がアセンブルコードレベルでの比較も行っていて面白かったです。(英語)
一つ一つの処理をC++とBPで比較した場合は、気にするほどの処理負荷の差ではない場合がほとんどだと思います。
(Tickや配列操作、数学計算、関数呼び出しなどが比較的処理負荷が高くなるところかと思います)
ただ、コンシューマゲーム機の限られた性能で大量のオブジェクトを処理しなければならない場合、塵も積もればで効いてくるところです。
これに関してはBPのNative化という機能を使うという手もあるかと思います。
ただ、Native化はクック時に行われるためエディタプレイやスタンドアローンでは確認できません。
つまり、エディタでの確認とゲーム機での確認にて生成されるものに大きな差が生まれてしまいます。
また、生成されるコードは人が読みやすいコードにはならないため、ゲーム機でのデバッグが不便にはなりそうです。
4,BPはゲーム機やスタンドアロン起動でデバッグがやりづらい
BPロジックのデバッグは、エディタに搭載されているBlueprintデバッグ機能からしかステップ実行などのデバッグできません。
ですので、VisualStudioから直接デバッグすることができません。
そのため、スタンドアロン実行や、実機で起動した際にはデバッグがたいへん難しく手間がかかりました。
コンシューマーゲーム開発では、もちろんPC以外にゲーム機で動かすことになります。
とくに開発終盤になるほど、ゲーム機での確認機会が増え、バグ調査や修正をゲーム機で行う機会も増えていきます。
最初はエディタを中心に開発できるので気にならないところなのですが、開発終盤になるほど時間を取られ、デバッグのやりづらさを感じてくるかと思います。
また、ゲーム機実行中にクラッシュした場合には、ダンプを解析する場合もあります。
その場合にBlueprintから呼び出された処理からのクラッシュを調査するのも手間がとてもかかります。
スタンドアロン実行については、ネットワークマルチプレイ対応のゲームを開発した際の実行確認によく使っていました。
この場合もBP処理はVisualStudioからデバッグがとてもやりづらく、処理を追いかけるのにとても苦労したことがあります。
5,BPは関数や変数の使用箇所を検索するときに、C++とBPのどっちも検索しないといけない
BPとC++のどちらかだけで作られている場合は検索が楽なんですが、
BlueprintCallableのついた関数、BlueprintReadOnlyのついて変数がたとえば使われていないかを検索したい場合、BPとC++の両方を検索しなければならず手間です。
あと、この問題は 他人が書いたソースなどを読んで解読している場合 に特に発生します。
BPとC++といったり来たりするような処理になっていると、処理を把握するのにとても時間がかかります
(過去に携わったプロジェクトではこれで多大に苦労しました)
とりあえずBlueprintCallableやUPROPERTY(BleurprintReadOnly)つけとく、みたいな運用も手間が増えていくのでやめたほうがいいです。
6,BPはSVNで自動マージできない
1の多人数で編集できない理由でもありますが、バイナリデータのためもちろんSVNなどによるマージはできません。
UE4エディタにはいちおうBPのマージ機能はありますが、テキストマージよりもちろんたいへんです。
特に、SVNブランチなどのソースコントロール上のブランチを切り始めた際にこの制限が厄介になります。
例えば、本流で作業した変更をブランチに反映させたい場合、テキストであればSVNのマージから操作すれば自動で行ってくれるほことがほとんどです。
BPはそれができないため、上書きか、差分だけを手動でエディタでマージする必要が出てきます。
手動でやる場合にはマージ漏れなどのリスクが発生することになり、確認の手間も増えてしまいます。
7,BPは開発終盤になるほど邪魔になってくる、BPを変更したくなくなる
いままでに出てきた理由の数々から起きることですが、
デバッグしづらさやマージのしづらさの結果、BPをいじりたくない という気持ちが開発が進むにつれてどんどん高まってきます。
本来ならBP直すべきところを別の処理を書く、という処理を時々見かけました。
誰しもめんどくさいことはやりたくないもので、その結果良くないコードが増え続ける、といったことが起きていました。
ちゃんと正しい処理を書くように気をつける、と言ってしまえばそれまでですが、
10人以上のプログラマが参加してゲームを開発している環境ではいろいろなスキル・性格の人が共同で開発しています。
こういった場合に、全員がそういう気をつけられる人であることは難しいことも多いと思います。
8,BPは後でC++に書き直すと誓っても全部書き直す時間なんて無くて残る
BPでゴリゴリ書いたコードをあとでC++で直す、というのはとてもコストのかかる作業です。
しかもBPの状態でも動いてはいるので、マイナスを0にする作業 になるため、動作を変えるわけでもなくて後回しにもされがちです。
こまめにやればいいですが、これまた動いているためよほどきっちりやらないと後回しにされる一方になりやすいと思います。
この状態がつづくと、保守も拡張もしづらい中途半端な状態になってしまいかねません。
開発が進むにつれてBPの処理を残し続けてしまうと、置き換えには大きな決意と多大な労力と時間が必要になってしまいます。
9、BP修正でつらい経験をした人達が多数いる(と思う)
BPでガッツリプログラムロジックを書く前に、以下のスライド資料なども読んで参考にしてみてください
・ バイキング流UE4活用術 ~BPとお別れするまでの18ヶ月~(一読おすすめ)
・ 『ドラゴンクエストXI S』はいかにUnreal Engine 4で最適化されたか?
(パフォーマンス面で問題になった事例 ※GCのヒッチ問題はFPropertyに変更されたことで改善されているかもしれません)
・ メカアクションゲーム『DAEMON X MACHINA』 信念と血と鋼鉄の開発事例
公に発表していない方でも、最初はBPで組んであとから苦しんだ、後悔したという方はけっこういらっしゃるのではと思います。
とはいえBPをまったく使わないのはもったいない
いままでBPを使うデメリットを上げてきましたが、じゃあまったく使わないほうがいいのかというとそうではないです。
あくまでC++をメインに使っていったほうがいいよ、ということでBPも必要に応じて使うことでより効果的に使えると思います。
例えば、以下のような場合にはBPを積極的に使っていくといいかもしれません。
①アセットの参照設定やロード
アクターなどに使うモデルやVFXなどのアセットの参照はEditDefaultOnlyなどの変数にしてBPから設定すると非常に便利です。
逆にC++からアセットのパスを直接文字列で渡してロードする、みたいな古いやり方をすると以下の問題が起きたりします。
・パッケージで読み込まれないなどの不具合が多発(C++から読み込んだ場合、自動でクック対象になりません)
・参照ビューワが使えない(アセットの使用箇所をたどれない)
②レイテントを多用したい処理
特定の処理を10秒待ってから行ったり、何かの処理後に何かをする、といったレイテント処理の記述にはとても便利です。
この機能を活かす用途としては、
・企画の人が進行などの処理を記述するためのスクリプトとして使う
・AI思考ロジックの記述
・かんたんなデモシーンやチュートリアルのようなもの
・企画さんやデザインさんが編集する目的のもの
プログラマー以外の人が記述するにはとても便利な機能です。
背景アセットなどの作成でもBPを取り入れたりして、プログラマ以外の人も拡張できるような使い方をすると効率が上がると思います。
ただし、自由にさせすぎるとカオスなBPができそうなので、プログラマが目を光らせてハンドリングする必要はありそうです。
③捨てる前提のプロトタイプや処理
絶対に捨てる!と割り切ってBPを中心に使うことはメリットが高いと思います。
コンパイルせずに編集と実行が繰り返せる環境はメリットがもちろんありますし、手っ取り早く動くものを作ってみせる必要があるプロトタイプ製作、のようなものにはいいと思います。
④デバッグ専用処理
例えば、テスト用に作成したレベルなどのレベルBPで、開始時にExecuteConsoleCommandノードでコンソールコマンドを呼び出したり、といったようなデバッグ用途ではとても使いやすいと思います。
最後に
Blueprintは恋人、結婚するならC++
バイキング流UE4活用術のスライドの結論に書いてあることですが、私は激しく同意です。
BPはトライアンドエラーがとてもやりやすく、これによって素早く動くものを作っていくことができます。
ただし、それだけを認めてBPでゲームを作っていくと、多人数大規模開発においては可読性、競合、デバッグ、コードレビューなど他の場所で逆に時間がかかってしまう場合があります。
また、BPからC++への置き換えは、実際やってみるととても大変でコストがかかる作業です。
ただし、せっかく便利な機能であるBPを全く使わないのも逆に非効率。
あくまでC++を使うという方針のもと、必要そうな箇所を見極めてBPで作っていくのがいいと思います。
例えば、プロトだからといって気にせずBPを使いまくると、あとで後悔することになるかもしれません。
安直にBPを使うのではなく、C++とBPの違いをよく理解した上での使い分けを早期から考えておくことをおすすめします。