20年オヤジのUnreal Engine 4 TIPS

皆さん、はじめまして!
セガ・インタラクティブ技術統括室の林田です。

1997年入社、20年越えのおっさんプログラマです。入社以来、おおむねアーケードゲームに携わってます。

いくつか私の代表作を紹介しておきましょう。Crazy Taxiシリーズ、頭文字D Arcade Stageシリーズ、WCCFシリーズなどなど。古いタイトルもありますので、あまりご存知ないかもしれませんね。ご興味があればWebやYoutubeなどで検索してみてください。その他、変わり種としては、初代ダーツライブのCPU戦、Answer×Answer Pocket (Objective-Cネイティブ!)、あとは古い時代の部内ライブラリなども担当したことがあります。

主な担当分野は、キャラクタアニメーション周り、あとは「火消し」と呼ばれる厄介な事件を解決する人、最近ではアンリアルエンジン4全般を担当しています。

今回の記事は、セガテックブログ初(!)の「アンリアルエンジン4(以下UE4)」についてです。初のUE4記事ゆえ力が入ってしまい、とても長くなってしまいました。お時間のある時にゆっくり読んでいただければと思います。

この記事は、UE4の(ちょっとマニアックな)TIPSを中心に書かせていただいています。なお、ちょっと紙面を余分に頂いて、我々がUE4タイトルをリリースする前にやっていた実験などの紹介などを、前半に書かせていただいています。「前置きはいいから、本題を!」という方は、途中を飛ばして「マクロは悪?」のあたりから読んでくださいませ。

もくじ

セガ製UE4タイトルは、お近くのゲームセンターで好評稼働中です。

見かけた際には、ぜひプレイしてみてくださいね!

セガUE4タイトル第1弾「SEGA World Drivers Championship」


『SEGA World Drivers Championship 2019』アドバタイズムービー

開発チームより

UnrealEngineの機能を存分に使って、SUPER GTの実車両や実在コース(実在コースは期間限定)を再現しています。
ぜひ、体感してみてください。

セガUE4タイトル第2弾「HOUSE OF THE DEAD ~SCARLET DAWN~」


HOUSE OF THE DEAD ~SCARLET DAWN~ JAEPO 2018 MOVIE

開発チームより

13年ぶりにアーケードゲームへ帰ってきました。
ゲームのグラフィックやプログラムは全てUE4エンジンで完結しています。
またアーケードゲームらしく、筐体の音や振動が非常に凝った演出となっていますので、お近くのゲームセンターで見かけたら、ぜひプレイしてみてください。

言い出しっぺが語るセガ×UE4

何を隠そう、私がセガのアーケードゲームにUE4を持ち込もうと騒ぎ始めた張本人、いわゆる「言い出しっぺ」です。そしてご多分に漏れず「言い出しっぺの法則」が発動しまして、ここ数年は社内UE4担当大臣として活動しています。

なんで自社ライブラリを持っている会社がゲームエンジンを?等々、会社の政治的・経済的・人事的な事情は脇に置いておいて(とは言え、おおむね多くの大手ゲームメーカーさんと同じ事情だと思います)、ここは「テック(技術)」ブログですから、UE4を使ってモノを作る話をしましょう。

タイトルを作る前は、こんな実験をしてました。

UE4を甘噛みしてみる実験(UE4.3のころ)

f:id:sgtech:20181125225944p:plain f:id:sgtech:20181125225942p:plain

四輪駆動のおもちゃが壁のあるコースを走るアレです。あのおもちゃと同様、前進しかできず、コリジョンで(壁に沿って)曲がるように作られています。原理的には非常に簡単ですので、興味のある方はぜひ真似をしてみてください。

作り方ヒント

  • f:id:yoshimasa_hayashida:20181019113423p:plain こういうポリゴンを使って、スプラインメッシュでコースを作っています。コリジョンは見た目よりもかなり高い壁を作りましょう。でないとクルマが簡単に吹き飛んでコースアウトします。
  • 車はアクセルボタンに応じた前進しかしません。あとは運を天に任せて、うまく曲がってくれるのを祈る感じです。場合によっては吹き飛んでしまいますが、それはそれで楽しいかと思います。何ならブレーキ操作をできるようにして、コーナーではうまく減速するゲームにしても面白いでしょう。
  • コースの各所にトリガーボリュームなどを置いて、「時間内に通過するとパワーアップ」などのギミックを入れても面白いでしょう。

 格好いい映像を作ってみる実験(UE4.5のころ)

上記の「四輪駆動のおもちゃ」はプログラマ主導でやっており、アーティストにはコース作成だけお願いしていましたが、今度の実験はアーティスト主導で行いました。この実験ではSubstance Designer/Painterも試しています。 

f:id:sgtech:20181125230654p:plain f:id:sgtech:20181125225931p:plain

さすが本職の人が作ると違いますね。HOUSE OF THE DEADを意識するというテーマを悪用して(?)、作ったメンバーの趣味が存分に盛り込まれています(笑)。

UE4の特徴である「物理ベースレンダリング」を活かそうと、光と影を意識した映像の実験となります。従来のセガのアーケードゲームとは多少趣が異なり、明暗のコントラストを強めに出しています。いわゆる「洋ゲー」に寄せた、といったところでしょうか。

一通りゲームを作ってみる実験(UE4.9のころ)

ここまでの実験である程度UE4が分かってきましたので、今度は一連のゲームの流れを作る実験をしました。何らかのタイトルプロジェクトを発足させる前に、踏める地雷はあらかじめ踏んでおこう、がテーマです。

パーティクルエフェクト担当のアーティストや、ユーザーインターフェース担当のアーティストも参加して、チェインクロニクルをアーケード向けにアレンジしたらどうなるか?を試しに作ってみたものです。

f:id:sgtech:20181125225927p:plain f:id:sgtech:20181125230016p:plain

本家チェインクロニクルの倍(10人!)のプレイヤーキャラクタ、倍どころじゃない数の敵、パーティクルエフェクトが派手に飛び交い、カメラもグルングルン回る。これを80型の大型タッチパネルモニタで両手を振り回して遊ぶ。アーケードゲーム化するんだったら、派手に!大味に!半ばパニックになっちゃうくらい忙しい!そんな風にしちゃいました。これは作るのも楽しかったですね。

この実験成果の報告会では、100名を超える社員が会議室に集まってしまい、大半は立ち見、エアコンが追いつかず会議室はどんどん暑くなるという事態に。おかげさまで社内へのUE4の認知度は高まり、UE4採用プロジェクトとしてSWDCとHODを発足させることが出来ました。

ご注意いただきたいのですが、本家チェインクロニクルがUE4に移植される予定も、アーケード版をつくる予定も全くありません。あくまで実験の題材として使用しただけですので、「次のチェインクロニクルはUE4だ!」なんて、間違った情報をうっかり拡散してしまわないようにお願いしますね!

さて、今回の記事は…

20年越えのオッサンプログラマが、その経験をBlueprintに活かすとどうなるか、そういう話をできればいいなと思っています。

UE4の紹介記事などもWebを検索すればたくさん見つかるようになりました。申し訳ありませんが、この記事には初心者向けの説明はあまりありません。それよりはもうちょっと深い部分、私視点でのUE4の「コーディング(code-ing)」ならぬ「ノーディング(node-ing)」事例などを紹介したいと思っています。

初心者の皆さんには、有名な書籍をご紹介。

初心者お断り!なんてバッサリ斬り捨ててしまうのも心が痛いので、書籍を数点ご紹介しますね。本を見つつ、Webで検索しつつ、おそらく最初は苦労されるかとは思いますが、それを乗り越えて楽しくなってきたら、ぜひこの記事に戻ってきてくださいませ!

では、書いてある通りにやれば、何か動くものが出来上がる、おそらくはUE4ユーザーの大半がお世話になっている良書から。

