Version: 2020.2
言語: 日本語
レンダリングの最適化
実験的機能

スクリプトの最適化

ここでは、ゲームに使用する実際のスクリプトとメソッドを最適化する方法について説明します。また、ある一定の状況で最適化が有効である理由と、それらを適用する利点を詳しく説明します。

プロファイラー はとても重要です

プロジェクトが円滑に実行されるように確認するチェックリストのようなものはありません。動きの遅いプロジェクトを最適化するには、プロファイラーで分析して無駄に処理時間がかかる部分を特定する必要があります。プロファイラーによる分析を行わない、または、その結果を十分理解せずに最適化することは、目隠ししながら最適化するようなものです。

ビルトインのモバイルプロファイラー

ビルトインのプロファイラー を使って、どの処理がゲームを遅くしているかを把握できます。 物理演算かもしれませんし、スクリプトやレンダリングなどかもしれませんが、問題を特定するために、特定のスクリプトやメソッドをドリルダウンすることはできません。ただし、ゲームに特定の機能を有効/無効にするスイッチを設定することにより、問題をかなり絞り込むことができます。例えば、敵キャラクターの AI スクリプトを削除することによってフレームレートが 2 倍になったら、そのスクリプト、または、スクリプト関連の何らかを、最適化しなければならないことがわかります。唯一の問題は、問題を見つけるまでに、多くのさまざまなことを試さなければならない場合があることです。

モバイルデバイスのプロファイリングについては、モバイルのための最適化実用ガイド を参照してください。

設計による最適化

最初から高速なゲームを開発しようとすることはリスクが高いといえます。なぜなら、最適化を行う前に高速化を行ったり、遅すぎるので後から作り直すのは、どちらも時間の無駄になる可能性があるためです。この点について正しい決断をするには、ハードウェアに対する直感と知識が大切です。特に、ゲームはそれぞれ異なるプラットフォームがあるため、あるゲームでは有効な最適化が別のゲームでは役に立たない事もあります。

オブジェクトプール

スクリプトとゲームプレイ方法 で良いゲームとコード設計の融合例としてオブジェクトプールを紹介しています。一時的なオブジェクトにオブジェクトプールを使うと、オブジェクトを作成し破棄するよりも時間がかかりません。なぜなら、メモリ割り当てが簡易になり動的メモリの割り当てのオーバーヘッドとガベージコレクション作業を削減できるからです。

メモリ割り当て

自動メモリ管理とは

Unity で書いたスクリプトは自動メモリ管理を使用しています。ほぼすべてのスクリプト言語にこれを行います。対照的に C や C++ のような低レベル言語には手動メモリ割り当てが行われ、プログラマーが直接メモリアドレスに対して読み書きを行います。そのため、作成するすべてのオブジェクトはプログラマーが削除しなければなりません。C++ でオブジェクトを作成すると、そのオブジェクトを使い終わったときに手動でメモリの割当てを解除する必要があります。一方、スクリプト言語では、ObjectReference= NULL; と記述するだけで終わります。

質問: GameObject myGameObject; または var myGameObject : GameObject; のようなゲームオブジェクト変数を使用するときに、myGameObject = null; としても、それが破棄されないのはなぜでしょうか。

答え: ゲームオブジェクトはまだ Unity によって参照されています。なぜなら それの描画や更新などを行うために参照を維持する必要があるからです。Destroy(myGameObject); を呼び出すことで、参照とオブジェクトを削除します。

Unity が理解することができないオブジェクト、例えば、何も継承していないクラスのインスタンス (対照的に、ほとんどのクラスやスクリプトコンポーネントは MonoBehaviour から継承されています) を作成し、その後、それに対する参照変数を null に設定すると、スクリプトと Unity はそのオブジェクトを見失ってしまいます。つまり、それにアクセスすることも、見つけることもできなくなってしまいますが、そのオブジェクト自体はメモリに残ります。その後、少し時間がたつと、ガベージコレクションが実行され、どこからも参照されていないメモリは削除されます。これは、裏でメモリの各ブロックへの参照数が追跡されているため可能です。このことは、スクリプト言語が C++ よりも遅い理由の 1 つでもあります。

