Microsoft XNA Game Studio Creator’s Guide- P3

Microsoft XNA Game Studio Creator’s Guide- P3:The release of the XNA platform and specifically the ability for anyone
to write Xbox 360 console games was truly a major progression
in the game-programming world. Before XNA, it was simply too complicated
and costly for a student, software hobbyist, or independent game developer to gain
access to a decent development kit for a major console platform.

Nội dung Text: Microsoft XNA Game Studio Creator’s Guide- P3

38 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE
This might sound strange, however, it does make sense if you consider that changing
the sprite’s direction on X and Y requires a rotation about the Z axis.
To get the versatility we need for implementing collision detection, this chapter in-
troduces the Z rotation matrix and the translation matrix.
Z Rotation Matrix
The Z rotation matrix is generated using the CreateRotationZ() method.
CreateRotationZ() receives an angle in radians as a parameter and uses it to generate
a matrix that will rotate sets of vertices around the Z axis:
Matrix rotationZ = Matrix.CreateRotationZ(float radians);
Translation Matrix
2D translations are lateral movements on X and Y. The translation matrix is created
using the CreateTranslation() method. The CreateTranslation() method receives
three floating point values to set the lateral movement on X and Y. The last parame-
ter, Z, is set to 0.0f for 2D since the drawing is done in two dimensions:
Matrix.CreateTranslation(float x, float y, float Z);
Building the Cumulative Transformation
Cumulative transformation matrices for transforming a set of vertices are calculated
by multiplying each individual transformation matrix with the others in this series.
Here is an example of a cumulative matrix being generated by multiplying the trans-
lation matrix and rotation matrix together:
Matrix cumulativeTransform
= Matrix.CreateTranslation(X,Y,Z) * Matrix.CreateRotationZ(radians);
The Intersects() Method
After your rectangle corners are transformed, the Intersects() method determines if
one rectangle overlaps another.
bool Rectangle rectangle0.Intersects(Rectangle rectangle1);

C H A P T E R 4 39
2D Games
FIGURE 4-3
False bounding rectangle collision
There are problems with this method. Figure 4-3 shows a situation where a colli-
sion is detected, but there actually is no collision. Another—more accurate—routine
is required to check for collisions, but you should still use rectangle collision checking
to determine if it is even worth executing a more accurate routine. Rectangle collision
checking requires little processing so it will save a lot of heavy lifting.
Per Pixel Collision Checking
Say your bounding rectangle collision check returns a true result which indicates that
two sprites have collided. As shown in Figure 4-3, it is possible for the solid portions
of the image to not be touching. You don’t want to react to this false collision. In a 2D
game, you can use a pixel collision checking algorithm to be more accurate. Here is a
high-level view of how pixel collision checking works.
The left section of Figure 4-4 shows two sprites drawn at their origin (their cen-
ters) at the top left of the window where X=0, Y=0 before each sprite is rotated and
translated. The middle section of Figure 4-4 shows each transformed sprite when a
bounding rectangle collision is detected. On the right of Figure 4-4, to make pixel col-
lision checking calculations easier, the rocket sprite is treated as if it were drawn at
the original starting position (at X=0, Y=0). The asteroid sprite is translated and ro-
tated as before. Then, the asteroid sprite is transformed by the inverse transforma-
tion of the rocket sprite.

40 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE
FIGURE 4-4
Steps for checking colored pixel overlap for accurate collision detections
H ANDLING USER INPUT DEVICES
The last topic introduced in this chapter is user input device handling. User input de-
vices are easy to implement and you may find this example is enough to not only get
you started, it may be all you need to take full advantage of the options available for
keyboard and game controller input. If you want more detail though, you can go to
Chapter 23, “Input Devices.”
Keyboard Input
The KeyboardState class allows you to poll for keyboard IsKeyDown and IsKeyUp
states for all keys on the keyboard. The KeyboardState object retrieves the current
key states with the GetState() method:
KeyboardState keyboardState = Keyboard.GetState();
You can use a Keys object to determine if a key is being pressed or released. The ex-
ample below shows a check to determine if the A key is pressed:
bool keyboardState.IsKeyDown(Keys.A);

