Unity Pro Tips

DEVELOPER詳細

ローエンドのモバイル端末で大容量ゲームを動かすヒント

<このページで学べる内容>

ローエンドのハードウェアで大容量のモバイルゲームを動作させる方法について、有用なテクニックをご紹介いたします。今回ヒントをご紹介いただくのは、Jason Booth 氏。彼は、25 年以上にわたってゲーム開発に携わり、現在は Disruptor Beam 社でグラフィックス & クライアントアーキテクトを務めている人物です(アセットストアのパブリッシャーでもあります)。 ここで紹介する最適化手法は、『The Walking Dead: March to War』の開発に使用されました。このゲームでは、開発当時の Android デバイスの約 40% を占めていた 1GB の RAM 搭載の OpenGLES 2.0 デバイスでもスムーズに動作させるという大胆な目標を掲げました。細やかなアート、昼夜と天気のサイクル、数千個ものオブジェクトで満ちているゲームを、100MB 以内に抑えなければなりませんでした。Jason 氏のチームがそれをどうやって実現したのかをご覧ください。

1)ゲームの概要

The Walking Dead: March to Warは、Robert Kirkman 氏の長期連載漫画『The Walking Dead』の世界で繰り広げられる、激しいマルチプレイヤーオンラインストラテジーゲームです。このゲームの最大同時接続プレイヤー数は 50,000 人です。バージニア州とワシントン D.C. を舞台とした巨大かつ詳細なフリースクロール型の世界は、何千体もの「ウォーカー」(ゾンビ)で溢れかえっていて、一触即発の状況です。

ゲームのワールドを構築する部品は、ロード時とレンダリング時のサイズは 32x64 ですが、ゲームプレイで使われるタイルの大きさは 2048x1024 です。また、常時 4 ~ 6 つの区域がレンダリングされています。ゲーム内のマップは、手動配置コンテンツとプロシージャルコンテンツを組み合わせて作成されています。

非常に簡単な仕組みの説明:手動配置コンテンツとプロシージャルシステムがマップコンパイラーでコンパイルされます。32x64 の各ブロックについて、システムがプロシージャルコンテンツを生成してから、手動配置データのブール値を生成します。マップコンパイラーが、さらにテレインデータと AI システム用のナビゲーション情報をコンパイルします。最終結果はプレハブストリーム形式で保存されます。これはスタジオ独自のプレハブシステムです。最後に、全データがアセットバンドルを使用してプレイヤーにストリーミングされます。

Unity Walking Dead:マップコンパイラー

2)容量を大幅に削減:カスタムプレハブ「ストリーム」

Disruptor Beam のカスタムプレハブソリューション(「ストリーム」)は、設計時と実行時の両方で使用されており、ネスティングをサポートしています。ストリームは InstanceEntry の配列を格納し、構築と配信に使用されます。

編集時にプレハブストリームで追加コードを使用することで、ランダム化などのアクションが可能になります。特定のブロックについて、プレハブストリームは複数の要素をランダム化し、微妙なバリエーションを生み出すことができます。家や乗物などのアイテムは、同じアイテムでもバリエーションが生まれるように、ミックスやマッチングが可能な小さなブロックから作成しています。

Unity Walking Dead:カスタムプレハブ「ストリーム」

コンパイル時に、プレハブストリームは 3 つの詳細レベルに分けられます。一部のオブジェクトをローエンドデバイスで非表示にできるように、オブジェクトは高、中、低の各レイヤーに割り当てることが可能です。コンパイルがすべて完了すると、レイヤーはフラット化されるため、利用者への配信時には階層はなくなっています。

実行時に、プレハブストリームはグラフィックエンジンの下位レベルの描画リストと同様の働きをします(どのメッシュをどのマテリアルで描画するかを示します)。その際、Transform の位置、つまり、特定のプレハブをどこに配置するかを指定する位置のリストを使用します。

プレハブストリームは Transform をパックします。ブロックのサイズがすでに判明しているため(160m の範囲)、Transform を 7 つの「ショート」(ポジション用に 3 つ、回転用に 3 つ、スケール用に 1 つ)にパックすることが可能です。