メモリ割り当てを避ける方法

オブジェクトを作成するたびにメモリが割り当てられます。スクリプト内ではとても頻繁に、気づくことさえなくオブジェクトを作成していることがあります。

  • Debug.Log("boo" + "hoo"); はオブジェクトを作成します。

    • 文字列を多く扱うとき、"" の代わりに System.String.Empty を使うようにします。
  • Immediate Mode の GUI (UnityGUI) は遅いため、パフォーマンスに問題がある場合はどのようなときでも使用すべきではありません。

  • クラスと構造体には以下のような違いがあります。

クラスはオブジェクトであり、参照と同じ挙動を持ちます。Foo がクラスで以下のように定義するとします。

  Foo foo = new Foo();
    MyFunction(foo);

MyFunction は、ヒープに割り当てられた元の Foo オブジェクトへの参照を取得します。MyFunction 内の foo への変更は、foo が参照されるどこにでも反映されます。

クラスはデータであり、データと同じ挙動を持ちます。Foo は構造体で以下のように定義するとします。

  Foo foo = new Foo();
    MyFunction(foo);

MyFunctionfoo のコピーを取得します。foo はヒープに割り当てられることはなく、決してガベージコレクションされることはありません。MyFunctionfoo のコピーを変更しても、他の foo は影響を受けません。

  • 長い間使用するオブジェクトはクラスである必要があります。そして、短期間 (一時的に) 使用するオブジェクトは構造体である必要があります。Vector3 はおそらくもっとも良く知られている構造体です。もし、それがクラスであったなら、すべてにずっと時間がかかったことでしょう。

なぜオブジェクトのプールは高速か

「なぜオブジェクトのプールは高速か」の結論は、Instantiate (インスタンス化) と Destroy (破棄) を多用すると、ガベージコレクションの作業が多くなり、ゲームに遅延を引き起こす原因になるからです。自動メモリ管理を理解する の説明のように、インスタンス化と破棄に関する一般的なパフォーマンスの問題を回避する方法は他にもあります。例えば、負荷が軽いシーン中に手動でガベージコレクションを起動したり、ガベージコレクションをとても頻繁に行って、使用していないメモリの大きなバックログが蓄積されないようにします。

もう 1 つの理由は、特定のプレハブを最初にインスタンス化すると、その他のものを RAM に追加でロードすることや、テクスチャやメッシュを GPU にアップロードすることが必要になる場合があるためです。これも遅延の原因になる可能性があります。オブジェクトプールを使用すると、ゲーム中ではなく、レベルをロードするときにこの問題が発生します。

無数の操り人形が入った箱を持つ人形使いを想像してみてください。スクリプトが人形使いを呼び出すたびに、操り人形のコピーを箱から取り出し、人形使いがステージから降りるたびに、使用していたコピーを捨てます。オブジェクトプールは、ショーが始まる前にすべての人形を箱から出して、観客から見えるべきでないときには舞台裏のテーブルにすべての人形を置いておくのと同じようなものです。

なぜオブジェクトプールが遅くなってしまうのか

問題の 1 つは、プールを作成すると、他の目的に使用できるヒープメモリの量を減らしてしまうことです。作成したばかりのプールにメモリを割り当て続けると、より頻繁にガベージコレクションが起動される場合があります。それだけでなく、残存オブジェクトの数が増えるほど、すべてのガベージコレクションの時間が増加するため、ガベージコレクションがより遅くなります。これらの問題を念頭に置き、大きすぎるプールを割り当てたり、プールに含まれているオブジェクトがしばらく不要であるにもかかわらずプールをアクティブにしている場合に、明らかにパフォーマンスが悪くなることがあります。さらに、オブジェクトプールに向かない種類のオブジェクトも多くあります。例えば、長時間持続する魔法のエフェクトや、大量に出現し、ゲームの進行とともに徐々に倒されていく敵がゲームに含まれている場合がなどです。このようなケースでは、オブジェクトプールによるパフォーマンスのオーバーヘッドのほうが利点を大幅に上回るので、オブジェクトプールは使用すべきではありません。