Unreal Engine 4で極めるゲーム開発:サンプルデータと動画で学ぶUE4ゲーム制作プロジェクト

Unreal Engine 4で極めるゲーム開発:サンプルデータと動画で学ぶUE4ゲーム制作プロジェクト

 

 同じく、「書いてある通りにやれば何かできる」もう一冊。

作れる!学べる!Unreal Engine 4 ゲーム開発入門

作れる!学べる!Unreal Engine 4 ゲーム開発入門

 

 こちらは残念ながら、おそらくUnityで使われてただろうキャラクタモデルが流用されており、Unlit(ライティングなし)前提ですので、UE4の美しいレンダリングには触れられていません。それでもキャラクタをアニメーションさせて動かす等々、おそらく多くの皆さんがやりたいと思っているだろう要素は、ちゃんと揃っていますのでご安心を。

もっとお手軽に試してみたい方には、書籍ではありませんが、こちらの動画はいかがでしょうか。


Unreal Engine 4 勉強会 『40分でBlock崩しを作る』(1)

これは「Unreal Engine 4で極める…」の著者、湊さんによる勉強会での公演の動画です。私も一番最初にこの動画をマネをすることで、UE4へのとっかかりを得ました。動画では画面の細かい文字が見えませんが、Blueprintのスクショを貼ってくれた方がいらっしゃいますので、こちら(↓)のページと合わせて見ると良いでしょう。

www.jonki.net

なお、この動画は慣れた方がサクサク組んでいらっしゃいますが、初心者にはテンポが速すぎて追いつけません(当時の私もそうでした)。一時停止や巻き戻しを繰り返して追いかける感じになりますので、決して、40分では完成しません。悪しからずご了承ください。2~3日かけてゆっくりやるくらいの心づもりで。

UE4はアップデートの周期がとても短く(この記事の執筆時点でUE4.20.3。記事のチェックをしている間にUE4.21が出てしまいました)、気を抜くとあっという間に情報が古くなってしまいます。上記3つの書籍・動画は古いバージョンのものですので、お手持ちのUE4バージョンとは違い、もしかしたらメニューの位置やコマンドの名前などが変わってしまっているかもしれません。新旧のバージョン間の機能の違いを、Web検索などを駆使してなんとか見つけ出すのもUE4の勉強の一環だと思って、頑張ってみてください。

Web上では、UE4は難しい…という声をチラホラ見かけます。

確かにそうかもしれません。私も最初の2か月くらいは、UE4が嫌いでした。
それを乗り越えて、ざっくりでもUE4が分かってきたころから、逆にサクサクものが作れていく感覚を覚え始めます。今では「テキストエディタを開いてプログラミング言語を入力する」こと自体に抵抗を覚えるくらいのUE4信者になってしまいました。特にBlueprintとMaterialは手放せません。C++/C#の時代は終わったなんて言っちゃっています。

皆さん、最初は我慢です!(これ、プロからの助言としてはどうかと思いますが…)

UE4に限らず、初めて使うツールって、体に馴染むまでは難しい顔してやるもんでしょ? ましてや、UE4はAAAタイトルすら作れちゃうくらいの豪華ツールです。大量の機能や設定の中から、今自分が欲しい機能を見出せるようになるには時間をかけて慣れるしかありません。

今回つかう題材は、UE4ぷちコン応募作品です。

株式会社ヒストリアさん主催の「UE4ぷちコン」はご存知でしょうか。毎回テーマが出され、それに沿ったゲームや映像作品をUE4で作って応募する、ちょっとしたゲームコンテストです。「UE4の勉強の成果」を発表する、勉強会の延長線上にあるもので、誰でも参加できます。皆さんも参加してみてはいかがでしょうか。

一方で、意外と本気勢も多く(私もその一人ですね)、「ガチコン」と言われてしまうこともあるようです。ヒストリアの佐々木社長はこの言葉を嫌がっているようですが、幸か不幸か、初心者作品からプロ作品まで非常に幅広いレベル帯の作品が集まるコンテストになっており、自分の現在のレベルと、次に目指すべきレベルが見えやすい、とても良いイベントです。

それはともかく、今回の記事は、私が第9回ぷちコンに応募したゲームを題材にお話しようと思っています。

第9回ぷちコン応募作品「Gravity One」


【UE4ぷちコン#9応募作品】GRAVITY ONE

テーマは「ループ」。なので、丸い地球上(マップがループしている)でのシューティングゲームを作ってみました。ありがたいことに、「カナマイ賞」を受賞することができ、アンリアルフェスWEST2018でプレイアブル展示していただきました。

社内でも、毎日昼休みにスコアアタックをしてくれるドハマリプレイヤーが出現。最終的にノーミスクリアを達成! 記念にご本人の強い要望に応えて、このゲームのexeを進呈しました。

第10回ぷちコン応募作品「MAGITYPE」

つい先日行われました、第10回ぷちコンにも応募させていただいています。


【UE4ぷちコン#10応募作品】MAGITYPE

テーマは「ぷち」。ぷちコンの「ぷち」です。
安易に思いつく「プチプチ」(梱包材のアレです)は断固避ける!と意気込んだものの、制作期間の6割くらいの間、ずっと迷走してしまい、結局は未完成のままの応募になってしまいました。テーマ「ぷち」の回収方法として私が選んだのは、過去10回の「ぷち」コンのテーマを全部使う!明らかに盛りすぎですね。間に合うわけもありません。

審査結果発表会では軽くご紹介いただきましたが(動画2:15:00くらいから)、もちろん落選です。プロとして恥ずかしい限りです。

そして、いまだに鋭意作成中…(どうなることやら)

この「MAGITYPE」からも、ご紹介したい事例がいくつもあるのですが、長くなってしまいそうなので、また次の機会に。例えば「MAGITYPE」が完成した後などに記事にできればと思います。

(困ったことに、2018年内にすら完成させられる自信はありませんが…)

プロなのに応募してずるい!

いやいや! 応募要項には「日本在住のUE4ユーザーなら誰でもOK!」と書いてあります。

…いえ。確かにズルいとは思っています。ですので、自分なりにハンデを設けて作っています。

  • 「絵も音もプログラムも、全素材を自分一人で作る!」

面白そうでしょ? やること多くて死ねますけど。

21年もゲームプログラマをやっていますと、アーティストやサウンドデザイナーとのやり取りなどで、それなりにツールの使い方や用語などは覚えるものです。なので、箱を作ってテクスチャを貼れる程度には使い方を知っています。まあ、その程度の技量で全素材づくりに挑むのですから、十分ハンデかと。

おかげさまで、2回のぷちコン参加で、SDキャラクタ作成・テクスチャ作成・アニメーション作成なども自分でできるようになりました(クオリティはともかく)。ほら、ちゃんと勉強になっているから、ズルくない!

参考までに、プログラムは組めるけど絵素材や音素材がなくて困っている皆さんや、インディーズを目指している皆さんに、私が使っているツールをご紹介しておきますね。(ちょっと世間の流行りからはズレていますけど…)

MODO indie(月々1500円ほど)

3Dモデリングツールの中では新しく、古い時代のしがらみなどが少ない分かりやすいツールです。MODO JAPAN GROUPのページにはチュートリアル動画やTIPS動画がたくさんあり、学習のしやすさもオススメしたい理由の一つです。

store.steampowered.com

CorelDRAW Graphics Suite(買い切り6万円ほど)

私は学生のころから論文などの図の作成に使っていました。未だにアップデートを繰り返して使い続けています。いわゆるフォトショップやイラストレーターに相当するツールがセットになっています。特に CorelDRAW(イラストレーター相当)は、私や多くのアーティストでない方々のように、フリーハンドで思い通りに線が描けない人にはありがたいツールです。筆で描くのではなく、方眼紙に定規やコンパスで図を描くイメージですね。私はベジエで描いてから、ビットマップに変換する流れでテクスチャを作成しています。

