This chapter is taken from book "Programming Windows Phone 7" by Charles Petzold published by Microsoft press. http://www.charlespetzold.com/phone/index.html
Learning how to use XNA to move text around the screen would provide a leg up in the art of moving regular bitmap sprites. This relationship becomes very obvious when you begin examining the Draw methods supported by the SpriteBatch. The Draw methods have almost the same arguments as DrawString but work with bitmaps rather than text. In this article I'll examine techniques for moving and turning sprites, particularly along curves.
The Draw Variants
Both the Game class and the SpriteBatch class have methods named Draw. Despite the identical names, the two methods are not genealogically related through a class hierarchy. In a class derived from Game you override the Draw method so that you can call the Draw method of SpriteBatch. This latter Draw method comes in seven different versions. The simplest one is:
Draw(Texture2D texture, Vector2 position, Color color)
The next two versions of Draw have five additional arguments that you'll recognize from the DrawString methods:
Draw(Texture2D texture, Vector2 position, Rectangle? source, Color color, float rotation, Vector2 origin, float scale, SpriteEffects effects, float depth)
Draw(Texture2D texture, Vector2 position, Rectangle? source, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float depth)
As with DrawString, the rotation angle is in radians, measured clockwise. The origin is a point in the texture that is to be aligned with the position argument. You can scale uniformly with a single float or differently in the horizontal and vertical directions with a Vector2. The SpriteEffects enumeration lets you flip an image horizontally or vertically to get its mirror image. The last argument allows overriding the defaults for layering multiple textures on the screen.
Within the Draw method of your Game class, you use the SpriteBatch object like so:
spriteBatch.Begin(); spriteBatch.Draw ... spriteBatch.End();
Within the Begin and End calls, you can have any number of calls to Draw and DrawString. The Draw calls can reference the same texture. You can also have multiple calls to Begin followed by End with Draw and DrawString in between.
A Hello Program
This time I'll compose a very blocky rendition of the word "HELLO" using two different bitmaps—a vertical bar and a horizontal bar. The letter "H" will be two vertical bars and one horizontal bar. The "O" at the end will look like a rectangle.
And then, when you tap the screen, all 15 bars will fly apart in random directions and then come back together. Sound like fun?
I'm going to use a little class called SpriteInfo to keep track of the 15 textures required for forming the text. If you're creating the project from scratch, right-click the project name, and select Add and then New Item (or select Add New Item from the main Project menu). From the dialog box select Class and give it the name SpriteInfo.cs.
namespace FlyAwayHello
{
public class SpriteInfo
{
public static float InterpolationFactor { set; get; }
public Texture2D Texture2D { protected set; get; }
public Vector2 BasePosition { protected set; get; }
public Vector2 PositionOffset { set; get; }
public float MaximumRotation { set; get; }
public SpriteInfo(Texture2D texture2D, int x, int y)
{
Texture2D = texture2D;
BasePosition = new Vector2(x, y);
}
public Vector2 Position
{
get
{
return BasePosition + InterpolationFactor * PositionOffset;
}
}
public float Rotation
{
get
{
return InterpolationFactor * MaximumRotation;
}
}
}
}
The required constructor stores a Texture2D along with positioning information. This is how each sprite is initially positioned to spell out the word "HELLO." Later in the "fly away" animation, the program sets the PositionOffset and MaximumRotation properties. The Position and Rotation properties perform calculations based on the static InterpolationFactor, which can range from 0 to 1.
Here are the fields of the Game1 class:
public
class Game1 :
Microsoft.Xna.Framework.Game
{
static readonly
TimeSpan ANIMATION_DURATION =
TimeSpan.FromSeconds(5);
const int
CHAR_SPACING = 5;
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Viewport viewport;
List<SpriteInfo>
spriteInfos = new
List<SpriteInfo>();
Random rand =
new Random();
bool isAnimationGoing;
TimeSpan animationStartTime;
...
}
This program initiates an animation only when the user taps the screen, so I'm handling the timing just a little differently than in earlier programs, as I'll demonstrate in the Update method.
The LoadContent
method loads the two
Texture2D objects
using the same generic Load
method that previous programs used to load a
SpriteFont. Enough
information is now available to create and initialize all
SpriteInfo objects:
protected
override void LoadContent()
{
spriteBatch = new
SpriteBatch(GraphicsDevice);
viewport = this.GraphicsDevice.Viewport;
Texture2D horzBar = Content.Load<Texture2D>("HorzBar");
Texture2D vertBar = Content.Load<Texture2D>("VertBar");
int x = (viewport.Width - 5 *
horzBar.Width - 4 * CHAR_SPACING) / 2;
int y = (viewport.Height -
vertBar.Height) / 2;
int xRight = horzBar.Width -
vertBar.Width;
int yMiddle = (vertBar.Height -
horzBar.Height) / 2;
int yBottom = vertBar.Height -
horzBar.Height;
// H
spriteInfos.Add(new
SpriteInfo(vertBar, x, y));
spriteInfos.Add(new
SpriteInfo(vertBar, x + xRight, y));
spriteInfos.Add(new
SpriteInfo(horzBar, x, y + yMiddle));
// E
x += horzBar.Width + CHAR_SPACING;
spriteInfos.Add(new
SpriteInfo(vertBar, x, y));
spriteInfos.Add(new
SpriteInfo(horzBar, x, y));
spriteInfos.Add(new
SpriteInfo(horzBar, x, y + yMiddle));
spriteInfos.Add(new
SpriteInfo(horzBar, x, y + yBottom));
// LL
for (int
i = 0; i < 2; i++)
{
x += horzBar.Width + CHAR_SPACING;
spriteInfos.Add(new
SpriteInfo(vertBar, x, y));
spriteInfos.Add(new
SpriteInfo(horzBar, x, y + yBottom));
}
// O
x += horzBar.Width + CHAR_SPACING;
spriteInfos.Add(new
SpriteInfo(vertBar, x, y));
spriteInfos.Add(new
SpriteInfo(horzBar, x, y));
spriteInfos.Add(new
SpriteInfo(horzBar, x, y + yBottom));
spriteInfos.Add(new
SpriteInfo(vertBar, x + xRight, y));
}
The Update method is responsible for keeping the animation going. If the isAnimationGoing field is false, it checks for a new finger pressed on the screen.
protected
override void
Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back
== ButtonState.Pressed)
this.Exit();
if (isAnimationGoing)
{
TimeSpan animationTime =
gameTime.TotalGameTime - animationStartTime;
double fractionTime = (double)animationTime.Ticks
/ ANIMATION_DURATION.Ticks;
if (fractionTime >= 1)
{
isAnimationGoing = false;
fractionTime = 1;
}
SpriteInfo.InterpolationFactor
= (float)Math.Sin(Math.PI
* fractionTime);
}
else
{
TouchCollection
touchCollection = TouchPanel.GetState();
bool atLeastOneTouchPointPressed
= false;
foreach (TouchLocation
touchLocation in touchCollection)
atLeastOneTouchPointPressed |=
touchLocation.State ==
TouchLocationState.Pressed;
if (atLeastOneTouchPointPressed)
{
foreach (SpriteInfo
spriteInfo in spriteInfos)
{
float r1 = (float)rand.NextDouble()
- 0.5f;
float r2 = (float)rand.NextDouble()
- 0.5f;
float r3 = (float)rand.NextDouble();
spriteInfo.PositionOffset = new
Vector2(r1 * viewport.Width,
r2 *
viewport.Height);
spriteInfo.MaximumRotation = 2 * (float)Math.PI
* r3;
}
animationStartTime = gameTime.TotalGameTime;
isAnimationGoing = true;
}
}
base.Update(gameTime);
}
When the animation begins, the animationStartTime is set from the TotalGameTime property of GameTime. During subsequent calls, Update compares that value with the new TotalGameTime and calculates an interpolation factor. The InterpolationFactor property of SpriteInfo is static so it need be set only once to affect all the SpriteInfo instances. The Draw method loops through the SpriteInfo objects to access the Position and Rotation properties:

Join the conversation! Your thoughts help the community grow.