The final type of input device supported by XNA Game
Studio APIs are multitouch displays available on all Windows Phone
devices. The multitouch APIs are supported on all Windows Phones.
Multitouch devices are unique
in that the user can directly touch the display screen and seem to be
actually touching and interacting with the images on the screen. This
provides an intimate level of interaction that is common on hand-held
devices.
Note
XNA Game Studio 4 is not the
first version of XNA Game Studio to support multitouch. The Microsoft
XNA Game Studio 3.1 Zune Extensions provides similar APIs for Zune HD
devices.
Multitouch devices are represented in XNA Game Studio using the TouchPanel class type. There are two ways you can read multitouch data from the TouchPanel.
The first is to poll the current state of the TouchPanel using the GetState
method. This returns a number of touch locations where the user has
made contact with the multitouch display. All Windows Phone devices
support at least four touch locations at a minimum. After you determine
the current touch locations, you can use that information to move,
rotate, or scale graphics on the display.
The second way to read multitouch data is to utilize gestures. A gesture
is an action that is performed by the user that can occur over time and
must be processed to determine which gesture the user intended to use.
For example, the user can drag his finger across the screen.
Reading the TouchPanel Device State
Just like the previous input device APIs, the TouchPanel
provides a state-based API that allows the current state of the
multitouch device to be polled at regular intervals. This state can then
be used to perform in game actions. Use the TouchPanel class to read the current state of a multitouch device. The GetState method returns a TouchCollection that contains all of the current touch locations. Table 1 and 2 contain a list of methods and properties for the TouchPanel object.
Table 1. Methods of TouchPanel
Method | Description |
---|
GetState | Returns collection of current touch locations |
GetCapabilities | Returns the current touch panel capabilities |
ReadGesture | Returns the next available gesture |
Table 2. Properties of TouchPanel
Property | Type | Description |
---|
DisplayWidth | int | The width of the touch panel |
DisplayHeight | int | The height of the touch panel |
DisplayOrientation | DisplayOrientation | Orientation of the touch panel |
EnabledGestures | GestureType | Gestures enabled on the touch panel |
IsGestureAvailable | bool | True if a gesture can be read |
WindowHandle | IntPtr | Window to receive touch input |
To return the current collection of touch locations, add the following line of code to your game’s Update method:
TouchCollection currentTouchState = TouchPanel.GetState();
The TouchCollection contains all of the touch locations that are currently pressed or were released since the last frame. To read each TouchLocation, you can loop over the TouchCollection and read each of the locations properties:
// Loop each of the touch locations in the collection
foreach (TouchLocation touchLocation in currentTouchState)
{
// The uniquie ID of this locaiton
int touchID = touchLocation.Id;
Vector2 touchPosition = touchLocation.Position;
TouchLocationState touchState = touchLocation.State;
}
The Position and State can then be used by your game to make interactive game play decisions. Table 3 contains a list of the properties exposed by the TouchLocation structure.
Table 3. Properties of TouchLocation
Property | Type | Description |
---|
Position | Vector2 | The position of the touch location |
State | TouchLocationState | The current state of the touch location |
Id | int | The unique ID of the touch location |
Each touch location contains three properties. Use the Position property to determine where on the screen the TouchLocation is located. Each TouchLocation can be in any of four states. Pressed means this specific TouchLocation was just pressed by the user. Moved means that this specific TouchLocation was already tracked and that it has moved. Released mean that the TouchLocation that was being tracked is no longer pressed by the user, and he or she released his or her finger. Invalid means the TouchLocation is invalid and should not be used. Use the TouchLocation.State property to determine the current state of a returned location. The final property Id is used to give each TouchLocation a unique ID that can be used to determine whether two locations across GetState calls are the same point. The index of the touch location in the TouchCollection should not be used as the identifier because the user can pick up a finger but leave others pressed.
Note
If a user touches the screen and then his or her finger slides off the screen, the TouchLocation for that finger changes state to Released.
Determine Number of Touch Points
The TouchPanel class contains the TouchPanel.GetCapabilities method and returns TouchPanelCapabilities a structure. Table 4 contains the properties exposed by the TouchPanelCapabilities structure.
Table 4. Properties of TouchPanelCapabilities
Property | Type | Description |
---|
IsConnected | Vector2 | Returns true if a multitouch capable device is connected |
MaximumTouchCount | int | Returns the maximum number of TouchLocations that can be tracked by the TouchPanel |
To determine the number of touch points the device supports, use the following lines of code:
// Query the capabilities of the device
TouchPanelCapabilities touchCaps = TouchPanel.GetCapabilities();
// Check to see if a device is connected
if(touchCaps.IsConnected)
{
// The max number of TouchLocation points that can be tracked
int maxPoints = touchCaps.MaximumTouchCount;
}
After you have determined that a touch device is connected, use the MaximumTouchCount property to read how many TouchLocation points that the TouchPanel can track at a time.
Note
Windows
Phone games should never require more than four touch points to play
the game. More than four can be used if they are available, but they
should not be required to play the game successfully.
TouchPanel Width, Height, and Orientation
By default, the width, height, and orientation are set for the TouchPanel. If your game supports autorotation, the orientation of the touch panel is automatically updated to match the graphics. TouchPanel
does provide three properties to read their current values but also
enables you to override their values. In most cases, this is not useful,
but it can be useful if you require your input in a different scale
than the display resolution. If your display is 480 wide and 800 tall
but you want TouchLocation positions that range from 400 wide to 500 tall, you can use the following lines of code:
TouchPanel.DisplayWidth = 400;
TouchPanel.DisplayHeight = 500;
Moving Sprite Based on Multitouch Input
It is time to get the sprite
moving around the screen again. This time, you use your newly learned
multitouch APIs. Use the first touch location to move the sprite around
the screen, and use the second touch location to rotate and scale the
sprite. First, you need member variables to store the sprite texture,
position, center, rotation, and scale. Add the following lines of code
to your Game class:
Texture2D spriteTexture;
Vector2 spritePosition;
Vector2 spriteCenter;
float rotation = 0;
float scale = 1;
Next, load a texture to use as your sprite and set the sprite’s center based on the texture width and height. In the game’s LoadContent method, add the following lines of code:
spriteTexture = Content.Load<Texture2D>("XnaLogo");
spriteCenter.X = spriteTexture.Width / 2.0f;
spriteCenter.Y = spriteTexture.Height / 2.0f;
Now, read the current state of the TouchPanel and update your sprite. Add the following lines of code to your game’s Update method:
// Get current touch state
TouchCollection currentTouchState = TouchPanel.GetState();
// Loop over each touch point
for (int i = 0; i < currentTouchState.Count; i++)
{
TouchLocation currentLocation = currentTouchState[i];
// Only process points that were just pressed or are moving
if (currentLocation.State == TouchLocationState.Pressed ||
currentLocation.State == TouchLocationState.Moved)
{
// Use the 1st point for the sprite location
if (i == 0)
{
spritePosition = currentLocation.Position;
}
// Use the 2nd point for rotation and scale
if (i == 0)
{
// Check distance between the 1st and 2nd points
Vector2 positionToSprite = currentLocation.Position - spritePosition;
float distance = positionToSprite.Length();
scale = distance / (spriteTexture.Width / 2);
// Normalize to get direction vector
positionToSprite.Normalize();
// Use the direction between fingers to determine rotation angle
float cosAngle = Vector2.Dot(positionToSprite, Vector2.UnitX);
if (spritePosition.Y > currentLocation.Position.Y)
rotation = 2 * MathHelper.Pi - (float)Math.Acos(cosAngle);
else
rotation = (float)Math.Acos(cosAngle);
}
else
break;
}
}
In the previous code, notice
that you loop over the first two touch locations. Each location is used
only if its state is set to Pressed or Moved. Released and Invalid
points are not used. The first touch point is used to set the sprite’s
position on the screen. The second point is then used to determine the
amount to scale and rotate the sprite. To determine the scale amount,
create a vector from the second touch location to the first touch
location. The distance of this vector is then calculated. The final
scale divides this distance by the width of the texture to achieve the
proper scale value. To determine the rotation of the sprite, find the
angle of the line formed between the first and second finger and the
unit X vector. You can then find the cosine of the angle between these
two vectors by using the vector dot product. The vector dot product is
equal to the cosine of the angle between them multiplied by the length
of each vector. To simplify, make sure both vectors have a length of one
also known as unit length. The Vector2.UnitX
vector already has a length of one, so you don’t need to normalize it.
The vector formed by the two touch locations is normalized. This sets
the vector’s length to one and creates a direction vector. Now that both
vectors have a length of one, the cosine of the angle between them can
be found by just using the vector dot product. To get the final angle,
take the arccosine
of the dot product. This gives you the angle between the vector formed
by the two touch locations and the unit X vector in radians.
Finally, draw the sprite to
the screen using the values you determined. To draw the sprite to the
screen, use the following lines of code in your game’s Draw method:
// Draw the sprite
spriteBatch.Begin();
spriteBatch.Draw(spriteTexture, spritePosition, null, Color.White,
rotation, spriteCenter, scale, SpriteEffects.None, 0);
spriteBatch.End();