Version: 2018.1
レンダリングの最適化
実験的機能

スクリプトの最適化

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

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

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

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

ビルトインのプロファイラー を使って、どの処理がゲームを遅くしているかを把握できます。 物理演算かもしれませんし、スクリプトやレンダリングなどかもしれませんが、問題を特定するために、特定のスクリプトやメソッドをドリルダウンすることはできません。ただし、ゲームに特定の機能を有効/無効にするスイッチを設定することにより、問題をかなり絞り込むことができます。例えば、敵キャラクターの 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[