Unity3dでゲームを最適化および洗練するための最初のステップ

画像 最初のプロジェクトを終えた後、モバイルデバイスに移植するか、少なくとも組み込みのGPUで実行するというアイデアを思いつきました。 すべての最適化ガイドで、最初のヒントの1つは、パフォーマンスを事前に心配する必要はなく、完了後に最適化を開始する必要があることを示しています。 そのため、最初にデスクトップでゲームをリリースしたので、モバイルデバイス向けに最適化するのに遅すぎることはないと判断しました。 残念ながら、モバイルゲームは最初から弱いハードウェアに目を向けて開発されるべきだと思われるため、目標を完全に達成することはできませんでした。 現時点では、モバイルプラットフォームをさらに最適化するには、ゲームプレイとゲームワールドのデザインを真剣に作り直す必要があるだけです。 ただし、現在のバージョンでは、 Unity3dの下で貴重な最適化の経験が得られ、その結果、統合GPUでのパフォーマンスが300%以上向上しました。



CPUから始めましょう



最適化中に形成されるかなり明白なリスト:



  1. プロパティを使用しないでください! フィールドメソッドはあなたの親友です。



  2. GetComponent <>を介して受け取るすべてをキャッシュします。これには、 transformsrigidbodiesなども含まれます。



  3. 同じデータを取得するためにオブジェクトに2回アクセスしないでください。 ほとんどの場合、これはプロパティを介したアクセスであり、破棄する必要があります。 多くの場合、同じオブジェクトの位置が異なるスクリプトでどのように要求されるかを見ることができます。同じ位置をキャッシュし、 UpdateまたはこのオブジェクトのFixedUpdate内で一度更新することで置き換える方が良いです。



  4. すべての数学をキャッシュします。 Vector.Upを呼び出すたびに、 内部でコンストラクターが呼び出されますが、これはそれほど高速ではありません。 私は静的なCachedMathクラスを作成しました。 このクラスには、ベクターやクォータニオンをよく使用するすべての方向が積み重ねられています。



  5. タイプStringを使用せずに実行してください。 各行にはメモリの割り当てが必要です。行を制御不能に使用すると、 GCが呼び出しのすべてのスレッドを停止する方法がわかります。 私の場合、メインラインソースはFPSインジケーターとレース中のタイマーでした。 解決策は、1〜100のすべての数字の文字列リテラルのプールを作成することでした。これにより、各フレームの行の選択が完全になくなりました。



  6. foreachを使用しないでください。GCと貴重なCPU時間を節約したい場合は、 foreachに置き換えてください。 テンプレートメソッド( ジェネリック )を使用すると、多くの場合、同じ結果になります。



  7. LINQGCの別の負荷源です。 LINQ式を単純化するか、さらに良いことに、それらを単純な構造に完全に置き換えてください。



  8. Animatorオブジェクトで使用されるすべての文字列は、 Animator.StringToHash()を介して整数識別子に変換する必要があります



  9. オブジェクトのインスタンス化は非常に難しい操作なので、頻繁に作成するためにオブジェクトのプールを使用してから再利用する価値があります。



  10. すべての空のUpdateおよびFixedUpdateメソッドを削除します 。 また、スクリプトで両方を使用する場合、または修正済みのみを使用する場合は、可能なロジックを修正済みから通常のUpdateに移行することを検討する必要があります


もちろん、プロファイラーウィンドウで遅延が発生した場合にのみ、最適化を行う必要があります。 主なことは、メモリの永続的な割り当てを破壊することです。



また、スクリプト内の一部のロジックや、合理的な制限内で処理されるデータの量を単純化するのに遅すぎることはありません。 ただし、プロファイラーを使用して得られる正当な理由が必要であること、特定の方法が遅すぎることをもう一度繰り返します。 変更後、プロファイラーが最適化の開始前よりも小さい数値を表示することを確認してください。



最適化の最悪の部分は、構造化された「 理想的な 」コードが読みにくい場所に広がることです。 残念ながら、これは避けられません。 覚えておくべき主なことは、これが生産性のための犠牲であることです。



GPUを今すぐ



CPUのヒントは非常に用途が広く、あらゆるプロジェクトに適用できます。 これは、 GPU最適化の場合には当てはまりません。GPU最適化は、多くの場合、特定のシーンに大きく依存しています。 ただし、シェーダーで強力なマジックを使用しない場合、明示的なインジケーターはGPUパス(パスコール)の数です。



私のゲームには、動きの基礎として海があり、風景としていくつかの島があるオープンワールドが含まれています。 私の場合、 2000以上のパスがあり、この値を約300に減らすことができました。



