Windows Phone 7 のゲーム開発でのタッチ操作 その 4 ピンチ

ページ更新日 :
ページ作成日 :

プログラミング! - 4.ピンチ、ストレッチによるスプライトのスケーリング

今回のサンプルについて

最近のスマートフォンでは画像ビューアーなど 2 つの指を使って指と指の間隔を広げたり縮めたりすることによって画像を拡大、縮小することができるようになっています。Windows Phone 7 に搭載されている Internet Explorer でも同様の操作を行うことができます。この操作を Windows Phone 7 では「ピンチ、ストレッチ」と呼びます。

また、ピンチ、ストレッチ操作のほかに、画面の 1 点を短時間タッチする(軽くたたく)「タップ」や画面をすばやくなぞる「フリック」などの操作があります。これらの操作を総称して「ジェスチャー」と呼びます。

今回のプログラムではこのピンチ、ストレッチによる操作で画面上に表示されているスプライトを拡大したり縮小したりしてみたいと思います。また、前回まではタッチパネルの情報を直接取得して処理を行っていましたが、今回はジェスチャー専用のメソッドなどを使用してタッチ操作を処理したいと思います。

このサンプルプログラムのゴール

ピンチ、ストレッチの操作によってスプライトが拡大、縮小します。

図 1 :ストレッチでスプライトを拡大
図 1 :ストレッチでスプライトを拡大

プログラム - フィールドの宣言

フィールドの宣言では特に新しいクラスなどは存在しませんが、「テクスチャーの中心座標」「スプライトの拡大率」「ピンチ操作を行った時の前回の 2 つのタッチ位置の距離」を持つようにしています。

/// <summary>
/// テクスチャー
/// </summary>
Texture2D texture;

/// <summary>
/// テクスチャーの中心座標
/// </summary>
Vector2 textureCenterPos;

/// <summary>
/// スプライトのスケール
/// </summary>
float scale = 1;

/// <summary>
/// 前回の 2 点間の距離
/// </summary>
float previousLength = 0;

プログラム - ジェスチャーの有効化

今回ジェスチャー専用の処理を行うのですが、デフォルトの状態ではどのジェスチャーも使用することができません。各ジェスチャー情報を取得するには「TouchPanel.EnabledGestures」プロパティに使用するジェスチャーを設定する必要があります。

// タッチパネルでピンチのジェスチャーを有効にする
TouchPanel.EnabledGestures = GestureType.Pinch | GestureType.PinchComplete;

すべてのジェスチャーを有効にするとパフォーマンスに影響するので、使用するジェスチャーのみを設定するようにしてください。ここではピンチ操作の情報を取得するための「GestureType.Pinch」とピンチ処理が完了したことを示す「GestureType.PinchComplete」を設定しています。設定場所は Game コンストラクタ内で設定しています。

プログラム - テクスチャーの読み込み

テクスチャーの読み込みは今までと特に変わりありませんが、今回は「テクスチャーの中心座標」を計算しています。スプライトをスケールさせる際に中心座標を原点としてスケールさせるためです。

// テクスチャーをコンテンツパイプラインから読み込む
texture = Content.Load<Texture2D>("Texture");

// テクスチャーの中心座標を求める
textureCenterPos = new Vector2(texture.Width / 2, texture.Height / 2);

プログラム - ジェスチャー情報の取得

Game.Update メソッド内でジェスチャー情報(ここではピンチ)の取得を行っています。ジェスチャー処理は有効にしたジェスチャーの数だけ繰り返して取得する形式になっています。前回まで使用していた「TouchCollection」構造体などは使用しません。

// 次のジェスチャー情報が取得できる場合は true を返し続けます
while (TouchPanel.IsGestureAvailable)
{
  // 次のジェスチャー情報を読み込みます
  GestureSample gs = TouchPanel.ReadGesture();
  
  switch (gs.GestureType)
  {
    case GestureType.Pinch:
      // ここにピンチ処理を記述します
      break;
    case GestureType.PinchComplete:
      // ここにピンチ完了処理を記述します
      break;
  }
}

ジェスチャー情報は while ループで「TouchPanel.IsGestureAvailable」プロパティの状態を確認し、次のジェスチャー情報が存在するか確認します。次のジェスチャー情報があれば「TouchPanel.ReadGesture」メソッドでジェスチャーの情報を取得します。

