前回までは、PROJECT KySSの薬師寺国安が、マーケットプレイスで公開しているアプリ(発行者名:kuniyasu)を例に出しながら、サンプルプログラムを解説しました。
最終回では、これまでの連載でお伝えしきれなかったWindows Phoneのセンサープログラミングについて、私、薬師寺聖から補足して解説します。3つのサンプルを紹介していきますので、ぜひアプリ制作の参考にしてください。
Windows Phone アプリ開発で使えるセンサー
まず、Windows Phone アプリ開発でセンサーからのデータを利用したい場合は、Microsoft.Devices.Sensors 名前空間のクラスを用います。次の4種類のデータを取得できます。
- (1)加速度センサー(Accelerometer)
- 実機の傾きが分かります。
- (2)コンパスセンサー(Compass)
- 磁気強度と方角が分かります。
- (3)ジャイロスコープセンサー(Gyroscope)
- どのくらいの速さで、どの方向に、どれだけの角度、デバイスの姿勢が変化したかが分かります。
- (4)複合モーション(Combined Motion API)
- (1)~(3)をまとめて扱える1つの複合APIです。
これらのセンサーにより取得できるデータは、表1のとおりです。
表1:4種類のセンサーで取得できるデータの一覧
 |
クリックで拡大します。 |
本稿では、加速度センサーを使った「転がるボール」と、ジャイロスコープセンサーを使った「カウンター」、複合モーションを使った「回転する針」の3つのサンプルについて解説します。
このうち、加速度センサーの処理を応用すると、筆者(薬師寺聖)がマーケットプレイスで公開中の「ビジュアル傾斜計 Ver.0.8」のようなアプリになります(図1)。
→参照:ビジュアル傾斜計(Windows Phone/Marketplace)
ジャイロスコープセンサーと複合モーションの処理を応用すると、「センサー計測セット Ver.0.8」のようなアプリになります(図2)。
→参照:センサー計測セット(Windows Phone/Marketplace)
 |
図1:「ビジュアル傾斜計 Ver.0.8」の一画面(クリックで拡大) |
 |
図2:「センサー計測セット Ver.0.8」の各計測ページ(クリックで拡大) |
サンプル一式は、会員限定特典としてダウンロードできます。記事末尾をご確認ください。
加速度センサーを使った、転がるボールのサンプル
最初に、加速度センサーを使った「転がるボール」の作り方を解説します。サンプルの動作は以下の通りです。
- ページをロードした時、楕円の中央に、緑色のボールが表示されています。
- 「開始」ボタンをタップして実機を傾けると、ボールが楕円の中を移動します。
- 実機の頭側を下に傾けた時は、遠近法のようにボールが小さくなります。
- 「停止」ボタンをタップすると、ボールの移動は止まります(図3)。
このボールはExpression Designで描いた単純なpng画像ですが、回転させることによって転がるように見せかけています。
 |
