# WinForms グラフィック デバイス サンプル

このサンプルは、XNA Framework の GraphicsDevice を使用して、WinForms アプリケーション内で 3D グラフィックを表示する方法を示します。

# サンプルの概要

XNA Framework の Game クラスにより、迅速かつ容易で、移植可能な方法で、ゲームをホストできます。このクラスは、内部でゲームを動作させるためのウィンドウを作成し、グラフィック ハードウェアを初期化して、オーバーライドの対象となる単純な Update メソッドと Draw メソッドを提供します。ただし、Game の動作の柔軟性が十分でない場合があります。ウィンドウの作成方法をより詳細に制御したり、レベル エディターを記述していて、Windows ユーザー インターフェイスのコントロールを 3D 描画サーフェイスの周辺に配置したりする場合があるかもしれません。

さいわい、XNA Framework は、このようなシナリオを考慮して設計されています。XNA Framework は、実際には、いくつかのアセンブリ (Microsoft.Xna.Framework、Microsoft.Xna.Framework.Graphics など) で構成されています。これらのアセンブリは、計算、グラフィック、入力、およびオーディオ クラスなどの核となる機能を提供し、Microsoft.Xna.Framework.Game は、Game クラスなどの、オプションのより高いレベルのコードを提供します。他の方法でゲームをホストする場合は、Microsoft.Xna.Framework.Game の機能を、独自のコードに置き換えることができます。

このサンプルでは、GraphicsDeviceControl クラスを実装します。このクラスは、System.Windows.Forms.Control を継承し、WinForms コントロールが、XNA Framework の GraphicsDevice を使用して、それ自体を描画できるようにします。また、複数のコントロール間で単一の GraphicsDevice を共有する方法、サイズの変更と失われたデバイスの処理方法、および ContentManager を介したデータの読み込みをサポートするための、IGraphicsDeviceService インターフェイスの実装方法について説明します。

独自のプログラムでこの機能を再利用するには、ソース ファイル GraphicsDeviceControl.cs、GraphicsDeviceService.cs、および ServiceContainer.cs をコピーします。GraphicsDeviceControl から独自のカスタム コントロール クラスを派生させ、Initialize メソッドと Draw メソッドをオーバーライドします。

このサンプルは、Windows でのみ動作することに注意してください。WinForms は、Xbox 360 または Windows Phone では使用できません。

# サンプルが動作するしくみ

このサンプルでは、XNA Framework によって提供される "Windows ゲーム" テンプレートではなく、標準の "Windows フォーム アプリケーション" プロジェクト テンプレートを使用します。XNA Framework の機能を使用するには、Microsoft.Xna.Framework および Microsoft.Xna.Framework.Graphics アセンブリを、プロジェクトの References セクションに手動で追加する必要があります。

サンプルにより、SpriteFontControl および SpinningTriangleControl の 2 つのカスタム コントロールが提供されます。両方とも、GraphicsDeviceControl を継承して、これを使用し、XNA Framework を利用してグラフィックを描画します。

## GraphicsDevice の管理

多数の GraphicsDeviceControl インスタンスが同時に使用される場合があります。効率性の観点から、基になる単一の GraphicsDevice オブジェクトのみを作成します。これは、GraphicsDevice を作成および所有する GraphicsDeviceService クラスを介して管理されます。参照数をカウントするシステムによって、共有の GraphicsDeviceService を使用している GraphicsDeviceControl インスタンスの数が追跡されます。AddRef メソッドで、GraphicsDeviceService によって、これが、グラフィック デバイスを必要とする最初のコントロールかどうかが確認されます。そうである場合は、シングルトン インスタンスが作成されます。そうでない場合は、単に既存のインスタンスが再利用されます。Release メソッドで、これが、グラフィック デバイスの使用を終了する最後のコントロールかどうかが確認されます。そうである場合は、共有の GraphicsDevice が破棄されます。

複数のコントロールが単一のグラフィック デバイスを共有している場合、どの程度のサイズのバック バッファーをそのデバイスに提供すべきか、という問題が発生します。コントロールがすべて同じサイズではない可能性がありますが、異なるサイズのコントロールに描画するたびに、バック バッファーのサイズを変更するためにデバイスをリセットしなければならないとしたら、その方法は非効率的です。解決策は、バック バッファーのサイズを、最も大きいコントロールに合わせ、小さいコントロールに描画する場合は、その一部のみを使用する方法です。これは、(現在のコントロールのサイズに対応する) バック バッファーの左上の部分にのみレンダリングするようにビューポートを設定する GraphicsDeviceControl.BeginDraw メソッドと、ディスプレイにコピーするバック バッファーの領域を正確に指定できるようにする GraphicsDevice.Present のオーバーロードを使用する GraphicsDeviceControl.EndDraw メソッドによって制御されます。EndDraw は、正しいウィンドウ ハンドルを Present に渡し、Present が、可能性のあるいくつものコントロールから、レンダリング対象のコントロールを特定できるようにする処理も行います。

グラフィック デバイスが常に使用可能であるという保証はありません。デスクトップをロックしたり、他のプログラムがフルスクリーン 3D モードに切り替えたりした場合、デバイスはデスクトップになります。また、他のプログラムがフルスクリーン 3D モードに切り替えると、一時的にデバイスにアクセスできなくなります。これが発生した場合、使用を継続するには、デバイスをリセットする必要があります。通常、このような状況は、XNA Framework の Game クラスによって処理されます。Game を使用していないため、自身でこれらを処理する必要があります。これは、BeginDraw によって呼び出される GraphicsDeviceControl.HandleDeviceReset メソッドによって実行されます。これは、デバイスの現在の状態を確認します。デバイスが失われている場合は、エラー メッセージが返されます。この場合、デバイスを使用して描画することはできません。デバイスが失われていて、リセットする必要がある場合、または、デバイスのバック バッファーが、レンダリングしようとしている対象のコントロールに対して小さすぎる場合は、デバイスがリセットされます。これにより、その有効性と適切なサイズが確保されます。

