Unity Pro Tips

オフィシャル記事詳細

開発段階別にみるパフォーマンスに悪影響を及ぼすミスとその回避策

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

Unityのフィールドエンジニアである Valentin Simonov がこれまでのユーザー支援で見て来た、Unityでの開発時にやってしまいがちなパフォーマンスに悪影響を及ぼすよくあるミスとその解決策を7つの開発段階別に合計41ピックアップして紹介します。解決策は開発パイプラインの効率化や負荷の軽減、フレームレートの改善を通じてプロジェクトの最適化に繋がりますので、パフォーマンスに優れたコンテンツをリリースすることができるようになります。開発中にガイドとして繰り返しご参照ください。

1)プランニングフェーズでよくあるミスと解決策

企画段階で下した判断を開発工程の後期になって変更することは困難です。いかなる開発プロジェクトにとってもプランニングは最も重要なフェーズです。

プロジェクト開始前のリサーチが不十分

  • すべてのターゲット・プラットフォームで搭載予定の全機能が実際に動作するかを、プロジェクト開始前のリサーチ段階で十分確認しておくことが重要です

プロジェクトの対象として最低限動作を保証するデバイスが指定されていない

  • 最低限コンテンツの動作を保証するデバイスを明確にする
  • 開発チームとQAチームが利用可能なデバイスを選定する
  • これを行うと、現実的なパフォーマンスとフレーム予算を設定することができる

これらを行うことで現実的なパフォーマンスとフレームのコストを設定できるようになります。

早い段階でフレームとアセットのコストを設定していない

早い段階で以下に対してコストを設定することをお勧めします。

  • モデル — ターゲットデバイスはいくつの頂点をレンダリングできるのか
  • アセット — モデルやテクスチャはどのくらいのディテールにするのか
  • スクリプトとレンダリング — ロジック、レンダリング、エフェクトやサブシステムは、フレームのうち何パーセント占めるのか

詳しくは以下のリンクからご参照ください:
グラフィックパフォーマンスの最適化
パフォーマンス最適化のためのキャラクターのモデリング

早い段階でシーンの分割およびプレハブの分割をしていない

… あるいは (つまり) 全員が同じシーンで作業してしまうことを避けるために以下を考慮してください。

  • レベルを (追加でロードされた) シーンごとに分割する
  • 個別のオブジェクトをプレハブに移動し、編集は個別のシーンで行う
  • メインシーンのロッキング機構の同意を形成する

アセットのパイプラインプロセスの計画が不十分なまま開発を進めてしまう

アーティストからの仕様に従ってアセットをプロジェクトに取り込むプロセスについて、事前に以下について考慮する必要があります。

  • 可能であれば、このプロセスを効率化し実行するために最初からテクニカルアーティストを加える
  • アセットのフォーマットと仕様に関する明確なガイドラインを定義する
  • インポート時間のテストを追加する

詳しくは以下のリンクからご参照ください:
アートアセットベストプラクティスガイド

ビルドとQAプロセスのスケジュール表を作っていない

  • ビルドマシンをセットアップするか、Unity Teams を有効にしてセットアップする
  • 機能をリリースビルドへ公開する判断基準、方法を決める
  • 新しいビルドをテストする段取りを決める
  • テストを自動化する
  • 統計情報を記録する

プロトタイプで承認を受けることなくプロジェクトをスタートしてしまう

プロトタイプを構築して開発開始の承認を受けた後に開発に着手することをお勧めします。プロトタイプ構築時に考慮すべきポイントは以下の通りです。

  • プロトタイピングの間はスピードを優先して決定する
  • 間に合わせで作ったもの(hacks)を寄せ集めてゲーム開発を始めるのではなく、スクラッチから開発する

2)開発フェーズでよくあるミスと解決策

以下のような開発中の間違った手順やミスによって、チーム全体の作業効率を下げ、最終製品のクオリティを低下させてしまいます。