その結果、利用者にストリーミングされるシーンデータのサイズが大幅に削減されます。ブロックをシーンとして保存すると、サイズは約 3.6MB ですが、プレハブ形式で保存した場合は約 2.1MB になり、プレハブストリーム形式で保存した場合はたったの 41KB になります。

3)メッシュフリップブックによるキャラクターのアニメーション化

『The Walking Dead: March to War』のキャラクターは、間近で表示されることはありません。身長は 60 ピクセルしかなく、アニメーションセットも限られています。また、ローエンドのデバイスでドローコールがうまく機能しなかったため、キャラクターをバッチ処理する必要がありました。通常の手順ならば、キャラクターにテクスチャを適用し、頂点シェーダーでサンプリングして、すべてのアニメーションをシェーダーで処理します。しかし、OpenGLES 2.0 は頂点シェーダーでのサンプリングをサポートしていないため、別の解決策が必要でした。

そこで、開発チームではアニメーションをメッシュフリップブックに変換しました。ロード時に、各アニメーションを取り出し、1 フレーム = 1 メッシュとなるよう、すべてのフレームを固有のメッシュとしてベイクしました。さらに、各キャラクターのメッシュをスワップしてアニメーション化しました。しかし、この方法を使用すると、多くのメモリが必要になります。

Unity Walking Dead:メッシュフリップブックによるキャラクターのアニメーション化

4)密度の高いナビゲーションシステム

ナビゲーションデータはマップコンパイラーで計算されます。レイキャスティングを使用し、キャラクターの移動可能範囲内のオープンスペースの位置を把握します。マップコンパイラーでは、キャラクターがその下を歩けるように、高架橋などのオブジェクトも考慮されます。最終的な結果では、1 領域あたり 64x64 ビットのデータとなります。密集させることにより、ワールド全体を約 1MB のデータに収めています。

5)パーティクルシステムと同様にアップデートループを構築する

十分に速く、かつ簡潔なアップデートのループを作成するには、Update() の使用、仮想関数、オブジェクト指向のオーバーヘッドを避けることが大事です。関数はインライン化し、余分なものを取り除くことをお勧めします。パーティクルシステムなどのコードを作成するときと同様です。パーティクルシステムでは、パーティクルの配列を素早く送信し、すべてを一度にアップデートします。ゾンビなど、何千ものオブジェクトを使用する場合、通常はこのようにしてコードを作成すべきです。

6)キャッシュに優しく

CPU は一定の順序でメモリに素早くアクセスしてデータを処理することを得意としています。そのため、データを適切に配置した巨大なブロックを用意し、それらすべてに対して同じルーチンを実行することが重要です。これはシェーダーの処理と似ています。シェーダーはピクセルをブロック単位で処理します。サイズができる限り小さくなるようデータを設計すれば、データをすべて効率的にキャッシュし、必要に応じて CPU の処理時間を短くすることができます。Walking Dead の例では、64x64 ビットのデータグリッドに対するレイキャスティングを、ほぼ何の制約もなく実行しています。データがすべてキャッシュに格納されているためです。そうすることで、全領域のナビゲーションデータを 1KB 以内に収めています。

7)スレッド化にすぐ飛びつかない

処理に時間がかかるコードは、複数のプロセッサーで実行しても 1 つのプロセッサーで実行しても同じです。可能な限りデータ構造がコンパクト化されていないならば、複数のプロセッサーに非効率的な構造をただコピーしているにすぎません。そういった場合は、償却解析を検討してみてください。多くの場合、スレッド化よりも簡単です。ただし、コードをスレッド化する必要がある場合は、コードを効率化して償却を可能にしてからスレッド化するほうがはるかに容易です。

8)モバイルで美しく見える負荷の軽いテレインを作成するには

見栄えの良いテレインを作成するために、開発チームでは JPEG の yCbCR 色空間を使用しました。JPEG 圧縮形式のこの色空間は、高解像度の輝度値と低解像度の彩度(CbCR)値を提供します。