実装

ここでは、単純な砲弾の簡単なスクリプトを横並びにして比較します。1 つはインスタンス化を使用し、他方はオブジェクトプールを使用します。

 // GunWithInstantiate.js                                                  // GunWithObjectPooling.js

  #pragma strict                                                            #pragma strict

  var prefab : ProjectileWithInstantiate;                                   var prefab : ProjectileWithObjectPooling;
                                                                            var maximumInstanceCount = 10;
  var power = 10.0;                                                         var power = 10.0;

                                                                            private var instances : ProjectileWithObjectPooling[];

                                                                            static var stackPosition = Vector3(-9999, -9999, -9999);

                                                                            function Start () {
                                                                                instances = new ProjectileWithObjectPooling[maximumInstanceCount];
                                                                                for(var i = 0; i < maximumInstanceCount; i++) {
                                                                                    // 不使用のオブジェクトの山を任意の場所に置きます
                                                                                    instances[i] = Instantiate(prefab, stackPosition, Quaternion.identity);
                                                                                    // デフォルトで無効、これらのオブジェクトはまだ、有効になっていません
                                                                                    instances[i].enabled = false;
                                                                                }
                                                                            }

  function Update () {                                                      function Update () {
      if(Input.GetButtonDown("Fire1")) {                                        if(Input.GetButtonDown("Fire1")) {
          var instance : ProjectileWithInstantiate =                                var instance : ProjectileWithObjectPooling = GetNextAvailiableInstance();
              Instantiate(prefab, transform.position, transform.rotation);          if(instance != null) {
          instance.velocity = transform.forward * power;                                instance.Initialize(transform, power);
      }                                                                             }
  }                                                                             }
                                                                            }

                                                                            function GetNextAvailiableInstance () : ProjectileWithObjectPooling {
                                                                                for(var i = 0; i < maximumInstanceCount; i++) {
                                                                                    if(!instances[i].enabled) return instances[i];
                                                                                }
                                                                                return null;
                                                                            }




  // ProjectileWithInstantiate.js                                           // ProjectileWithObjectPooling.js

  #pragma strict                                                            #pragma strict

  var gravity = 10.0;                                                       var gravity = 10.0;
  var drag = 0.01;                                                          var drag = 0.01;
  var lifetime = 10.0;                                                      var lifetime = 10.0;

  var velocity : Vector3;                                                   var velocity : Vector3;

  private var timer = 0.0;                                                  private var timer = 0.0;

                                                                            function Initialize(parent : Transform, speed : float) {
                                                                                transform.position = parent.position;
                                                                                transform.rotation = parent.rotation;
                                                                                velocity = parent.forward * speed;
                                                                                timer = 0;
                                                                                enabled = true;
                                                                            }

  function Update () {                                                      function Update () {
      velocity -= velocity * drag * Time.deltaTime;                             velocity -= velocity * drag * Time.deltaTime;
      velocity -= Vector3.up * gravity * Time.deltaTime;                        velocity -= Vector3.up * gravity * Time.deltaTime;
      transform.position += velocity * Time.deltaTime;                          transform.position += velocity * Time.deltaTime;

      timer += Time.deltaTime;                                                  timer += Time.deltaTime;
      if(timer > lifetime) {                                                    if(timer > lifetime) {
                                                                                    transform.position = GunWithObjectPooling.stackPosition;
          Destroy(gameObject);                                                      enabled = false;
      }                                                                         }
  }                                                                         }


もちろん、大型で複雑なゲームのために、すべてのプレハブに有効な一般的な解決策が必要でしょう。

他の例: コインゲーム

スクリプトとゲームプレイ方法で述べられている「回転し、動的に照らされる、収集可能な何百ものコインをいっぺんに画面上に表示」する例は、スクリプトコード、パーティクルシステムのような Unity コンポーネント、カスタムシェーダーを効果的に使って、低性能なモバイルハードウェアに負荷をかけずに素晴らしい効果を作成できることを示すために使用されます。