C H A P T E R 4 41
2D Games
GamePad Input
You can also plug a game controller into your PC or Xbox. In case a game controller
is being used, you can add the GamePadState object to listen for button press, DPad,
bumper, trigger, and thumbstick events using the GetState() method. On the Xbox
360, up to four game controllers are supported. You can specify the player with an
integer in the GetState() method with an index.
GamePadState gamePad = GamePad.GetState(int PlayerIndex);
The IsConnected property is used to check if the game pad is actually active on
your PC or Xbox 360:
bool gamePad.IsConnected
The game pad thumbstick controls return floating point values ranging between
-1.0f and 1.0f when they are shifted horizontally and vertically. X=-1.0f for all the
way left, X=0.0f for the middle, and X=1.0f for the all the way right. Similarily, the Y
values are -1.0f when these controls are pulled down, Y=0.0f when the control is at
rest in the middle, and Y=1.0f when these controls are pushed all the way forward:
float rightSideways, rightForwards, leftSideways, leftForwards;
rightSideways = gamePad.ThumbSticks.Right.X; rightForwards = gamePad.ThumbSticks.Right.Y;
leftSideways = gamePad.ThumbSticks.Left.X; leftForwards = gamePad.ThumbSticks.Left.Y;
P ORTING YOUR 2D GAMES TO THE ZUNE
The screen resolution for the Zune is either 240x320 or 320x240 pixels depending
on how you rotate your Zune. You can set these dimension with code similar to the
following:
this.graphics.PreferredBackBufferWidth = 240;
this.graphics.PreferredBackBufferHeight = 320;
this.graphics.ApplyChanges();
If you are developing a game for the Zune you are better off to build the game with
this specified resolution during the design and build phase. Shrinking your sprites
and other resources into a smaller area may work but you will be wasting valuable
Zune space. For more information on Zune development please see Chapter 1.

42 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE
A STARTER 2D GAME EXAMPLE
The starter 2D game you’ll be creating for this chapter features a rocket that the user
can navigate around the screen. The object of the game is to fly the rocket around
without heading off into space or making contact with the spinning asteroid rock
that bounces back and forth across the screen.
This example shows all of the steps needed to draw and control animated sprites
and how to implement proper collision detection.
Unlike almost all other examples in this book (which use the book’s base code for
a 3D framework), this example begins with the XNA game template. If you want,
you can find the solution for this example in the book’s download, but we recom-
mend you follow these steps instead. Working through the example will help you
learn how to implement this game.
You can build the project template by launching Visual Studio. From the menu,
first select File | New Project. Then, under Project Types in the New Project dialog se-
lect XNA Game Studio 3.0. From there you can select the Windows Game, Xbox 360
Game, or Zune template depending on where you want to run your game—the code
is the same. After you assign your project a name, and specify a location for it, click
OK and you are ready to go.
Adding the Images
The first part of this demonstration shows how easy it is to draw a sprite.
Variable Setup
When setting up the project, some variables are needed at the top of the game class to
store the texture, position, dimension, and direction information for the rocket and
the asteroid:
Texture2D shipTexture, rockTexture; // image files
Vector2 shipPosition = new Vector2(100.0f, 100.0f);// position data
Vector2 rockPosition = new Vector2(100.0f, 29.0f);
float shipRotation, rockRotation; // rotation radians
Vector2 shipCenter; int shipWidth, shipHeight; // ship dimensions
Vector2 rockCenter; int rockWidth, rockHeight; // rock dimensions
You can use the ship and asteroid images from the Images folder in the book’s
download. They have been designed with transparent backgrounds. Of course, you
may want to try using your own images for this example. These images need to be ref-

