ゲームのオン デマンド インストール
ソフトウェア デザイン エンジニア、Jack Lin 著
Microsoft Corporation
2004 年 12 月
はじめに
この技術関連記事では、Windows インストーラーを使用するオン デマンド インストールとバックグラウンド インストールの 2 つの手法について説明します。この 2 つのインストール手法をゲームで利用してインストール時間を削減することで、プレイヤーがさらに円滑かつ快適にゲームを操作できるようになります。
目次
- 概要
- 修正プログラム適用のサポート
- InstallOnDemand SDK サンプル
- サンプルの機能とコンポーネント
- インストール
- 制限事項
概要
インストールは、長い間コンピューター ベースのアプリケーションの 1 つの側面でした。現在では、ほとんどのアプリケーションが、ユーザーのローカル ハード ドライブにインストールしてからでないと使用できないようになっています。コンピューター ゲームも例外ではありません。Microsoft Windows ゲームを購入したユーザーは、ゲームを実行する前に、まずインストール プロセスを実行してゲーム ディスクからハード ドライブに必要なファイルをコピーする必要があります。通常、このインストール プロセスには時間がかかり、完了までに 1 時間を要することもあります。一部のプレイヤーでコンピューター ベースのゲームよりコンソール ゲームの方が好まれているのは、このインストール時間が一因です。コンソール ゲームでは、ゲーム ディスクを挿入するとすぐにプレイできるからです。この記事で説明するテクノロジは、インストール時間を徹底的に削減することでこの問題を改善します。
従来より、ゲームを起動するには、事前に全ファイルまたはほとんどのファイルをインストールする必要があります。オン デマンド インストールを実現するには、ゲーム リソースをモジュール化する必要があります。つまり、開発者はアプリケーションのリソース (グラフィックやオーディオなど) をコンポーネントに分割する必要があります。各コンポーネントは、1 つの単位としてインストールまたは削除できるリソースのセットで構成されます。分割を行ったら、一般にはレベルまたはゾーンごとに、1 つ以上の機能を定義します。アプリケーションの各機能では、その機能を実行するために必要な一連のコンポーネントを指定します。アプリケーションがインストールされるときに、その機能を "インストール" (コンポーネントがインストール時にローカル ハード ドライブにコピーされることを示す) または "アドバタイズ済み" (コンポーネントが最初のインストール後にアプリケーションがその機能を使用するときにローカル ハード ドライブにコピーされることを示す) とマークすることができます。ゲーム開発者は、最小限の機能がインストールされた状態で起動および実行されるようにゲームを設計することで、インストール時間を削減できます。残りの機能は、アドバタイズ済みとマークして、アプリケーションが実際にその機能を使用する必要が生じたときにオン デマンドでインストールできます。
ゲームでは、Windows インストーラーを呼び出して、インストールされていない特定の機能をインストールできます。インストールをバックグラウンドで実行するには、プライマリ スレッドがゲームのロジックを処理して画面をレンダリングしている間に、ワーカー スレッドを使用してインストーラーを呼び出します。これによって、インストールによるゲームプレイの中断を最小限に抑えます。ゲームではいつでもインストールを開始できます。ただし、インストールでプロセッサ サイクルが消費されるため、一般にプライマリ スレッドが処理能力を緊急に必要としているとき (アクションの最中など) にインストールを実行するのは賢明ではありません。インストールを実行するタイミングとして適しているのは、ゲーム メニューが表示されているとき、ゲームがポーズ中または最小化されているとき、ユーザーがイントロダクション ムービーやカットシーンを見ているときなどです。
修正プログラム適用のサポート
最近のゲームのほとんどは、出荷した後も、バグの修正や新機能の追加などで更新を必要とします。更新では多くの場合修正プログラムの適用が必要となりますが、ゲームでは従来からこの処理は簡単です。必要なすべてのファイルがユーザーのハード ドライブにインストールされているので、ゲームに修正プログラムを適用する処理では、変更されたファイルをハード ドライブにコピーして既存のファイルを上書きします。オン デマンド インストールが採用されている場合は、修正プログラムを適用する時点ですべてのファイルがインストールされ、コピーされているとは限りません。したがって、修正プログラムを適用する際に、更新されたファイルを単純にゲーム フォルダーに書き込むことはできません。
Windows インストーラーは、オン デマンド インストールを使用するアプリケーションに修正プログラムを適用するための機能を備えています。このインストーラーは、修正プログラムを適用する際に修正プログラムをシステムにキャッシュします。この機能は、小規模な差分で構成される修正プログラムに適しています。最初にリリースされたファイルは、修正プログラムを適用する時点でディスク上に存在している必要がないため、アドバタイズすることができます。後からアプリケーションが実行されてそのファイルにアクセスする必要が生じたときに、インストーラーは最初にリリースされたバージョンをメディア (CD など) からコピーし、保存された修正プログラム データを読み取った後に修正プログラムを適用することで、そのファイルの最新バージョンをインストールします。
InstallOnDemand SDK サンプル
ゲームのオン デマンド インストール サンプルでは、この記事で説明するオン デマンド インストールの手法が示されています。他のサンプルとは異なり、ゲームのオン デマンド インストール はサンプル ブラウザーから直接実行できません。このサンプルは Windows インストーラーを使用して自身のインストールを管理するため、インストールされているアプリケーションのインストーラーのデータベースに組み込む必要があります。このサンプルを起動する手順は次のとおりです。
- サンプル ブラウザーの [Install Project] リンクを使用して、サンプルのファイルをフォルダーにコピーします。
- InstallOnDemand.msi をダブルクリックしてサンプルをインストールします。
- [標準インストール] を選択します。
- インストールされたフォルダー (通常は Program Files\InstallOnDemand) にある InstallOnDemand.exe を起動するか、[スタート] メニューの [プログラム] から起動することで、サンプルを開始します。
InstallOnDemand.msi は、インストーラーによって認識されるデータベースです。このデータベースでは、インストール プロセス全体が定義されています。つまり、ディレクトリ構造、コピーの対象と対象外、一緒にコピーするリソース、書き込むレジストリ値、作成するショートカットなどが定義されています。
サンプルを起動すると、イントロダクション シーケンスが再生されます。プレイヤーは、Esc キーを押すことでこのシーケンスを終了してメイン メニューを表示できます。イントロダクションの後に、プレイヤーはキャラクター名を入力し、統計データをスクロールすることで新しいゲームを開始します。サンプルでは、イントロダクション シーケンスの再生を開始する前に、インストーラー関数を呼び出してレベル 1 の機能がインストールされているかどうかをチェックします。レベル 1 の機能がインストールされていない場合、サンプルはバックグラウンド スレッドを使用してインストーラーにゲームのインストールを要求します。この間、プライマリ スレッドでは別の処理が実行されています (イントロダクション シーケンスの再生、メニューのレンダリング、キャラクターを作成しているプレイヤーとの対話など)。従来のゲームのインストールと異なるのは、ユーザーがゲームを操作している間 (イントロダクションを見たり新しいキャラクターを作成したりしている間) にインストールが進行する点です。プレイヤーがキャラクターの作成を完了すると、サンプルでレベル 1 のリソースがロードされます。
サンプル画面の右側には、[Play Level 1] から [Play Level 5] までの 5 つのボタンが表示されます。これらのボタンで、プレイヤーの現在のレベルの完了と次のレベルへの昇格がシミュレートされます。いずれかのボタンがクリックされると、プレイヤーが完了したレベルに関する情報を示す統計画面が表示されます。サンプルでは、このタイミングでインストーラーに次のレベルのチェックとインストールを要求します (まだインストールされていない場合)。このインストールはプレイヤーが統計画面を見ている間に行われます。これにより、ユーザーが [OK] をクリックして次のレベルを開始したときに、そのレベルのリソースがすべてインストールされてロード可能な状態となっているようにします。
サンプルの機能とコンポーネント
従来より、ゲームを起動するには、事前に全ファイルまたはほとんどのファイルをインストールする必要があります。オン デマンド インストールを実現するには、ゲーム リソースをモジュール化する必要があります。つまり、開発者はアプリケーションのリソース (グラフィックやオーディオなど) をコンポーネントに分割する必要があります。各コンポーネントは、1 つの単位としてインストールまたは削除できるリソースのセットで構成されます。分割を行ったら、一般にはレベルまたはゾーンごとに、1 つ以上の機能を定義します。アプリケーションの各機能では、その機能を実行するために必要な一連のコンポーネントを指定します。アプリケーションがインストールされるときに、その機能を "インストール" (コンポーネントがインストール時にローカル ハード ドライブにコピーされることを示す) または "アドバタイズ済み" (アプリケーションが後からその機能を使用するときにコンポーネントがローカル ハード ドライブにコピーされることを示す) とマークすることができます。ゲーム開発者は、最小限の機能がインストールされた状態で起動および実行されるようにゲームを設計することで、インストール時間を削減できます。残りの機能は、アドバタイズ済みとマークして、アプリケーションが実際にその機能を使用する必要が生じたときにオン デマンドでインストールできます。
次の表に、サンプルで定義されている 6 つの上位機能を示します。
機能名 | 機能 | 成分 | ファイル |
---|---|---|---|
コア | レベルに関係なく常時必要なリソースが含まれています。そのリソースとは、サンプルの実行可能ファイル、イントロダクション シーケンスと画面のロードに必要なメディア、およびサンプルのすべてのレンダリングを処理する .fix ファイルです。 | コア | InstallOnDemand.exe、InstallOnDemand.fx、Loading.bmp、Level.x |
コア | (同上) | CoreUI | Media\UI\dxutcontrols.dds、Media\UI\DXUTShared.fx、Media\UI\arrow.x |
コア | (同上) | CoreMisc | Media\Misc\seafloor.x、Media\Misc\seafloor.bmp |
コア | (同上) | CoreSpeeder | Media\PRT Demo\LandShark.x、Media\PRT Demo\speeder_diff.jpg |
コア | (同上) | CoreReg | なし (レジストリ値) |
Level1 | レベル 1 で使用されるリソースを提供します。 | Level1 | Level1.jpg |
Level1 | (同上) | L1Skybox | Media\Light Probes\galileo_cross.dds |
Level2 | レベル 2 で使用されるリソースを提供します。 | Level2 | Level2.jpg |
Level2 | (同上) | L2Skybox | Media\Light Probes\grace_cross.dds |
Level3 | レベル 3 で使用されるリソースを提供します。 | Level3 | Level3.jpg |
Level3 | (同上) | L3Skybox | Media\Light Probes\rnl_cross.dds |
Level4 | レベル 4 で使用されるリソースを提供します。 | Level4 | Level4.jpg |
Level4 | (同上) | L4Skybox | Media\Light Probes\stpeters_cross.dds |
Level5 | レベル 5 で使用されるリソースを提供します。 | Level5 | Level5.jpg |
Level5 | (同上) | L5Skybox | Media\Light Probes\uffizi_cross.dds |
Level1 から Level5 の各機能には、サンプルで直接使用されないファイルで構成されている追加のサブ機能があります。これらのサブ機能ファイルを追加することで、インストールの時間を引き延ばしています。これは、サンプルの実行中にバックグラウンドで実行される進行中のインストール処理を説明するためです。
このサブ機能の一覧を次の表に示します。
機能 | サブ機能 | ファイル |
---|---|---|
Level1 | L1PH1、L1PH2、L1PH3、L1PH4、L1PH5 | Level1 Placeholder Data\L1PH1.dat Level1 Placeholder Data\L1PH2.dat Level1 Placeholder Data\L1PH3.dat Level1 Placeholder Data\L1PH4.dat Level1 Placeholder Data\L1PH5.dat |
Level2 | L2PH1、L2PH2、L2PH3、L2PH4、L2PH5 | Level2 Placeholder Data\L2PH1.dat Level2 Placeholder Data\L2PH2.dat Level2 Placeholder Data\L2PH3.dat Level2 Placeholder Data\L2PH4.dat Level2 Placeholder Data\L2PH5.dat |
Level3 | L3PH1、L3PH2、L3PH3、L3PH4、L3PH5 | Level3 Placeholder Data\L3PH1.dat Level3 Placeholder Data\L3PH2.dat Level3 Placeholder Data\L3PH3.dat Level3 Placeholder Data\L3PH4.dat Level3 Placeholder Data\L3PH5.dat |
Level4 | L4PH1、L4PH2、L4PH3、L4PH4、L4PH5 | Level4 Placeholder Data\L4PH1.dat Level4 Placeholder Data\L4PH2.dat Level4 Placeholder Data\L4PH3.dat Level4 Placeholder Data\L4PH4.dat Level4 Placeholder Data\L4PH5.dat |
Level5 | L5PH1、L5PH2、L5PH3、L5PH4、L5PH5 | Level5 Placeholder Data\L5PH1.dat Level5 Placeholder Data\L5PH2.dat Level5 Placeholder Data\L5PH3.dat Level5 Placeholder Data\L5PH4.dat Level5 Placeholder Data\L5PH5.dat |
インストール時に、コア機能に "インストール" とマークし、それ以外のすべての機能に "アドバタイズ済み" とマークする必要があります。インストールする機能を 6 つではなく 1 つにすることで、ゲームが起動されるまでにプレイヤーが待たされる時間が大幅に削減されます。
インストール
Windows インストーラーは、アプリケーションがアドバタイズされた機能のインストールを要求するためのメカニズムを備えています。ただし、このメカニズムは同期 API (アプリケーション プログラミング インターフェイス) 呼び出しです。つまり、アプリケーションはインストールが完了するまで呼び出し内で待機する必要があります。バックグラウンド インストールを実現するには、メインのアプリケーション スレッドが他の重要なタスク (プレイヤーに継続して視覚フィードバックを提供するための画面でのレンダリングなど) を実行できるようにするために、ワーカー スレッドが必要となります。
サンプルでは、サンプル実行時に 3 種類のインストールの状態が発生します。アクティブ インストール、パッシブ インストール、およびインストール禁止です。
- アクティブ インストールは、サンプルが 1 つ以上の機能によって提供されるリソースをアクセスまたはロードする必要があるときに開始する要求です。サンプルでは、リソースをインストールしないと処理を続行できない場合にこのインストールが行われます。
- パッシブ インストールは、プレイヤーがメニューを表示しているときやカットシーンを見ているときなど、サンプルがクリティカルなタスクを実行していないときに起動されます。この場合は、ワーカー スレッドによってサンプルにアドバタイズされている機能が残っていないかがチェックされます。このような機能が検出された場合は、インストーラーが呼び出されてその機能がインストールされます。この処理は、サンプルのすべての機能がインストールされるまで繰り返されます。基本的にパッシブ インストールでは、メインのサンプルへの割り込みが最小限となるタイミングで、余分なプロセッサ サイクルを利用してバックグラウンドでインストールが実行されます。
- インストール禁止は、プレイヤーが活発にゲームをプレイしているときに発生する状態です。これにより、ユーザーの操作性を損なうフレームレートの低下を防ぎます。
サンプルでは、インストールに関連するすべてのタスクを処理する CMsiUtil クラスが定義されています。基本的に CMsiUtil では、インストーラーを起動してサンプルの機能をループ形式でインストールするワーカー スレッドが使用されます。このクラスには、インストール要求を格納する 2 つのキューがあります。1 つはアクティブ インストール用の優先度の高いキュー、もう 1 つはパッシブ インストール用の優先度の低いキューです。初期化時に、このクラスによって製品の全機能が列挙され、パッシブ インストール キューに追加されます。このように製品全体がキューに登録されるため、サンプルが十分なプロセッサ サイクルを使用できる場合は、最終的に製品全体がインストールされます。
サンプルでアクティブ インストールを要求する必要があるときは、CMsiUtil::UseFeatureSet() が呼び出され、最上位レベルの機能の名前が渡されます。UseFeatureSet() は、要求された機能とそのすべてのサブ機能をアクティブ インストール キューに登録して、ワーカー スレッドがそれらの機能をインストールできるようにします。
ワーカー スレッドは、インストール要求を実行する間に、アクティブ インストール キューとパッシブ インストール キューをチェックして、これらのキューに要求が追加されていないかを確認します。スレッドは、要求を検出するたびに、インストーラー API を呼び出して実際にインストールを実行します。両方のキューが空になると、ワーカー スレッドは WaitForSingleObject を呼び出してスリープ状態になります。初期化時に製品全体がパッシブ インストール キューに登録されるため、キューが空であることは、製品全体がインストールされたことを意味します。
サンプルでは、CMsiUtil::EnablePassiveInstall() を呼び出してパッシブ インストールを有効または無効にします。EnablePassiveInstall(true) はパッシブ インストールの有効カウントをインクリメントし、EnablePassiveInstall(false) はデクリメントします。有効カウントが 0 より大きい場合は、このクラスによってパッシブ インストール キューが処理されます。サンプルでは、次のいずれかが当てはまる場合にパッシブ インストールが許可されます。
- ユーザーが最初のイントロダクション シーケンスを見ている。
- ユーザーがサンプル メニューを操作している。
- ユーザーがレベル終了時に統計データを見ている。
- サンプル アプリケーションがフォーカスを失ってバックグラウンドで実行されている。
CMsiUtil のメソッドの一覧を次に示します。
メソッド | 説明 |
---|---|
AbortAllRequests | 現在のインストールを中止し、アクティブ インストール要求キューを空にします。 |
AbortCurrentRequest | 進行中のインストールを中止します。その後にワーカー スレッドがキュー内の次の要求 (存在する場合) を処理します。 |
EnablePassiveInstall | パッシブ インストール有効カウントをインクリメントまたはデクリメントします。サンプルでは、この呼び出しを使用して、パッシブ インストールを実行できるタイミングとできないタイミングを制御します。 |
GetCurrentFeatureName | 現在インストール中の機能の名前を返します。 |
GetFeatureProgress | インストールされている機能の現在の目盛り位置を返します。 |
GetFeatureProgressMax | インストールされている機能の進行状況目盛りの最大数を返します。 |
GetLastError | 前のインストール要求のリターン コードを取得します。 |
GetPassiveProgress | パッシブ インストールの進行状況バーの目盛り位置を返します。 |
GetPassiveProgressMax | パッシブ インストールの現在の目盛り位置と目盛りの最大数を返します。サンプルでは、この 2 つの数値を使用してパッシブ インストールの全体的な進行状況を示します。 |
GetProgress | アクティブな機能セット インストールの進行状況バーの目盛り位置を返します。このメソッドは、サンプルがインストールの進行状況バーをレンダリングするときに使用されます。Windows インストーラーからはインストールされている 1 つの機能の進行状況情報しか提供されないため、このメソッドで進行状況バーを要求された機能の間で分けて、インストール全体が 1 つのタスクとしてユーザーに表示されるようにします。 |
GetProgressMax | アクティブな機能セット インストールの進行状況バーの最大目盛りカウントを返します。このメソッドは、サンプルがインストールの進行状況バーをレンダリングするときに使用されます。 |
Initialize | 製品のグローバル一意識別子 (GUID) でクラスを初期化します。またこのメソッドでは、アドバタイズされてまだインストールされていないアプリケーションの機能をすべて列挙し、パッシブ インストール キューに入れることで、パッシブ インストールを設定します。 |
IsInstallInProgress | このメソッドは、アクティブ インストールが処理されているかどうかを確認する場合に使用します。 |
UseFeature | UseFeatureSet から呼び出されるプライベート メソッドです。機能がインストールされているかどうかをチェックします。要求した機能がインストールされている場合は、メソッドが戻ります。機能がまだインストールされていない (アドバタイズされている) 場合、メソッドはワーカー スレッドに対する新しいアクティブ インストール要求をキューに登録し、戻ります。要求されたインストールが完了したときに通知されるオプションのイベント ハンドルです。 |
UseFeatureSet | 特定の機能またはそのサブ機能が提供する機能にアクセスする必要が生じたサンプルによって呼び出されます。このメソッドは、サンプルのすべての機能を列挙し、指定されたルート機能のサブ機能に対して UseFeature() を呼び出します。サンプルでは、機能セット全体がインストールされるときに通知されるイベント ハンドルを渡すことができます。機能はいずれもセットとしてインストールされるので、要求したすべての機能がインストールされた時点でサンプルが通知を受けられるように、このハンドルはすべての機能ではなく UseFeature() によって最後にキューに登録された機能に対して指定されます。 |
UseProduct | ワーカー スレッドがインストーラーを呼び出して製品の完全インストールを実行できるように、アクティブ インストール要求をキューに登録します。要求されたインストールが完了したときに通知されるオプションのイベント ハンドルです。 |
制限事項
現在のバージョンのインストーラーは、複数のスレッドによる同時アクセスに対応できるように設計されていません。したがって、ワーカー スレッドがインストーラーを呼び出しているときは、メイン スレッドでインストーラーを呼び出さないようにする必要があります。この制限に該当する例として、サンプルでメイン スレッドが機能を要求し、ワーカー スレッドがインストールを完了する前に同じ機能をもう一度要求する場合が挙げられます。2 回目の要求では MsiQueryFeatureState() を呼び出して、要求された機能がインストールされているかどうかを確認します。これは、ワーカー スレッドがまだファイルをコピーしているときに、インストーラーが機能のインストールが完了していることを示す場合があるためです。
幸い、この制限には簡単な対処法があります。CMsiUtil では、ワーカー スレッドが機能をインストールしているかどうかをチェックしてから、MsiQueryFeatureState() や MsiUseFeature() などの関数を呼び出して対象の機能のインストール状態を確認します。この制限が他の場所で問題となる可能性もあるので注意してください。
修正プログラムの適用が、エンド ユーザーのコンピューターでのオン デマンド インストールの効果に影響する可能性があります。前のバージョンから変更されたデータのみを含んでいる修正プログラムを適用するには、差分を適用するために、更新されるファイルの前のバージョンがインストールされている必要があります。この場合は、ゲームに修正プログラムを適用する前に、修正プログラムで、関係するアドバタイズされた機能のインストールをインストーラーに要求しなければなりません。
このサンプルは、Windows インストーラーがコンピューター上に存在することを前提としているため、InstallOnDemand.msi を起動することでインストールされます。インストーラーが存在しない場合は、.msi ファイルが認識されず、起動しても動作しません。この問題に対処するには、アプリケーションでインストール プログラムを使用してこのタスクを実行する必要があります。このプログラムでは、まずインストーラーが存在するかどうかを確認し、存在する場合はそのバージョンを確認します。バージョンがアプリケーションの要件と合わない場合は、インストール プログラムで Windows インストーラーをインストールしてから .msi ファイルを起動する必要があります。このプロセスはブートストラップと呼ばれます。一般に、アプリケーションではブートストラップ インストール プログラムに Setup.exe という名前が付けられます。このサンプルではブートストラップが行われません。ブートストラップの詳細については、「Windows Installer (Windows インストーラー)」を参照してください。
開発者は、ゲームの各機能のサイズにも注意を払う必要があります。インストールが進行中に取り消されると、インストーラーはコンピューターをインストール前の状態に復元します。つまり、機能のインストールは完全に取り消され、機能が部分的にインストールされることはありません。機能が大きいとインストールにかかる時間が長くなるため、インストールが中断されて取り消されたり、インストールがメイン アプリケーションを妨害する可能性が高くなります。たとえば、ユーザーがゲーム プレイの途中でゲーム メニューを開いたときにパッシブ インストールを有効にしたとします。機能のインストール中にユーザーがプレイに戻った場合、ゲームでは 2 とおりの処理を実行できます。つまり、パッシブ インストールを最後まで実行するか、パッシブ インストールを取り消します。機能が大きいと、どちらのケースでも問題が生じます。ゲームで大規模なインストールを最後まで実行すると、長時間にわたってゲームのレンダリング パフォーマンスが阻害される場合があります。逆に、ゲームでインストールを取り消すと、ゲームに戻るまでにユーザーがメニューで長時間待たされることになります。開発者は、個々のゲームに最も適したバランスのとれた機能サイズを特定する必要があります。