この効果が大量のコインが落ち、跳ね返って、回転する 2D の横スクロールのゲームのコンテクストで使用されることを想像してみてください。コインは動的にポイントライトに照らされています。ゲームをより素晴らしくするために、コインにライトのきらめきをを与える必要があります。

もし高性能なハードウェアを持っているのなら、この問題への標準的な解決策を使用することができます。すべてのコインをオブジェクトにして、それぞれに対して、頂点ライト、フォワードライト、ディファードライトのいずれかを使ったシェーディングを行い、それからその上にイメージエフェクトのきらめきを加え、明るく反射するコインが周囲に光を放つようにします。

しかし、モバイルのハードウェアは多くのオブジェクトのせいで動作が重くなることでしょう。グローエフェクトを適用することはまったく問題外です。では、どうすればよいでしょうか。

アニメーション化したスプライトパーティクルシステム

すべてが似た動きをし、プレイヤーに詳細に観察されない多くのオブジェクトを表示する場合は、パーティクルシステムを使用して短時間で大量のオブジェクトを描画できる場合もあります。以下は、このテクニックのいくつかの典型的な活用例です。

  • アイテムやコイン
  • フライングデブリ
  • シンプルな敵の大群や群れ
  • 歓声を上げる群衆
  • 何百もの砲弾や爆発

SpritePacker と呼ばれる無料のエディター拡張機能があり、アニメーション化したスプライトパーティクルシステムを作成できます。テクスチャに複数フレーム分のオブジェクトを描画し、それをパーティクルシステムでアニメーション化したスプライトシートとして使用することができます。ここでは、回転するコインでそれを使用することにします。

実装例

SpritePacker プロジェクトに含まれているプロジェクトは、まさにこの問題に対する解決策を示す例です。

演算を低負荷に抑え見事な効果を実現するために、あらゆる種類のアセットファミリーを使用します。

  • 制御スクリプト
  • SpritePacker からの出力結果から作成した専用テクスチャ
  • 制御スクリプトとテクスチャの両方に密接に接続する専用シェーダー

readme ファイルには、システムが動く理由と仕組みを説明する例が含まれ、必要な機能とその実装の仕方を決めるために使用されたプロセスの概要を説明しています。これはそのファイルです。

問題を「回転し、動的に照らされる、収集可能な何百ものコイン」と定義します。

