Microsoft XNA Game Studio Creator’s Guide- P3

Chia sẻ: Cong Thanh | Ngày: | Loại File: PDF | Số trang:30

0
72
lượt xem
13

Microsoft XNA Game Studio Creator’s Guide- P3

Mô tả tài liệu

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.

Chủ đề:

Bình luận(0)

Lưu

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

1. 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);
2. 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.
3. 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);
6. 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;
7. 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);
8. 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;
9. 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.
10. C H A P T E R 4 47 2D Games FIGURE 4-5 The angle of the rocket ship MoveShip() implements this algorithm from the game class to allow the user to control the direction and movement of the rocket ship: private void MoveShip(GameTime gameTime){ const float SCALE = 20.0f; // speed float speed = gameTime.ElapsedGameTime.Milliseconds/100.0f; KeyboardState keyboard = Keyboard.GetState(); // user input GamePadState gamePad = GamePad.GetState(PlayerIndex.One); if (move && !gamePad.IsConnected){ // KEYBOARD if (keyboard.IsKeyDown(Keys.Down) && keyboard.IsKeyDown(Keys.Up)){ // Up and Down pressed at same time so do not move } if (keyboard.IsKeyDown(Keys.Up)){ // forwards shipPosition.X += (float)Math.Sin(shipRotation)*speed*SCALE; shipPosition.Y -= (float)Math.Cos(shipRotation)*speed*SCALE; } else if (keyboard.IsKeyDown(Keys.Down)){ // reverse shipPosition.X -= (float)Math.Sin(shipRotation)*speed*SCALE; shipPosition.Y += (float)Math.Cos(shipRotation)*speed*SCALE; } }
11. 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];
12. 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;
13. 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:
14. C H A P T E R 4 51 2D Games public static Rectangle TransformRectangle(Matrix transform, int width, int height){ // Get each corner of texture Vector2 leftTop = new Vector2(0.0f, 0.0f); Vector2 rightTop = new Vector2(width, 0.0f); Vector2 leftBottom = new Vector2(0.0f, height); Vector2 rightBottom = new Vector2(width, height); // Transform each corner Vector2.Transform(ref leftTop, ref transform, out leftTop); Vector2.Transform(ref rightTop, ref transform, out rightTop); Vector2.Transform(ref leftBottom, ref transform, out leftBottom); Vector2.Transform(ref rightBottom, ref transform, out rightBottom); // Find the minimum and maximum corners Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop), Vector2.Min(leftBottom, rightBottom)); Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop), Vector2.Max(leftBottom, rightBottom)); // Return transformed rectangle return new Rectangle((int)min.X, (int)min.Y, (int)(max.X - min.X), (int)(max.Y - min.Y)); } The driving routine which should be added to check for collisions is added to the game class. The pixel collision routine is expensive, so it is not entered until a bound- ing rectangle collision has been established. First, the asteroid and ship sprites are transformed and rectangle collision is used to determine if both sprites are close. If the two objects are close, then pixel collision detection is triggered to search for over- lapping colored pixels: private void CheckCollisions(){ Matrix shipTransform, rockTransform; Rectangle shipRectangle, rockRectangle; // transform the rectangles which surround each sprite rockTransform = Transform(rockCenter, rockRotation, rockPosition); rockRectangle = TransformRectangle(rockTransform, rockWidth, rockHeight); shipTransform = Transform(shipCenter, shipRotation, shipPosition);
15. 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:
16. 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.”
18. CHAPTER 5 Introduction to 3D Graphics Programming
19. 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
20. 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.