「GestureSample.GestureType」にはどのジェスチャータイプの情報が含まれているか格納されるのでそれをもとに処理を switch ステートメントで分岐させます。今回のサンプルではコンストラクタで「TouchPanel.EnabledGestures」プロパティに「GestureType.Pinch」「GestureType.PinchComplete」列挙型を設定したのでそれぞれ分岐させています。

プログラム - ピンチ ジェスチャーの処理

処理内容についてはコードのコメントを見ていただいた方が早いのですが、やっていることを簡単にまとめると、前回の 2 点間のタッチポイントの距離と今回の 2 点間のタッチポイントの距離の差分を求め、その差分をもとにスケール値を増やしたり減らしたりしています。

case GestureType.Pinch:
  // 現在の2点間の距離を求めます
  float nowLength = (gs.Position - gs.Position2).Length();

  if (previousLength == 0)
  {
    // 前回の2点間の距離が 0 の場合は
    // 現在の2点間の距離を設定します
    // 初回はスケール計算を行いません
    previousLength = nowLength;
  }
  else
  {
    // 前回の2点間の距離との差分を計算します
    float diffLength = nowLength - previousLength;
    
    // ピンチ、ストレッチした距離に応じて
    // スケールを変化させています
    // 補正値は適当に設定します
    scale = MathHelper.Max(scale + diffLength / 150, 0);
    
    // 今回の距離を次の計算のために保持します
    previousLength = nowLength;
  }
  break;

2 つのタッチ位置はそれぞれ「GestureSample.Position」「GestureSample.Position2」で取得することができます。取得した 2 つのベクトルを引き算で差分を求め、「Vector2.Length」メソッドを呼び出すことにより 2 点間の距離を求めることができます。

ちなみにここではピンチ処理を行っていないときは前回の距離を0として定義しているので、ピンチ処理を開始したときと 2 回目以降のループで処理を分岐させています。ピンチ処理の初回の時は前回の距離がないのでスケーリングする必要がないためです。

また今回は「GestureSample.Position」「GestureSample.Position2」の 2 つのプロパティしか使用していませんが他にも「GestureSample.Delta」「GestureSample.Delta2」プロパティがあり、こちらは前回のタッチ位置からの差分情報を取得することができます。プロパティがそれぞれ 2 つずつ用意されていますが、これはマルチタッチのためであり、シングルタッチしか使用しないジェスチャーの場合は「2」のついていないプロパティを使用することになります。

プログラム - ピンチジェスチャー完了時の処理

ピンチジェスチャーが完了したとき(どちらかの指をタッチパネルから放したとき)は前回の 2 点間の距離を0に戻しています。本来は別途フラグを用いた方がいいのかもしれませんが、同じ位置を 2 つの指でタッチすることが物理的に不可能なので、距離0の場合はピンチ処理をしていないこととして定義しています。(解像度が低ければ同じ位置をタッチすることが可能かもしれませんが・・・)

case GestureType.PinchComplete:
  // ピンチが終了した場合は保持している距離を 0 に戻して
  // 完了したことにします
  previousLength = 0;
  break;

プログラム - スプライトの描画

ここはスプライトの描画だけの内容なのであまり詳しいことは説明しませんが、画面の中心にスプライトを配置し、計算したスケール値をスプライトの中心を原点として拡大、縮小して描画しています。

// スプライトの描画準備
spriteBatch.Begin();

// スプライトを描画する
spriteBatch.Draw(texture,
                 new Vector2(graphics.PreferredBackBufferWidth / 2,
                             graphics.PreferredBackBufferHeight / 2),
                 null,
                 Color.White,
                 0.0f,
                 textureCenterPos,
                 scale,
                 SpriteEffects.None,
                 0.0f);

// スプライトの一括描画
spriteBatch.End();

今回のサンプルのまとめ

今回はジェスチャー専用の処理について説明しました。サンプルではピンチ処理ついてのみ作成していますが、他にも「フリック」や「ホールド」などがあります。どんなジェスチャーがあるかは XNA Game Studio 4.0 のヘルプで「GestureType 列挙型」について調べてみてください。

ジェスチャー専用のメソッドや構造体などを使用しなくても TouchPanel.GetState メソッドから取得したタッチ情報から同じような実装はできるのですが、その場合マルチタッチの ID 管理やタッチしている時間、フリックなどの速度を自前で計算する必要が出てきます。ジェスチャー専用のメソッドを使用することによってこれらの実装を簡略化することもでき、また、どのゲーム、アプリケーションでも同じ感覚で操作できるメリットがあります。