www.coreldraw.com

Logic Pro(買い切り2.5万円ほど)

いわゆるDTMソフト、音楽編集ソフトです。私の使っているのは古いバージョンの方、Logic Pro 9です。当時は5.5万円ほどしていたのが、今では1.7万円ですね!驚愕です。なお、私が音楽の世界に足を踏み入れたのは、ボカロ(なぜかミクさんではなくルカさん)の影響です。ありがちですね。

www.apple.com

C-Deck(540円×3種)

トランプ大のカードにコード進行が書かれた作曲ツールです。Aメロ、Bメロ、サビカードをランダムに選ぶだけでコード進行が完成です!私はこれがないと作曲できません。古いツールのなので、今ではAmazonなどでも欠品してますね…。再販してほしいものです。

blog.shimamura.co.jp

マクロは悪? いえ、善です。(Blueprintに限る)

前置きが長くなってしまいました。
さて、ここからは具体的なUE4プログラミングやTIPSの話を書いていきましょう。

以下、Blueprintのスクリーンショットが多く登場します。紙面の都合上、どうしても画像が小さくなってしまいますが、クリックすると大きく表示されますので、その機能をご活用ください。

また、ブラウザを複数立ち上げて、片方では文章、片方では画像の拡大を表示する、などの小技を使えば、より見やすくなるでしょう。

定数はどうしてますか?

最初に固定値を決めたっきり、以後は絶対変更したくない変数、つまり定数のことですが、皆さんはBlueprintではどのようにしていますか? 残念ながら、Blueprintには定数という概念がありません。

多くの方が、例えば変数名を大文字とアンダースコア(_)の組み合わせにしてみたり、

f:id:sgtech:20181125230013p:plain

接頭辞や接尾辞を付けるなど、命名規則を工夫して対処しているかと思います。

f:id:sgtech:20181125230011p:plain

C++ならconst修飾子がありましたが、

const int MaxPositionHistory = 256;

Blueprintではそうはいきません。

ここでマクロの出番です。
特にC言語の時代を知っている人は、マクロは危険という認識から、UE4でも避けてしまっているのではないでしょうか。

#define MAX_POSITION_HISTORY (256)

こんな風に書いちゃった日には、確実に先輩にドヤされます。なぜなら、C/C++言語のマクロとは、その数値が整数型なのか実数型なのか、ましてやそれが数値であるかどうかすら、コンパイラは全く気にせず、単なる文字列の置き換えで対応してしまうからです。

ところが、ご存知の通り、UE4のマクロはきっちり型を意識してくれます。ですので、

f:id:sgtech:20181125230008p:plain

こういうマクロを作ると、これは間違いなくInteger型の256となってくれます。
そうすると、

f:id:sgtech:20181125230005p:plain

ミソは、詳細(Details)パネルの「Compact Node Title」の欄にノード名を書くことです。

f:id:sgtech:20181125230053p:plain

これを書くことで、「いかにも定数です」みたいな見た目になってくれます。

f:id:sgtech:20181125230050p:plain

ゲーム用定数マクロライブラリを用意する。

上記の例はアクタの中で作っていましたが、ゲーム全体で使う定数はどうしましょうか。答えは簡単、マクロライブラリを作れば解決します。私はUE4で何か作る際には必ず「Game Constants Macro Library」といった名前のマクロライブラリを作り、ゲーム全体で使う種々の定数を入れています。

これで、このマクロライブラリを編集するだけで、ゲーム中の様々な状態を変更することができるようになりますね。例えば、バランス調整用の定数マクロライブラリなどがあっても良いかもしれません。(ゲームエンジンの時代に定数でバランス調整って、時代遅れな気もしますが…)

なお、定数を変更したらコンパイルしなくてはなりません。ここはご注意ください。

ノードグラフをより見やすく、便利にするための定数マクロ

それでは、ゲーム専用に限らず、Blueprintそのものの見通しをよりよくするために、こういうマクロを作ってみてはいかがでしょうか?

f:id:sgtech:20181125230047p:plain

作り方は先ほどの Max Position History と同様です。Boolean型のFalseを返すマクロを作り、Compact Node Title に「False」と書くだけ。

f:id:sgtech:20181125230630p:plain

同様の方法でTrue マクロも用意しておきましょう。

これらは、作成者の意図を明示するだけでなく、ノードグラフをサクサク組むための助けにもなってくれます。「Compact Node Title」に書かれた文字列は、右クリックで出てくる検索ウィンドウに引っかかるようになってくれますので、例えば「True」や「False」と入力するだけで、今作った Trueマクロや Falseマクロがサクっと引っぱり出せるようになります。

f:id:sgtech:20181125230043p:plain

これらもマクロライブラリを作って、便利そうなものはどんどん入れておきましょう。参考までに、私のマクロライブラリに入っている便利定数をご紹介します。UE4/Blueprintライフが向上すること請け合いです。

f:id:sgtech:20181125230701p:plain

よく使う整数として「Integer 0」や「Integer 1」。このほかにも「Integer -1」などを用意してます。これを使うと、Make Literal Int を検索して、(あの小さい枠に)0を入力して、の手間がまるっと解消されます。

f:id:sgtech:20181125230037p:plain

同じく「Float 0, 1, -1」。ちなみに上記は軸との内積の正負で、回転角度のプラスマイナスを決めるような、比較的よくありそうなノードグラフですね。

f:id:sgtech:20181125230144p:plain

あの巨大な「Make Transform」ノードが非常にコンパクトになる「Transform Identity」ノード。ちなみにIdentityとは「単位行列」の意味。高校数学でしたっけ?内容は、Location=(0,0,0), Rotation=(0,0,0), Scale=(1,1,1) です。これも右クリックのノード検索メニューで「Identity」と入力するだけで出てきます。

下側のSpawn Emitter at Locationに繋がっているのは、意外と面倒な(0, 0, 0)とか(1, 1, 1)を一発で書くためのものです。

マクロ内ローカル変数を活用する。

 下のGIFアニメはぷちコン応募作品「Gravity One」に登場するザコ敵です。「プレドニゾローン」という、とても覚えられない名前がついてます。さて、このプレドニゾローン、キノコ状の「フタ(Flap)」が一定時間ごとに上がったり下がったりします。これをBlueprintで書いてみるとどうなるでしょうか。

f:id:sgtech:20181125230128g:plain

普通のコーディング例、もとい「ノーディング」例

Event BeginPlayとEvent Tick

f:id:sgtech:20181125230124p:plain

  • Event BeginPlay: 使っているメンバ変数に初期値をセットしています。
  • Event Tick: 後述のTickEvent UpdateFlap(カスタムイベント)を呼び出しています。ザコ敵とは言え、弾を撃つなど、もっとたくさんの処理がぶら下がるはずですが、上の図はあくまでサンプルだととらえてください。

TickEvent UpdateFlap

f:id:sgtech:20181125230120p:plain

  • Event Tickから呼び出され、「フタ(Flap)」の回転や上下動を行うカスタムイベントです。
  • カスタムイベントTickEvent Open/Close Flap を呼び出しています。このカスタムイベントは、フタの高さ(Float FlapHeight)を決めています。
  • FlapYaw変数の値を算出しています。現在のYaw角度+DeltaSeconds × RotationSpeedで、次の角度を求めています。
  • Set Relative Location And Rotation で、フタモデルSM_Flap (Static Mesh Component)の相対位置と相対角度をセットしています。

TickEvent Open/Close Flap