バージョンコントロールが正しく設定されていない

  • テキストのシリアライゼーションを使用 (Unityのデフォルト)
  • ビルトインのYAMLマージツールをセットアップする。SmartMerge の詳細はこちら
  • コミットをフックする。詳しくはこちらを参照

キャッシュサーバーを使用していない

静的データを JSON または XMLファイルに保存している

この結果、ロードが遅くなったりゴミが生成される原因になります。

  • 代わりに、ビルトインされている静的データを取得するためにカスタムエディターツールで ScriptableObject を使用する

プロジェクトに未使用のアセット、プラグイン、複製されたライブラリなどを含んでしまっている

プロジェクトの未使用のアセットがゲームに組み込まれるケースは非常に良くあります。プロジェクトにゴミを残さないようにするコツは以下の通りです。

  • バージョン管理システムをセットアップする場合、簡単にファイルの復元ができるようにする
  • アセットストアから入手したアセットをプロジェクトに取り込む場合、どのような依存関係かを確認する(プロジェクト内に5種の JSON ライブラリがあることに気づき驚く場合がある)
  • 初期のプロトタイプの古いアセットやスクリプトが残ったままになるケースが多いので注意する
  • 古いアセットを「削除済み」フォルダに移動したとしても、ゲームに組み込まれているリソースとスクリプトは変わらずそのままであるケースがよくあるので注意する

反復タスクを手作業で行ってしまう

手作業で反復タスクを繰り返すと漏れが原因で大きなミスを引き起こしてしまいますので、以下のポイントを踏まえて回避しましょう。

  • 反復タスクはスクリプトで自動化する
  • どのシーンからでもゲームやインタラクティブコンテンツを「プレイ」できること
  • ビルドプロセスのすべてのステップを自動化する
  • アプリケーションをボタン 1 つで Cloud Build またはローカルで構築できるようにする

エディター内だけでプロジェクトのプロファイリングを行ってしまう

常にターゲットデバイス上でコンテンツのプロファイルを行うようにしてください。
エディターだけでプロファイルした場合、実際のパフォーマンスのボトルネックを見逃してしまう可能性があります。

プロファイリングツール・デバッグツールについて、ビルトインのツールとプラットフォーム固有のツールの両方を使用していない

詳しくは以下のリンクをご参照ください:
パフォーマンス最適化全般

プロファイリングおよび最適化を開発サイクル中に行ってしまう

  • プロファイリングを待つ時間が長くなればなるほど、パフォーマンスコストが高くなる
  • 早い段階でプロファイリングを始めて、プロジェクトのフレーム、メモリ、ディスクサイズが見積もったコスト内に収まるように心がける

テストデータに基づかずに最適化をしてしまう

  • 実際のボトルネックを確認してから最適化に着手する
  • 前述のツールを使用して正しいデータを収集する

ターゲットプラットフォームに関する知識が不十分なまま開発を進めてしまう

デスクトップ、モバイル、コンソールなどのプラットフォームごとに多数の異なるボトルネックが存在しますので、ターゲットプラットフォームについて十分な知識を蓄えておくことが重要です。

3)アセットの設定でよくあるミスと解決策

アセット (モデル、テクスチャ、サウンド) は、ゲームの大部分を占めます。プロジェクト内のメッシュが1つでも間違っていたら、プログラマーが行ったすべての最適化が台無しになってしまいます。

スプライトアトラスを正しく設定していない

サードパーティのツール(TexturePacker など)を使用して、Unity でアトラスか、グループスプライトを作成しましょう。これでゲームでのドローコールの回数を減らせます。

テクスチャを正しく設定していない

ターゲットプラットフォームのテクスチャ設定で間違えやすいポイントは以下の通りです。

  • プラットフォームはどの圧縮形式をサポートしているのか
  • テクスチャ設定でミップマップは必要か否か
  • こちらで示している形で AssetPostprocessor APIを使用して、新しいテクスチャーにまつわる設定を適用できるように自動化するセットアップを行う
  • アーティストが間違った設定でテクスチャをコミットしないで済むように配慮する

