This chapter is taken from book "Programming Windows Phone 7" by Charles Petzold published by Microsoft press. http://www.charlespetzold.com/phone/index.html
Game Components
To give your fingers a target to touch and drag, the programs display translucent disks at the Texture2D corners. It would be nice to code these draggable translucent disks so they're usable by multiple programs. In a traditional graphics programming environment, we might think of something like this as a control but in XNA it's called a game component.
Components help modularize your XNA programs. Components can derive from the GameComponent class but often they derive from DrawableGameComponent so they can display something on the screen in addition to (and on top of) what goes out in the Draw method of your Game class.
To add a new component class to your project, right-click the project name, select Add and then New Item, and then pick Game Component from the list. You'll need to change the base class to DrawableGameComponent and override the Draw method if you want the component to participate in drawing.
A game generally instantiates the components that it needs either in the game's constructor or during the Initialize method. The components officially become part of the game when they are added to the Components collection defined by the Game class.
As with Game, a DrawableGameComponent derivative generally overrides the Initialize, LoadContent, Update, and Draw methods. When the Initialize override of the Game derivative calls the Initialize method in the base class, the Initialize methods in all the components are called. Likewise, when the LoadComponent, Update, and Draw overrides in the Game derivative call the method in the base class, the LoadComponent, Update, and Draw methods in all the components are called.
As you know, the Update override normally handles touch input. In my experience that attempting to access touch input in a game component is somewhat problematic. It seems as if the game itself and the components end up competing for input.
To fix this, I decided that my Game derivative would be solely responsible for calling TouchPanel.GetState, but the game would then give the components the opportunity to
process this touch input. To accommodate this concept, I created this interface for GameComponent and DrawableGameComponent derivatives:
using Microsoft.Xna.Framework.Input.Touch;
namespace Petzold.Phone.Xna
{
public interface IProcessTouch
{
bool ProcessTouch(TouchLocation touch);
}
}
When a game component implements this interface, the game calls the game component's ProcessTouch method for every TouchLocation object. If the game component needs to use that TouchLocation, it returns true from ProcessTouch, and the game then probably ignores that TouchLocation.
The first component I'll show you is called Dragger, and it is part of the Petzold.Phone.Xna library. Dragger derives from DrawableGameComponent and implements the IProcessTouch interface:
public class
Dragger :
DrawableGameComponent, IProcessTouch
{
SpriteBatch spriteBatch;
int? touchId;
public event
EventHandler PositionChanged;
public Dragger(Game
game)
: base(game)
{
}
public
Texture2D Texture { set;
get; }
public
Vector2 Origin { set;
get; }
public
Vector2 Position { set;
get; }
....
}
A program making use of Dragger could define a custom Texture2D for the
component and set it through this public Texture property, at which time it
would probably also set the Origin property. However, Dragger defines a default
Texture property for itself during its LoadContent method:
protected
override void LoadContent()
{
spriteBatch = new
SpriteBatch(this.GraphicsDevice);
// Create default texture
int radius = 48;
Texture2D texture =
new Texture2D(this.GraphicsDevice,
2 * radius, 2 * radius);
uint[] pixels =
new uint[texture.Width
* texture.Height];
for (int
y = 0; y < texture.Height; y++)
for (int
x = 0; x < texture.Width; x++)
{
Color clr =
Color.Transparent;
if ((x - radius) * (x -
radius) +
(y - radius) * (y - radius) <
radius * radius)
{
clr = new
Color(0, 128, 128, 128);
}
pixels[y * texture.Width + x] = clr.PackedValue;
}
texture.SetData<uint>(pixels);
Texture = texture;
Origin = new
Vector2(radius, radius);
base.LoadContent();
}
The Dragger class implements the IProcessTouch interface so it has a
ProcessTouch method that is called from the Game derivative for each
TouchLocation object. The ProcessTouch method is interested in finger presses
that occur over the component itself. If that is the case, it retains the ID and
basically owns that finger until it lifts from the screen. For every movement of
that finger, Dragger fires a PositionChanged event.
public bool
ProcessTouch(TouchLocation touch)
{
if (Texture ==
null)
return
false;
bool touchHandled =
false;
switch (touch.State)
{
case
TouchLocationState.Pressed:
if ((touch.Position.X >
Position.X - Origin.X) &&
(touch.Position.X < Position.X - Origin.X +
Texture.Width) &&
(touch.Position.Y > Position.Y - Origin.Y) &&
(touch.Position.Y < Position.Y - Origin.Y +
Texture.Height))
{
touchId = touch.Id;
touchHandled = true;
}
break;
case
TouchLocationState.Moved:
if (touchId.HasValue &&
touchId.Value == touch.Id)
{
TouchLocation previousTouch;
touch.TryGetPreviousLocation(out
previousTouch);
Position += touch.Position - previousTouch.Position;
// Fire the event!
if (PositionChanged != null)
PositionChanged(this,
EventArgs.Empty);
touchHandled = true;
}
break;
case
TouchLocationState.Released:
if (touchId.HasValue &&
touchId.Value == touch.Id)
{
touchId = null;
touchHandled = true;
}
break;
}
return touchHandled;
}
The Draw override just draws the Texture2D at the new position:
public override
void Draw(GameTime
gameTime)
{
if (Texture !=
null)
{
spriteBatch.Begin();
spriteBatch.Draw(Texture, Position,
null, Color.White,
0, Origin, 1,
SpriteEffects.None, 0);
spriteBatch.End();
}
base.Draw(gameTime);
}
Affine and Non-Affine Transforms
Sometimes it's convenient to derive a transform that maps a particular set
of points to a particular destination. For example, here's a program that
incorporates three instances of the Dragger component I just described, and lets
you drag three corners of the Texture2D
to arbitrary locations on the screen:

This program uses an affine transform, which means that rectangles are always mapped to parallelograms. The fourth corner isn't draggable because it's always determined by the other three:

You can't choose just any three points. Everything goes kaflooey if you attempt to make an interior angle greater than 180 degree.
A static class named MatrixHelper in the Petzold.Phone.Xna library has a method named ComputeAffineTransform that creates a Matrix object based on these formulas:



Join the conversation! Your thoughts help the community grow.