SilverlightからXNAを操作する
SilverlightでUIを構築する場合、一般的に「LayoutRoot」という名称のUIコントロール(画面全体に広がる透明なパネル)を配置します。今回は具体例として、このLayoutRootをタッチした際に発生するイベントを利用して、3Dモデルを操作できるように実装してみましょう。
まずは「GamePage.xaml」のLayoutRootに、ManipulationDeltaイベントを登録します。
<!--LayoutRoot は、すべてのページコンテンツが配置されるルートグリッドです--> <Grid x:Name="LayoutRoot" Background="Transparent" ManipulationDelta="LayoutRoot_ManipulationDelta">
次に「GamePage.xaml.cs」において、ManipulationDeltaイベントハンドラを定義し、平面的な指の動作を3Dモデルの回転角度に変換します。例えば、指を上から下に移動させた場合は前傾、左から右に移動させた場合は左回りとなるように回転角度を算出します。さらに、GameTimer.Drawイベント内において、上記の回転角度から回転行列を生成してワールド行列に合成し、3Dモデルの姿勢を制御します。
public partial class GamePage : PhoneApplicationPage { … float yaw, pitch; private void LayoutRoot_ManipulationDelta(object sender, ManipulationDeltaEventArgs e) { // ジェスチャーによる平面移動を3Dモデルの回転角度に変換します yaw = MathHelper.WrapAngle(yaw + (int)e.DeltaManipulation.Translation.X * 0.01f); pitch = MathHelper.WrapAngle(pitch + (int)e.DeltaManipulation.Translation.Y * 0.01f); } private void OnDraw(object sender, GameTimerEventArgs e) { … // 3Dモデルの位置と向きと傾きを定義して、回転角度を合成します Matrix world = Matrix.CreateWorld(Vector3.Zero, Vector3.Forward, Vector3.Up) * Matrix.CreateFromYawPitchRoll(yaw, pitch, 0); … } }
上記のコードを実行すると、画面をドラッグして3Dモデルを操作することができます。
このように、Silverlight/XNAを利用すると、SilverlightからXNAに干渉できるようになります。上記の例では、UIコントロールのイベントハンドラから間接的に3Dモデルを操作していますが、ストーリーボードを利用してカメラワークを制御するといったことも可能です。次項では逆に、XNAのゲームループからSilverlightのUIコントロールを操作してみましょう。
UIElementRendererクラスを利用する場合、UIコントロールで発生する各種イベントや、ストーリーボードによるアニメーションなどは、通常のSilverlightと同様に使用できます。
ただし、執筆時点において最新のUIElementRendererクラスは、OSにより制御される画面の変化に対応できない場合があります。例えば、テキストボックスを編集する際にSIP(ソフトウェア入力パネル)が表示されますが、SIPによりテキストボックスが隠れてしまう場合、画面が上方にスライドするようにOSが制御しています。このように、SilverlightではなくOSが制御する画面の変化は、UIElementRendererクラスでは再現することができません。
このため、Silverlight/XNAでUIコントロールを利用する場合は、事前に十分な検証を行うことを推奨します。
XNAからSilverlightを操作する
具体例として今回は、リストボックスから選択された3Dモデルのパーツが発光するようにしてみましょう。さらに、チェックボックスでライティングの有無を変更できるようにします。
まずは「GamePage.xaml」に対して、3Dモデルのパーツを選択するためのリストボックスと、エフェクトの有無を設定するためのチェックボックスを配置します。
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <!--3Dモデルのパーツを選択するリストボックスです--> <StackPanel VerticalAlignment="Top" HorizontalAlignment="Right"> <TextBlock Text="Parts" Style="{StaticResource PhoneTextGroupHeaderStyle}"/> <ListBox x:Name="partsListBox" ItemsSource="{Binding}" BorderThickness="5" BorderBrush="White" Margin="{StaticResource PhoneHorizontalMargin}" Padding="{StaticResource PhoneMargin}" HorizontalAlignment="Left"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Name}" FontSize="{StaticResource PhoneFontSizeLarge}"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </StackPanel> <!--エフェクトの有無を設定するチェックボックスです--> <StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Left"> <TextBlock Text="Effects" Style="{StaticResource PhoneTextGroupHeaderStyle}"/> <CheckBox x:Name="enabledLightingCheckBox" Content="ライティングを有効" VerticalAlignment="Bottom" IsChecked="True"/> <CheckBox x:Name="showWireFrameCheckBox" Content="ワイヤーフレームを表示" VerticalAlignment="Bottom" IsChecked="False"/> </StackPanel> </Grid>
次に「GamePage.xaml.cs」において、PhoneApplicationPage.OnNavigatedTo()メソッド内でリストボックスに3Dモデルをバインドし、パーツが一覧に表示されるように設定します。また、GameTimer.Drawイベント内でチェックボックスを参照し、ライティングの有無を変更するために、BasicEffect.LightingEnabledプロパティを設定します。さらに、リストボックスから選択されている3Dモデルのパーツを発光させるため、BasicEffect.EmissiveColorプロパティを設定します。
protected override void OnNavigatedTo(NavigationEventArgs e) { … // リストボックスに3Dモデルのパーツをバインドします partsListBox.ItemsSource = model.Meshes; … } private void OnDraw(object sender, GameTimerEventArgs e) { … foreach (ModelMesh mesh in model.Meshes) { foreach (BasicEffect effect in mesh.Effects) { … // ライティングの有無を設定します effect.EnableDefaultLighting(); effect.LightingEnabled = enabledLightingCheckBox.IsChecked.Value; // リストボックスで選択されているパーツを発光させます effect.EmissiveColor = (partsListBox.SelectedItem == mesh) ? Color.Red.ToVector3() : Vector3.Zero; } mesh.Draw(); } … }
上記のコードを実行すると、以下のようにリストボックスから選択した3Dモデルのパーツが発光します。また、チェックボックスの状態に応じて、ライティングの有無を設定できます。
このようにSilverlight/XNAを利用すると、XNAからSilverlightに干渉できるようになります。上記の例では、XNAのゲームループからUIコントロールを参照していますが、UIコントロールの値やストーリーボードの状態を変更するといったことも可能です。次項では、3Dモデルをワイヤーフレームで描画して、3Dアプリケーションらしさを演出してみましょう。
通常のSilverlightでは、UIコントロールの操作はUIスレッドで実施する必要があります。しかしながら、Silverlight/XNAを利用した場合、XNAのゲームループ(GameTimer.Updateイベント並びにGameTimer.Drawイベント)から、SilverlightのUIコントロールを操作することができます。これが可能なのは、XNAのゲームループがSilverlightのUIスレッドで実行されているためです。
ただし、これには弊害も存在します。例えば、Silverlightのイベントハンドラ内で時間のかかる処理を実施した場合、その間はXNAのゲームループが実行されません。これにより、XNAによる描画はもちろん、ゲームロジックの更新も停止し、容易に処理落ちが発生してしまいます。
このため、Silverlight/XNAを利用する場合は、UIスレッドを長時間占有しないように注意してください。