Games

マルチゲーム戦略

LUCY ANNUNZIATA / UNITY TECHNOLOGIESContributor
Jul 17, 2024|9 undefined
ゲームメニューを最適化し、ロード時間を短縮
このウェブページは、お客様の便宜のために機械翻訳されたものです。翻訳されたコンテンツの正確性や信頼性は保証いたしかねます。翻訳されたコンテンツの正確性について疑問をお持ちの場合は、ウェブページの公式な英語版をご覧ください。

カスタマー・サクセス・チームのコンサルタントとしてプロジェクト・レビューに参加する際、私はゲームを変えるようなアプリケーションを開発する顧客と仕事をすることが多い。これらのアプリケーションには、メインメニューまたはテーマメニューが1つあり、プレイヤーに複数のゲームの選択肢を提示する。そのようなセットアップでは、ゲームを切り替える間の時間をできるだけ短くする方法と、ゲーム全体で最適なパフォーマンスを確保する方法が主な関心事となる。このブログ記事では、プロジェクトのニーズに基づいたさまざまなアプローチと、ゲーム切り替え設定の有無にかかわらず、どのようなゲーム環境にも役立つベストプラクティスを探ります。

実行可能ファイルの取り扱い

ゲーム、エンターテインメント、産業シミュレーションなど、マルチアプリケーション環境を計画する際に最も重要な決定は、ゲーム実行ファイルをどのように管理するかということです。この決断を左右する要因はたくさんある:

  • プラットフォームが扱うゲームの数は?
  • 試合の規模は?
  • Unityのバージョンは同じですか?アプリケーションのボトルネックは何か?
  • その他の要因としては、ターゲットとなるハードウェア、メモリ、CPU、ディスク速度(SSD対HDD対SDカード)などがある。

これらの質問に答え、実行ファイルをどのように扱うかを決定することは、アプリケーションのパフォーマンスを最適化するために、ゲームごとに個別の実行ファイルが必要なのか、複数のゲームに対して1つの共有実行ファイルが必要なのか、あるいはその両方を組み合わせる必要があるのかを理解する上で極めて重要である。

複数の実行ファイルを持つことは、異なるUnityバージョンで作られたゲームを処理するための素晴らしいオプションです。このアプローチでは、実行ファイルをメモリにキャッシュし、各インスタンスをバックグラウンドに残すことで、ゲームの切り替え時間を短縮することができる。しかし、すべての実行可能ファイルをメモリ上に保持することは、メモリを圧迫する可能性があるため、必ずしも最良の選択とは言えない。個々のゲームのメモリフットプリントが大きい場合、および/またはゲームスイッチングアプリケーションに多くのゲームがある場合は、これを避ける必要があります。

メモリの制約を緩和するために、ゲームは単一の実行ファイルを共有することが可能です。Unityのバージョンが同じであれば、ゲームは1つのUnityプロジェクトでも、それぞれのプロジェクトでも構いません。WindowsのUnity 2022 LTS以降、-datafolder引数を使用して、コマンドライン経由で変数パスを渡すことができます(-datafolder <path_to_folder> )。この方法の潜在的な欠点は、ゲームの切り替え時間が遅くなることです。したがって、この欠点を減らすために、ローディングのベストプラクティスに従うことが重要です。

ベスト・ストラテジーのロード

開発するゲームの性質やプラットフォームにかかわらず、ゲームを選択してから画面に完全にロードされるまでの時間をできるだけ短くすることが重要です。この目標は、ゲームのスイッチング・アプリケーションで特に重要になる。

ローディングを処理する素晴らしい方法は、Addressablesを使用することです。Addressablesでは、コンテンツは必要に応じてダウンロードされ、リリースされる。この遅延ロード戦略は、初期起動時にロードしなければならないデータ量を制限するため、ゲームのロード時間を短縮する最も効率的な方法です。さらに、CPUのボトルネックの原因となるバックグラウンドゲームに関連するCPUのバックグラウンド動作を防ぐこともできる。アドレス可能:プランニングとベストプラクティス ブログの記事は、アドレス可能ファイルと、それがどのようにあなたのゲームを向上させるのに役立つかを学ぶための素晴らしい出発点です。

実行ファイルの数に関係なく、より高速なロードを保証する素晴らしい方法は、非同期ロードAPIを使うことだ。非同期でロードする場合、Unityのメインスレッドは「メインスレッド統合」と呼ばれるプロセスを実行し、ネイティブオブジェクトと管理オブジェクトの初期化をタイムスライス方式で行います。この処理はスレッドセーフでない処理を行うため、メインスレッド上で実行されることになり、ゲームが長時間フリーズするのを防ぐため、メインスレッド統合の実行時間は制限されている。統合に費やせる時間は、Application.backgroundLoadingPriorityプロパティによって定義される。ローディング画面中はBackgroundLoadingPriorityをHigh(50ミリ秒)に設定し、ローディングが完了したらBelowNormal (4ミリ秒)またはLow (2ミリ秒)に戻すことをお勧めします。

読み込みを高速化するもう1つの方法は、非同期テクスチャアップロードです。非同期テクスチャロードは、テクスチャとメッシュをGPU設定にアップロードするために使用される時間とメモリを調整することによって、ロード時間を減らすことができます。Understanding Async Upload Pipelineブログポストでは、このプロセスがどのように機能するかについての詳細情報を提供しています。