素材 使用する材料の量をできるだけ減らします。 マテリアルの各変更は新しいパスであり、マテリアル内のすべてのテクスチャレイヤーも新しいパスです。 もちろん、私は物事を少し単純化し、パスの形成はそれほど簡単ではありませんが、事実は残っています-パスが多すぎると、弱いGPUが途方もなくロードされます 。 モバイルデバイスの場合、 40〜60パス程度の領域を推奨します。 より高度なデバイスは、この地域で数百を処理できます。 だから、あなたは努力するものがあります!



可視オブジェクト。 私のシーンには、画面に常に表示されているオブジェクトが多すぎます。 唯一の問題は、それらが見える必要があることです! もちろん、遠くからは近くと同じ詳細は必要ないので、明らかな解決策はLODオブジェクトを使用することでした。



詐欺師。 私はオブジェクトを詐欺師に置き換えることを好みました(一般に、 ビルボードに非常に似ていますが、これはオブジェクトのプリレンダーがすべての側面から受け取った多くのテクスチャです)。 Unity3dに組み込まれているAsset-Store は、 LODおよび詐欺師向けのさまざまな既製の有料ソリューションを提供します 。 しかし、私は基本的なアルゴリズムを自分で再現することにしました。 エディターのスクリプト拡張機能を作成し、必要なオブジェクトのコピーを作成し、レイヤーを変更してから、この特別なレイヤーによってのみ制限されるカメラを作成し、すべての側面からテクスチャ内にオブジェクトを作成しました。 テクスチャ付きの結果フォルダーの名前、結果のテクスチャの解像度、オブジェクトまでの距離、高さオフセット、辺の数、および詐欺師の作成中に照明を保存または無効にするためのフラグなど、主なパラメーターが追加されました。 すべてのアクションが完了した後、スクリプトはオブジェクトのすでに不要なコピーを削除しました。



スプライト。 現在、ほとんどすべてのオブジェクトは、カメラから一定の距離にあるスプライトに置き換えられています。 しかし、パスの数はまだ膨大でした。 それから、スプライトが常に表示する軽量のフォームとはほど遠いことを発見しました。 デフォルトでは、各スプライトは画像を三角形化し、多くの頂点を作成します。 (公式ドキュメントによると) 900頂点ごとに別のパッセージが作成されます(公式にグループ化|パッキング|バッチ処理 -多くのオブジェクトのデータをGPUの 1つの命令に保存します-一般にSpriteRendererオブジェクトには適用されません)。 同時に、すべての透明ピクセルをレンダリングが必要であり、 GPUがそれらを通過させないため、すべてのスプライトを透明の完全な正方形領域に置き換えることはできません。 また、透明度は、レンダリングの深さをチェックするため、すべてのスプライトのレンダリング中に問題を引き起こします。 GPUは、1つまたは2つのスプライトに追加のパスを作成します。これは、深度チェックで必要なため、すでにグループ化された多くのスプライトを描画する間です。 行うことができた唯一のことは、スプライトのタイプをMultipleに変更することでした。これにより、三角形分割の内部メカニズムが変更され、頂点の数が大幅に少なくなります。



スプライトパッカー これは、スプライトを使用するときに覚えておく必要のある最後のものです。 アトラスマップでのパッケージ化の必要性をスプライトに明示的に示すには、そのTagを指定する必要があります。 1つのアトラスからスプライトを描画するとき、 GPUは、詳細なレンダリングの順序がアンパックスプライトにとって最適ではない場合でも、追加のパスを作成しません。 結果のアトラスのサイズも重要です。 デフォルトでは、値は2048x2048に制限されています。 これはアトラスの最大サイズであり、充填量に応じて最適なサイズに動的に調整されます。 私の場合、これは必要なすべてのスプライトを1ページにまとめるのに十分ではありませんでした。 パッケージアルゴリズムを独自のアルゴリズムに置き換えます。これは基本的なアルゴリズムに基づいていますが、サイズを変更した値が4096x2048であるため、パフォーマンスが大幅に向上しました。



4096x4096にさらに増加しもパスの数にほとんど影響がありませんでしたが、同時にパフォーマンスがわずかに低下しました。 いくつかのスプライトを1つのアトラスに一緒に配置できないことを覚えておく価値があります。このため、同じ圧縮設定と他のパラメーターが必要です。そうでない場合、自動的に異なるグループに分割されます。 したがって、アトラスごとに論理的および視覚的にスプライトをグループ化してみてください。画面上に少なくともできるだけ少ない数のアトラスが表示されるようになります。これは、最適ではない場所を含むそれらを切り替えるたびにパスにかかるためです。



私の場合、アトラスをUIスプライトに分割し、次にすべてのオブジェクトを非常に遠くに配置しました-複数のアトラスを使用する必要がありましたが、世界では正反対のグループに分割され、同時に画面上でそれらを表示することは困難です中間オブジェクト。



すべての変更の後、パフォーマンスが大幅に向上したため、すべての偽者オブジェクトを無効にしても、結果のFPSにはほとんど影響がありません。