f:id:sgtech:20181125230116p:plain

  • TickEvent UpdateFlapから呼び出され、フタ(Flap)の上下動を行います。
  • Sequence - Then 0側:
    ・経過時間(Float ElapsedSeconds)を算出しています。
    ・経過時間÷開け閉めの秒数(Open/Close Duration)で開け閉めの割合(Alpha)を求ています。
    ・フタの高さを求めるマクロ(Make Flap Height Macro)(後述)に、開け閉めの割合(Alpha)と現在の開け閉め状態(Boolean CloseFlag)を渡しています。
    ・算出された新しい高さを、Float変数 FlapHeightに格納します。
  • Sequence - Then 1側: 経過時間が、開け閉めの秒数(Open/Close Duration)+待ち秒数(Wait Duration)を越えたら、経過時間をゼロクリアし、開け閉め状態のフラグ(CloseFlag)を反転します。
  • Open/Close Duration、Wait Duration、Elapsed Seconds の使い方がちょっとだけトリッキーなので注意してください。
    ・Elapsed Secondsの変化する範囲は 0~(Open/CloseDuration+WaitDuration)ですが、Make Flap Height Macro に渡す Alphaの値は WaitDurationを考慮に入れていないため、1を超えてしまいます。
    ・Make Flap Height Macro 内で Alphaの値を0~1にクランプしています。これで、Wait Duration秒数だけ開いたまま/閉じたまましばらく待つ、という状態を作ってます。

Make Flap Height Macro

f:id:sgtech:20181125230226p:plain

  • 入力された開け閉め率(Float Alpha)を0~1の範囲にクランプしています。
  • 入力された開け閉め状態のフラグ(Boolean Close)に応じて、OpenHeightからCloseHeightまでのをEaseするのか、CloseHeightからOpenHeightまでをEaseするのかを切り替えています。
  • Easeで求められた高さをNewHeightとして出力します。

多少トリッキーな書き方をしているのは認めますが、考え方は至って普通です。

  • 経過時間を計算する。
  • その経過時間から、フタの高さを求める。
  • 一定時間経過したら、開け/閉めを切り替える。

さて、ここで話題に挙げたいのは、この「フタの開け閉め」で使われている4つのメンバ変数です。

f:id:sgtech:20181125230223p:plain

  • Boolean CloseFlag: このフラグがTrueの時は閉め、Falseの時は開け、の動作を行う。状態切り替えのためのフラグ。
  • Float ElapsedSeconds: フタを開け始めてから/あるいは閉め始めてからの経過時間(秒)。
  • Float FlapHeight: フタの高さ(Z値)
  • Float FlapYaw: フタの回転角度(Yaw値)

 

さて、この、至って普通のノーディング、20年オヤジは何が気に食わないのでしょうか。

これらの変数は、単にフタを開け閉めするだけのための用意されました。しかし、フタの開け閉めなんて、このActorにとっては影響度/重要度はかなり低いです。私としては、重要度の低い変数(や関数)は、できる限り重要度の高いものとは分けて置いておきたいのです。本音を言うと、メンバ変数にすらしたくない。さもないと、クラスに機能が増えていくにつれ、メンバ変数の欄はどんどん肥大化し、重要度の高い変数と低い変数が同レベルでゴチャっと並んでしまうのです。

実はこの問題、マクロ内ローカル変数を使うことで解消できます。

マクロ内ローカル変数で重要度の低いメンバ変数を減らした例

(Event BeginPlayやEvent Tickは、おおむね変更はなく、想像がつくと思いますので割愛します)

TickEvent UpdateFlap (Modified)

f:id:sgtech:20181125230220p:plain

まずは簡単にできるところから。

フタの回転角度として使っていたメンバ変数FlapYawをなくしてしまいました。その代わりにUpdate Yaw Macroというマクロを作りました。これがミソです。

Update Yaw Macroの中身はこうです。

f:id:sgtech:20181125230217p:plain

Local Floatノードが「マクロ内ローカル変数」です。

  • 普通に「+」ノードなどに挿して、Floatとして使えます。
  • いわゆるSETノードに相当するののが「Assign」ノードです。
  • 上の図では「マクロ内ローカル変数に、YawAdderを足して、マクロ内ローカル変数にAssign(セット)する」「マクロ内ローカル変数をNewYawとして出力する」が行われています。

マクロ内ローカル変数の制限などについて。

  • 変数名がつけられませんので、複数のマクロ内ローカル変数を使うときは混乱しがちです。
  • 使う場合は、Local Floatから必ずラインを伸ばす必要があります。上の図のように、ひたすらLocal Floatノードから、相手のノードに直接つなげる必要があります。したがって、マクロ内ローカル変数をたくさん使うと、非常にたくさんのラインが飛び交うスパゲッティコードになりがちです。

このマクロ内ローカル変数、「Event Graph内に限り、常に値は保持される」という特徴があります。例えば、入力イベントに1つカウントアップするマクロを繋げると、ボタンを押すたびに1, 2, 3... と一つずつ増えてくれます。このアクタが破棄されるまで、この値がクリアされることはありません。(もちろん、自分でクリアした場合は除きます)

f:id:sgtech:20181125230212p:plain

ボタンを押すたび1増やす例

f:id:sgtech:20181125230325p:plain

Count Upマクロの実装

ただし、イベントグラフ内ではなく、関数内でマクロ内ローカル変数を使った場合、関数呼び出しのたびに値がクリアされますので注意してください。例えば、関数内で DoOnce マクロを使って、思ったように動かなかった経験のある方はたくさんいらっしゃると思います。これもマクロ内ローカル変数のこの性質が原因です。(どちらかというと、イベントグラフが特殊で、関数が普通の挙動をしているのですが…それはさておき)

興味のある方は、UE4標準の種々のマクロの実装を見てみてください。ダブルクリックするだけで見られます。皆さんが良く使うであろう ForLoop や ForEachLoop もマクロ内ローカル変数を使って実装されていますよ。

TickEvent Open/Close Flap (Modified)

さて、次は TickEvent Open/Close Flap関数がどう変わったか見てみましょう。

f:id:sgtech:20181125230322p:plain

こちらは比較的トリッキーです。戸惑われた方もいらっしゃるかもしれません。マクロが2か所あるのは良いとして、恐るべきことに(?)1つのグラフにカスタムイベントが3つ繋がっています。

それでは個々に見ていきましょう。まずは、Elapsed Seconds Macroから。経過時間を格納していたメンバ変数Elapsed Secondsをこのマクロで置き換えています。

f:id:sgtech:20181125230318p:plain

ここまでに紹介してきたマクロとほぼ同じような形ですが、InputsノードにあるResetピンで経過時間をゼロクリアできるようにしてあります。カスタムイベントTickEvent Open/Close Flap (Modified)のグラフをもう一度見てみてください。このResetピンには、カスタムイベント Reset ElapsedSeconds が繋がっており、さらにそのReset ElapsedSecondsイベントは、Sequence - Then 1の、時間経過後のOpen/Close切り替え時に呼び出されています。ここで、ElapsedTimeにゼロをセットするのと同等の処理を行っているわけです。

次に Make Flap Height Macro (Modified) マクロについて。これは「普通版」の同名マクロを変更して、メンバ変数CloseFlagを置き換えました。

f:id:sgtech:20181125230314p:plain

変更前のMake Flap Height Macroと見比べていただくと分かると思いますが、

  • CloseFlagの代わりにマクロ内ローカル変数を用意。
  • Toggle Open/Close ピンを新設して、CloseFlagの役割をするBoolean型のマクロ内ローカル変数のTrue/Falseを切り替え。
  • Toggle Open/Closeピンには、カスタムイベントToggle Open/Closeを繋げ、時間経過でのOpen/Close切り替えのタイミングでこのカスタムイベントを呼び出し。

という仕組みになっています。

いかがでしょうか。これでActorクラスにとって重要度の低い3つの変数 CloseFlag、ElapsedSeconds、FlapYawがメンバ変数から消えて、まさに必要な部分だけに登場する適材適所なグラフになりました。
(その代償として、初心者お断りなトリッキーなグラフになっていますが…)