図3:実機を傾けると、ボールが楕円の中を転がる。図は、エミュレーターでの動作確認画面(クリックで拡大) |
プロジェクトの作成
VS 2010のメニューから[ファイル(F)/新規作成(N)/プロジェクト(P)]と選択します。次に、「Windows Phone アプリケーション」を選択して、「名前(N)」に任意のプロジェクト名を指定します。ここでは「AccelerometerSample」という名前を付けています。
Windows Phoneのバージョンは7.1を選択します。ソリューションエクスプローラー内にImageというフォルダを作って、PNG画像(Ball1.png)を追加しておきます。サンプルファイルには画像ファイルは追加済みです。
MainPage.xamlの編集とコントロールの追加
ツールボックスからButtonコントロールを1個、TextBlockコントロールを1個配置します。また、Ellipse(楕円)を描き、Imageコントロールを1個配置します。このImageコントロールの名前は「BallImage」としておき、SourceプロパティにはImageフォルダ内の画像を指定します。
書き出されるXAMLコードをリスト1のように編集します。
リスト1 編集されたXAMLコード(MainPage.xaml)
Nameが「BallImage」のImage要素の中に、Image.Projection要素を追加します。さらに、その子として、PlaneProjection要素を追加します。x:Nameは「MyAngleProjection」としておきます。
01 | ~前略~ <!--LayoutRoot は、すべてのページ コンテンツが配置されるルート グリッドです--> |
02 | <Grid x:Name="ContentPanel" Grid.Row="1" Margin="0,0,0,0"> |
03 | <Button Content="開始" Height="100" HorizontalAlignment="Left" Margin="140,20,0,0" Name="Button1" VerticalAlignment="Top" Width="200" /> |
04 | <Ellipse Height="260" HorizontalAlignment="Left" Margin="60,200,0,0" Name="Ellipse1" Stroke="White" StrokeThickness="1" VerticalAlignment="Top" Width="360" Fill="Black" /> |
05 | <Image Height="60" HorizontalAlignment="Left" Margin="210,300,0,0" Name="BallImage" Stretch="Fill" VerticalAlignment="Top" Width="60" Source="/AccelerometerSample;component/Image/Ball1.png"> |
07 | <PlaneProjection x:Name="MyAngleProjection"/> |
10 | <TextBlock Height="50" HorizontalAlignment="Left" Margin="15,530,0,0" Name="TextBlock1" Text="センサーの状態" VerticalAlignment="Top" Width="440" FontSize="24" /> |
13 | <!--ApplicationBar の使用法を示すサンプル コード--> ~後略~ |
ロジックコードを記述する前に、「プロジェクト/参照の追加」で、Microsoft.Devices.Sensors名前空間への参照を追加しておきます。また、Microsoft.Xna.Framework名前空間への参照も追加しておきます。
参照の追加ができたら、xaml.vbを開いて、ロジックコードを記述します。
ロジックコードを記述する
リスト2 (MainPage.xaml.vb)
名前空間のインポートと変数の宣言
加速度計にアクセスする API を提供する、Microsoft.Devices.Sensors名前空間をインポートします。また、Vector3型のセンサーデータを扱うために、Xna.Framework名前空間もインポートしておきます。
加速度センサーオブジェクトと、加速度センサーの生データを格納する変数、楕円の中心点の座標値と長軸と短軸、ボールの画像の幅と高さを代入する変数を宣言します。
02 | Imports Microsoft.Devices.Sensors |
03 | Imports Microsoft.Xna.Framework |
05 | Partial Public Class MainPage |
06 | Inherits PhoneApplicationPage |
13 | Dim MyAccelerometer As Accelerometer '加速度センサー |
14 | Dim MyAcceleration As Vector3 'センサーデータ |
16 | Dim MyAreaLeft, MyAreaTop As Double '楕円の中心点の座標値 |
17 | Dim MajorAxis, MinorAxis As Double '楕円の長軸、短軸 |
18 | Dim MyBallWidth, MyBallHeight As Double 'ボールの幅、高さ |
このページがロードされた時の処理
デバイスがセンサーをサポートしているかどうかをチェックして、サポートしていなければ、メッセージを表示します。
ボールの幅と高さ、楕円の長軸と短軸の半径を取得します。また、楕円のマージンを取得して、中心点の座標値をもとめておきます。このように、XAML中の値を利用するように記述しておくと、楕円のサイズなどを変更した場合でも、プログラムを変更する必要がないので、レイアウトの微調整が容易です。
01 | Private Sub MainPage_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded |
02 | '加速度センサーがサポートされていない機種への対応 |
03 | If Accelerometer.IsSupported = False Then |
04 | MessageBox.Show("このデバイスは加速度センサーをサポートしていません。") |
05 | Button1.IsEnabled = False |
08 | Button1.IsEnabled = True |
12 | MyBallWidth = CDbl(BallImage.GetValue(FrameworkElement.ActualWidthProperty)) |
13 | MyBallHeight = CDbl(BallImage.GetValue(FrameworkElement.ActualHeightProperty)) |
16 | MajorAxis = CDbl(Ellipse1.GetValue(FrameworkElement.ActualWidthProperty)) / 2 |
17 | MinorAxis = CDbl(Ellipse1.GetValue(FrameworkElement.ActualHeightProperty)) / 2 |
20 | MyAreaLeft = Ellipse1.Margin.Left + MajorAxis |
21 | MyAreaTop = Ellipse1.Margin.Top + MinorAxis |
ボタンがタップされた時の処理
このサンプルでは、1個のボタンをタップする都度、センシングの開始と停止を切り替えます。センサーオブジェクトが有効であれば、センサーを停止し、ボタンの文字列を「開始」にします。
一方、センサーオブジェクトが空であれば、加速度センサーの新しいインスタンスを作成して、ボタンの文字列を「停止」にしたうえで、センシングを実行します。
AddHandlerステートメントを用いて、センサーの情報が変化したときに発生するCurrentValueChanged イベントにイベント ハンドラーを追加します。非 UI スレッドのデータを UI スレッド上で処理するため、Dispatcher クラスを使います。
01 | Private Sub Button1_Tap(sender As Object, e As System.Windows.Input.GestureEventArgs) Handles Button1.Tap |
03 | If MyAccelerometer IsNot Nothing AndAlso MyAccelerometer.IsDataValid Then |
05 | MyAccelerometer.Stop() |
06 | MyAccelerometer = Nothing |
07 | TextBlock1.Text = "実行中の加速度センサーを停止しました。" |
08 | Button1.Content = "開始" |
10 | MessageBox.Show("加速度センサーの停止に失敗しました。") |
13 | If MyAccelerometer Is Nothing Then |
14 | MyAccelerometer = New Accelerometer |
15 | AddHandler MyAccelerometer.CurrentValueChanged, Sub(senderValue As Object, eValue As SensorReadingEventArgs(Of AccelerometerReading)) |
17 | Deployment.Current.Dispatcher.BeginInvoke(Sub() CurrentValueChanged(eValue.SensorReading)) |
22 | Button1.Content = "停止" |
23 | TextBlock1.Text = "加速度センサーを開始しました。" |
24 | MyAccelerometer.Start() |
26 | MessageBox.Show("加速度センサーを開始できません。") |
センサーデータが更新された時の処理
センサーデータが更新された時、X、Y、Zの3つに分解して生データを取得します。この値は基本的に-1~1の範囲になりますが、念のために範囲外の値をカットしておきます。
実機が頭側に傾くほどボールを小さくして遠近感があるように見せるため、Yの値をもとに、ボールの幅と高さを算出して適用します。
楕円の中のボールの可動範囲と角度からボールの移動距離を計算し、楕円の中心点と幅と高さと移動距離から、ボールの表示位置の座標を計算します。
さらに、ボールそのものにも動きを付けます。XとYのセンサーデータをもとにMath.Atanを使って、ボールを回転させます。
ボールの画像に、計算した座標を適用して移動させます。
01 | Private Sub CurrentValueChanged(MyReading As AccelerometerReading) |
02 | MyAcceleration = MyReading.Acceleration |
05 | Dim XValue As Double = MyAcceleration.X |
06 | Dim YValue As Double = MyAcceleration.Y |
07 | Dim ZValue As Double = MyAcceleration.Z |
10 | If MyAcceleration.X > 1 Then |
12 | ElseIf MyAcceleration.X < -1 Then |
16 | If MyAcceleration.Y > 1 Then |
18 | ElseIf MyAcceleration.Y < -1 Then |
22 | If MyAcceleration.Z > 1 Then |
24 | ElseIf MyAcceleration.Z < -1 Then |
29 | Dim MyBallWidth2, MyBallHeight2 As Double |
30 | MyBallWidth2 = (MyBallWidth * (1 + (-YValue * 0.3))) |
31 | MyBallHeight2 = (MyBallHeight * (1 + (-YValue * 0.3))) |
32 | BallImage.Width = MyBallWidth2 |
33 | BallImage.Height = MyBallHeight2 |
35 | '楕円の可動範囲と角度からボールの移動距離を計算 |
36 | Dim MyMoveX2D As Integer = CInt(XValue * (MajorAxis - MyBallWidth2 / 2)) |
37 | Dim MyMoveY2D As Integer = CInt(YValue * (MinorAxis - MyBallHeight2 / 2)) |
39 | '楕円の中心点と幅高さと移動距離から、ボールの表示位置の座標を計算 |
40 | Dim MyXPos2D As Integer = CInt(MyAreaLeft - MyBallWidth2 / 2 + MyMoveX2D) |
41 | Dim MyYPos2D As Integer = CInt(MyAreaTop - MyBallHeight2 / 2 - MyMoveY2D) |
44 | MyAngleProjection.RotationZ = (Math.Atan(YValue / XValue) * 360 / Math.PI) |
47 | BallImage.Margin = New Thickness(MyXPos2D, MyYPos2D, 0, 0) |