Gravity Tutorial – Windows Phone 7 Version – Part III

If you’re not up to speed, make sure you go back and read Part I and Part II of this tutorial.

A Few Fixes

Before we go into user interaction, I wanted to make a few changes to yesterday’s code. We had a variable called ballSize which was set to 40, the circumference of the ball image. The actual position of the ball was the top left corner of the graphic, as that’s how textures are drawn. So in checking if it had hit any of the walls, I was checking for position.X < 0 on the left and position.X + ballSize > width on the right. I’m changing that up a bit here now. The position will actually be the center of the ball. To do this, I changed ballSize to radius and set it to 20. This allows the wall collision code to become:

[csharp]if (position.X + radius > width)
{
position.X = width – radius;
velocity.X *= bounce;
}
else if (position.X < radius) { position.X = radius; velocity.X *= bounce; } if (position.Y + radius > height)
{
position.Y = height – radius;
velocity.Y *= bounce;
}
else if (position.Y < radius) { position.Y = radius; velocity.Y *= bounce; }[/csharp] which is more like what it should be. Now we just need to force the ball to be drawn so that its center is on position x, y, instead of its top left. To do that, I made another Vector2 called centerOffset and set that to -21, -21. This is because although the ball itself is 40 pixels across, the graphic is actually 42x42 to allow a bit of space for antialiasing or whatever. Now, when we draw the ball in the Draw method, instead of drawing it at position, we draw it at position + centerOffset. This puts the center of the ball on the position point. [csharp]spriteBatch.Draw(ball, position + centerOffset, Color.White);[/csharp] Getting Touchy

OK, now we deal with the touch screen and finding out when and where the user might have touched it. I’m not sure the following is the BEST way of handling this, but it certainly works. Basically, we need to get a list of all the active touch points. I believe XNA lets you have a total of four at any one time. These come in as a TouchCollection by calling GetState() on an object called the TouchPanel, like so:

[csharp]TouchCollection touchCollection = TouchPanel.GetState();[/csharp]

Now we can do a foreach loop through this collection. Each object in it will be a TouchLocation, which has a State, which can be Pressed, Released, Moved, or Invalid. A TouchLocation also has a Position property, which is simply a Vector2. So, what we can do is the following:

– If Pressed, check the distance of the touch to the position of the ball. If it’s within a certain range, set a variable called dragging to true and set position to the touch position.

– If Released, just set dragging to false.

– If Moved, set position to the touch position, but also see how much the touch has moved since the last time around and use this to set the velocity. More on that soon.

Here’s what that chunk of code looks like:

[csharp]TouchCollection touchCollection = TouchPanel.GetState();
foreach (TouchLocation tl in touchCollection)
{
if (tl.State == TouchLocationState.Pressed)
{
if (Vector2.Distance(tl.Position, position) < radius * 2) { dragging = true; position = tl.Position; } else { dragging = false; } } else if (tl.State == TouchLocationState.Released) { dragging = false; } else if (dragging && tl.State == TouchLocationState.Moved) { velocity = (tl.Position - position) / elapsed; position = tl.Position; } }[/csharp] For the Pressed state, we check: if (Vector2.Distance(tl.Position, position) < radius * 2), checking if the distance between the two points is less than radius * 2. Technically, this should just check if it's less than radius. If I were doing a traditional mouse based game, that's what I would do. But on a touch screen, it's hard to be very precise with a finger, so I'm allowing some leeway on how close the finger has to be to the center of the ball. This is something that I would probably want to test on an actual device and come up with a better number based on real world testing. A few things to note about the Moved state: of course, we only want to change the position and velocity if we are actually dragging, so we check that right off. Then we subtract the current touch position with the previous ball position. That's how far it's moved since the last frame. We divide that by the elapsed time to convert it from pixels per frame to pixels per second. Then we are safe to assign the touch position to the ball position. Finally, if dragging is false after all those checks, just carry on with the existing motion code that we wrote yesterday. But if dragging is true, we just skip over all that stuff and allow the touch to dictate the location of the ball. Again, this is all covered in my ActionScript Animation book and previous tutorials. Not going to beat a dead horse. We just wrap all the motion code in a if (!dragging) statement and be done with it. So there you have it. The ball bounces around, and you can pick it up and toss it too. I guess that for the time being, I'm going to skip over the accelerometer code, because you really need to have a device to test that. Anyway, here is the entire code for today's post: [csharp]using System; using System.Collections.Generic; using System.Linq; using System.Diagnostics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input.Touch; using Microsoft.Xna.Framework.Media; namespace GravityTutorial { ///

/// This is the main type for your game
///

public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D ball;
Vector2 position;
Vector2 velocity;
Vector2 centerOffset;
float radius = 20;
float gravity = 500;
float bounce = -0.9f;
Boolean dragging;

public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;

// Frame rate is 30 fps by default for Windows Phone.
TargetElapsedTime = TimeSpan.FromTicks(333333);
}

