SilverlightとXNAを共存させる
第2回の記事で紹介した通り、SilverlightとXNAを同一の画面で併用するためには、以下の手順を実施します。
- Visual Studioに定義されている[Windows Phone Silverlight/XNA アプリケーション]プロジェクトテンプレートを利用して、プロジェクトを作成する
- UIElementRendererクラスを利用して、SilverlightのUIコントロールをXNAで描画する
前回作成したサンプルは[Windows Phone Silverlight/XNA アプリケーション]プロジェクトテンプレートから作成したプロジェクトです。このため、UIElementRendererクラスを利用することにより、UIコントロールと3Dモデルを共存させることができます。ただし、この際に注意するべきポイントがありますので、復習も兼ねてUIElementRendererクラスの利用方法を見ていきましょう。
まずは「GamePage.xaml」に対して、適当なUIコントロールを配置します。例えば、以下のコードでは「MainPage.xaml」の内容を複製し、不要なボタンを削除してアプリケーション名とページタイトルを変更しています。
<phone:PhoneApplicationPage x:Class="SlXnaApp.GamePage" … > <!--LayoutRoot は、すべてのページコンテンツが配置されるルートグリッドです--> <Grid x:Name="LayoutRoot" Background="Transparent"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!--TitlePanel は、アプリケーション名とページタイトルを格納します--> <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> <TextBlock x:Name="ApplicationTitle" Text="Silverlight and XNA" Style="{StaticResource PhoneTextNormalStyle}"/> <TextBlock x:Name="PageTitle" Text="DROID 3D" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/> </StackPanel> <!--ContentPanel は、ページコンテンツを格納します--> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> </Grid> </Grid> </phone:PhoneApplicationPage>
次に「GamePage.xaml.cs」に対して、フィールドにUIElementRendererクラスを定義し、PhoneApplicationPage.LayoutUpdatedイベント内で上記のフィールドを初期化します。さらにGameTimer.Drawイベント内において、UIElementRenderer.Render()メソッドを実行してUIコントロールを2D画像に変換し、SpriteBatchクラスを用いて描画します。
public partial class GamePage : PhoneApplicationPage { … UIElementRenderer elementRenderer; public GamePage() { … // イベントハンドラを登録します LayoutUpdated += new EventHandler(GamePage_LayoutUpdated); } private void GamePage_LayoutUpdated(object sender, EventArgs e) { // 更新されたレイアウトを基にしてUIElementRendererクラスを再構築します if (elementRenderer != null) { elementRenderer.Dispose(); } elementRenderer = new UIElementRenderer(this, SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.Width, SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.Height); } private void OnDraw(object sender, GameTimerEventArgs e) { … // SilverlightのUIコントロールをXNAで描画します elementRenderer.Render(); spriteBatch.Begin(); spriteBatch.Draw(elementRenderer.Texture, Vector2.Zero, Color.White); spriteBatch.End(); } }
上記のコードを実行すると、以下のようにUIコントロールと3Dモデルが正常に描画され……ているように見えますが、ドロイドくんの足に注目してください。大変です!
![実行結果](http://cz-cdn.shoeisha.jp/static/images/article/6374/6374_result_uielementrenderer_1.gif)
上記の現象は、2D画像(UIコントロール)と3Dモデルを同一の画面で描画しているために発生します。具体的には、平面的な2D画像を描画する場合、立体的な3Dモデルを描画するために必要な計算処理が不要となるため、これらの計算処理が実施されないように、描画設定(レンダーステート)が自動的に変更されます。この際、3Dモデルの奥行を計算するために必要な各種プロパティも無効化されるため、上記のように3Dモデルが崩れて描画されてしまいます。
このため、UIコントロールと3Dモデルを1つの画面で描画する場合は、SpriteBatchクラスを用いてUIコントロールを描画した後に、レンダーステートを元に戻す必要があります。
private void OnDraw(object sender, GameTimerEventArgs e) { … // SpriteBatchクラスで変更されたレンダーステートを元に戻します SharedGraphicsDeviceManager.Current.GraphicsDevice.BlendState = BlendState.Opaque; SharedGraphicsDeviceManager.Current.GraphicsDevice.DepthStencilState = DepthStencilState.Default; }
上記のコードを追加して実行すると、以下のようにUIコントロールと3Dモデルが正常に描画されるようになります。
![実行結果](http://cz-cdn.shoeisha.jp/static/images/article/6374/6374_result_uielementrenderer_2.gif)
このように、多少注意するポイントはあるものの、UIコントロールと3Dモデルを共存させることが可能になりました。この状態であれば、同一の画面に存在するUIコントロールと3Dモデルを、相互に連携させることができます。次項ではまず、SilverlightのUIコントロールを利用してXNAの3Dモデルを操作してみましょう。