安易なアプローチでは、大量のコインプレハブをインスタンス化 (Instantiate) します。しかし、代わりに、ここでは、コインを描画するのにパーティクルを使用します。ただし、この方法には解決しなければならない多くの課題があります。

  • パーティクルはビュー方向を持たないため、ビュー方向が問題です。
    • カメラを右上に配置し、コインは、Y 軸を中心に回転すると想定します。
    • SpritePacker を使ってパックしてテクスチャをアニメーション化し、それを使ってコインが回転するような視覚的効果を作成します。
      • 新しい問題:すべてのコインが同じ早さで同じ方向に回転する単調さ
      • これを修正するために、私たちは、独自に回転と生存期間 を追跡し、スクリプトのパーティクルの生存期間 (lifetime) に回転を「描画」します。
  • パーティクルが法線を持たないので、法線が問題です。リアルタイムのライティングが必要です。
    • SpritePacker によって生成された各アニメーションフレームのコインの面に 1 つの法線ベクトルを生成します。
    • 上記のリストから得られる法線ベクトルに基づいて、スクリプトで各パーティクルに Blinn-Phong ライティングを行います。
    • その結果を色としてパーティクルに適用します。
    • コインの面と縁をシェーダーで別々に処理します。新しい問題が発生: シェーダーは縁がどこにあり、縁のどの部分にシェーダーがあるのかどうやって知ることができるのか。
      • UV を使用することはできません。それらはすでにアニメーションに使用されてます。
      • テクスチャマップを使用します。
        • コインに関連付けられた Y 位置が必要です。
        • バイナリの「面の上」 vs 「縁の上」 が必要です。
      • これ以上のテクスチャの使用、より多くのテクスチャの読み取り、より多くのテクスチャメモリの消費は行いたくありません。
      • 1 つのチャンネルに必要な情報をまとめ、それとテクスチャの色チャンネルの 1 つを交換します。
        • 現在、私たちのコインの色は間違っています。どうするべきでしょうか。
        • シェーダーを使用して、残りの 2 つのチャンネルを組み合わせ、欠落しているチャンネルを再構築します。
  • コインにライトが当たった輝きを表したいとしても、モバイルデバイスにはポストプロセスはあまりにも高価です。
    • 別のパーティクルシステムを作成し、それにソフトなタッチの、輝くバージョンのコインアニメーションを与えます。
    • 対応するコインの色が極度に明るいときだけ輝きに色付けします。
    • すべてのフレームのすべてのコインに輝きを描画することはできません。これは、フィルレートに大問題を起こします。
      • グローをフレームごとにリセットします。brightness (輝度) > 0 のものだけ配置します。
  • 物理とコインの収集は課題です - パーティクルがあまりうまく衝突しません。
    • ビルトインのパーティクルの衝突を使用できるか。
    • その代わりに、スクリプトに衝突を書きました。
  • 最後にもう 1 つ問題があります。このスクリプトは、多くの処理を行うので遅くなります。
    • パフォーマンスは、アクティブなコインの数に反比例します。
      • コインの最大数を制限します。こうすることによって、私たちの目標は十分に達成されます。コイン 100 とライト 2 は、モバイルデバイス上でかなり速く実行できます。
  • 最適化をさらにすすめるには、以下を試すことができます。
    • すべてのコインのライトを個別に計算する代わりに、世界をいくつかの部分に分割し、各部分のすべての回転フレームのライティングの条件を計算します。
      • コイン位置とコインの回転をインデックスとしてルックアップテーブルとして使用します。
      • 位置の双線形補間を使用して見た目の本物らしさを増加します。
      • まれに更新する、または、完全に静的なルックアップテーブル。
      • このためにライトプローブを使用するか。
      • スクリプト内のライトを計算する代わりに、法線マップされたパーティクルを使うか。
      • 法線のフレームアニメーションをベイクするために Display Normals シェーダーを使用します。
      • ライトの数を制限します。
      • スクリプトが遅いという問題を修正します。

この例の最終的な学び、つまり「話の教訓」は、あなたのゲームに本当に必要な何かを、従来の手段で達成しようとするとそれが遅延の原因となりますが、それを達成するのが不可能という意味ではありません。それは単に、独自のシステムをずっと高速に実行できるようにするために、いくらかの工夫をする必要があることを意味します。

数千のオブジェクトを管理するための技術

ここでは、数百または数千の動的オブジェクトを扱う状況に適用するための特殊なスクリプトの最適化を説明します。ゲームのすべてのスクリプトにこの技術を適用するというのはまったくひどい考えです。この方法は、実行時に大量のオブジェクトや大量のデータを扱う大規模なスクリプト用のツールや設計ガイドラインとして保存しておく必要があります。

大規模なデータセットで O(N2) の操作を避ける、または最小限に抑える

コンピューターサイエンスでは、O(n) で示される操作の順序は、適用されるオブジェクトの数 (n) が増えると、操作を評価する必要回数が増えることを意味します。

例えば、基本的なソートアルゴリズムを検討してみましょう。n 個の数があります。これらを最小から最大にソートします。

 void sort(int[] arr) {
    int i, j, newValue;
    for (i = 1; i &lt; arr.Length; i++) {
        // 記録します
        newValue = arr[i];
        //大きいものをすべて右にシフト
        j = i;
        while (j &gt; 0 && arr[j - 1] &gt; newValue) {
            arr[j] = arr[j - 1];
            j--;
        }
        // 記録していた値を大きな値の左に置きます
        arr[j] = newValue;
    }
  }

重要なのは、ここで 2 つのループがあり、1 つが他方の内側にあるということです。

 for (i = 1; i &lt; arr.Length; i++) {
    ...
    j = i;
    while (j &gt; 0 && arr[j - 1] &gt; newValue) {
        ...
        j--;
    }
  }

