Touch interactions in game development for Windows Phone 7 Part 4 Pinch

Page update date :
Page creation date :

Programming! - 4.Pinch, stretch to scale sprites

About this sample

Modern smartphones, such as image viewers, allow you to enlarge and shrink an image by using two fingers to increase or shrink the distance between your fingers. You can do the same with Internet Explorer on Windows Phone 7. This action is called "pinch, stretch" in Windows Phone 7.

In addition to pinching and stretching, you can also use actions such as "tap" to briefly touch (tap) one point on the screen and "flick" to quickly trace the screen. These actions are collectively referred to as gestures.

In this program, I would like to enlarge and reduce the sprite displayed on the screen by this pinch and stretch operation. Also, until last time, I directly obtained the information of the touch panel and processed it, but this time I would like to handle touch operations using methods dedicated to gestures.

Goals of this sample program

Pinch and stretch operations to enlarge and shrink the sprite.

図 1 :ストレッチでスプライトを拡大
Figure 1: Stretch to enlarge the sprite

Program - Declaring Fields

There is no new class in the field declaration, but it has "the center coordinate of the texture", "the magnification of the sprite", and "the distance between the last two touch positions when pinching".

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

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

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

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

Program - Enabling Gestures

This time, we will perform a gesture-only process, but in the default state, none of the gestures can be used. To retrieve each gesture information, you must set the gesture to be used for the TouchPanel.EnabledGestures property.

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

Enabling all gestures affects performance, so make sure you only set the gestures that you want to use. Here, "GestureType.Pinch" is set to obtain pinch operation information and "GestureType.PinchComplete" is set to indicate that the pinch process is complete. The setting location is set in the Game constructor.

Program - Loading textures

Loading textures is the same as before, but this time we are calculating the "center coordinate of the texture". This is because when scaling a sprite, it scales with the center coordinate as the origin.

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

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

Program - Get gesture information

We get gesture information (pinch here) in the Game.Update method. Gesture processing is in the form of repeating as many gestures as you have enabled. The "TouchCollection" structure that was used until last time is not used.

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

Check the state of the TouchPanel.IsGestureAvailable property in the while loop for gesture information and check if the following gesture information is present: If you have the following gesture information, use the "TouchPanel.ReadGesture" method to get the gesture information.

"GestureSample.GestureType" stores information about which gesture type is included, so branch the process with a switch statement based on it. In this sample, the constructor sets the "GestureType.Pinch" and "GestureType.PinchComplete" enums to the "TouchPanel.EnabledGestures" property, so they are branched respectively.

Program - Pinch gesture handling

It's faster to look at the comments in the code for what we're doing, but to summarize what we're doing, we find the difference between the distance between the previous two touchpoints and the touchpoint distance between the two points this time, and use that difference to increase or decrease the scale value.

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;

The two touch positions can be obtained with "GestureSample.Position" and "GestureSample.Position2", respectively. You can subtract the difference between the two obtained vectors and find the distance between the two points by calling the "Vector2.Length" method.

By the way, here we define the previous distance as 0 when pinching is not performed, so the process is branched when pinching is started and in the second and subsequent loops. This is because the first time of pinching, there is no previous distance, so there is no need to scale.

Also, this time we use only two properties, "GestureSample.Position" and "GestureSample.Position2", but there are also "GestureSample.Delta" and "GestureSample.Delta2" properties, which can get the difference information from the previous touch position. There are two properties each, but this is for multi-touch, and for gestures that use only single touch, you will use properties without a "2".

Program - Handling when pinch gesture completes

When the pinch gesture is complete (releasing either finger from the touch panel), the distance between the previous two points is set back to 0. Originally, it may be better to use a separate flag, but since it is physically impossible to touch the same position with two fingers, the distance of 0 is defined as not pinching. (If the resolution is low, it may be possible to touch the same position ...)

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

Program - Drawing Sprites

I won't go into too much detail here because it's just about drawing the sprite, but I placed the sprite in the center of the screen and drew the calculated scale value by enlarging and shrinking it with the center of the sprite as the origin.

// スプライトの描画準備
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();

Summary of this sample

This time, we explained the process dedicated to gestures. In the sample, we only create a pinch process, but there are also "flicks" and "holds". To find out what gestures are available, check out the XNA Game Studio 4.0 Help for GestureType enums.

You can implement the same thing from the touch information obtained from the TouchPanel.GetState method without using gesture-specific methods or structures, but in that case, you will need to calculate the speed of multi-touch ID management, touch time, flicks, etc. yourself. By using gesture-specific methods, these implementations can be simplified, and there is also the advantage that all games and applications can be operated in the same way.

When you create a touch panel process yourself, if you can substitute a similar process as a gesture, we recommend that you use this.

Programming! - 5.Pinch to rotate sprites

About this sample

Pinching usually refers to "pinching" and "stretching", but XNA Game Studio 4.0's pinch process doesn't specifically limit the process to those two, so you can also perform operations that circle one touchpoint around another touchpoint.

Here, I would like to rotate the sprite with that operation method. By the way, no new methods or classes appeared this time, and it is based on the previous scaling.

In the description of the sample code in this article, the same parts as the previous scaling sample are omitted.

Goals of this sample program

By rotating the two touchpoints, the sprite rotates. The previous scaling operation also works.

図 2 :タッチポイントを回してスプライトを回転させています
Figure 2: Rotating the sprite by turning the touchpoint

Program - Declaring Fields

"Sprite rotation amount" and "Last rotation angle" are added to the previous scaling program. All angles are calculated in radians.

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

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

Program - Rotation Process

The rotation process is performed during pinch gestures in the same way as when scaling.

I won't go into too much detail about calculating the rotation because it's a math story, but you can get the angle radian with the "Math.Atan2" method by finding the difference vector from touchpoint 1 to touchpoint 2. Find the difference between the obtained angle and the previously acquired angle and add it to the rotation angle of the sprite.

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;
}

Program - Drawing Sprites

There are no major changes to drawing sprites. The fifth argument of the SpriteBacth.Draw method is set to the calculated rotation angle.

// スプライトの描画準備
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();

Summary of this sample

This time, I tried to rotate the sprite by pinch operation. We don't use any new classes in particular, but I hope you understand that we can realize applied processing based on the functions provided.

At last

We walked through samples and demonstrated the implementation of touch interactions using Windows Phone 7 and XNA Game Studio 4.0. The content included single-touch and multi-touch touch acquisition and manipulation of touch information, and processing by touch gesture manipulation using gesture-specific methods.

This time, I have focused on the touch panel, but there are still some functions that I have not introduced yet. Windows Phone 7 also includes new input features in XNA Game Studio 4.0, such as accelerometer input and voice input. There are some interesting features that I couldn't introduce this time, so please enjoy exploring the functions you want to use.