次はさらにマニアックな技です。ご注意を!

さて、重要度の低い4つの変数のうち3つを「適材適所」な位置に移すことが出来ました。こうなると、最後の1つFlapHeightもメンバ変数から外してしまいたくなります。

あらかじめ言っておきます。ここから先はマニアックな使い方になります(ここまでも十分マニアックかもしれませんが)。ですので、他人がノードグラフを見ると、頭の上にクエスチョンマークを浮かべてしまうかもしれません。あるいは先輩に「やりすぎだ!可読性を上げろ!」と怒られてしまうかもしれません。

f:id:sgtech:20181125230310p:plain

  • TickEvent UpdateFlap (Maniac)
    ・TickEvent UpdateFlapのマニアック版です。
    ・ここまでに登場した同名イベントと同様、算出された高さと回転角度をフタ(Flap)モデルにセットしています。
  • TickEvent Open/Close Flap (Maniac)
    ・TickEvent Open/Close Flapのマニアック版です。
    ・ここまでに登場した同名イベントと同様、経過時間を計算し、そこからフタ(Flap)の高さを算出しています。
  • Flap LotationマクロとFlap Rotationマクロ
    ・Set Relative Location And Rotation 関数のNew Location引数とNew Rotation引数をバラすのが面倒になったので、マクロを作って見た目をシンプルにしています。
    ・Flap Location マクロ
     f:id:sgtech:20181125230429p:plain
    ・Flap Rotationマクロ
     f:id:sgtech:20181125230426p:plain

さて、このマニアック版でのミソは、カスタムイベントSet Flap Height (Maniac)と、Set Flap Height Macroです。

Set Flap Height Macro

f:id:sgtech:20181125230422p:plain

  • Inputsノードに指定されたマクロの引数In Flap Heightを、Float型のマクロ内ローカル変数に格納しています。
  • Outputsノードから、その格納された値をOut Flap Heightとして出力しています。

一見、全く何の意味もないプログラムのようにも思えますが、このマクロは「Event Graph内に限り、マクロ内ローカル変数の値は保持され続ける」特性を利用して、変数のGETとSETの両方の機能を持たせたものです。

  • SET機能: カスタムイベントSet Flap Height (Maniac)が呼び出されると、その引数の値をマクロ内ローカル変数にSETする。
    ・つまり、カスタムイベント、TickEvent Open/Close Flap (Maniac)でフタ(Flap)の高さが決まった際に、FlapHeight変数にその高さをSETする代わりにカスタムイベントSet Flap Height (Maniac)を呼び出します。
    ・SET(Assign)された値は、再びSET(Assign)されるまで、変更されることはありません。
  • GET機能: Out Flap Height を通じて、マクロ内ローカル変数に格納された値をGETする。
    ・マクロ内ローカル変数に格納された値は、Event Graph内であれば、いつでもGETすることができます。

このマクロを用いて、TickEvent UpdateFlap (Maniac)や TickEvent Open/Close Flap (Maniac)イベントは、以下のメカニズムで動いています:

  • TickEvent UpdateFlap (Maniac)で、Set Relative Location And Rotationが呼び出される際、Set Flap Height Macroの出力Out Flap Height経由で、マクロ内ローカル変数に格納された値(現時点のフタ(Flap)の高さ)をGETしています。
  • TickEvent Open/Close Flap (Maniac)で、Make Flap Height Macro (Maniac)で算出されたフタ(Flap)の高さを、Set Flap Height (Maniac)イベントを使って、マクロ内ローカル変数にSETしています。

さすがに無理矢理やった感は否めませんが、Actorのメンバ変数からはフタ(Flap)の上下動に関する4つの変数をメンバ変数欄から消すことができ、Event Graph内の必要なところでのみGETあるいはSETされる「適材適所」な形に整理することができました。

マクロ内ローカル変数を活用する際の注意点

  • デバッグが難しい: ウォッチができないので、Print Stringなどのログ出力を使う必要があります。
  • スパゲッティになりがち: マクロ内変数が増えるたびに、おびただしい数のラインが増えてしまい、可読性が非常に下がります。

簡潔に言うと、気を付けないとバグの温床になりやすい、ということです。そういう意味では、UE4やプログラミングにある程度慣れた人以外は、多用しない方が良いかもしれません。まずは、小さなサンプルを作って試し、正しく動くことを確認出来たら本使用に移すなど、守備的な方法で運用するように注意してください。特にチームで作業している場合は、チーム全員でマクロ内ローカル変数をどう使うかの意思統一してから使うべきでしょう。

弾幕で分かるBlueprintの限界

さて、次はBlueprintの最適化とプロファイリングの話をしましょう。

ぷちコン応募作品「Gravity One」は弾幕シューティングを目指しました。その意気込みの割には密度が薄かったのは反省点ですが… やはり弾が多いと処理落ちしてしまうのです。

Blueprintは重いと知りつつも、Blueprint信者としてC++に逃げることは許されません。結局、弾一個一個をアクタで作ってしまいました。結果、本当に重くなってしまい、ボス戦などでは60FPSをキープできていません。というか、40FPSくらいです。ホント、プロとしてお恥ずかしい限りです。

f:id:sgtech:20181125230408p:plain

この弾幕の最適化、今までずっと放置していたのですが、今回のブログのネタとしても面白そうだと思ったのでやってみました。Blueprint信者としての信念を曲げてのC++化もやっています。ここではそのあたりのお話をさせていただこうと思います。

当初から重くなるのは覚悟してました。

もともと弾幕を出すつもりでしたので、作り始めた当初から重くなるのは想定していました。ですので、弾のActorはできる限りシンプルに組んだつもりです。

まずは、Tickの仕事を最小限にするように気を付けました。

f:id:sgtech:20181125230359p:plain

  • Sequence - Then 0: 移動計算は地球表面に沿って直進するだけ(Ballisitic Object.Update)。その結果をSet Actor Location And Rotationに渡している。
  • Sequence - Then 1: Dynamic Material Instance を使って、時間に応じて色を変化させる。
  • Sequence - Then 2: 一定時間で自滅させる。

コンポーネントの構成も非常にシンプルです。

f:id:sgtech:20181125230512p:plain

  • Sphere Collision: ルートコンポーネントとして球コリジョンを使っています。
  • Bullet: その子コンポーネントとして、Material Billboard Component を使っています。つまりメッシュですらありません。

コリジョンプロファイルも限定的にしました。

f:id:sgtech:20181125230509p:plain

  • プレイヤーオブジェクトに対してのみ「Overlap」
  • その他のオブジェクトに対してはすべて「Ignore」
  • 「Block」は一切使っていません。

セッションフロントエンドでCPU負荷をチェックしてみる。

セッションフロントエンド、使ったことのない方は、これを機にぜひ使ってみてください。使い方は、UE4公式にあります。ちゃんとわかりやすく書かれているのでご安心を。

では、ボス戦をDevelopmentパッケージで調べてみた結果を見てみましょう。

f:id:sgtech:20181125230505p:plain

画像をクリックすると拡大して見られますが、それでも見づらいと思いますので要点をまとめます。

  • 敵の弾Actor (BP_EnemyBullet01)がダントツで重い。14.404 ms。案の定、弾幕がボトルネックになっていた。

そのうち顕著なのは、

  • Set Actor Location And Rotation 7.167 ms/コール数 814.3
  • BP_BallisticObject.Update 2.582 ms/コール数 814.3

となっています。

ちなみに60FPSですと、1フレームは16.667 msです。
つまり、弾幕だけでCPUリソースの14.404 / 16.667(9割弱!)使っていることになります。

