Creating your first touch application/game
For Windows Phone 7, touchscreen is the most convenient way of allowing the user to interact with your game. The screen, where all actions and gestures take place, is 800 * 480 for landscape mode and 480 * 800 for portrait mode. Based on the hardware, Windows Phone 7 will give the player a hand to actually touch the game as it unfolds, bringing it to life. In this recipe, you will discover how Windows Phone 7 touchscreen works and see how to get the benefits of using this functionality.
Getting ready
For Windows Phone touchscreen interactions, the most common behavior is Tap. When your finger touches the touchscreen, the tap event will be triggered. In XNA, the touchscreen input is referred to as a TouchPanel class and the method is referred to as GetState(). This static class provides the input and gesture information. We will begin with the basic concepts and properties.
The most Important methods for the TouchPanel class are: TouchPanel.GetState() and TouchPanel.TryGetPreviousLocation().
The GetState() method
The GetState() method will return TouchCollection, a list-based data structure. The element TouchLocationState actually represents the touch tap position. The code for it is as follows:
[code]
public enum TouchLocationState
{
Invalid = 0,
Released = 1,
Pressed = 2,
Moved = 3,
}
[/code]
The frequently used state is TouchLocationState.Pressed. For a TouchCollection object, you can use an integer as the index to look up a special finger and a TouchLocation, when Pressed or Released. If there is no finger touching the screen, TouchCollection.Count will be zero. When a finger first touches the screen, touch collection contains only one TouchLocation object with the State set to Pressed. Subsequently, if the finger has moved, TouchLocation.State will change to TouchLocationState.Moved. When the finger is lifted from the screen, TouchLocation. State will change to TouchLocationState.Released. After that, the TouchCollection will be empty if no screen operations take place. The following table is generated when a finger taps the screen:
The following table is generated when a finger touches the screen, moves across the screen, and is then lifted:
The previous description is about how one finger can interact with touchscreen, but how about using more fingers? When Multi-Touch happens, fingers will be touching, moving, and lifting from the screen independent of each other. You can track the particular finger using the Id Property, where the Id will be the same for the Pressed, Moved, and Released State. The following table is generated when two fingers simultaneously touch the screen, move across the screen, and then each finger is lifted individually:
Dictionary objects with keys based on ID are very common for getting the designated finger information.
The TryGetPreviousLocation() method
This method allows you to calculate the difference between the positions under the Moved state. If the state is TouchLocationState.Pressed, this method will return false, and the previous state tried to get will be invalid.
The GetCapabilities() method
This method returns a data structure, TouchPanelCapabilities, which provides access to information about the capabilities of the input:
[code]
public struct TouchPanelCapabilities
{
public bool IsConnected { get; }
public int MaximumTouchCount { get; }
}
[/code]
The IsConnected attribute indicates the availability of the touch panel for use; it is always true. The MaximumTouchCount attribute gets the maximum number of touch locations that can be tracked by the touch pad device; for Windows Phone 7 the number is not less than 4.
Although this was a little theoretical, these materials are very useful for your future Windows Phone XNA game programming. Now, let’s begin with an example which is simple, but clear. It will help you understand the main concepts of the XNA Touch Technique.
How to do it…
This application presents a white ball in the center of the landscape mode screen. When your finger taps it, the ball color will change to red:
- First, you will have to create a new Windows Phone 7 Game Project: go to File | New Project | Windows Phone Game and name it TouchEventTap. Then, add the following lines in the TouchTap class field:
[code]
Texture2D texRound;
Rectangle HitRegion;
bool isSelected = false;
[/code] - The next step is to insert the code to the LoadContent() method:
[code]
texRound = Content.Load<Texture2D>(“Round”);
HitRegion = new Rectangle(400 – texRound.Width / 2, 240 –
texRound.Height / 2, texRound.Width, texRound.Height);
[/code] - Create an UpdateInput() method. This is the most important method in this example. Your application could actually interact with your fingers with the help of this method:
[code]
private void UpdateInput()
{
// Get the touch panel state as a TouchCollection
TouchCollection touches = TouchPanel.GetState();
// Check the first finger touches on screen
if (touches.Count > 0 && touches[0].State ==
TouchLocationState.Pressed)
{
// Examine whether the tapped position is in the
// HitRegion
Point touchPoint = new
Point((int)touches[0].Position.X,
(int)touches[0].Position.Y);
if (HitRegion.Contains(touchPoint))
{
isSelected = true;
}
else
{
isSelected = false;
}
}
}
[/code] - When you have finished creating the UpdateInput() method, you should call UpdateInput() in Update():
[code]
protected override void Update(GameTime gameTime)
{
. . .
UpdateInput();
. . .
}
[/code] - The final step is drawing, so add the following code to Draw() before base. Draw(gameTime). It will look similar to the following code:
[code]
spriteBatch.Begin();
if (isSelected)
{
spriteBatch.Draw(texRound, HitRegion, Color.Red);
}
else
{
spriteBatch.Draw(texRound, HitRegion, Color.White);
}
spriteBatch.End();
[/code] - Build and run the application. The game will run as shown in the screenshot to the left. When you tap the ball, the ball color will be changed to red. Once you tap the outside region of the ball, the color will go back to white:
How it works…
In step 1, you used the Texture2D object for the ball image, the Rectangle for the HitRegion, and a bool value isSelected for ball hitting status.
In step 2, you must make sure the Round.png file is added to your referred content project before technically loading the round image in code. The initialization for the rectangle object HitRegion is to define the center of the screen in landscape mode; 400 is half of the screen width, and 240 is half of the screen height.
In step 3, as a typical Windows Phone XNA program, TouchPanel.GetState() should be the first line for Touch operations. Then check that touches.Count is over 0 and TouchLocationState is Pressed to make sure your finger has tapped on the screen. Subsequently, we shall examine the tapped position to check whether it is in the HitRegion. HitRegion.Contains() does the job for us. HitRegion is a rectangular object, the Rectangle.Contains() method computes the four-sided area of a rectangle to check whether it includes the position. If yes, the bool value isSelected will be set to true. Otherwise, it will be false. Update() and Draw() use the isSelected value to do the corresponding work.
In step 5, with isSelected value, we determine whether to draw a red ball or a white ball. When your finger taps inside the ball, its color will be red; otherwise, it restores to white. That’s all. You can review the complete project in our book code files.
Taking your touch application to the next level
Now, we have done the first sample of touch tap, very easy, huh? Maybe you feel it’s not exciting enough yet. The next example will be more interesting. The ball will randomly show up in every possible place within the touchscreen, and your job is to click the ball as soon as possible; every valid click will be counted and shown as scores at the top-left of the screen. The new sample’s name is TouchTapRandomBall.
How to do it…
Create a new Windows Phone 7 Game project in Visual Studio 2010 named TouchTapRandomBall. Change the name from Game1.cs to TouchTapRandomBallGame. cs. Then add the Round.jpg and gamefont.spritefont to the associated content project:
- The first operation is to add the lines as field variables:
[code]
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D texRound;
Rectangle HitRegion;
bool isSelected = false;
//start position of round, in the center of screen
int positionX = 400;
int positionY = 240;
//random number Axis X and Y
Random randomX;
Random randomY;
//the range for random number of start and end of X, Y
int startX, endX;
int startY, endY;
//total time
float milliseconds = 0f;
//score count
int count = 0;
//game font
SpriteFont font;
[/code] - Based on the variables, the next step is to add the lines into the LoadContent() method:
[code]
spriteBatch = new SpriteBatch(GraphicsDevice);
texRound = Content.Load<Texture2D>(“Round”);
randomX = new Random();
randomY = new Random();
// The X axis bound range of touch for ball
startX = texRound.Width ;
endX = GraphicsDevice.Viewport.Width – texRound.Width;
// The X axis bound range of touch for ball
startY = texRound.Height;
endY = GraphicsDevice.Viewport.Height – texRound.Height;
// Define the HitRegion of ball in the middle of touchscreen
HitRegion = new Rectangle(positionX – texRound.Width / 2,
positionY – texRound.Height / 2, texRound.Width,
texRound.Height);
// Load the font definition file
font = Content.Load<SpriteFont>(“gamefont”);
[/code] - The next block of code is for the Update() method:
[code]
// Accumulate the elapsed milliseconds every frame
milliseconds +=
(float)gameTime.ElapsedGameTime.TotalMilliseconds;
if (milliseconds > 1000)
{
// When the milliseconds greater than 1000 milliseconds,
// randomly locate a new position for the ball
HitRegion.X = randomX.Next(startX, endX + 1);
HitRegion.Y = randomY.Next(startY, endY + 1);
// Reset the milliseconds to zero for new milliseconds
// count
// make the ball not been selected
milliseconds = 0f;
if (isSelected)
isSelected = false;
}
[/code] - Besides the previous code, we still want to count how many times we tapped the ball. The following code would reach the point. Insert the highlighted line to UpdateInput():
[code]
Point touchPoint = new Point((int)touches[0].Position.X,
(int)touches[0].Position.Y);
if (HitRegion.Contains(touchPoint))
{
isSelected = true;
count++;
}
else
{
isSelected = false;
}
[/code] - Add the following lines to the Draw() method:
[code]
spriteBatch.Begin();
if (isSelected)
{
spriteBatch.Draw(texRound, HitRegion, Color.Red);
}
else
{
spriteBatch.Draw(texRound, HitRegion, Color.White);
}
spriteBatch.DrawString(font, “Score:” + count.ToString(),
new Vector2(0f, 0f), Color.White);
spriteBatch.End();
[/code] - Now you’ve done all the necessary code work, let’s build and run the application. You will discover a jumping ball in the touchscreen. Each time you tap the ball successfully, the score on the top-left will increase by one, as shown in the following screenshots:
How it works…
In step 2, the randomX and randomY objects indicate the random location of the ball. startX and endX are the range in the X axis for randomX and startY and endY are the range for randomY. The time will be calculated in milliseconds and the count variable will be responsible for the score.
In step 3, the calculation for startX, endX, startY, and endY stand for controlling the ball moving inside the screen because the ball position is randomly generated. Then we make the HitRegion locate in the middle of the screen. The last line is for loading and initializing the font.
In step 4, ElapsedGameTime—a Timespan object—represents the amount of elapsed game time since the last update or last frame. In every Update, we add the elapsed time to the milliseconds variable. Once the value is greater than 1000 milliseconds, the code will generate two random numbers for X and Y of the HitRegion position that will be used in the Draw() method in the same frame. After the new values are generated, we reset the milliseconds variable to 0 and deselect the ball.
In step 5, when your finger taps on the ball, the UpdateInput() method will handle it and add one to the count variable; this number will appear on the top-left of the touchscreen.
Creating a Touch Directional Pad (D-pad)
In a Windows game, we have arrow keys to control directions. In Xbox, we have the gamepad controller to set different orientations. In Windows Phone 7, we only have the touchscreen for the same work, so we need another tool to accomplish this aim. The solution is the Touch Directional Pad. Touch Directional Pad gives you comfortable controlling experiences in game playing when adjusting the directions. In this recipe, you will learn how to create your own Touch Directional Pad on Windows Phone 7.
How to do it…
- The first step is to create a new Windows Phone 7 Game project named WindowsPhoneDpad, and change Game1.cs to DpadGame.cs. Then add field variables in the DpadGame class, as follows:
[code]
//Font for direction status display
SpriteFont font;
//Texture Image for Thumbstick
Texture2D texDpad;
//4 direction rectangles
Rectangle recUp;
Rectangle recDown;
Rectangle recLeft;
Rectangle recRight;
//Bounding Rectangle for the 4 direction rectangles
Rectangle recBounding;
//Corner Margin, 1/4 of Width or Height of square
int cornerWidth;
int cornerHeight;
//Direction String
string directionString;
[/code] - The next step is to add the following lines to the LoadContent() method:
[code]
//Load the Texture image from content
texDpad = Content.Load<Texture2D>(“Dpad”);
recBounding = new Rectangle(0,
this.GraphicsDevice.Viewport.Height –
texDpad.Height, texDpad.Width,
texDpad.Height);
//Load the game font file
font = Content.Load<SpriteFont>(“gamefont”);
//Calculate the corner height and width
cornerWidth = texDpad.Width / 4;
cornerHeight = texDpad.Height / 4
//Calculate the Up rectangle
recUp = new Rectangle(recBounding.X + cornerWidth,
recBounding.Y,
cornerWidth * 2, cornerHeight);
//Calculate the Down rectangle
recDown = new Rectangle(recBounding.X + cornerWidth,
recBounding.Y + recBounding.Height – cornerHeight,
cornerWidth * 2, cornerHeight);
//Calculate the Left rectangle
recLeft = new Rectangle(recBounding.X,
recBounding.Y + cornerHeight, cornerWidth,
cornerHeight * 2);
//Calculate the Right rectangle
recRight = new Rectangle(recBounding.X + recBounding.
Width – cornerWidth, recBounding.Y + cornerHeight,
cornerWidth, cornerHeight * 2);
[/code] - The third step is to insert the code to the Update() method:
[code]
// Check the Tap point whether in the region of the 4
// direction rectangle
TouchCollection touches = TouchPanel.GetState();
if (touches.Count > 0 && touches[0].State ==
TouchLocationState.Pressed)
{
Point point = new Point((int)touches[0].Position.X,
(int)touches[0].Position.Y);
// Check the Tap point whether in the 4 direction
// rectangle
if (recUp.Contains(point))
{
directionString = “Up”;
}
else if (recDown.Contains(point))
{
directionString = “Down”;
}
else if (recLeft.Contains(point))
{
directionString = “Left”;
}
else if (recRight.Contains(point))
{
directionString = “Right”;
}
}
[/code] - In the Draw() method, we add the following lines:
[code]
//Draw the font and Touch Directional Pad texture
spriteBatch.Begin();
spriteBatch.DrawString(font, “Direction : ” + directionString,
new Vector2(0, 0), Color.White);
spriteBatch.Draw(texDpad, recBounding, Color.White);
spriteBatch.End();
[/code] - Finally, build and run the application. Click the Right and the Up part on the thumbstick, and you will see something similar to the following screenshots:
How it works…
In step 1, the four rectangle objects: recUp, recDown, recLeft, and recRight individually represent the Up, Down, Left, and Right directions. The recBounding is a rectangle that surrounds the D-pad image. It is convenient for you to control the position of a D-pad and locate the relative positions of the four direction rectangles. The cornerWidth and cornerHeight variables are used for calculating the square gap to corners. Actually, for a square thumbstick, the two variables have the same value. The last variable directionString shows the direction when you tap on the different parts of the thumbsticks.
In step 2, the code is mainly responsible for loading the D-pad image, calculating, and defining the four clickable direction rectangles. The main idea is easy. You can understand the logic and layout from the following figure:
The initialization of recBounding gives the bounding rectangle position, at the bottom-left of the screen. Since the thumbstick image is a square, the cornerWidth and cornerHeight is a quarter of the side. You can see in the previous screenshot that the width of recUp and recDown is half of the length and the height is a quarter. For recLeft and recRight, the width is a quarter of the side and the height is half. For every direction rectangle, the shorter side should be at a distance of cornerWidth or cornerHeight to the thumbstick bounding rectangle side.
In step 3, when you get the tapped position, you should check whether the position is within one of the four direction rectangles. If yes, then according to the rectangle, the direction will receive the appropriate value, that is: Up, Down, Left, or Right. Once the updating is done, the last step will present the image and string on the screen.
In step 4, the method renders the direction string on the top-left of the screen and the thumbstick image at the bottom-left.
Dragging and swiping objects
For Windows Phone 7, XNA 4.0 provides two main ways to get touchscreen input. Basically, one is based on tap. With the TouchPanel.GetState()method, you can look up the particular finger by the ID for the raw access to touch point. The Gesture System is another advanced input approach, which provides a number of pre-defined touch gestures, so that you don’t have to work out how to read the common touch gestures using raw data. The TouchPanel.ReadGesture() method offers you a chance to interact with the touch screen in another way. In this recipe, you will get close to two of the most exciting gestures of touchscreen: dragging and swiping.
Getting ready
For Windows Phone XNA programming, the TouchPanel class has an important subclass GestureSample and a corresponding method ReadGesture(). Based on GestureType enum to interact with your gestures, Windows Phone 7 supports the following:
- Tap: You touch the screen and move away one time, a single point.
- DoubleTap: You touch on the screen two times in a short time.
- Hold: You touch and hold the screen at one point for more than one second.
- FreeDrag: Touch and freely move your finger on the screen.
- HorizontalDrag: Move your finger around the X axis of the screen, either in landscape mode or portrait mode.
- VerticalDrag: Move your finger along the Y axis of the screen, either in landscape mode or portrait mode.
- DragComplete: Lift your finger from the screen.
- Flick: A quick swipe on the screen. The velocity of flick can be retrieved by reading the Delta member of GestureSample.
- Pinch: Pinch behaves like a two-finger drag. Two fingers concurrently moving towards each other or apart.
- PinchComplete: Lift your fingers from the screen.
If you want to use some of the gestures in your Windows Phone 7 XNA game, the best way is to enable them in the Initialize() method as follows:
[code]
TouchPanel.EnabledGestures =
GestureType.Tap |
GestureType.FreeDrag |
GestureType.Flick;
[/code]
Then in the Update() method, you could interact with the gestures as follows:
[code]
while (TouchPanel.IsGestureAvailable)
{
GestureSample gesture = TouchPanel.ReadGesture();
switch (gesture.GestureType)
{
case GestureType.Tap:
. . .
break;
case GestureType.FreeDrag:
. . .
break;
case GestureType.Flick:
. . .
break;
}
}
[/code]
The while loop is used to check whether the gesture property is enabled. If you have set the TouchPanel.EnableGestures in the Initialize() method, then at least one gesture, the IsGestureAvailable will be true. The TouchPanel.ReadGesture() method will then retrieve the gesture taking place on the screen and you can write your own logic to react to the different gesture types.
Now, you know the basic skeleton code for manipulating the Windows Phone 7 gestures. Moreover, I will explain the GestureSample class, which defines the four properties of type Vector2:
- Delta: Holds delta information about the first touchpoint in a multitouch gesture
- Delta2: Holds delta information about the second touchpoint in a multitouch gesture
- Position: Holds the current position of the first touchpoint in this gesture sample
- Position2: Holds the current position of the second touchpoint in this gesture sample
The Position property indicates the current position of the finger relative to the screen. The Delta property presents the finger movements since the last position. The Delta is zero when the finger touches on the screen and remains there.
Furthermore, we should thank Charles Petzold’s who reminds us of the following:
- Position is valid for all gestures except Flick. Flick is positionless, only the Delta value could be tracked.
- Delta is valid for all Drag gestures, Pinch and Flick.
- Position2 and Delta2 are valid only for Pinch.
- None of these properties are valid for the DragComplete and PinchComplete types.
Now that we’ve covered the basic ideas of gestures, let’s look at a simple example in which you can drag the ball to the middle of the screen. If you swipe it, the ball will fly away and will come back when it collides with the screen bounds.
How to do it…
- In Visual Studio 2010, click File | New | Project | Windows Phone Game and create a Windows Phone Game project named DragSwipe. Change Game1.cs to DragSwipe.cs and then add the following lines as fields:
[code]
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D texBall;
Rectangle HitRegion;
// Ball position
Vector2 positionBall;
bool isSelected;
// Ball velocity
Vector2 velocity;
// This is the percentage of velocity lost each second as
// the sprite moves around.
const float Friction = 0.9f;
// Margin for screen bound
const int Margin = 5;
// Viewport bound for ball
float boundLeft = 0f;
float boundRight = 0f;
float boundTop = 0f;
float boundBottom = 0f;
[/code] - The second step is to add the following lines in the Initialize() method:
[code]
// Enable gestures
TouchPanel.EnabledGestures =
GestureType.Tap |
GestureType.FreeDrag |
GestureType.Flick;
[/code] - After the gestures are enabled, you need to insert the following code to LoadContent():
[code]
spriteBatch = new SpriteBatch(GraphicsDevice);
texBall = Content.Load<Texture2D>(“Round”);
// Set the HitRegion of ball in the center
HitRegion = new Rectangle(
GraphicsDevice.Viewport.Width / 2 – texBall.Width / 2,
GraphicsDevice.Viewport.Height / 2 – texBall.Height / 2,
texBall.Width, texBall.Height);
// Set the ball position to the center
positionBall = new Vector2(
GraphicsDevice.Viewport.Width / 2 – texBall.Width / 2,
GraphicsDevice.Viewport.Height / 2 – texBall.Height / 2);
// Define the bound for ball moving
boundLeft = 0;
boundRight = GraphicsDevice.Viewport.Width – texBall.Width;
boundTop = 0;
boundBottom = GraphicsDevice.Viewport.Height – texBall.Height;
[/code] - Add the following code to the Update() method:
[code]
TouchCollection touches = TouchPanel.GetState();
// Check the first finger touches on screen
if (touches.Count > 0 && touches[0].State ==
TouchLocationState.Pressed
{
// Examine the tapped position is in the HitRegion
Point point = new Point((int)touches[0].Position.X,
(int)touches[0].Position.Y);
if (HitRegion.Contains(point))
{
isSelected = true;
}
else
{
isSelected = false;
}
}
// Check the available gestures
while (TouchPanel.IsGestureAvailable)
{
// Read the on-going gestures
GestureSample gesture = TouchPanel.ReadGesture();
//Process different gestures
switch (gesture.GestureType)
{
case GestureType.FreeDrag:
if (isSelected)
{
// When the ball is being dragged, update
// the position of ball and HitRegion
positionBall += gesture.Delta;
HitRegion.X += (int)gesture.Delta.X;
HitRegion.Y += (int)gesture.Delta.Y;
}
break;
case GestureType.Flick:
{
if (isSelected)
{
// When the ball is swiped, update its
// velocity
velocity = gesture.Delta *
(float)gameTime.ElapsedGameTime.
TotalSeconds;
}
}
break;
}
}
// Accumulate the velocity of every frame
positionBall += velocity;
// Reduce the velocity of every frame
velocity *= 1f – (Friction *
(float)gameTime.ElapsedGameTime.TotalSeconds);
// Check Bound, once the ball collides with the bound, change
// its direction to the opposite.
if (positionBall.X < boundLeft)
{
positionBall.X = boundLeft + Margin;
velocity.X *= -1;
}
else if (positionBall.X > boundRight)
{
positionBall.X = boundRight – Margin;
velocity.X *= -1;
}
else if (positionBall.Y < boundTop)
{
positionBall.Y = boundTop + Margin;
velocity.Y *= -1;
}
else if (positionBall.Y > boundBottom)
{
positionBall.Y = boundBottom – Margin;
velocity.Y *= -1;
}
// Update the position of HitRegion
HitRegion.X = (int)positionBall.X;
HitRegion.Y = (int)positionBall.Y;
[/code] - The final Draw() method is very simple:
[code]
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(texBall, positionBall, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
[/code] - Build and run the project. The application will look similar to the following screenshot:
How it works…
In step 1, the first four variables are the necessary objects for rendering texture and string on the screen. positionBall indicates the ball position. The Friction variable is used for calculating the ball velocity that changes in every frame. The Margin variable defines the distance between the active limited bound for ball and viewport. The four integers boundLeft, boundRight, boundTop, and boundBottom are the active bound values for controlling the ball movement inside the screen.
In step 2, the code enables the Tap, FreeDrag, and Flick gestures in this application. This means that you could track these actions and perform the corresponding logic.
In step 3, we set the HitRegion and positionBall to the center of the screen. This makes the ball moving-bound have a distance away from the ball texture width to the screen right side and from the screen bottom to the height of the ball texture.
In step 4, the first section of the code is used to check whether the tapped point is inside the ball. If yes, isSelected will be true. The code in the while loop is used for reading and processing the different gestures. While the GestureType is FreeDrag, we update the ball position by Gesture.Delta, the same as the HitRegion. When the GestureType is equal to Flick, it means you are swiping the ball very fast. We use the Delta value to update the ball velocity along the game elapsed time. After the while loop, you can use the latest value to perform your own logic. Here, we update the velocity following the inertia law with the game elapsed time in every frame. The next section is the bounding checks for the moving ball, as long as the ball collides with the bound, the ongoing ball direction will be changed to the opposite.
Controlling images with Multi-Touch control
Windows Phone 7 supports Multi-Touch Control. With this technique, developers can do any creative operation using their fingers such as to control car steel in a racing game, to rotate and zoom in/out images, to hit balls that come up from different positions concurrently, and so on. In this recipe, you will know how to control images using the Multi-Touch technology in Windows Phone 7.
Getting ready
In the movie Minority Report, Tom Cruise wears gloves with flashing lights on every finger, grasping and zooming the images on the glass wall without touching them. I am sure the incredible actions and effects impressed you at that time. Actually, I am not Tom Cruise, I am not the director Steven Spielberg, and the glass wall is not in front of me. However, I have Windows Phone 7, and although the effect can be as cool as the movie, I still want to present you with the amazing Multi-Touch on image controlling. Now, let’s begin our journey.
How to do it…
- The first step is to create a Windows Phone Game project named MultiTouchImage. Change Game1.cs to ImageControl.cs and then add a new class file Mountain.cs to the project. The complete class will be as follows:
[code]
public class Mountain
{
// The minimum and maximum scale values for the sprite
public const float MinScale = 0.5f;
public const float MaxScale = 3f;
// The texture object
private Texture2D texture;
// The scale factor for pinch
private float scale = 1f;
// The center position of object
public Vector2 Center;
// The Scale property of object
public float Scale
{
get { return scale; }
// Control the scale value within min and max
set
{
scale = MathHelper.Clamp(value, MinScale,
MaxScale);
}
}
// The HitRegion Property
public Rectangle HitRegion
{
get
{
// Create a rectangle based on the texture center
// and scale
Rectangle r = new Rectangle(
(int)(Center.X – (texture.Width /2 * Scale)),
(int)(Center.Y – (texture.Height /2 *Scale)),
(int)(texture.Width * Scale),
(int)(texture.Height * Scale));
return r;
}
}
public Mountain(Texture2D texture)
{
this.texture = texture;
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(
texture,
Center,
null,
Color.White,
0,
new Vector2(texture.Width / 2,
texture.Height / 2),
Scale,
SpriteEffects.None,
0);
}
}
[/code] - Now you have completed the Mountain class. The second step is to add the fields to your ImageControl main game class for interacting with the mountain texture:
[code]
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
// Texture file for mountain
Texture2D texMountain;
// Mountain object
Mountain mountain;
// Bool value tracks the mountain object selection
bool isSelected;
[/code] - The next step is to insert the code in to the LoadContent() method:
[code]
// Create a new SpriteBatch, which can be used to draw
textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
texMountain = Content.Load<Texture2D>(“Mountain”);
// Initialize the mountain object
mountain = new Mountain(texMountain);
// Define the center of mountain
mountain.Center = new Vector2(
GraphicsDevice.Viewport.Width / 2,
GraphicsDevice.Viewport.Height / 2);
[/code] - Next, you should enable the Pinch and Drag gestures in the Initialize method:
[code]
TouchPanel.EnabledGestures =
GestureType.Pinch | GestureType.FreeDrag;
[/code] - Then add the following core logic code for Multi-Touch in Windows Phone 7 to the Update() method:
[code]
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
// Get the touch position and check whether it is in HitRegion
TouchCollection touches = TouchPanel.GetState();
if (touches.Count > 0 && touches[0].State ==
TouchLocationState.Pressed)
{
Point point = new Point((int)touches[0].Position.X,
(int)touches[0].Position.Y);
if (mountain.HitRegion.Contains(point))
{
isSelected = true;
}
else
{
isSelected = false;
}
}
// Check the Gestures available or not
while (TouchPanel.IsGestureAvailable)
{
//Read the gestures
GestureSample gestures = TouchPanel.ReadGesture();
// Determine which gesture takes place
switch (gestures.GestureType)
{
// When gesture type is Pinch
case GestureType.Pinch:
// When the mountain texture is selected
if (isSelected)
{
// Get the current touch position
// and calculate their previous position
// according to the Delta value from gesture.
Vector2 vec1 = gestures.Position;
Vector2 oldvec1 =
gestures.Position – gestures.Delta;
Vector2 vec2 = gestures.Position2;
Vector2 oldvec2 =
gestures.Position2 – gestures.Delta2;
// Figure out the distance between the current
// and previous locations
float distance = Vector2.Distance(vec1, vec2);
float oldDistance = Vector2.Distance(oldvec1,
oldvec2);
// Calculate the difference between the two
// and use that to alter the scale
float scaleChanged =
(distance – oldDistance) * 0.01f;
mountain.Scale += scaleChanged;
}
break;
// When gesture is FreeDrag
case GestureType.FreeDrag:
// When the mountain texture is selected
if (isSelected)
{
mountain.Center += gestures.Delta;
}
break;
}
}
[/code] - In the Draw() method the code is easy:
[code]
spriteBatch.Begin();
mountain.Draw(spriteBatch);
spriteBatch.End();
[/code] - Now, build and run the project. Then start the Multi-Touch simulator, if you do not have Windows Phone 7. When the application runs well, you can experience the amazing Multi-Touch feeling, as shown in the following screenshots. When your fingers move outwards, the application runs similar to the screenshot to the right:
How it works…
In step 1, in the Mountain class, we have defined the MinScale and MaxScale to limit the scale value variation range. In the set operation of Scale property, the MathHelp. Clamp() method is used to perform the value limitation work of scale. The HitRegion property is responsible for returning the start point and size of the texture bounding box based on the texture center and size. In the Draw() method, we use another overload method of SpriteBatch.Draw() because we want to change the scale value of the texture. The complete parameter specification is as follows:
[code]
public void Draw (
Texture2D texture,
Vector2 position,
Nullable<Rectangle> sourceRectangle,
Color color,
float rotation,
Vector2 origin,
Vector2 scale,
SpriteEffects effects,
float layerDepth
)
[/code]
The texture, position, and color parameters are easy to understand. The sourceRectangle is used to determine which part of the texture needs to be rendered. In this example, we set it to null to draw the entire texture. The rotation parameter will rotate the texture with the actual passing value. The origin parameter defines the position around which rotation and scale can take place. The effects parameter applies SpriteEffects to texture; here, we set SpriteEffects to None. The last parameter layerDepth gives the layer order for drawing.
In step 3, the code snippet loads the Mountain image and initializes the mountain position to the center of the screen.
In step 5, the code tests the touch point to check whether it is inside the mountain region. Then in the while loop, when TouchPanel.IsGestureAvailable is true, you get into the core code of Multi-Touch Pinch gesture. In this case, once two of your fingers tap on the Windows Phone 7 touchscreen, the code will read these two positions and calculate the previous position after you move the two fingers. It then calculates the distance between the current positions and the previous position. Based on the subtraction value of the two distances, you will get the scale change factor to change the size of the mountain texture and render it in Draw().
Using accelerometer to make a ball move on your phone
Accelerometers, which are useful for game programming, are becoming more common nowadays. An accelerometer is a device, a kind of a sensor, contained in the Windows Phone 7 that can report the device’s current axis or orientation. In other words, it can tell if the device is lying on the horizontal plain or rotated to the vertical position. This data from accelerometer presents the Windows Phone 7 game programmers with the opportunity to work with gravity, orientations, and so on. Instead of using the touchscreen, or pressing a button to move objects on the screen, the accelerometer makes it possible for players to shake or adjust the Windows Phone 7 device in whichever direction they want. The game play will take the corresponding actions. This feature of Windows Phone 7 creates a lot of possibilities for a game programmer. In this chapter, you will learn how to use this feature.
Getting ready
When you have a Windows Phone 7 in your hand, you will enjoy the accelerometer in the device. Just imagine playing a racing game, such as Need for Speed or GTA without any controller or keyboard, instead only using your hands. It is very exciting! As a Windows Phone programmer, understanding and mastering the accelerometer technique will make your game more attractive and creative. The two most common usages of accelerometer are:
- Orientation adjustment
- Movement track from initial position
The accelerometer sensor in Windows Phone 7 will tell you the direction of the earth relative to the phone because when the phone is still, the accelerometer will react to the force of gravity. Besides this, corresponding to a sudden action, such as a shake of the device, is also a very special function for inspiring your creativity.
Representing the output of Windows Phone 7 accelerometer in 3D vector is straightforward for its developers. Vector has direction and length, the (x, y, z) for 3D vector means the direction from the origin point (0, 0, 0) to the (x, y, z) point. We could learn the basic concepts and calculation from some computer graphics or linear algebra books.
While programming on a Windows Phone 7, you should be clear on the 3D coordinate system, no matter how the phone orients. The 3D coordinate system is completely different from the 2D coordinate system, in which the origin point is located at the top-left of the screen of Window Phone 7. The 3D coordinate system in the phone is a right-hand system. Here, you just need to know that the positive Z axis always points to the front as shown in the following screenshots. The screenshot to the left is for the landscape mode and the one to the right is for the portrait mode:
In landscape mode, the increasing Y towards the top side is parallel to the control pad and the increasing X is perpendicular to the control pad towards the right side. In portrait mode, the increasing Y towards the top side is perpendicular to the control pad and the increasing X towards the right side is parallel to the control pad. In addition, the 3D coordinate system remains fixed relative to the phone regardless of how you hold the phone or whatever the orientation is. The accelerometer is the reason to change the Windows Phone 7 orientation. In the next section, I will introduce you to the basic programming skeleton for the Windows Phone 7 accelerometer
For a typical Windows Phone 7 XNA accelerometer application, the first step is to add a Microsoft.Devices.Sensors reference to your project and then add data members to the game to hold the accelerometer data:
[code]
Accelerometer accelSensor;
YourClass substance;
bool accelActive;
Vector3 accelReading = new Vector3();
const float ACCELFACTOR = 2.0f;
[/code]
The Vector3 variable accelReading will be used to read the position data from AccelerometerReadingEventArgs. The second step is to add an event handler for the ReadingChanged event Accelerometer object:
[code]
public void AccelerometerReadingChanged(object sender,
AccelerometerReadingEventArgs e)
{
accelReading.X = (float)e.X;
accelReading.Y = (float)e.Y;
accelReading.Z = (float)e.Z;
}
[/code]
This method returns void, and passes two parameters; one is the sender, another is the AccelerometerReadingEventArgs to get the accelerometer reading. After this, you should add a reference in the Initialize() method:
[code]
accelSensor = new Accelerometer();
substance = new YourClass();
// Add the accelerometer event handler to the accelerometer
// sensor.
accelSensor.ReadingChanged += new EventHandler
<AccelerometerReadingEventArgs>(AccelerometerReadingChanged);
[/code]
When you are done with the preparation, you need to start the accelerometer:
[code]
// Start the accelerometer
try
{
accelSensor.Start();
accelActive = true;
}
catch (AccelerometerFailedException e)
{
// the accelerometer couldn’t be started. No fun!
accelActive = false;
}
catch (UnauthorizedAccessException e)
{
// This exception is thrown in the emulator – which doesn’t
// support an accelerometer.
accelActive = false;
}
[/code]
After it is started, the accelerometer calls your event handler when the ReadingChanged event is raised. Update your stored AccelerometerReadingEventArgs class (previously shown in the event handler code), and then use its data in your Update() method:
[code]
if (accelActive)
{
// accelerate the substance speed depending on
// accelerometer
// action.
substance.speed.X += accelReading.X * ACCELFACTOR;
substance.speed.Y += -accelReading.Y * ACCELFACTOR;
}
[/code]
The final code is a skeleton snippet used to stop the accelerometer sensor. To avoid having your event handler being called repeatedly when your game is not actually using the accelerometer data, you can stop the accelerometer when the game is paused, when menus are being shown, or at any other time by calling the Stop() method. Like the Start() method, this method can throw an exception, so allow your code to handle the AccelerometerFailedException:
[code]
// Stop the accelerometer if it’s active.
if (accelActive)
{
try
{
accelSensor.Stop();
}
catch (AccelerometerFailedException e)
{
// the accelerometer couldn’t be stopped now.
}
}
[/code]
The complete accelerometer skeleton snippet will be as follows:
[code]
using Microsoft.Devices.Sensors;
. . .
public class Game : Microsoft.Xna.Framework.Game
{
Accelerometer accelSensor;
YourClass substance;
bool accelActive;
Vector3 accelReading = new Vector3();
const float ACCELFACTOR = 2.0f;
protected override void Initialize()
{
base.Initialize();
accelSensor = new Accelerometer();
substance = new YourClass();
// Add the accelerometer event handler to the
// accelerometer sensor.
accelSensor.ReadingChanged += new EventHandler
<AccelerometerReadingEventArgs>
(AccelerometerReadingChanged);
// Start the accelerometer
try
{
accelSensor.Start();
accelActive = true;
}
catch (AccelerometerFailedException e)
{
// the accelerometer couldn’t be started. No fun!
accelActive = false;
}
catch (UnauthorizedAccessException e)
{
// This exception is thrown in the emulator – which
// doesn’t support an accelerometer.
accelActive = false;
}
}
protected override void LoadContent()
{
. . .;
}
protected override void Update(GameTime gameTime)
{
if (accelActive)
{
// accelerate the substance speed depending on
// accelerometer action.
substance.speed.X += accelReading.X * ACCELFACTOR;
substance.speed.Y += -accelReading.Y * ACCELFACTOR;
}
}
protected override void UnloadContent()
{
// Unload any non ContentManager content here
// Stop the accelerometer if it’s active.
if (accelActive)
{
try
{
accelSensor.Stop();
}
catch (AccelerometerFailedException e)
{
// the accelerometer couldn’t be stopped now.
}
}
}
}
[/code]
So far, I suppose you are familiar with the basic code of Windows Phone 7 accelerometer. Now, let’s make a new accelerometer project: a white ball will move around within the Windows Phone screen depending on your hand movement.
How to do it…
- First, create a Windows Phone Game project in Visual Studio 2010, named AccelerometerFallingBall and then change Game1.cs to FallingBallGame.cs. For accelerometer, you should add a Microsoft. Devices.Sensors reference to the project’s reference. Then, add Ball.cs.
- After the preparation work, you should insert the following lines to the FallingBallGame class field:
[code]
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
// Ball object
Ball ball;
Texture2D texBall;
Rectangle recBound;
Vector2 position;
//Accelerometer object
Accelerometer accelSensor;
// The accelActive bool value indicates whether the accelerometer
// is turned on or off.
bool accelActive;
// Demonstrate the direction of accelerometer
Vector3 accelReading = new Vector3();
// Amplify the variegation of ball velocity
const float ACCELFACTOR = 2.0f;
// Viewport bound
int boundLeft = 0;
int boundRight = 0;
int boundTop = 0;
int boundBottom = 0;
[/code] - Add the following lines to the Initialize() method of the FallingBallGame class:
[code]
accelSensor = new Accelerometer();
accelSensor.ReadingChanged += new EventHandler
<AccelerometerReadingEventArgs>
(AccelerometerReadingChanged);
// Start the accelerometer
try
{
accelSensor.Start();
accelActive = true;
}
catch (AccelerometerFailedException e)
{
// the accelerometer couldn’t be started. No fun!
accelActive = false;
}
catch (UnauthorizedAccessException e)
{
// This exception is thrown in the emulator – which
// doesn’t support an accelerometer.
accelActive = false;
}
[/code] - Then, add the AccelerometerReadingChanged() method in the FallingBallGame class:
[code]
public void AccelerometerReadingChanged(object sender,
AccelerometerReadingEventArgs e)
{
accelReading.X = (float)e.X;
accelReading.Y = (float)e.Y;
accelReading.Z = (float)e.Z;
}
[/code] - The fifth step is to insert the code into the LoadContent() method of the FallingBallGame class:
[code]
// Create a new SpriteBatch, which can be used to draw
// textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
texBall = Content.Load<Texture2D>(“Round”);
recBound = new Rectangle(GraphicsDevice.Viewport.Width / 2 –
texBall.Width / 2,
GraphicsDevice.Viewport.Height / 2 – texBall.Height / 2,
texBall.Width, texBall.Height);
// The center position
position = new Vector2(GraphicsDevice.Viewport.Width / 2 –
texBall.Width / 2,
GraphicsDevice.Viewport.Height / 2 – texBall.Height / 2);
//Bound Calculation
boundLeft = 0;
boundRight = GraphicsDevice.Viewport.Width – texBall.Width;
boundTop = 0;
boundBottom = GraphicsDevice.Viewport.Height – texBall.Height;
//Initialize ViewPortBound
ViewPortBound viewPortBound = new ViewPortBound(boundTop,
boundLeft, boundRight, boundBottom);
// Initialize Ball
ball = new Ball(this, spriteBatch, texBall, recBound,
position,viewPortBound);
[/code] - This step makes the ball interact with the accelerometer’s latest value. Add the following lines to the Update() method of the FallingBallGame class:
[code]
if (Microsoft.Devices.Environment.DeviceType ==
DeviceType.Device)
{
if (accelActive)
{
// Accelerate the substance speed depending on
// accelerometer action.
ball.Velocity.X += accelReading.X * ACCELFACTOR;
ball.Velocity.Y += -accelReading.Y * ACCELFACTOR;
}
}
else if (Microsoft.Devices.Environment.DeviceType ==
DeviceType.Emulator)
{
// Simulate the Keyboard when running on emulator
KeyboardState keyboardCurrentState = Keyboard.GetState();
if (keyboardCurrentState.IsKeyDown(Keys.Left))
ball.Velocity.X -= 5f;
if (keyboardCurrentState.IsKeyDown(Keys.Right))
ball.Velocity.X += 5f;
if (keyboardCurrentState.IsKeyDown(Keys.Up))
ball.Velocity.Y -= 5f;
if (keyboardCurrentState.IsKeyDown(Keys.Down))
ball.Velocity.Y += 5f;
}
ball.Update(gameTime);
[/code] - Build and run the project. The ball will move with the shake of your hand, as application is shown in the next screenshot:
How it works…
In step 2, in the first section, we declare the Ball object, the texture, bound, and position of the ball. Then we declare the Accelerometer object. The bool value is used for indicating whether the accelerometer is active or not. The Vector3 variable accelReading stands for the direction of the accelerometer. ACCELFACTOR will make the accelerometer change more obvious. The next section covers the variables used for bound check.
In step 3, the code initializes the accelSensor object and associates the EventHandler with the ReadingChanged event for the accelerometer object. It then enables the accelerometer.
In step 4, the AccelerometerReadingChanged method is responsible for updating accelReading with every accelerometer direction change.
In step 6, notice that with the Microsoft.Devices.DeviceType, we do a check on the device type for Windows Phone 7 development. It is a challenge when using the emulator that you want to work with the accelerometer having specific hardware. You can even simulate it through the keyboard. When your application is running on a real Windows Phone 7 device, the code will read the actual data from the accelerometer in the device to update the ball velocity. Otherwise, you should enable your ball to simulate the information to change the ball velocity by 5 units per valid keyboard press.