これらの実践は、ロード時間の短縮に役立つ:

  • シーンの内容をできるだけ少なくする。ブートストラップ・シーンを使って、ゲームがプレイ可能な状態になるために必要なものだけをロードし、必要なときに追加のシーンをロードする。
  • ローディング画面中のカメラを無効にする。
  • ロード中にUI Canvasesが入力されている間、UI Canvasesを無効にする。
  • ネットワークリクエストを並列化する。
  • 複雑なAwake/Start実装を避け、ワーカースレッドを利用する。
  • 常にテクスチャ圧縮を使用する。
  • 大容量のメディアファイル(オーディオファイルやテクスチャなど)をメモリに保持する代わりにストリーミングする。
  • JSONシリアライザーは避け、代わりにバイナリー・シリアライザーを使う。
バックグラウンドCPU使用率

前述したように、マルチゲーム環境における懸念はメモリだけではありません。バックグラウンドでのCPU動作も、プレイヤーのゲーム体験に負担をかける可能性があります。ゲームがアクティブにプレイされていないとき、CPUはまだ稼動しており、CPU飢餓を引き起こすことにより、アクティブなゲームのパフォーマンスが最適化されない。アクティブなゲームやその他のバックエンドプラットフォームプロセスのCPUスターベーションを防ぐ方法は、Unity Settingsで Run in Backgroundplayerをfalseに 設定することです。Run in Backgroundは、ゲームがフォーカスされていない間、Unityのゲームループを停止させます。設定はスクリプトで動的に変更することもできます。

public class ExampleClass : MonoBehaviour 
{
    void Example() 
    {
        Application.runInBackground = false;
    }
}

そのため、Thread.SleepC#メソッドを使用して、ゲームをプレイしていないスレッドをスリープさせることが重要です。Unityでバックグラウンド・スレッドを扱うには、慎重なプログラミングが必要であることを忘れないでください。これらのスレッドはUnityのAPIに直接アクセスできないため、デッドロックや競合状態などの問題が発生する可能性が高くなります。これを防ぐには、Unityのメインスレッドと適切に同期する必要がある。マルチスレッドを適切に実装するには、Unityのマニュアルページ「Overview of .NET inUnity 」の「Limitations of async and await tasks」のセクションと、スレッドとスレッディングの使用に関するMSDNの記事を確認してください。Unity 6ではAwaitableクラスが導入され、async/awaitのサポートが強化されました。

メモリリークの回避

メモリー・リークの原因を特定し修正するのは、特に開発の後期段階では困難で時間がかかる。陳腐に聞こえるかもしれないが、予防は常に治療に勝る。ここでは、どのような試合環境でも漏れを防ぐのに役立つ推奨事項をいくつか紹介する:

  1. メモリ上に新しいオブジェクトやアセットを作成する場合は、不要になったら必ず削除すること。Addressableを使用する場合は、必ず未使用のアセットを解放してください。
  2. シーンの ロード/アンロード時に、アセットがメモリから適切に削除されること。Unityはレベルがアンロードされたときに自動的にアセットをアンロードしないので、メモリからのアクセスを確実に削除することが重要です。Resources.UnloadUnusedAssetsAPIは、アセットをクリーンアップするのに役立ちます。しかし、操作が完了するまでオブジェクトを返すため、CPUスパイクを引き起こす可能性がある。
  3. GameObjectのInstantiateと Destroyを 頻繁に使わないようにする。そうすることで、不必要なマネージド・アロケーションが発生する可能性がある。ただし、Destroyを使用する必要がある場合は、Leaked Shell Objectsを避けるために、オブジェクトへの参照をすべて削除するようにしてください。オブジェクトやその親がDestroyによって破壊されるとき、C#のコードはUnityオブジェクトへの参照を保持し、管理されたラッパーオブジェクト(Managed Shell)をメモリに保持します。そのNative Memoryは、それが存在するSceneがアンロードされるか、それがアタッチされているGameObjectかその親がDestroyによって破壊されると、アンロードされる。したがって、アンロードされなかった他の何かがまだそれを参照している場合、管理されたメモリはLeaked Shell Objectとして生き続ける可能性がある。
  4. Singletonsを使用したイベントを実装する際には注意が必要です。シングルトンインスタンスは、そのイベントをサブスクライブしたすべてのオブジェクトへの参照を保持する。これらのオブジェクトがシングルトンインスタンスほど長く生きず、これらのイベントの購読を解除しない場合、メモリリークの原因となるメモリに残ることになる。イベントソースがリスナーより先に破棄されると、参照はクリアされ、リスナーが適切に登録解除されると、参照も残りません。この問題を解決・防止するには、シングルトンイベントをリッスンするすべてのオブジェクトにWeak Event PatternまたはIDisposableを実装し、コード内で適切に破棄されるようにすることをお勧めします。Weakイベント・パターンは、イベント駆動型プログラミングにおいて、特に寿命の長いオブジェクトに関して、メモリとガベージ・コレクションを管理するのに役立つデザイン・パターンである。購読者が短命で、発行者が長命の場合に特に有効です。これらはC#固有のソリューションであり、C#イベントでのみ動作し、UnityEventsやUnity UI Toolkitでは直接サポートされていないことに留意してください。そのため、これらの解決策はMonoBehaviour以外のスクリプトにのみ実装することをお勧めします。

最後に、開発の初期段階からプロファイリング、CI/CDテストの実施、ストレステストを行うことは、本当に時間の節約になる。リークが発生した時点で検出することで、問題に迅速に対処することができ、デバッグの時間を節約し、最適なパフォーマンスを確保することができるからだ。