それでは、アルゴリズムにとって可能な最悪のケースを仮定してみましょう。入力された数字はソートされていますが、逆の順番にソートされています。その場合、もっとも内側のループは j 回実行されます。平均して、i1 から arr.Length–1 まで、jarr.Length/2 になります。O(n) に関して、arr.Length は、n であり、したがって、合計で、もっとも内側のループは、n*n / 2 回、つまり n2/2 回実行されます。しかし、O(n) では、1/2 のようなすべての定数は必要はありません。なぜなら、実際の操作数ではなく、操作数が増加する様子について説明したいからです。そのため、アルゴリズムは O(n2) になります。データセットが大きい場合、操作の順序は非常に重要です。操作の数が指数関数的に増加する可能性があるためです。

O(n2) 操作のゲーム内で見られる例が 100 体の敵です。各敵の AI は、他のすべての敵の動きを考慮に入れます。マップをセルに分割して各敵の動きを最も近いセルに記録し、各敵に最も近いいくつかのセルをサンプリングさせるほうが高速かもしれません。つまり、O(n) の操作になります。

不要な検索を実行する代わりにキャッシュを参照

ゲームに 100 体の敵があり、それらすべてがプレイヤーに向かってくるとします。

 // EnemyAI.js
  var speed = 5.0;
 
  function Update () {
    transform.LookAt(GameObject.FindWithTag("Player").transform);
    // 下のほうが、もっと悪い
    //transform.LookAt(FindObjectOfType(Player).transform);
 
    transform.position += transform.forward * speed * Time.deltaTime;
  }

敵の多くが同時に走ると、遅くなる可能性があります。あまり知られていない事実は、MonoBehaviour のすべてのコンポーネントにアクセスするもの、つまり、transformrendereraudio などは、それらに対応する GetComponent(Transform) と同様です。実際には、少し遅いです。GameObject.FindWithTag は最適化されていますが、場合によっては、例えば、内部ループや、多数のインスタンスで実行されるスクリプトでは、このスクリプトは少し遅くなることがあります。

これは改良したスクリプトです。

 // EnemyAI.js
  var speed = 5.0;
 
  private var myTransform : Transform;
  private var playerTransform : Transform;
 
  function Start () {
    myTransform = transform;
    playerTransform = GameObject.FindWithTag("Player").transform;
  }
 
  function Update () {
    myTransform.LookAt(playerTransform);
 
    myTransform.position += myTransform.forward * speed * Time.deltaTime;
  }

高価な数学関数を最小減にする

超越関数 (Mathf.SinMathf.Pow など)、除算、平方根は、すべて乗算の時間の 100 倍ほどかかります。大きなスケールで考えると大した時間ではないですが、それらを各フレームで何千回も呼び出すと、それは積もって大きくなります。

もっとも一般的なケースはベクトルの正規化です。何度も繰り返し同じベクトルを正規化している場合は、それを 1 回正規化し、後で使用するためにそれをキャッシュすることを検討してください。

ベクトルの長さを使用することと、それを正規化すること両方をおこなう場合、normalized プロパティを使うより、長さの逆数でベクトルを乗算して正規化したベクトルを得る方が速いでしょう。

距離を比較する場合は、実際の距離を比較する必要はありません。代わりに、sqrMagnitude プロパティを使用して距離の2 乗を比較し、平方根の計算を回避できます。

もう 1 つは、定数 c で何度も除算する場合には、代わりに逆数を掛けることができます。1.0/c を掛けて、最初に逆数を計算します。

高価な操作は、たまにだけ実行する。例えば Physics.Raycast()

高価な操作をしなければならない場合は、それほど頻繁に行わずに結果をキャッシュすることで最適化できることがあります。例えば、Raycast を使用した砲弾のスクリプトを考えてみます。

 // Bullet.js
  var speed = 5.0;
 
  function FixedUpdate () {
    var distanceThisFrame = speed * Time.fixedDeltaTime;
    var hit : RaycastHit;
 
    // 毎フレームで、現在の場所から前方に、次のフレームでいるであろう場所に、レイを放ちます
    if(Physics.Raycast(transform.position, transform.forward, hit, distanceThisFrame)) {
        // 発射します
    } else {
        transform.position += transform.forward * distanceThisFrame;
    }
  }