最初からシンプルになるように組んだことが功を奏しています。なぜなら、ボトルネックのトップとしてUE4の標準関数(Set Actor Location And Rotation)が挙がっており、自作の関数たちは次点以下になっているからです。自作の関数BP_BallisticObject.Updateが次点ですので、ここはもちろん考察しておくべきですね。

まずは、自作関数を見てみましょうか。

では、ボトルネック次点となった自作関数BP_BallisiticObject.Updateから。

f:id:sgtech:20181125230459p:plain

正直なところ、この関数もとてもシンプルで、これを最適化しろと言われると困ってしまいます。やっていることは、1フレーム前の「弾を地球中心から見たベクトル」を、今回の移動量分だけ地球の中心で回転しているだけです。

この関数については、最適化方針が見出せなかったので見送ります。これ以上軽くするには「とんち」(試行錯誤の労力、あるいは天才的な閃き)が必要そうです。後述しますが、結局は、こんなシンプルな関数でも大量に呼び出されるとボトルネックとなってしまうということですね。なんせ、ボス戦では700~1000個の弾が動いているわけですから。

では、Set Actor Location And Rotation をどうにかしましょうか。

念のため言っておきますが、UE4標準関数のSet Actor Location and RotationのC++コードにテコを入れるという話ではありませんよ。ここで問題視したいのは、コール回数の多さです。ざっくり調べてみたところ700~1000回の呼び出しがありました。ですので、今回の最適化の方針は

  • Set Actor Location And Rotation を呼ばなくていい時は呼ばない

ということにしてみます。

それはそうと、Set Actor Location And Rotation をはじめ、アクタのトランスフォームをセットする関数(以下、総称して Set Actor Transform系と呼びます)が重いということを知らない人が意外と多いようです。

f:id:sgtech:20181125230455p:plain

せっかくセッションフロントエンドを開いていますので、Set Actor Location And Rotation のやっていることをざっくり確認してみましょう。

f:id:sgtech:20181125230559p:plain

見づらいでしょうから、要約します。なお、エンジンのC++コードを開いて詳しく調べたわけではなく、関数名から想像した話でしかありませんので、ご注意を。

GeomSweepMultiple 2.480 ms/コール数 814.3

関数名から、コリジョンのSweep形状を作っていると想像できます。Set Actor Transform系の「Sweep」パラメータが関連するのでしょう。

f:id:sgtech:20181125230556p:plain

ということは、Sweepしない方が軽いのでしょうが、さすがにSweep処理なしでシューティングゲームを作るのは至難の業ですね。

UpdateComponentToWorld 2.037 ms/コール数 814.3

関数名から、そのアクタに付随するコンポーネントのトランスフォームをワールド系に変換していると想像できます。つまりは、コンポーネントが多ければ多いほど重くなるのでしょう。

Set Actor Location And Rotationのその他の処理

  • Component PostUpdateNavData: NavMesh系の処理だと想像できます。NavMeshが不要なら切りましょう。
  • UpdateOverlaps Time, MoveComponent FastOverlap: どちらもコリジョンのOverlap系の処理だと想像できます。

これらをまとめると、下記のようになるでしょうか。

  • Sweepが不要なら、積極的にOFFにする。
  • コンポーネントの数を必要最小限にする。
  • NavMeshが不要なら、積極的にOFFにする。

また、Set Actor Transform系が重いということから、UE4の基本として:

  • そもそも、Set Actor Transform系の呼び出しは、Tick1回の流れで1回だけにとどめるべき。

についても留意するようにしましょう。

カリング処理を作ってみる。

f:id:sgtech:20181125230551p:plain

上の画像は、地球を非表示にした状態でのボス戦です。見ての通り、実は地球の裏側でも弾幕が動いていたのです。どうしようもなく無駄ですね。このような見えないところにあるオブジェクトの処理を省くことを、カリング処理といいます。

カリング処理については、Gravity Oneを実装していた当初から、気にしつつも放置してきた部分です。と言いますか、プロとしてカリング処理をしていないこと自体お恥ずかしい。それくらい基本中の基本です。では、当時の私はなぜカリング処理を実装しなかったのか。もちろん1番の理由は、やることが多すぎてそれどころじゃなかったからですが、実際のところ他にも躊躇する理由があります。

  • 重いと言われているBlueprintでカリング処理を組んだ場合、逆にそのカリング処理が重くてボトルネックになってしまう可能性がある。

Blueprintでは関数コールは重い処理の一つです(関数名で検索してから呼び出す仕組みとの話です)。ノード1個1個が関数に相当しますから、ノードの数が増えると比例して重くなる、ということです。したがって、カリング処理もできる限りシンプルに組む必要がありますが、カリング処理の多くは算術演算が比較的多くなってしまいがちです。ご存知の通りBlueprintでは「+」や「-」すら一つのノードになっています。これも、もちろん関数呼び出しです。つまり算術演算は関数コールが増えやすいものの一つです(なお、Math Expressionを使って軽減することはできます)。この算術演算周りの負荷が高くなってしまった場合、せっかく作った最適化処理(カリング処理)は逆効果となってしまいます。ここを懸念していました。

Blueprintでの最適化処理は、(今の私のUE4技能レベルですと)「とりあえず作ってみて、効果を確認する」の試行錯誤を繰り返すことが必要だと思います。労力がかかる上に、うまく行くかどうかが不明。困ったものです。そういう意味では、Blueprintでの最適化に悩まずに、とっととC++へ移行した方が、費用対効果は高いかもしれません。

今、このブログの執筆時点では、時間に追われているわけではありませんし、面白そうだし!ということで、やってみました。

内積1発で済ますカリング処理。

上記に書いた通り、Blueprintで組んだカリング処理が重くなっては意味がありません。ある意味、C++で組まれたUE4標準関数Set Actor Location And Rotationに勝てるような、できる限りシンプルなカリング処理を選ぶ必要があります。そこで今回は「内積1発でざっくり判定」する方法を選びました。

もともと、プレイヤー機や敵の弾をはじめ、すべてのアクタのUpベクトルが地球表面の法線ベクトル(球の中心から球面を垂直に突き抜けるベクトル)に一致するように作ってあります。このUpベクトル同士の内積を取れば、プレイヤー機と敵の弾の「球面に対する」位置関係が分かるはずです。内積の値が1に近ければ、球面上でプレイヤー機に近い位置にいて、-1に近ければ、球面上では反対側にいるはずです。

結果、弾のアクタのTickはこうなりました。

f:id:sgtech:20181125230547p:plain



  • クラスメンバ変数Location (OptTest)とRotation (OptTest)を用意。修正前ではGet Actor Locationから取得し、Set Actor Location And Rotation でセットしていたところを、いったんこの変数に保存している。これは、Set Actor Location And Rotation を呼ばなくても、Tickのたびに更新される位置と回転を保持しておくため。
  • 修正前ではSet Actor Location And Rotationを呼び出していた場所で、カスタムイベント「TickEvent Cull If Outsight」を呼び出している。このカスタムイベントについては後述する。
  • その他の個所については、変更なし。

カスタムイベント TickEvent Cull If Outsight

f:id:sgtech:20181125230625p:plain

  • TickEvent Cull If Outsight: プレイヤー機と敵の弾のUpベクトル同士の内積の大小を判定しているところ。Math Expressionの 0.7071は cos(45度)の値。つまりプレイヤー機のUpベクトルとの角度差が45度以内のものを可視と判定している。
  • SubEvent Cull If Outsight > Sequence - Then 0: 可視圏内/可視圏外へ切り替わった時の処理。SweepのOFFと、コリジョンのON/OFF、アクタの表示/非表示を切り替えている。可視圏内に入った直後はSweepをOFFにしておかないと、過去の可視圏内だった位置から可視圏内に戻った現在位置まで、長いSweepが作られてしまい、意図せぬコリジョンが発生してしまう。
  • SubEvent Cull If Outsight > Sequence - Then 1: 可視であれば Set Actor Location And Rotation を呼び出した後、SweepフラグをTrueにしている。