## デザイナーのサポート

WinForms コントロールは、プログラムを実行するときにのみ使用されるわけではありません。MainForm.cs ファイルをデザイナーに読み込むと、それに含まれる 2 つのコントロールのプレビューが表示されます。これらのプレビューは、カスタム コントロール クラスのインスタンスの読み込みと作成を行うデザイナーによって実装されます。ほとんどの場合、これは、便利な動作ですが、アニメーション表示される 3D グラフィック コントロールは、リソースが過度に使用されるため、デザイナー内では動作させることができません!

デザイナー シナリオを適切に処理するために、GraphicsDeviceControl クラスは、OnCreateControl メソッドから、その DesignMode プロパティを確認します。これは、デザイナー内で動作している場合は、グラフィック デバイスの初期化を省略します。その結果、Initialize メソッドまたは Draw メソッドが呼び出されなくなります。代わりに、より単純な PaintUsingSystemDrawing メソッドを使用して、コントロールのプレースホルダー表現を示します。これは、デザイナーで表示することができます。

## コンテンツの読み込み

モデル、テクスチャー、SpriteFont データなどのグラフィック コンテンツを読み込むためには、2 つのものが必要です。

まず、プロジェクトに沿ってコンテンツをビルドする必要があります。これは、XNA Framework のコンテンツ プロジェクトをソリューションに追加することで行います。また、コンテンツ プロジェクトは自身を直接ビルドすることができないため、コンテンツをビルドするためのゲーム ライブラリー プロジェクトも追加する必要があります。最初に、ソリューションに "空のコンテンツ プロジェクト" を追加し、新しいコンテンツ プロジェクトにコンテンツ ファイル (このサンプルで使用されている Arial.spritefont など) を追加します。次に、コンテンツのビルドに使用する "Windows ゲーム ライブラリー" プロジェクトを追加します。このライブラリー プロジェクトに実際に何らかのコードを追加する必要はないので、プロジェクト テンプレートによって追加された .csproj ファイルは削除してかまいません。次に、ゲーム ライブラリー プロジェクトを右クリックして [コンテンツ参照の追加] を選択し、コンテンツ プロジェクトを選択します。最後に、メインの WinForms プロジェクトを右クリックして [参照の追加] を選択し、ゲーム ライブラリー プロジェクトを選択します。これで、コンテンツ ファイルは、XNA Framework Content Pipeline を使用してビルドされ、実行可能ファイルと共にビルド出力フォルダーに自動的にコピーされます。

次に、コンテンツ ファイルを読み込むための ContentManager を作成する必要があります。ContentManager は、グラフィック データを読み込むために、カスタム GraphicsDevice にアクセスする必要があります。これら 2 つを接続するための何らかのプラミングを配置する必要があります。GraphicsDeviceService クラスは、標準の IGraphicsDeviceService インターフェイスを実装します。ContentManager は、グラフィック デバイスを見つける必要があるたびに、このインターフェイスを使用します。このインターフェイスを公開するために、GraphicsDeviceControl は、Services プロパティを提供し、その OnCreateControl メソッド内の IGraphicsDeviceService を登録します。このプラミングが適切に配置されると、SpriteFontControl.Initialize メソッドで見られるように、カスタム Services を ContentManager コンストラクターに渡すことができます。

このサンプルで使用されている方法では、プロジェクトの一部としてビルドできるように、事前にすべてのコンテンツ ファイルが既知となっている必要があります。コンテンツをより動的にビルドする、および読み込むための方法については、WinForms コンテンツの読み込みサンプルを参照してください。

## アニメーション

通常は、WinForms アプリケーションはアニメーション表示されません。大抵の場合、イベントによって、キー押下またはマウス イベントなどのユーザー アクションが通知されるまでは、そこに配置されるだけであり、何も実行しません。通知された時点で、すばやく動作し始め、イベントを処理し、変更があった場合は画面を再描画して、再び活動を休止します。

ゲームは、このようには動作しません。XNA Framework の Game クラスを使用した場合、このクラスは、ユーザーから何の入力も提供されていなくても、Update メソッドと Draw メソッドを立て続けに呼び出します。

Game クラスを置き換えるときは、WinForms スタイルのイベントベースの更新を使用するか、ゲームスタイルの継続的なアニメーションを使用するかを決定する必要があります。このサンプルでは、両方の方法について説明しています。SpriteFontControl クラスは、アニメーション表示されません。単に Draw メソッドを使用して、テキストを表示します。このクラスは、ウィンドウのサイズが変更された場合、または他のウィンドウがその上にドラッグされて無効化された場合にのみ、通常の WinForms コントロールの場合とまったく同じ方法で、それ自体を再描画します。一方で、SpinningTriangleControl は、ゲームスタイルのアニメーションを使用します。これは、Initialize メソッドで、1 行で実装されます。

C#

            // 常にアニメーションを再描画するように、アイドル状態のイベントをフックします。
            Application.Idle += delegate { Invalidate(); };

これにより、WinForms が、他に処理すべきイベントがなくなるたびに、コントロールを再描画するようになります。次に、Draw メソッドで、Stopwatch を使用して、前回の Draw からどれだけの時間が経過したかを測定します。その後、この時間の値を使用して、回転するトライアングルの速度が制御されます。