アセットバンドルに重複したテクスチャを含んでしまう

  • 特にテクスチャが重複する場合にはアセットバンドルのビルドシステムのセットアップは間違えやすいので、こちらのガイドラインを参考にする
  • アセットバンドルブラウザを使用して依存関係を追跡する

4)プログラミングでよくあるミスと解決策

開発前に策定したコードアーキテクチャーや開発時の雑なコーディングが原因で、開発の生産性が低下してしまいます。

他のメンバーにとって難解なコーディングをしてしまう

難解なコードは結果的により遅く実行され、IL2CPP がより多くのコードを生成しますので非効率ですので、以下のポイントを考慮したコーディングを心がけてください。

  • Abstract Enterprise コードは滅多に正当性が示されないので避ける
  • 難解なコードや推論し辛いコードにならないように心掛ける

アーキテクチャの規約を定義していないか、あまりドキュメント化していない

ドキュメント化が曖昧なままコーディングしていると、同じタスクを実行するために異なるメソッドを使用してしまいがちです。例えば以下のようなタスクについては予めドキュメント化しておくことをお勧めします。

  • フォーマット設定 (ファイル、プロパティ、アセット)
  • イベント (Unity イベント、C# イベント、SendMessage)

どのマネージャーがどのオブジェクトを担当するのかも、事前に定義しておくことをお勧めします。

以下のような Unity フレームループの要素を十分に理解せずにコーディングを進めてしまう

以下のポイントは重要ですのでコーディング前に習熟しておくことをお勧めします。

  • Awake、OnEnable、Update メソッドが呼び出されるタイミング
  • コルーチンが更新されるタイミング
  • どのように FixedUpdate が実行されるのか

スクリプト初期化のロジックを Unityの実行順序に依存させてしまう

「これでも動作する」ことは多々ありますが、各スクリプトの実行順(Script Execution Order)を誤用しないように留意してください。

フレームレートを考慮しないでロジックやアニメーションを記述してしまう

端末の負荷状況によるFPSの変動に影響されたくないスクリプトでは Time.deltaTime を使用しましょう。

詳しくは以下のリンクをご参照ください:
Scriptable Objects を使って構築するための 3 つの方法
より優れたスクリプティング体験を

5)CPUパフォーマンスに影響を与えるミスと解決策

CPU 使用率が高くなるほどゲームプレイ時に「タイムラグ多発状態」となり、かつ、バッテリーの消費も早くなってしまいます。

Update() メソッドを持つスクリプトが多すぎる

ネイティブマネージドコールにはある程度のオーバーヘッドがあります。代わりにカスタムマネージャーを使うなど、こちらのブログ記事を参考に最適化を検討してください。

全てのカスタムビヘイビアを Update/Awake/Startメソッドが定義された抽象クラスから継承してしまう

すべてのスクリプトに Update() メソッドが追加されることで無駄が増えてしまいますのでご注意ください。

すべてのゲームシステムをフレームごとに Updateしてしまう

ゲーム/コンテンツ内において、次のようなさまざまなシステムの Update頻度を定義しておきましょう。

  • オブジェクトの移動
  • AI機能やパス探索
  • ゲームステートのロギングおよび保存
  • その他、「重くなる」システム。

頻繁に必要になるデータやオブジェクトの参照をキャッシュしていない

以下のような頻繁に必要になるデータはキャッシュに入れて効率化を図りましょう。

  • リフレクション
  • Find()
  • Camera.main
  • GetComponent()

頻繁にインスタンス化されるオブジェクトをプールしていない

オブジェクトのインスタンス化は遅いので、ゲーム起動時にオブジェクトプールを作ったり、新しいオブジェクトを作る代わりにオブジェクトを再利用するなどしましょう。

毎フレーム、メモリの割り当てを行ってしまっている