タッチパネルの処理を自分で作成したときに、似たような処理がジェスチャーとして代用できるならこちらを使用することをお勧めします。

プログラミング! - 5.ピンチ処理でスプライトの回転

今回のサンプルについて

通常ピンチ操作と言ったら「ピンチ」「ストレッチ」のことを指しますが、XNA Game Studio 4.0 のピンチ処理は特にその2つに処理を限定させていないので、1 点のタッチポイントの周りをもう 1 点のタッチポイントで周るような操作を行うこともできます。

ここではその操作方法でスプライトを回転させてみたいと思います。ちなみに今回は特に新しいメソッドやクラスは登場せず、前回のスケーリングをベースに応用させた形になっています。

今回のサンプルコードの説明で前回のスケーリングのサンプルと同じ部分については省いています。

このサンプルプログラムのゴール

2 点のタッチポイントを回すように動かすことにより、スプライトが回転するようになっています。前回のスケーリング処理も同時に動作します。

図 2 :タッチポイントを回してスプライトを回転させています
図 2 :タッチポイントを回してスプライトを回転させています

プログラム - フィールドの宣言

前回のスケーリングのプログラムに対して「スプライトの回転量」と「前回の回転角度」を追加しています。角度はすべて「ラジアン(radian)」で計算します。

/// <summary>
/// スプライトの回転量(radian)
/// </summary>
float rotate;

/// <summary>
/// 前回の回転角度(radian)
/// </summary>
float previousAngle;

プログラム - 回転処理

回転処理はスケーリングの時と同じようにピンチジェスチャーの時に行います。

回転の計算については数学の話になるのであまり詳しく説明はしませんが、タッチポイント 1 からタッチポイント 2 へ向かう差分ベクトル求めて「Math.Atan2」メソッドで角度を radian で取得できます。取得した角度と前回に取得した角度の差分を求め、スプライトの回転角度に加算します。

switch (gs.GestureType)
{
  case GestureType.Pinch:
    //===== スケール計算 =====//
    // 前回と同じなので省略

    //===== 回転計算 =====//

    // 2点間のベクトルを求めます
    Vector2 pinchVector = gs.Position2 - gs.Position;

    // Atan2 で角度を求めます
    float nowAngle = (float)Math.Atan2(pinchVector.Y,
                                       pinchVector.X);

    if (previousAngle == 0)
    {
      // 前回の角度が 0 の場合は
      // 現在の角度を設定します
      // 初回は回転計算を行いません
      previousAngle = nowAngle;
    }
    else
    {
      // 前回の角度との差分をスプライトの回転角度に加算します
      rotate += nowAngle - previousAngle;

      // 今回の距離を次の計算のために保持します
      previousAngle = nowAngle;
    }
    break;
  case GestureType.PinchComplete:
    // ピンチが終了した場合は保持している距離、角度を 0 に戻して
    // 完了したことにします
    previousLength = 0;
    previousAngle = 0;
    break;
}

プログラム - スプライトの描画

スプライトの描画については大きな変更点はありません。SpriteBacth.Draw メソッドの第5引数に計算した回転角度を設定しています。

// スプライトの描画準備
spriteBatch.Begin();

// スプライトを描画する
spriteBatch.Draw(texture,
                 new Vector2(graphics.PreferredBackBufferWidth / 2,
                             graphics.PreferredBackBufferHeight / 2),
                 null,
                 Color.White,
                 rotate,
                 textureCenterPos,
                 scale,
                 SpriteEffects.None,
                 0.0f);

// スプライトの一括描画
spriteBatch.End();

今回のサンプルのまとめ

今回はピンチ操作でスプライトを回転させてみました。特に新しいクラスなどは使用していませんが、提供されている機能をもとに応用した処理が実現できることがわかっていただけたかと思います。

最後に

サンプルを取り上げながら Windows Phone 7 と XNA Game Studio 4.0 を使用してタッチ操作の実装について説明しました。内容としてはシングルタッチ、マルチタッチによるタッチ情報の取得と操作、ジェスチャー特有のメソッドを使用したタッチ ジェスチャー操作による処理を行いました。

今回はタッチパネルに絞って説明をしてきましたが、それでもまだ紹介していない機能がいくつか存在します。また Windows Phone 7 では XNA Game Studio 4.0 で新たに追加された入力機能として加速度センサー入力や音声入力なども存在します。今回紹介しきれなかった機能にも面白い機能がありますので、各自使ってみたい機能を調べて楽しんでみてください。