さて、「内積1発で済ます」と言った割には大きなノードグラフになってしまいました。これがプログラミングの怖いところですね。果たしてこの修正でパフォーマンスは改善したのでしょうか。

セッションフロントエンド再び。

f:id:sgtech:20181125230544p:plain

  • 敵の弾Actorの負荷は 2.991 ms改善(14.404 msから11.413 msへ)
  • Set Actor Location And Rotation のコール数が半減(814.3から429.0へ)。今回作ったカリング処理で、半数の弾の Set Actor Location And Rotation が呼ばれなくなったため。結果、負荷が半減(7.167 msから3.566 msへ)。
  • BP_BallisticObject.Update は現状維持(変更を加えていないため)

おおよそ3 msの軽減という、良い結果が得られました。Blueprintでも負荷の低いカリング処理が組めたようです。3 msというと、単純計算で弾があと234.5個出せます。これはかなり良い結果と言えます。成果が得られずに色々な手法を試行錯誤する覚悟をしていましたが、一回で結果が出てホッとしています。

現実のゲーム開発では0.5 msも稼げれば相当頑張ったと言えます。各プログラマがおのおの自分の担当分野で頑張って少しずつ稼ぎ、合計で3 ms稼ぐ、といったところです。そういう意味では、今回の3 msという成果は、逆に怒られる領域かもしれませんね。「こんな基本的なところを今までなぜ放置してた!」と。(はい。申し訳ありません…)

C++化も試してみる。

さて、せっかくですからC++化も試してみましょう。C++化と言っても「Nativize」の方です。ところで「Nativize」とは日本人には非常に読みにくい綴りです。Web辞書で調べてみたところ「ネイティバイズ」と発音するようです。このNativize機能はUE4による「自動C++化」です。非常にありがたい機能が実装されたものです。

プロジェクトセッティングを見てみると、Nativizeには3つのモードがあります。Project Settings >Project > PackagingにあるBlueprint > Blueprint Nativization Method の項目があります。

f:id:sgtech:20181125230639p:plain

  • Disabled: Nativizeなし。デフォルト。
  • Inclusive: プロジェクトのBlueprintをまるっと一式Nativizeする。
  • Exclusive: 指定されたBlueprintのみNativizeする。(ただし関連するBlueprintも一緒にNativizeされる)

残念ながら、Nativize機能はうまく行く場合とうまく行かない場合があるようです。本シューティングゲームGravity Oneも、Inclusive(まるっと全部)のNativizeはエラーが出てしまい、うまく行きませんでした。今後のアップデートに期待です。

一方、今回の「敵の弾」に対してのみNativizeするように指定した、Exclusiveモードではうまく行きました。今回はこちらについてお話します。

ExclusiveなNativizeのしかた

まずは、NativizeしたいBlueprintを開き、上のボタンバーから「Class Settings」をクリックします。そうすると詳細パネルにクラス設定一覧が出ますので、その中の「Packaging > Nativize」にチェックマークを入れます。

f:id:sgtech:20181125230636p:plain

これをNativizeしたいすべてのBlueprintに対して行います。なお、チェックマークを入れたBlueprintだけでなく、それと参照関係にある他の(チェックマークを入れていない)BlueprintもNativizeの対象となるようです。それがいわゆるReference Viewerでみられる参照関係と同じなのか、他に法則があるのか、などは調べていないので分かりません。悪しからずご了承を。

次に、プロジェクトセッティングを開き、Project > Packagingから、Blueprints > Blueprint Nativization Methodを「Exclusive」にします。(意外とこの設定を忘れがち)

f:id:sgtech:20181125230634p:plain

あとは、パッケージングするだけです。ちゃんとNativizedされたかどうかを確認するには、Output Logで「NativizedAssets.cpp」を検索すると良いでしょう。また、NativizedAssets.cppの付近には、NativizeされたBlueprint名に妙な数字と.cppがつけられて並んでいますので、何がC++化されたかを知ることもできます。

今回は、カリング処理なし/ありの両方についてNativizeしてみました。それらのプロファイリング結果を見てみましょう。

Nativizeされた弾幕はどれくらい軽いか?

結果を以下に並べてみます。()内の数字は、最適化前の状態のNativizeなし/カリングなしの状態に対して、何 ms高速化できたかを表しています。

  • Nativeなし/カリングなし 14.404 ms(0.000 ms)
  • Nativeなし/カリングあり 11.413 ms(2.991 ms高速化!)
  • Nativeあり/カリングなし 7.618 ms(6.786 ms高速化!)
  • Nativeあり/カリングあり 7.266 ms(7.138 ms高速化!)

見ての通り、小躍りしたくなるような結果を得られました。これを見ると手動でC++を組むのがばかばかしくなってきますね! 問題はNativizeが失敗することが多々あることですが、今後のUE4アップデートに期待することにしましょう。

弾幕で分かるBlueprintの限界

ここまでの話をまとめます。文字ばっかりになりますが、ご容赦くださいね。

Blueprintをできるだけ軽く組むために。

Blueprintは非常にサクサク組めて、イテレーションサイクル(試行錯誤の繰り返し)も速いので、UE4でのゲーム開発では積極的に取り入れるべきものだと思います。一方で、Blueprintは所詮スクリプトに過ぎないという一面もありますので、CPUパフォーマンスに問題が出やすいのも事実です。大事なことは、Blueprint/Nativize/手動C++の境界をどこに置くかで、これを見極めることができると、サクサク作れて高パフォーマンス、という夢のような環境を手に入れることも不可能ではないでしょう。

その見極めの基準の一例として、Blueprintで注意するべき項目を並べてみようと思います。これはあくまで私個人の見解で、セガのUE4エンジニアの総意ではありませんので、その辺はご注意ください。

  • アクタの数やTickの数を必要最低限に絞る: この数は純粋にCPU負荷に影響します。今回の弾幕のようにアクタ数が多いと、明確に分かりやすく処理落ちし始めます。まずは、アクタの数を必要最低限に絞ることです。また、Set Actor Tick Enabledなどを使って、不要なTickは切ってしまいましょう。
  • Set Actor Transform系はTick1回につき1回まで: 前述したとおり、一見トランスフォームをセットするだけのように見えて、その中ではコリジョンのSweep処理や、コンポーネントの座標変換など多くの処理が行われています。位置や回転などのトランスフォーム値は変数で計算しておき、確定した段階でSet Actor Transform系を呼び出すようにしましょう。また、不要なSweepは行わないこと、コンポーネントの数を絞ることも大事です。
  • ループの数を減らす: 今回の記事では取り上げていませんが、Blueprintのループ処理も重い処理の一つです。試しに、256回回るForLoopを2重にして(つまり256x256=65536回ループ)実行してみてください。劇的に重いことが分かるかと思います。これは、パッケージ化すると改善はされますが、劇的に軽くなるわけではありません。また、ループはエディタでの実行とパッケージでの実行との速度差が大きく、開発中にCPU負荷を見積もるのが難しいので、ループを多用している場合は、パッケージでの動作チェックを頻繁にすべきでしょう。
  • できる限りノードの数を減らす: Blueprintの関数呼び出し、イベント呼び出しは、比較的重い処理のようです。できる限りシンプルなノードグラフを組んで、関数呼び出しそのものを減らす工夫をしましょう。また、UE4標準で用意されている関数は、たいていは内部がC++化されています。似た処理を複数のBlueprintノードで自作するよりは、UE4標準関数を積極的に使いましょう。また、UE4アップデートのたびに便利関数がこっそり増えていたりします。UE4リリースノートや右クリックの検索ウィンドウなどを活用して、新しいノードや機能をマメにチェックしましょう。