C H A P T E R 4 43
2D Games
erenced in the Solution Explorer as shown in Figure 4-2 (near the start of this chapter).
The texture objects are initialized in the LoadContent() method. Also, after the tex-
tures are loaded, the texture’s Height and Width properties are used to store the tex-
ture dimensions and pixel centers. This will help you reference the properties later:
shipTexture = Content.Load("Images\\ship");
rockTexture = Content.Load("Images\\asteroid");
rockWidth = rockTexture.Width; rockHeight = rockTexture.Height;
shipWidth = shipTexture.Width; shipHeight = shipTexture.Height;
rockCenter = new Vector2(rockWidth/2, rockWidth/2);
shipCenter = new Vector2(shipWidth/2, shipHeight/2);
You can now draw your images by placing the following code block inside the
Draw() method:
spriteBatch.Begin(SpriteBlendMode.AlphaBlend); // start drawing 2D images
spriteBatch.Draw(rockTexture, rockPosition, null, Color.White,
rockRotation, rockCenter, 1.0f, SpriteEffects.None, 0.0f);
spriteBatch.Draw(shipTexture, shipPosition, null, Color.White,
shipRotation, shipCenter, 1.0f, SpriteEffects.None, 0.0f);
spriteBatch.End(); // stop drawing 2D images
If you run your code now, you will see the asteroid and spaceship.
Animating the Asteroid
In XNA, animations are created by updating position and rotation values every
frame. These updates are scaled by the time lapse between frames to ensure the ani-
mations run at a uniform speed on all systems. You may have seen some older games
that didn’t have this feature (such as Microsoft Hearts). If these games were run on a
system much faster than the typical processor when the game was developed, the
games would run so fast that they could be unplayable. Variables are needed at the
top of the game class to assist in tracking the asteroid’s lateral speed, rotation speed,
and direction:
float rockSpeed, rockRotationSpeed;
bool move = true;
When the program begins, inside Initialize() you need to assign some speed values
to set rates for the sprite’s continuous lateral and rotational movement:
rockSpeed = 0.16f;
rockRotationSpeed = 0.3f;