///

/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
///

protected override void Initialize()
{
// TODO: Add your initialization logic here

base.Initialize();
}

///

/// LoadContent will be called once per game and is the place to load
/// all of your content.
///

protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);

ball = Content.Load(“ball”);
position.X = 100;
position.Y = 100;
velocity.X = 150;
velocity.Y = 175;
centerOffset.X = -21;
centerOffset.Y = -21;
}

///

/// UnloadContent will be called once per game and is the place to unload
/// all content.
///

protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}

///

/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
///

/// Provides a snapshot of timing values. protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();

float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

TouchCollection touchCollection = TouchPanel.GetState();
foreach (TouchLocation tl in touchCollection)
{
if (tl.State == TouchLocationState.Pressed)
{
if (Vector2.Distance(tl.Position, position) < radius * 2) { dragging = true; position = tl.Position; } else { dragging = false; } } else if (tl.State == TouchLocationState.Released) { dragging = false; } else if (dragging && tl.State == TouchLocationState.Moved) { velocity = (tl.Position - position) / elapsed; position = tl.Position; } } if (!dragging) { float width = graphics.GraphicsDevice.Viewport.Width; float height = graphics.GraphicsDevice.Viewport.Height; velocity.Y += gravity * elapsed; position += velocity * elapsed; if (position.X + radius > width)
{
position.X = width – radius;
velocity.X *= bounce;
}
else if (position.X < radius) { position.X = radius; velocity.X *= bounce; } if (position.Y + radius > height)
{
position.Y = height – radius;
velocity.Y *= bounce;
}
else if (position.Y < radius) { position.Y = radius; velocity.Y *= bounce; } } base.Update(gameTime); } ///

/// This is called when the game should draw itself.
///

/// Provides a snapshot of timing values. protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);

spriteBatch.Begin();
spriteBatch.Draw(ball, position + centerOffset, Color.White);
spriteBatch.End();

base.Draw(gameTime);
}
}
}
[/csharp]

This entry was posted in Windows Phone 7. Bookmark the permalink.

5 Responses to Gravity Tutorial – Windows Phone 7 Version – Part III

  1. Scott T says:

    Hi Keith, first of all let me say these are great tutorials, please keep going! It’s really interesting for me coming from an AS3 only background to see how similar the languages are (at least for simple stuff like this anyway).

    I was just wondering if, instead of setting the centerOffset at a specified amount like you’ve done here, you could set it dynamically by calling ball.width / 2 or something similar? That way you could change the graphic’s size and not have to worry about changing variables within the code.

  2. Noble Kale says:

    Greetings,
    This tutorial is very well written. Can we please see more? 🙂

    Cheers,
    Kale

  3. Paolo says:

    Congratulations, you’re really good.
    How have derived mathematical formulas for calculating?

  4. Darren says:

    This was extremely helpful for a project I am working on.
    Thank you for posting this.
    Will you be doing more?

Leave a Reply