FixedUpdate を Update に、fixedDeltaTime を deltaTime に置き換えることで、すぐにスクリプトを改善できました。FixedUpdate は、物理演算の更新を参照し、フレーム更新よりも頻繁に発生します。 しかし、n 秒毎のレイキャストだけにすると、もっと改善できます。n が小さいほど時間分解能が大きくなり、n が大きいほどパフォーマンスが向上します。ターゲットが大きくて遅いほど、Temporal Aliasing が発生する前の n が大きくなります。(出現の遅延は、プレイヤーがターゲットを砲撃すると、n 秒前にターゲットがあった場所に爆発が見えたり、プレイヤーがターゲットを砲撃したら、弾丸が通り過ぎてしまったりすること)。

 // BulletOptimized.js
  var speed = 5.0;
  var interval = 0.4; // これは 'n' 秒
 
  private var begin : Vector3;
  private var timer = 0.0;
  private var hasHit = false;
  private var timeTillImpact = 0.0;
  private var hit : RaycastHit;
 
  // 最初のインターバルを設定
  function Start () {
    begin = transform.position;
    timer = interval+1;
  }
 
  function Update () {
    // フレームより短いインターバルは不可
    var usedInterval = interval;
    if(Time.deltaTime &gt; usedInterval) usedInterval = Time.deltaTime;
 
    // インターバルごとに、インターバルの最初にいた場所から前方に、
    // 次のインターバルの最初にいるであろう場所に、レイを発します。
    if(!hasHit && timer &gt;= usedInterval) {
        timer = 0;
        var distanceThisInterval = speed * usedInterval;
 
        if(Physics.Raycast(begin, transform.forward, hit, distanceThisInterval)) {
            hasHit = true;
            if(speed != 0) timeTillImpact = hit.distance / speed;
        }
 
        begin += transform.forward * distanceThisInterval;
    }
 
    timer += Time.deltaTime;
 
    // レイキャストが何かにヒットした後に、実際にヒットするために
    // 弾丸がレイと同じ距離を進むまで待ちます。
    if(hasHit && timer &gt; timeTillImpact) {
        // ヒットする
    } else {
        transform.position += transform.forward * speed * Time.deltaTime;
    }
  }

内側のループでコールスタックのオーバーヘッドを最小限に抑える

ただ関数を呼び出すこと自体でも、オーバーヘッドが少し発生します。フレームあたり数千回 X= Mathf.Abs(x) のようなものを呼び出す場合は、代わりに、単に x = (x > 0 ? x : -x); を呼び出す方がよい場合があります。

Physics のパーフォーマンスの最適化

Unity で使われる NVIDIA の PhysX 物理エンジンはモバイルで利用できますが、モバイルプラットフォーム上では、デスクトップよりも容易にハードウェアの性能限界に達します。

ここではモバイル上で、よりよいパフォーマンスを得るために物理演算を調整するためのヒントを紹介します。

  • Fixed Timestep の設定 (Time ウィンドウ) を調整して、物理演算の更新に費やされる時間を削減できます。Timestep を高くすると、物理演算の精度を犠牲にして CPU のオーバーヘッドを減らすことができます。 多くの場合、精度を下げることと引き換えに速度を上げるのは許容できるトレードオフです。
  • Maximum Allowed Timestep の設定 (Time ウィンドウ) を 8–10 fps の範囲で調整して、最悪のシナリオで物理演算に費やされる時間を制限します。
  • メッシュコライダーはプリミティブコライダーよりもはるかに高いパフォーマンスオーバーヘッドをもつため、節約してそれらを使用します。プリミティブコライダーと子オブジェクトを使用して、メッシュの形状に似せることがしばしば可能です。子コライダーは、親のリジッドボディによって単一の複合コライダーとして一括して制御されます。
  • ホイールコライダーは、ソリッドオブジェクトという意味では厳密にはコライダーではありませんが、それにもかかわらず、高い CPU オーバーヘッドがあります。
レンダリングの最適化
実験的機能