ゲームプログラムが動作する最短の時間単位(フレーム)で毎回メモリ割り当てを行うので、オーバーヘッドやGCのスパイクを起こし、深刻な場合は動作が一瞬固まるような挙動になりえますのでご注意ください。
まずは全ての割り当ての排除を試みることから始めることをお勧めします。

ある機能をメモリ割り当てを起こさずに実現できる API があるのに、メモリ割り当てを起こすバージョンを使っている

  • LINQの使用を避ける
  • 文字列を連結する
  • 配列を返す Unity APIを、メモリ割り当てを行わない代替APIと差し替える
    • Physics.RaycastAll, Mesh.vertices, GetComponents など

詳しくは以下のリンクをご参照ください:
C# のデータ構造と Unity API を使って最適に作業を行いましょう

6)GPU パフォーマンスに影響を与えるミスと解決策

GPU使用率が高いほどフレームレートが低下し、かつバッテリーの消耗が早くなり、さらにゲーム/コンテンツの「動作が重い」と受けとめられてしまいます。

モバイル:プロジェクト内でオーバードローが大量に発生している

モバイル GPUが毎秒描画できる性能の限界は PCに搭載されているGPUと比べて一般的には低いです。オーバードローは、モバイルにおける最大のパフォーマンスのボトルネックとなりますので、以下の点にご注意ください。

  • 不要な透過画像の描画をしない
  • 複雑なメッシュを用いない
  • 完全に透明な領域をトリミングするために不必要に複雑なメッシュを使うことは避ける

モバイル:シェーダーが複雑すぎる

  • スタンダードシェーダーはモバイルには不向きなので使用しない
  • 専用のカスタムシェーダーを作成する
  • ローエンドのデバイスについては簡易版を使用する
  • いくつかのエフェクトを無効にすることを検討する

あまりに多くのダイナミックライトをフォワードレンダリングで使用してしまう

ライトは照らしているオブジェクトすべてについてレンダリングパスを追加するので、処理が非常に重くなりますのでご注意ください。

プロジェクトの間違った設定が原因で動的バッチ処理が中断してしまう

  • オブジェクトが動的にバッチ処理されるには「類似」している必要がある
  • フレームデバッガーで、特定のオブジェクトについてバッチされなかった理由を絞り込む

LOD を使用していないか、正しく設定していない

LODを使用すると、遠くのオブジェクトのレンダリングリソースを節約できます。

詳しくは以下のリンクをご参照ください:
ドローコールがバッチ処理されない理由
モバイル最適化に関するベストプラクティス

7)UI のパフォーマンスに影響を与えるミスと解決策

Unity UIはアーティストにとって使いやすいツールです。ただし、設定を間違え易くもあるため、CPUと GPUのリソースを浪費させてちまいがちですので注意が必要です。

解像度やアスペクト比の違いを考慮していない

  • さまざまな解像度とアスペクト比のデバイスで UIのテストを行う
  • デバイスに応じて UIを作成する方が良い場合もある

アニメーション要素を UIと同じ Canvasに配置してしまう

要素が変更されると Canvasはメッシュを結合し直しますので、以下のポイントを考慮してください。

  • 複雑な Canvasはコストが嵩む場合がある
  • アニメーション要素ごとに別々の Canvasに移動することを検討する

新しいウィンドウを「開く」ことが最適化されていない

新しいウィンドウまたは UIの大きな塊(チャンク)が作成されるとゲームに顕著なタイムラグが生じますので、以下の点を考慮して設計してください。

  • エフェクトを最小限に抑えるようにする
  • UIウィンドウをより単純にする
  • UIを分割する
  • ウィンドウをキャッシュする

リストに膨大な量のアイテムを含めてしまう

  • 一度にすべてを作成する代わりに、リスト項目を動的に再利用する
  • リストにネストされた Canvasを作る
  • こちらのようなオープンソースの利用を検討する

詳しくは以下のリンクをご参照ください:
Unity UI の最適化に関するヒント

この記事はいかがでしたか?