開発チームでは、4 つの輝度テクスチャを 1 つのテクスチャにパッキングし、色を変えることでさまざまなテレインに使用できる 4 つの汎用テレインタイプを用意しました。たとえば、茶色にした場合は土のように見え、緑色にした場合は草のように見えます。

Unity Walking Dead:モバイル向けテレイン

さらに、RGBA 頂点カラーチャンネル(RGBA)を使用してスプラット加重マスクを追加しました。輝度チャンネルをハイトマップと高さベースのブレンドに使用することで、美しい遷移を作成しました。最後に、低解像度の彩度レイヤーを輝度ハイトマップに適用し、見栄えの良いテレインを実現しました。

Unity Walking Dead:輝度チャンネル

結果的に、輝度データは 1024x1024 のテクスチャとなり、スプラットマップはワールド全体で 3.1MB のデータとなりました。

9)水にテレインメッシュを再利用する

Walking Dead の水メッシュは、テレインメッシュの複製です。深度マップを使用する必要はありませんでした。その代わりに、頂点を水の高さに移動させることで(vertexHeight=waterHeight)、「ただで」デプスバッファを用意しました。最終的に生じる差が深さになります。この方法によって容量が大幅に節約されました。テレインのドローコールでも水のドローコールでも、使用されるのは 1 つのテクスチャサンプルです。

Unity Walking Dead:水のテレインメッシュ

10)ライティングの高速化におけるソリューション

完全な PBR のレンダリングは、モバイルでは負荷が大きすぎるため、開発チームでは、球状ライティング近似法(SLA)という方法を使用しました。この方法を使用して、球状にマッピングされたテクスチャに対し、完全な PBR ライティング環境をレンダリングし、拡散とスペキュラライティングの効果を実現しました。後で使用するミップレベルのために、事前ミップレベルとして、2 分の 1 の Smoothness の値を保存しました。また、ログスペース符号化方式を使用して最大 4 倍の強度の HDR を可能にしました。

そして実行時に、ライティング計算を実行する代わりに、ミップレベルを選択してテクスチャ内で検索できます。ライティングテクスチャは、text2Dlod を使って適切なスムースネスレベルでサンプリングされます。

この方法の利点は、球体をライティングしてレンダリングするだけで済むため、ライトをいくつも使用できるという点です。これによって、無数のライトやスカイボックスなどを使用して、複雑なライティング環境を思いどおりに作成することが可能になりました。このカスタマイズされた完全な PBR ワークフローは、標準的な PBR ワークフローよりも 20% 速くなります。

Unity Walking Dead:ライティングの高速化におけるソリューション

11)シャドウ

効率的なシャドウを実現するために、開発チームでは、シャドウ平面からの高さを使用してシャドウをトップダウンでレンダリングする方法を考え出しました。これによって、高さの距離フィールドが効率的に作成され、ソフトシャドウとセルフシャドウで高さに応じたぼかしを使用できるようになりました。

さらに、オブジェクトの高さを保存しているため、シャドウ平面付近の値をクリッピングしてぼかすことで、アンビエントオクルージョンにかなり近いものを作り出すことができます。

Unity Walking Dead:シャドウ

12)マルチ解像度レンダリング

開発チームは、マルチ解像度レンダリングを使用することで、UI を高解像度に保ちながら、フィルレートの低下がもたらすメリットを 3D 世界で活かせるようにしました。さまざまなデバイスサイズで一貫した結果が得られるよう、DPI を使用して、ターゲット DPI(200 DPI から 400 DPI)に基づいて解像度を設定しました。

13)アセットバンドルのカスタムビルドに関するヒント

  • コードからバンドルにロードされるデータのみマークする
  • 依存関係を解析する
    • 独自のバンドルに含まれている大きなアセット(テクスチャやサウンド)
    • 独自のバンドルに含まれている共有アセット
    • 親バンドルに含まれている固有のアセット
    • パスで命名されたバンドル
  • バリアントを使用しない
    • 代わりに、バリアントレベルごとに固有のマニフェストを作成する
    • P4 チェックアウト、すべてのテクスチャとサウンドの事前処理、バンドルの作成、P4 リバート
この記事はいかがでしたか?