44 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE
To ensure your most important graphics stay within the title safe area of your win-
dow, you need to specify the area. The TitleSafeRegion() method is used in your
game class to return the margins of a rectangle that surrounds the visible area on your
game display. This method returns a Rectangle object which contains the Top, Bot-
tom, Left, and Right margin values of this title safe area:
Rectangle TitleSafeRegion(int spriteWidth, int spriteHeight){
#if Xbox
// some televisions only show 80% of the window
Vector2 start = new Vector2(); // starting pixel X & Y
const float MARGIN = 0.2f; // only 80% visible on
// Xbox 360
start.X = graphics.GraphicsDevice.Viewport.Width * MARGIN/2.0f;
start.Y = graphics.GraphicsDevice.Viewport.Height
* (1 - MARGIN/2.0f);
// ensure image drawn in safe region on all sides
return new Rectangle(
(int)start.X, // surrounding safe area
(int)start.Y, // top,left,width,height
(int)(1.0f-MARGIN)*Window.ClientBounds.Width - spriteWidth,
(int)(1.0f-MARGIN)*Window.ClientBounds.Height - spriteHeight);
#endif
// show entire region on the PC or Zune
return new Rectangle(0,0,Window.ClientBounds.Width - spriteWidth,
Window.ClientBounds.Height - spriteHeight);
}
Next is the code to update the position and orientation of the rock; this happens
for each frame. A rotation is added in to make them look more interesting. In the as-
teroid code, unlike the rocket, there are checks for the screen bounds (e.g., Win-
dow.ClientBounds.Width). These checks ensure that the rock doesn’t leave the
viewable area of the screen. If the rock hits the side, it reverses direction and heads
straight back the other way.
private void UpdateAsteroid(GameTime gameTime){
// time between frames
float timeLapse = (float)gameTime.ElapsedGameTime.Milliseconds;
if (move == true)
{ // asteroid centered at the middle of the image
Rectangle safeArea = TitleSafeRegion(rockWidth/2, rockHeight/2);

C H A P T E R 4 45
2D Games
// asteroid right edge exceeds right window edge
if (rockPosition.X > safeArea.Right){
rockPosition.X = safeArea.Right; // move it back
rockSpeed *= -1.0f; // reverse direction
}
// asteroid left edge precedes the left window edge
else if (rockPosition.X - rockCenter.X < 0){
rockPosition.X = rockCenter.X; // move it back
rockSpeed *= -1.0f; // reverse direction
}
// asteroid within window bounds so update rockPosition
else
rockPosition.X += rockSpeed * timeLapse;
// Scale radians by time between frames so rotation is uniform
// rate on all systems. Cap between 0 & 2PI for full rotation.
const float SCALE = 50.0f;
rockRotation += rockRotationSpeed * timeLapse/SCALE;
rockRotation = rockRotation % (MathHelper.Pi * 2.0f);
}
}
Updates to the asteroids’ position and rotation values should be done from the Up-
date() method:
UpdateAsteroid(gameTime);
If you run your code now, the asteroid will move back and forth continuously.
Controlling the Ship
In this example, the ship angle is determined using input from either the LEFT and
RIGHT arrow keys, or from the left thumbstick’s X value on the game controller. The
change to the rotation is scaled by the time lapse between frames to ensure a uniform
rotation. Since the angle of a circle is 360 degrees (2π radians), the cumulative ship
rotation, stored in the “shipRotation” variable, is modded by 2π to keep the rotation
angle between 0 and 2π at all times.
private float RotateShip(GameTime gameTime){
float rotation = 0.0f;
float speed = gameTime.ElapsedGameTime.Milliseconds/300.0f;

46 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE
if (!move) // collision has occurred so don’t rotate ship any more
return rotation;
// handle user input
KeyboardState keyboard = Keyboard.GetState();
GamePadState gamePad = GamePad.GetState(PlayerIndex.One);
if(!gamePad.IsConnected){ // keyboard input
if(keyboard.IsKeyDown(Keys.Right)
&& keyboard.IsKeyDown(Keys.Left)){
// don't rotate if Right or Left not pressed
}
else if(keyboard.IsKeyDown(Keys.Right)) // right
rotation = speed;
else if (keyboard.IsKeyDown(Keys.Left)) // left
rotation =-speed;
}
else // controller input
rotation = gamePad.ThumbSticks.Left.X * speed;
// update rotation based on time scale and only store between 0 & 2pi
shipRotation += rotation;
shipRotation = shipRotation % (MathHelper.Pi * 2.0f);
return shipRotation;
}
Trigonometry is used to implement the forward and backward movement of the
rocket ship. The ship moves when the user presses the UP or DOWN arrow keys or
when they shift the left thumbstick.
Now for a quick trigonometry primer. We use these formulas to calculate the dis-
tance moved in the X and Y planes. Where:
Sinφ = Opposite/Hypotenuse and Cosφ = Adjacent/Hypotenuse
We can say:
X = Opposite = Hypotenuse*Sinφ and Y = Adjacent = Hypotenuse*Cosφ
In the case of our rocket ship, shown in Figure 4-5, we have already stored the
ship’s rotation angle and if we treat the speed as the hypotenuse we can calculate the
change on X and Y.