何を基準にNativize/手動C++化に踏み切るか。

個人的には、上記4項目がそこそこ出来ているならば、最適化処理をBlueprintで組んで試行錯誤するより、Nativizeを試すべきだと思っています。そういう意味では、上記4項目がBlueprintの限界点だと言えます。

今回例に挙げたカリング処理は幸いにも成果を得られましたが、一方で、Blueprintで最適化を模索するのは、Blueprintそのものの重さが災いして、うまく行かない可能性も高いと思っています。その試行錯誤にかけた手間暇に対して、得られる効果が見合ったものかどうか、いわゆるコストパフォーマンスは、私のUE4技能レベルではまだまだ謎の領域にあります。

ですので、Blueprintが一定のレベルまで組めているなら、最適化の模索より先にNativizeを試すほうが建設的かと思います。前述したとおり、得られる効果は絶大です。Nativizeでエラーが出てしまうようなら、そこで初めて、Blueprintでの最適化を模索するか、手動でC++を組むかを悩むべきでしょう。

ここで、Blueprintでの最適化と、手動でのC++化が、同レベルで天秤に掛かっていることを不思議に思う方もいらっしゃるでしょう。一見、手動C++化で決まりだと思えます。しかし、私が手動によるC++化を避けている理由は、関連するゲームプログラム周りの設計を再構成する必要がでてしまいやすい点にあります。例えば、ゲームバランスに直結するようなBlueprintを安易にC++化してしまうと、以後ゲームバランスを変更するたびに Visual Studio でのビルドが必要になり、作業スピードが落ちてしまいます。また、アーティストやレベルデザイナーが頻繁に触っているBlueprintをC++化するわけにもいきません。そのためには、基底クラスをC++で作って、そのBlueprintの親にする等、何を表(Blueprint)に出して、何を裏(C++)に隠すかを設計しなおさなくてはならないのです。一方のNativizeはパッケージングにのみ影響する機能ですから、その辺の心配はいりません。

手動C++化については、もっと気楽な方法もあります。Blueprintでゲーム全体を組むと決めてしまい、C++はそのためのツールを提供するためのものと割り切ってしまうことです。例えば、基本機能を充実させた、そのゲーム専用の基底クラスをC++で作っておいて、Blueprint側はその基底クラスを派生して使うようにする、他には便利関数や便利クラスなどの小さいが高速なモジュールをC++で用意しておくなどです。

まあ、これらはBlueprint信者の妄信かもしれません。参考にしていただきつつも、皆さんは皆さん流のBlueprint/C++使い分け術を見出していただければと思います。

おわりに

 夢中になって書いてしまった結果、非常に長くなってしまいました。ここまで読んでくださった皆さん、本当にありがとうございます! 楽しんでいただけましたでしょうか?

とは言え、これでも書こうと思っていたトピックの4分の1だったりします。現時点でもあと6つくらいトピックがありますし、今後もUE4を使う限りどんどん増えていくでしょう。また、社内の他のUE4エンジニアたちも、あれこれたくさんトピックを持っているようです。今回紹介できなかったトピックも、また機会を作って紹介できればと思っています。

ですので、今回の記事をお楽しみいただけた方、ぜひご反響をくださいませ! 今後もUE4のトピックをどんどん増やしていきたいと思います。

インディーズのススメ

ゲームエンジンの時代が到来して久しいですね。それに伴い、そのゲームエンジンで活かしてもらおうと、3Dモデリング環境や、画像・映像制作環境など、ゲームの素材を作る環境もどんどん安価に・身近になってきています。冒頭でもいくつかツールを紹介しましたが、今やゲーム制作環境の大部分が一般に降りてきています。何ならフリーのツールだけでも全部作れてしまいます。やる気さえあれば、誰でもゲームを作れる時代が到来しました。

今回題材に使いましたぷちコン応募作品「Gravity One」も、未完ながら「MAGITYPE」も、概ね全部の素材を私一人で作っています(もちろん、ものすごく大変でしたけど)。一昔前なら、チームを組まなくては作れない規模のゲームも、いまや個人や少人数グループの頑張りで作れてしまいます。

明らかに時代は変わってきています。ゲームエンジンが台頭する前は「特定の分野一つに強い職人」を集め、チームを組んで、リーダーが全体を統括しながら作る時代でした。それゆえに、企業が強い分野でもありました。一方、昨今では、高度な/専門的な知識はあらかじめゲームエンジンに内包されており、ゲーム開発者は「その使い方と効能」を理解してさえいれば良くなりました。その結果、ゲーム開発者は、特定の分野一つを専門にして深く理解/応用する労力から解放され、複数の分野に渡って広く知る余裕が出来てきました。

今後は、「複数の分野を知っていて、それらを上手く組み合わせてコンテンツを作れる人」同士がチームを組んで、その相乗効果でより面白いものを作る時代になるのでしょう。ゲーム開発者はより「面白さ」に専念できるようになりました。

皆さんも、小規模で良いので、「一つのパッケージを自分一人で作り上げる」を試してみてはいかがでしょうか。他人の評価を得ることは何にもまして勉強になりますし、その成果を何らかの形で発表すると、なお良いでしょう。ゲーム制作の全部の流れを何となくでも知っておく(体に通しておく)と、非常に視野が広がり、例えば、プログラマがアーティストの作業を知ると、以後、お互いの作業領域に重なりができることで、お互いにカバーしあえる体制が出来上がります。これは非常に良い信頼関係となります。

ゲームエンジンの台頭によって、ゲーム開発者の価値が変わりつつあります。今はまだ「UE4を上手く使える」「Unityを上手く使える」という技能面だけで、それなりのステータスを得られますが、将来は、ゲームエンジンを上手に使えることよりも、「どんなテイストのゲームが作れるか」「どんな触り心地のゲームが作れるか」といった「クリエイティブ力」が、今まで以上に、ゲーム開発者の価値となる時代が来るでしょう。例えば、いわゆる「絵師さん」。そのテイストや画風が人々に受け入れられ、その絵師さんの価値となるように、あなたの作ったゲームの「テイスト」や「触り心地」が人々に受け入れられ、あなたの価値となる、そういう時代になるのでしょう。

セガ・インタラクティブではUE4エンジニアを募集しています。

長いうえに説教臭い話でしたね。すみません。私も年を取ったもんです。

それはさておき。

我ら「セガ・インタラクティブ」は、アーケードゲームをはじめ、スマホ、テーマパーク、ショッピングモール、はたまたeスポーツなどなど、様々なマーケットにエンタメを投入する、セガの中でも一風変わったオモシロカンパニーであります。弊社では、UE4エンジニアがまだまだ足りません。まさに、この長い記事を最後まで読んでくださったイケてるUE4遣い(とその候補)の皆さん! 弊社にご興味を持っていただけると嬉しいです。

採用情報などはこちらに記載されています。是非ご確認ください。

採用情報|株式会社セガ・インタラクティブ - 【SEGA Interactive Co., Ltd.】

 

また、UE4インターンも「画策」しています。「開催」や「予定」と書かず「画策」と書いたのは、まだいつ開催できるかが不明だからですが、弊社のUE4エンジニアと、イケてるUE4遣い(とその候補)の皆さんとで、一緒にゲーム開発を体験できれば!と思っています。開催された折にはぜひご参加くださいませ。

弊社では、もちろんUnityタイトルも出しています。スマホに限らず、ゲームセンターにもUnityタイトルを出していますので、イケてるUnity遣い(とその候補)の皆さんも是非、セガ・インタラクティブへ! そして、おそらく最も万能な、イケてるC++遣い(とその候補)の皆さんも是非、セガ・インタラクティブへ!

私たちと一緒にオモシロ体験を作っていきませんか?

 

©SEGA

Powered by はてなブログ