モバイルゲームのパフォーマンスを最適化しよう:専門家が語る物理演算、UI、オーディオの設定に関するヒント

Integrated Success チームは、Unity の顧客が抱える複雑な技術的問題をサポートします。シニアソフトウェアエンジニアで構成されるこのチームに、モバイルゲームの最適化に関する知見を聞かせてもらいました。
当社の Accelerate Solutions チームはソースコードを熟知しており、無数に存在する Unity の顧客みんながエンジンを最大限に活用できるようサポートしています。このチームは、クリエイターのプロジェクトに深く入り込み、パフォーマンスを最適化することで、スピード、安定性、効率性を向上させるポイントを見つけ出します。
Unity のエンジニアたちがモバイルゲームの最適化に関する知見のシェアを始めるとすぐに、元々予定していた 1 本のブログ記事に収まりきらないほどの素晴らしい情報があることに気づきました。そこで私たちは、彼らの膨大な知識を e ブックとしてまとめ(ダウンロードはこちら)、そこに収録されている 75 以上の実用的なヒントの一部を紹介する一連のブログ記事を作成することにしました。
シリーズ 2 本目となる今回は、UI、物理演算、オーディオ設定でパフォーマンスを向上させる方法を詳しく見ていきます。前回の記事では、プロファイリング、メモリ、コードアーキテクチャについてご紹介しました。まだご覧になっていない方はぜひそちらもご覧ください。また、次回の記事ではアセット、プロジェクト設定、グラフィックスについて取り上げますので、そちらもご期待ください。
e ブック(英語)のダウンロードはこちらからどうぞ。
それでは早速、ご紹介していきます。
Unity のビルトインの物理演算エンジン(Nvidia PhysX)は、モバイルで使うには高価なものとなることがあります。しかし、以下のヒントを参考にすれば、1 秒あたりのフレーム数をより多くすることができます。
有効ならば、プレイヤー設定で Prebake Collision Meshes をチェックしましょう。

物理演算の設定(Project Settings > Physics)を編集し、Layer Collision Matrix を出来るだけ簡素にするようにしてください。
また、Auto Sync Transforms を無効にし、Reuse Collision Callbacks を有効にしてください。


メッシュコライダーは高価です。複雑なメッシュコライダーを、元の形状を近似するプリミティブのコライダーまたは簡略化されたメッシュコライダーに置き換えましょう。

MovePosition や AddForce などのクラスメソッドを使って、Rigidbody の付いたオブジェクトを動かします。オブジェクトの Transform コンポーネントを直接使って移動させると、物理演算でワールドの再計算を行う必要が生じ、複雑なシーンでは計算のコストが高くなります。物理ボディの移動は Update ではなく FixedUpdate で行いましょう。
プロジェクト設定にある Fixed Timestep のデフォルトは 0.02(50Hz)です。これを目標とするフレームレートに合わせて変更してください(例:30fps なら 0.03)。
実行時にフレームレートが低下する場合、これは Unity が FixedUpdate をフレームごとに複数回呼び出していることを意味し、物理演算を多用するコンテンツでは CPU パフォーマンスに問題が生じさせる可能性があります。
Maximum Allowed Timestep は、フレームレートが低下した場合に、物理演算や FixedUpdate イベントを使用できる時間を制限するものです。この値を下げると、パフォーマンスが低下したときに、物理演算やアニメーションが遅くなる可能性がありますが、同時にフレームレートへの影響も少なくなります。

問題のあるコライダーや不具合のトラブルシューティングには、Physics Debug ウィンドウ(Window > Analysis > Physics Debugger)を使いましょう。これにより、互いに衝突する可能性のあるゲームオブジェクトが色分けされて表示されます。

詳しくは、Unity ドキュメントの物理演算デバッグの可視化をご覧ください。
Unity UI(UGUI)は、しばしばパフォーマンス問題の原因となります。Canvas コンポーネントは、UI 要素のメッシュを生成・更新し、GPU にドローコールを発行します。その機能の実行コストは高くなることもあるので、UGUI を利用する際には以下のことに注意してください。
何千もの要素を持つ大きなキャンバスがある場合、1 つの UI 要素を更新するとキャンバス全体が更新されることになり、CPU の使用率が急上昇することがあります。
UGUI の複数のキャンバスをサポートする機能を活用しましょう。更新頻度に応じて UI 要素を分割しましょう。静的な UI 要素のキャンバスは分離して置き、同時に更新される動的な要素は小さなサブキャンバスに配置しておきます。
また、あるキャンバス内のすべての UI 要素が、同じ Z 値、マテリアル、テクスチャを持つようにしましょう。
例えば、キャラクターがダメージを受けたときに表示される体力バーのように、ゲーム中に不定期にしか表示されないUI要素があります。UI 要素が見えなくてもアクティブになっていれば、ドローコールを使用している可能性があります。見えない UI コンポーネントは明示的に無効にし、必要に応じて再度有効にするようにしましょう。
キャンバスが見えないようになればいいのであれば、GameObject ではなく Canvas コンポーネントを無効にしてください。これにより、メッシュや頂点の再ビルドを避けることができます。
画面上でのタッチやクリックなどの入力イベントを処理するには、GraphicRaycaster コンポーネントが必要です。これは単純に画面上の各入力ポイントをループし、それが UI の RectTransform 内にあるかどうかをチェックするものです。
デフォルトの GraphicRaycaster をヒエラルキーの一番上にあるキャンバスから削除します。代わりに、インタラクションが必要な個別の要素(ボタン、Scroll Rect など)にのみ、GraphicRaycaster を追加します。