水。 私の場合、より生産的な水を得る必要がありました。 最初は、屈折をオンにしたwaterProDaytimeがシーンで使用され、海岸線に沿って泡をサポートするために最小限の変更が行われました。 屈折チャンバーは取り外され、 グラブパスコールに置き換えられました。 問題は、屈折を正しく表示するために、カメラがクリッピングマトリックスをオフにする必要があることです。そうしないと、水位より上のすべてのオブジェクトが単にシャドウを投影しなかったためです。 この制限のため、カメラはシーン全体を追加でレンダリングし、この場合grabpassの呼び出しはより高速であることが判明しました。 乗算器のパラメーターLODも、反射を描画する時間に合わせて変更されました。 したがって、水の中に詐欺師が少し長く表示され、負荷がさらに軽減されます。



すべての変更により、 統合GPUのパフォーマンスが毎秒6-8フレームから22-24フレームに向上しました。 レートはまだ低いですが、これまでのところ最高を達成することは不可能でした。 個別のグラフィックスでゲームを起動することをお勧めします。



研磨



パフォーマンスを変更しただけで新しいリリースをリリースしたくなかったため、ゲームの外観、つまりUIのかなり重要なポイントをいくつか閉じることにしました。



最初のリリースで私のゲームを見た人は皆、 UIが悪いと言いました。 彼は単に死んでいるだけで、私がこれを見たことがないとしても、今、彼の何が悪いのか理解しています。



音も、動きも、いのちもありませんでした。 プロジェクトの1つを開始すると、メインメニューの詳細に気付き始めました。これは、以前は単に目に見えませんでした。 そのため、最初の近似で不足しているものをすべて追加しました。 これで、選択されたボタンがアニメーション化され、Asset-Storeの無料リソースからフォーカスの翻訳とボタンのクリックが表明されます。



ゲーム自体のすべての要素をアニメートすることは、プレイヤーにとって気が散りすぎて煩わしいため、 UI要素に非常に似ている内部の虹色の輝きを追加しました。



そして最後はメダルスクリーンでした。 それはただ静的で、そこにいるのはひどく退屈だった。 しかし、これはプレイヤーが自分の進歩を見ることを楽しむ場所でなければなりません。 だから私は少し人生を追加しました。 現在では、3つのパーティクルシステムで装飾され、ホタルに似た柔らかい玉虫色のボールを作成しています。 残念ながら、パーティクルはデフォルトではUIで使用できないため、これらのパーティクルのみをテクスチャに描画してからUIをオーバーレイする追加のカメラを作成する必要がありました。



残されたもの



モバイルデバイスでゲームを実行できませんでした。 最も強力なハードウェアでさえ、許容可能なパフォーマンスでゲームをプレイしません。 おそらくUnity3dの将来のバージョンで何かが変わるかもしれませんが、現時点では、私が述べたように、モバイルデバイスに目を向けたゲームの開発は当初とは異なる方法で実行する必要があります。



また、私の場合、モバイルデバイスでのレンダリングの非常に奇妙なアーティファクトが発生しました。これは、文字通りレーザーのように、デスクトップでは非常に普通に見える素材が現れました。



最適化中にアプリケーションのクイック起動またはスムーズな起動という形で現れた別の目標も達成されませんでした。 私の場合、コールドスタートは1分以上続きます。 さらに、その後の起動ごとに、この時間はほぼ半分に短縮されます。 そのため、これはUnityプレーヤーの何らかの内部要件のようです。 最も重要な欠点は、シーンがアクティブになったときにUIストリームがハングすることです。 すでに非同期オプションを使用してシーンをロードし、それを加算モードに切り替えましたが、 allowSceneActivationフラグを切り替える必要がある場合ロードの90%後にUIが停止します。 Unity5.xの回避策、またはスレッドセーフでイベントコールなど、再描画で最も優先度の高いUiオブジェクトを変更して、アプリケーションプロセスを少なくともある程度示すようなものを誰かが教えてくれたら素晴らしいと思います。



PS



もちろん、これは単なる私の話であり、すべての問題を魔法のように解決するわけではありません。 一部の場所では、あまりにも主観的ですが、これらのヒントが役に立つと誰かが思うことを今でも願っています。



PPS
技術的な詳細に深く入り込むことなく、主なことを説明しました。 プロジェクトには非常に多くの変更が加えられ、すべてが説明されたわけではありませんでした-前回の出版物から、彼らがボートから浸透する水に対処するように頼んだことを確実に覚えています-そしてこれは間違いなく閉じられました。 また、チェックポイントの通過とメダルの取得中にサウンドが追加され、チェックポイントのタイマーの小さなアニメーションと時間に関する色情報も追加されました。

残念ながら、動作中のすべてを見ることに興味がある人は、まだビデオはありませんが、試用版は無限です。




All Articles