48 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE
else if(move){ // GAMEPAD
shipPosition.X += (float)Math.Sin(shipRotation) *
gamePad.ThumbSticks.Left.Y*speed*SCALE;
shipPosition.Y -= (float)Math.Cos(shipRotation) *
gamePad.ThumbSticks.Left.Y*speed*SCALE;
}
}
The ship rotation and movement updates are triggered from the Update() method
to ensure consistency each frame:
RotateShip(gameTime);
MoveShip(gameTime);
Adding in Collision Detection
In the high-level view of the collision detection algorithms discussed earlier, we ex-
plained that a bounding rectangle algorithm can be used as a quick check to deter-
mine if texture borders intersect. If a bounding rectangle occurs, pixel comparisons
are made between the two textures to determine if two colored pixels overlap. Now
let’s look at the code.
First, identifiers are used to distinguish the rock and rocket ship objects:
const int ROCK = 0; const int SHIP = 1;
Color[] rockColor; Color[] shipColor;
These color values are initialized at the end of LoadContent() after the textures are
loaded:
rockColor = new Color[rockTexture.Width * rockTexture.Height];
rockTexture.GetData(rockColor);
shipColor = new Color[shipTexture.Width * shipTexture.Height];
shipTexture.GetData(shipColor);
To keep things simple, we store the color data for each texture in an array. The fol-
lowing routine is added to the game class to return the specific color for each pixel in
each sprite:
public Color PixelColor(int objectNum, int pixelNum){
switch (objectNum){
case ROCK:
return rockColor[pixelNum];

C H A P T E R 4 49
2D Games
case SHIP:
return shipColor[pixelNum];
}
return Color.White;
}
The PixelCollision() and TransformRectangle() methods implemented here are
based on code samples provided from the XNA Creator’s Club website ( http://cre-
ators.xna.com). This site is a fantastic resource for anyone working with the XNA
framework. Basically, the PixelCollision() method transforms sprite A by A’s origi-
nal transformation and then transforms it again by the inverse of sprite B’s transfor-
mation. These calculations express sprite A with the same relative positioning to
sprite B during the bounding rectangle collision. However, B is treated as if it hasn’t
moved from the original starting pixel position at X=0, Y=0.
When traversing across and downward through A’s rows of pixels, a unit change
in X and a unit change in Y must be calculated to determine the increment for X and
Y values of each neighboring pixel. Unit normal vectors are used to calculate these
rates of change. Normal vectors and unit vectors are discussed in more detail in
Chapter 15, “Vectors,”, but we recommend you stay focused on this chapter and the
others leading up to it.
For each pixel in sprite A: if it is colored, the position is used to retrieve the pixel
color from sprite B if it exists. If both pixels are colored a collision has occurred (refer
to Figure 4-4):
public bool PixelCollision(
Matrix transformA, int pixelWidthA, int pixelHeightA, int A,
Matrix transformB, int pixelWidthB, int pixelHeightB, int B){
// set A transformation relative to B. B remains at x=0, y=0.
Matrix AtoB = transformA * Matrix.Invert(transformB);
// generate a perpendicular vectors to each rectangle side
Vector2 columnStep, rowStep, rowStartPosition;
columnStep = Vector2.TransformNormal(Vector2.UnitX, AtoB);
rowStep = Vector2.TransformNormal(Vector2.UnitY, AtoB);
// calculate the top left corner of A
rowStartPosition = Vector2.Transform(Vector2.Zero, AtoB);
// search each row of pixels in A. start at top and move down.
for (int rowA = 0; rowA < pixelHeightA; rowA++){
// begin at the left
Vector2 pixelPositionA = rowStartPosition;

50 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE
// for each column in the row (move left to right)
for (int colA = 0; colA < pixelWidthA; colA++){
// get the pixel position
int X = (int)Math.Round(pixelPositionA.X);
int Y = (int)Math.Round(pixelPositionA.Y);
// if the pixel is within the bounds of B
if (X >= 0 && X < pixelWidthB && Y >= 0 && Y < pixelHeightB){
// get colors of overlapping pixels
Color colorA = PixelColor(A, colA + rowA * pixelWidthA);
Color colorB = PixelColor(B, X + Y * pixelWidthB);
// if both pixels are not completely transparent,
if (colorA.A != 0 && colorB.A != 0)
return true; // collision
}
// move to the next pixel in the row of A
pixelPositionA += columnStep;
}
// move to the next row of A
rowStartPosition += rowStep;
}
return false; // no collision
}
When checking for bounding rectangles, the Transform() method generates a cu-
mulative transformation matrix according to the sprites’ current rotation and posi-
tion. But first, this method shifts the sprite to the origin. It then performs the Z
rotation and translation on X and Y and returns the cumulative result:
public Matrix Transform(Vector2 center, float rotation, Vector2 position)
{ // move to origin, scale (if desired), rotate, translate
return Matrix.CreateTranslation(new Vector3(-center, 0.0f)) *
// add scaling here if you want
Matrix.CreateRotationZ(rotation) *
Matrix.CreateTranslation(new Vector3(position, 0.0f));
}
When checking for collisions between the bounding rectangles of each sprite, each
corner of each rectangle must be transformed. Then a new rectangle is generated us-
ing the top left vertex from the newly transformed corners and the X and Y distance
to the opposite corner. TransformRectangle() does this from the game class:

52 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE
shipRectangle = TransformRectangle(shipTransform, shipWidth,
shipHeight);
// collision checking
if (rockRectangle.Intersects(shipRectangle)) // rough collision check
if (PixelCollision( // exact collision check
rockTransform, rockWidth, rockHeight, ROCK,
shipTransform, shipWidth, shipHeight, SHIP))
move = false;
}
The collision checking routine is called from Update() after the ship and asteroids
are updated:
CheckCollisions();
If you run the program now, you will see a moving rock and a ship that you can
control with the arrow keys. If your rocket ship gets hit by an asteroid, you will no
longer be able to move your ship and the asteroid will stop moving. That’s it! You
have built your own 2D starter game.
C OMPLETING THE 2D GAME
After reading the code discussion in this chapter, you will see that you can create a
very powerful 2D game foundation in minutes. The sample discussed shows all steps
needed to implement animated sprites, handle user input devices, and perform 2D
collision detection. However, we are sure you want to build a complete 2D game and
this book does explains how to do so from start to finish. After reading this chapter,
you can also follow the steps in the following chapters to complete your 2D frame-
work:
Chapter Title
12 “Combining Images for Better Visual Effects,” SpriteBatch on the Heads-Up-Display Example
13 “Score Tracking and Game Statistics,” Font Example: Displaying a Frames-per-Second Count
23 “Zune Input Device Example”
We also think you will find adapting the following chapters to suit a 2D game is a
simple process:

C H A P T E R 4 53
2D Games
Chapter Title
23 “Zune Input Device Example”
29 “Networking”
Lastly, if you are deploying your 2D games to the Zune please read Chapter 1, “Set
Up an XNA Development Environment,” for steps and best practices on porting
code to the Zune.
C HAPTER EXERCISES
1. Add another sprite to your game.
2. Change the behavior of the rock so that it doesn’t move in a straight line.
3. Add collision code that prevents the rocket from leaving the screen.
4. Add more code to launch a missile from the rocket when you press the
space bar. For an additional hint on how to do this, see the section on
“Adjusting the Input Device Responsiveness” for toggling states in
Chapter 23, “Input Devices.”

This page intentionally left blank

CHAPTER 5
Introduction
to 3D
Graphics
Programming

chapter discusses the basic elements and methods for drawing
THIS primitive 3D game graphics with points, lines, and triangles. By
the end of this chapter, you will be able to use these structures to build something like
a simple village in a 3D world. Learning how to draw basic shapes in a game window
might not grab you at first, but all great graphic effects, and even 3D models, begin
with the structures presented here.
3D graphics start with shapes that are created from points, lines, or triangles.
These basic elements are referred to as primitive objects. Primitive objects are drawn
in 3D space using a Cartesian coordinate system where position is mapped in the X,
Y, and Z planes (see Figure 5-1).
Even complex shapes are built with a series of points, lines, or triangles. A static
3D model is basically made from a file containing vertex information that includes X,
Y, Z position, color, image coordinates, and possibly other data. The vertices can be
rendered by outputting points for each vertex, with a grid of lines that connects the
vertices, or as a solid object that is built with a series of triangles—which are linked
by the vertices.
P RIMITIVE OBJECTS
Complex shapes are created with primitive objects that regulate how the vertices are
displayed. The vertex data could be rendered as points, linear grids, or solid triangles
(see Figure 5-2).
FIGURE 5-1
Cartesian coordinate system for drawing in 3D
56

C H A P T E R 5 57
Introduction to 3D Graphics Programming
FIGURE 5-2
Primitive strips and lists
D RAWING SYNTAX
XNA delivers simple syntax for drawing shapes from primitive objects.
Primitive Object Types
Table 3-1 details the five common primitive object types. You will notice that trian-
gles and lines can be drawn in strips or in lists. Lists are required for drawing separate
points, lines, or triangles. Strips, on the other hand, are more efficient where the lines
or triangles are combined to create a complex shape—such as a 3D model.