また、すべての UI テキストや画像について、必要なければ Raycast Target を無効にします。UI が多くの要素で構成されている複雑なものであれば、こうした小さな変更の積み重ねが無駄な計算を減らすことにつながります。

レイアウトグループは非効率的に更新されるので、使用は控えめにしましょう。動的なコンテンツを扱わない場合はまったく使わないようにし、プロポーショナルレイアウトを作る際はアンカーを使用するようにしましょう。これをやりたくない場合は、カスタムコードを作成して、UI を設定した後に Layout Group コンポーネントを無効にしてください。
動的な要素にレイアウトグループ(Horizontal、Vertial、Grid)を使用する必要がある場合は、パフォーマンスを向上させるために入れ子にしないようにしてください。

大きなリストビューやグリッドビューは高価です。大規模なリストビューやグリッドビューを作成する必要がある場合(例:数百のアイテムを含む持ち物画面)、アイテムごとに UI 要素を作成するのではなく、より小さな UI 要素のプールを作ってその中の要素を再利用することを検討してください。このサンプル GitHub プロジェクトでは、実際にそれを行っているところを見ることができます。
UI 要素を多数重ねる(例:カードバトルゲームでカードを重ねる)と、オーバードローになります。コードをカスタマイズして、層になった要素を実行時により少数の要素やバッチにマージすることができます。
現在、モバイルの解像度や画面サイズはデバイスによって異なっているため、各デバイスで最高の体験を提供するために、代替バージョンの UI を作成しましょう。
デバイスシミュレーター を使用して、サポートされているさまざまなデバイスで UI をプレビューすることができます。また、XCode や Android Studio でもバーチャルデバイスを作成することができます。

一時停止画面やスタート画面で、シーンのそれ以外の部分がすべて隠れてしまう場合は、3D シーンをレンダリングしているカメラを無効にしてください。同様に、一番上のキャンバスの後ろに隠れている背景のキャンバス要素も無効にします。
この時、60fps で更新する必要はないはずなので、フルスクリーン UI を表示している間は、Application.targetFrameRate を下げることも検討してください。
Event や Render Camera フィールドを空白にしておくと、Unity はそこに Camera.main を強制的に割り当てます。これは描画コストを不必要に増大させます。
キャンバスの Render Mode に Screen Space - Overlay を使えば、カメラは必要なくなります。これを使うことも検討してください。

オーディオは通常、パフォーマンスのボトルネックにはなりませんが、ここも最適化してメモリを節約することができます。

圧縮されたフォーマット(MP3 や Vorbis など)を使うと、Unity はそれを一旦展開し、ビルド時に再圧縮します。この結果、品質を低下させる部分が 2 つできることになり、最終的な品質が低下します。
圧縮することで、クリップのサイズとメモリ使用量を削減することができます。
- 大多数のサウンドには Vorbis を使用するとよいでしょう(ループさせないサウンドには MP3)。
- 頻繁に使われる短い音(足音、銃声など)には、ADPCM を使用してください。これは非圧縮の PCM に比べてファイルサイズを小さくしつつ、再生時のデコードも速いのが特徴です。
モバイル機器での効果音は、最大でも 22,050Hz とします。これより低い値に設定しても、最終的な品質への影響はほとんどありませんが、これはご自身の耳で聴いて判断してください。
クリップサイズによって設定が異なります。
- 小さなクリップ(200k バイト未満):Decompress on Load を選びましょう。これは、サウンドを raw 16 ビット PCM オーディオデータに展開し CPU コストとメモリを消費するため、短いサウンドのみに使うべきものです。
- 中くらいのクリップ(200k バイト以上):Compressed in Memory にしましょう。
- 大容量ファイル(BGM):Streaming に設定します。こうしないと、アセット全体が一度にメモリに読み込まれ、メモリを使いつくしてしまいます。
ミュートボタンを実装する際には、単に音量を 0 にするだけでなく、AudioSource コンポーネントを破棄して、メモリからアンロードすることができます。ただし、プレイヤーがミュート状態を頻繁に切り替える必要がない場合に限ります。
次回のブログでは、グラフィックとアセットについてご紹介します。今すぐチームからのヒントとコツをすべて乗せたリスト全体を見てみたい方は、こちらのページから完全版の e ブックをダウンロードしてご覧ください。

Integrated Support サービスについてもっと知りたい、あるいはチームにエンジニアと直接やりとりする窓口や、専門家のアドバイスやプロジェクトのベストプラクティスガイダンスを提供したいとお考えの方は、こちらのページでご案内している Unity のサクセスプランをご検討ください。
私たちは皆さんの Unity アプリケーションに最大限のパフォーマンスを発揮させるお手伝いをしたいと考えています。他にも取り上げてほしい最適化に関わるトピックがありましたら、コメントセクションでお知らせください。